superlocalmemory 3.1.1 → 3.2.1

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/CHANGELOG.md CHANGED
@@ -16,6 +16,34 @@ SuperLocalMemory V3 - Intelligent local memory system for AI coding assistants.
16
16
 
17
17
  ---
18
18
 
19
+ ## [3.2.1] - 2026-03-26
20
+
21
+ ### Fixed
22
+ - **Windows `slm --version` / `slm -v`** — `.bat` and `.cmd` wrappers now intercept `--version`/`-v` directly (fast path, no Python needed) and set `PYTHONPATH` to the npm package's `src/` directory before launching Python. Previously, Windows users hitting `slm.bat` instead of the Node.js wrapper got `unrecognized arguments: --version` because Python resolved an older pip-installed version without the flag.
23
+ - **Unix bash wrapper** (`bin/slm`) — now sets `PYTHONPATH` and intercepts `--version`/`-v`, matching the Node.js wrapper's behavior. Previously relied on npm's shim always routing to `slm-npm`.
24
+ - **`postinstall.js`** — now runs `pip install .` to install the `superlocalmemory` Python package itself (not just dependencies). Prevents stale pip-installed versions from shadowing the npm-distributed source. Falls back to `--user` for PEP 668 environments.
25
+ - **`preuninstall.js`** — corrected version string from "V2" to "V3".
26
+ - **Windows Python detection** — added `py -3` (Python Launcher for Windows) as a fallback candidate in `slm.bat`.
27
+ - **Environment parity** — all three entry points (`slm-npm`, `slm`, `slm.bat`) now set identical PyTorch memory-prevention env vars (`PYTORCH_MPS_HIGH_WATERMARK_RATIO`, `TORCH_DEVICE`, etc.).
28
+
29
+ ---
30
+
31
+ ## [3.2.0] - 2026-03-26
32
+
33
+ ### Added
34
+ - **`slm doctor` command** — comprehensive pre-flight check: Python version, all dependency groups, embedding worker functional test, Ollama connectivity, API key validation, disk space, database integrity. Supports `--json` for agent-native output.
35
+ - **`slm hooks install`** listed in CLI reference and README.
36
+ - Dashboard, learning (lightgbm), and performance (diskcache, orjson) dependencies now install automatically during `npm install`.
37
+
38
+ ### Fixed
39
+ - **Warmup reliability** — increased subprocess timeout from 60s to 180s for first-time model download. Added step-by-step progress output and direct in-process import diagnostics when worker fails.
40
+ - **Mode B default model** — changed from `phi3:mini` to `llama3.2` to match `provider_presets()` and reduce first-time setup friction.
41
+ - **postinstall.js** — now installs all 5 dependency groups (core, search, dashboard, learning, performance) with clear status messages per group.
42
+ - **Error messages** — all embedding worker failures, engine fallbacks, and dashboard errors now suggest `slm doctor` for diagnosis.
43
+ - **pyproject.toml** — added `diskcache` and `orjson` to core dependencies; aligned optional dependency versions with core.
44
+
45
+ ---
46
+
19
47
  ## [3.0.31] - 2026-03-21
20
48
 
21
49
  ### Fixed
package/README.md CHANGED
@@ -54,6 +54,7 @@ Mathematical layers contribute **+12.7 percentage points** on average across 6 c
54
54
  ```bash
55
55
  npm install -g superlocalmemory
56
56
  slm setup # Choose mode (A/B/C)
57
+ slm doctor # Verify everything is working
57
58
  slm warmup # Pre-download embedding model (~500MB, optional)
58
59
  ```
59
60
 
@@ -323,13 +324,15 @@ Three new tools for AI assistants:
323
324
  | `slm trace "..."` | Recall with per-channel score breakdown |
324
325
  | `slm status` | System status |
325
326
  | `slm health` | Math layer health (Fisher, Sheaf, Langevin) |
327
+ | `slm doctor` | Pre-flight check (deps, worker, Ollama, database) |
326
328
  | `slm mode a/b/c` | Switch operating mode |
327
329
  | `slm setup` | Interactive first-time wizard |
328
330
  | `slm warmup` | Pre-download embedding model |
329
331
  | `slm migrate` | V2 to V3 migration |
330
- | `slm dashboard` | Launch web dashboard |
332
+ | `slm dashboard` | Launch 17-tab web dashboard |
331
333
  | `slm mcp` | Start MCP server (for IDE integration) |
332
334
  | `slm connect` | Configure IDE integrations |
335
+ | `slm hooks install` | Wire auto-memory into Claude Code hooks |
333
336
  | `slm profile list/create/switch` | Profile management |
334
337
 
335
338
  ---
@@ -367,13 +370,16 @@ Three new tools for AI assistants:
367
370
  | **Node.js** | 14+ | npm package manager |
368
371
  | **Python** | 3.11+ | V3 engine runtime |
369
372
 
370
- All Python dependencies install automatically during `npm install`. If anything fails, the installer shows exact fix commands. BM25 keyword search works even without embeddings — you're never fully blocked.
373
+ All Python dependencies install automatically during `npm install` — core math, dashboard server, learning engine, and performance optimizations. If anything fails, the installer shows exact fix commands. Run `slm doctor` after install to verify everything works. BM25 keyword search works even without embeddings — you're never fully blocked.
371
374
 
372
375
  | Component | Size | When |
373
376
  |:----------|:-----|:-----|
374
377
  | Core libraries (numpy, scipy, networkx) | ~50MB | During install |
378
+ | Dashboard & MCP server (fastapi, uvicorn) | ~20MB | During install |
379
+ | Learning engine (lightgbm) | ~10MB | During install |
375
380
  | Search engine (sentence-transformers, torch) | ~200MB | During install |
376
381
  | Embedding model (nomic-embed-text-v1.5, 768d) | ~500MB | First use or `slm warmup` |
382
+ | **Mode B** requires [Ollama](https://ollama.com) + a model (`ollama pull llama3.2`) | ~2GB | Manual |
377
383
 
378
384
  ---
379
385
 
package/bin/slm CHANGED
@@ -2,6 +2,19 @@
2
2
  # SuperLocalMemory V3 CLI
3
3
  # Part of Qualixar | https://superlocalmemory.com
4
4
 
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ PKG_ROOT="$(dirname "$SCRIPT_DIR")"
7
+ SRC_DIR="$PKG_ROOT/src"
8
+
9
+ # Handle --version / -v directly (fast path)
10
+ for arg in "$@"; do
11
+ if [ "$arg" = "--version" ] || [ "$arg" = "-v" ]; then
12
+ VER=$(grep '"version"' "$PKG_ROOT/package.json" 2>/dev/null | head -1 | sed 's/.*"\([0-9][0-9.]*\)".*/\1/')
13
+ echo "superlocalmemory ${VER:-unknown}"
14
+ exit 0
15
+ fi
16
+ done
17
+
5
18
  # Find Python
6
19
  PYTHON=""
7
20
  for cmd in python3 python; do
@@ -12,9 +25,24 @@ for cmd in python3 python; do
12
25
  done
13
26
 
14
27
  if [ -z "$PYTHON" ]; then
15
- echo "Error: Python 3 not found. Install Python 3.10+ from python.org"
28
+ echo "Error: Python 3 not found. Install Python 3.11+ from python.org"
16
29
  exit 1
17
30
  fi
18
31
 
32
+ # Set PYTHONPATH so Python finds the npm package's src/ directory
33
+ if [ -n "$PYTHONPATH" ]; then
34
+ export PYTHONPATH="$SRC_DIR:$PYTHONPATH"
35
+ else
36
+ export PYTHONPATH="$SRC_DIR"
37
+ fi
38
+
39
+ # Prevent PyTorch Metal/MPS GPU memory reservation
40
+ export PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0
41
+ export PYTORCH_MPS_MEM_LIMIT=0
42
+ export PYTORCH_ENABLE_MPS_FALLBACK=1
43
+ export TOKENIZERS_PARALLELISM=false
44
+ export TORCH_DEVICE=cpu
45
+ export CUDA_VISIBLE_DEVICES=""
46
+
19
47
  # Run V3 CLI
20
48
  exec "$PYTHON" -m superlocalmemory.cli.main "$@"
package/bin/slm.bat CHANGED
@@ -6,22 +6,60 @@ REM Repository: https://github.com/qualixar/superlocalmemory
6
6
 
7
7
  setlocal enabledelayedexpansion
8
8
 
9
+ REM Resolve the package src/ directory for PYTHONPATH
10
+ set "SLM_PKG_DIR=%~dp0..\src"
11
+
12
+ REM Handle --version / -v directly (fast path, no Python needed)
13
+ if "%~1"=="--version" goto :show_version
14
+ if "%~1"=="-v" goto :show_version
15
+
9
16
  REM Find Python 3
10
- where python3 >/dev/null 2>&1
17
+ where python3 >nul 2>&1
11
18
  if %ERRORLEVEL% EQU 0 (
12
19
  set PYTHON_CMD=python3
13
20
  goto :run
14
21
  )
15
- where python >/dev/null 2>&1
22
+ where python >nul 2>&1
16
23
  if %ERRORLEVEL% EQU 0 (
17
24
  set PYTHON_CMD=python
18
25
  goto :run
19
26
  )
27
+ where py >nul 2>&1
28
+ if %ERRORLEVEL% EQU 0 (
29
+ set PYTHON_CMD=py -3
30
+ goto :run
31
+ )
20
32
 
21
33
  echo Error: Python 3.11+ not found.
22
34
  echo Install from: https://python.org/downloads/
23
35
  exit /b 1
24
36
 
37
+ :show_version
38
+ REM Read version from package.json via findstr
39
+ for /f "tokens=2 delims=:," %%a in ('findstr /C:"\"version\"" "%~dp0..\package.json"') do (
40
+ set "VER=%%~a"
41
+ set "VER=!VER: =!"
42
+ echo superlocalmemory !VER!
43
+ exit /b 0
44
+ )
45
+ echo superlocalmemory unknown
46
+ exit /b 0
47
+
25
48
  :run
49
+ REM Set PYTHONPATH so Python finds the npm package's src/ directory
50
+ if defined PYTHONPATH (
51
+ set "PYTHONPATH=%SLM_PKG_DIR%;%PYTHONPATH%"
52
+ ) else (
53
+ set "PYTHONPATH=%SLM_PKG_DIR%"
54
+ )
55
+
56
+ REM Prevent PyTorch Metal/MPS GPU memory reservation
57
+ set "PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0"
58
+ set "PYTORCH_MPS_MEM_LIMIT=0"
59
+ set "PYTORCH_ENABLE_MPS_FALLBACK=1"
60
+ set "TOKENIZERS_PARALLELISM=false"
61
+ set "TORCH_DEVICE=cpu"
62
+ set "CUDA_VISIBLE_DEVICES="
63
+
26
64
  %PYTHON_CMD% -m superlocalmemory.cli.main %*
27
65
  exit /b %ERRORLEVEL%
package/bin/slm.cmd CHANGED
@@ -1,5 +1,5 @@
1
1
  @echo off
2
2
  REM SuperLocalMemory V3 - Windows CLI (CMD variant)
3
- REM Calls slm.bat for compatibility
3
+ REM Delegates to slm.bat which handles PYTHONPATH and --version
4
4
  call "%~dp0slm.bat" %*
5
5
  exit /b %ERRORLEVEL%
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlocalmemory",
3
- "version": "3.1.1",
3
+ "version": "3.2.1",
4
4
  "description": "Information-geometric agent memory with mathematical guarantees. 4-channel retrieval, Fisher-Rao similarity, zero-LLM mode, EU AI Act compliant. Works with Claude, Cursor, Windsurf, and 17+ AI tools.",
5
5
  "keywords": [
6
6
  "ai-memory",
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "superlocalmemory"
3
- version = "3.1.1"
3
+ version = "3.2.1"
4
4
  description = "Information-geometric agent memory with mathematical guarantees"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -27,27 +27,29 @@ dependencies = [
27
27
  "uvicorn>=0.42.0",
28
28
  "websockets>=16.0",
29
29
  "lightgbm>=4.0.0",
30
+ "diskcache>=5.6.0",
31
+ "orjson>=3.9.0",
30
32
  ]
31
33
 
32
34
  [project.optional-dependencies]
33
35
  search = [
34
36
  "sentence-transformers>=2.5.0,<4.0.0",
35
- "einops>=0.7.0,<1.0.0",
37
+ "einops>=0.8.2",
36
38
  "torch>=2.2.0",
37
39
  "scikit-learn>=1.3.0,<2.0.0",
38
40
  "geoopt>=0.5.0",
39
41
  ]
40
42
  ui = [
41
- "fastapi>=0.109.0,<1.0.0",
42
- "uvicorn[standard]>=0.27.0,<1.0.0",
43
+ "fastapi[all]>=0.135.1",
44
+ "uvicorn>=0.42.0",
43
45
  "python-multipart>=0.0.6,<1.0.0",
44
46
  ]
45
47
  learning = [
46
- "lightgbm>=4.0.0,<5.0.0",
48
+ "lightgbm>=4.0.0",
47
49
  ]
48
50
  performance = [
49
- "diskcache>=5.6.0,<6.0.0",
50
- "orjson>=3.9.0,<4.0.0",
51
+ "diskcache>=5.6.0",
52
+ "orjson>=3.9.0",
51
53
  ]
52
54
  full = [
53
55
  "superlocalmemory[search,ui,learning,performance]",
@@ -102,6 +102,7 @@ const coreDeps = [
102
102
  'numpy>=1.26.0', 'scipy>=1.12.0', 'networkx>=3.0',
103
103
  'httpx>=0.24.0', 'python-dateutil>=2.9.0',
104
104
  'rank-bm25>=0.2.2', 'vaderSentiment>=3.3.2',
105
+ 'einops>=0.8.2', 'mcp>=1.0.0',
105
106
  ];
106
107
 
107
108
  if (pipInstall(coreDeps, 'core')) {
@@ -127,6 +128,68 @@ if (pipInstall(searchDeps, 'search')) {
127
128
  console.log(' pip install sentence-transformers einops geoopt');
128
129
  }
129
130
 
131
+ // Dashboard dependencies (IMPORTANT — enables web dashboard + MCP server)
132
+ const dashboardDeps = ['fastapi[all]>=0.135.1', 'uvicorn>=0.42.0', 'websockets>=16.0'];
133
+ console.log('\nInstalling dashboard & server dependencies...');
134
+ if (pipInstall(dashboardDeps, 'dashboard')) {
135
+ console.log('✓ Dashboard & MCP server dependencies installed (fastapi + uvicorn)');
136
+ } else {
137
+ console.log('⚠ Dashboard installation failed.');
138
+ console.log(' Run manually: pip install \'fastapi[all]\' uvicorn websockets');
139
+ }
140
+
141
+ // Learning dependencies (enables adaptive retrieval after 200+ signals)
142
+ const learningDeps = ['lightgbm>=4.0.0'];
143
+ console.log('\nInstalling learning engine...');
144
+ if (pipInstall(learningDeps, 'learning')) {
145
+ console.log('✓ Learning engine installed (lightgbm — adaptive ranking)');
146
+ } else {
147
+ console.log('⚠ Learning installation failed (retrieval still works without it).');
148
+ console.log(' Run manually: pip install lightgbm');
149
+ }
150
+
151
+ // Performance dependencies (optional — improves caching and JSON speed)
152
+ const perfDeps = ['diskcache>=5.6.0', 'orjson>=3.9.0'];
153
+ console.log('\nInstalling performance optimizations...');
154
+ if (pipInstall(perfDeps, 'performance')) {
155
+ console.log('✓ Performance optimizations installed (diskcache + orjson)');
156
+ } else {
157
+ console.log('⚠ Performance deps skipped (system works fine without them).');
158
+ }
159
+
160
+ // --- Step 3b: Install the superlocalmemory package itself ---
161
+ // This ensures `python -m superlocalmemory.cli.main` always resolves the
162
+ // correct version, even when invoked outside the Node.js wrapper (e.g.,
163
+ // via slm.bat on Windows or direct Python invocation).
164
+ console.log('\nInstalling superlocalmemory Python package...');
165
+ const pkgRoot = path.join(__dirname, '..');
166
+ const pipInstallPkg = spawnSync(pythonParts[0], [
167
+ ...pythonParts.slice(1), '-m', 'pip', 'install', '--quiet', '--disable-pip-version-check',
168
+ pkgRoot,
169
+ ], { stdio: 'pipe', timeout: 300000, env: { ...process.env, PATH: '/opt/homebrew/bin:/usr/local/bin:/usr/bin:' + (process.env.PATH || '') } });
170
+
171
+ if (pipInstallPkg.status === 0) {
172
+ console.log('✓ SuperLocalMemory Python package installed');
173
+ } else {
174
+ // Try with --user if PEP 668
175
+ const stderr = (pipInstallPkg.stderr || '').toString();
176
+ if (stderr.includes('externally-managed') || stderr.includes('PEP 668')) {
177
+ const retryResult = spawnSync(pythonParts[0], [
178
+ ...pythonParts.slice(1), '-m', 'pip', 'install', '--quiet', '--disable-pip-version-check',
179
+ '--user', pkgRoot,
180
+ ], { stdio: 'pipe', timeout: 300000, env: { ...process.env, PATH: '/opt/homebrew/bin:/usr/local/bin:/usr/bin:' + (process.env.PATH || '') } });
181
+ if (retryResult.status === 0) {
182
+ console.log('✓ SuperLocalMemory Python package installed (--user)');
183
+ } else {
184
+ console.log('⚠ Could not pip install the package. The Node.js wrapper (slm-npm)');
185
+ console.log(' sets PYTHONPATH automatically, so CLI will still work.');
186
+ }
187
+ } else {
188
+ console.log('⚠ Could not pip install the package. The Node.js wrapper (slm-npm)');
189
+ console.log(' sets PYTHONPATH automatically, so CLI will still work.');
190
+ }
191
+ }
192
+
130
193
  // --- Step 4: Detect V2 installation ---
131
194
  const V2_HOME = path.join(os.homedir(), '.claude-memory');
132
195
  if (fs.existsSync(V2_HOME) && fs.existsSync(path.join(V2_HOME, 'memory.db'))) {
@@ -149,13 +212,17 @@ console.log(' ✓ SuperLocalMemory V3 installed successfully!');
149
212
  console.log('');
150
213
  console.log(' Quick start:');
151
214
  console.log(' slm setup # First-time configuration');
152
- console.log(' slm status # Check system status');
215
+ console.log(' slm doctor # Pre-flight check (verify everything works)');
216
+ console.log(' slm warmup # Pre-download embedding model (~500MB)');
153
217
  console.log(' slm remember "..." # Store a memory');
154
218
  console.log(' slm recall "..." # Search memories');
219
+ console.log(' slm dashboard # Open 17-tab web dashboard');
155
220
  console.log('');
156
221
  console.log(' Prerequisites satisfied:');
157
222
  console.log(' ✓ Python 3.11+');
158
223
  console.log(' ✓ Core math & search libraries');
224
+ console.log(' ✓ Dashboard server (fastapi, uvicorn)');
225
+ console.log(' ✓ Learning engine (lightgbm)');
159
226
  console.log(' ✓ Data directory (~/.superlocalmemory/)');
160
227
  console.log('');
161
228
  console.log(' Docs: https://github.com/qualixar/superlocalmemory/wiki');
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * SuperLocalMemory V2 - NPM Preuninstall Script
3
+ * SuperLocalMemory V3 - NPM Preuninstall Script
4
4
  *
5
5
  * Copyright (c) 2026 Varun Pratap Bhardwaj
6
6
  * Solution Architect & Original Creator
@@ -16,7 +16,7 @@ const os = require('os');
16
16
  const fs = require('fs');
17
17
 
18
18
  console.log('\n════════════════════════════════════════════════════════════');
19
- console.log(' SuperLocalMemory V2 - Uninstalling');
19
+ console.log(' SuperLocalMemory V3 - Uninstalling');
20
20
  console.log('════════════════════════════════════════════════════════════\n');
21
21
 
22
22
  const SLM_DIR = path.join(os.homedir(), '.superlocalmemory');
@@ -32,6 +32,7 @@ def dispatch(args: Namespace) -> None:
32
32
  "update": cmd_update,
33
33
  "status": cmd_status,
34
34
  "health": cmd_health,
35
+ "doctor": cmd_doctor,
35
36
  "trace": cmd_trace,
36
37
  "mcp": cmd_mcp,
37
38
  "warmup": cmd_warmup,
@@ -592,6 +593,254 @@ def cmd_health(args: Namespace) -> None:
592
593
  print(f" Mode: {config.mode.value.upper()}")
593
594
 
594
595
 
596
+ def cmd_doctor(args: Namespace) -> None:
597
+ """Comprehensive pre-flight check — verify everything works."""
598
+ import shutil
599
+ from pathlib import Path
600
+
601
+ use_json = getattr(args, "json", False)
602
+ checks: list[dict] = []
603
+ passed = warned = failed = 0
604
+
605
+ def _check(name: str, status: str, detail: str, fix: str = ""):
606
+ nonlocal passed, warned, failed
607
+ checks.append({"name": name, "status": status, "detail": detail, "fix": fix})
608
+ if status == "PASS":
609
+ passed += 1
610
+ elif status == "WARN":
611
+ warned += 1
612
+ else:
613
+ failed += 1
614
+ if not use_json:
615
+ tag = {"PASS": "[PASS]", "WARN": "[WARN]", "FAIL": "[FAIL]"}[status]
616
+ line = f" {tag} {name}: {detail}"
617
+ if fix:
618
+ line += f"\n Fix: {fix}"
619
+ print(line)
620
+
621
+ if not use_json:
622
+ print("SuperLocalMemory V3 — Doctor (Pre-flight Check)")
623
+ print("=" * 50)
624
+ print()
625
+
626
+ # 1. Python version
627
+ v = sys.version_info
628
+ if v >= (3, 11):
629
+ _check("Python", "PASS", f"{v.major}.{v.minor}.{v.micro} (>= 3.11)")
630
+ else:
631
+ _check("Python", "FAIL", f"{v.major}.{v.minor}.{v.micro} (need >= 3.11)",
632
+ "Install Python 3.11+ from https://python.org/downloads/")
633
+
634
+ # 2. Core deps
635
+ core_modules = {
636
+ "numpy": "numpy", "scipy": "scipy", "networkx": "networkx",
637
+ "httpx": "httpx", "dateutil": "python-dateutil",
638
+ "rank_bm25": "rank-bm25", "vaderSentiment": "vadersentiment",
639
+ "einops": "einops",
640
+ }
641
+ core_ok, core_versions = [], []
642
+ for mod, pkg in core_modules.items():
643
+ try:
644
+ m = __import__(mod)
645
+ ver = getattr(m, "__version__", "?")
646
+ core_ok.append(mod)
647
+ core_versions.append(f"{mod} {ver}")
648
+ except ImportError:
649
+ pass
650
+ if len(core_ok) == len(core_modules):
651
+ _check("Core deps", "PASS", ", ".join(core_versions[:4]) + "...")
652
+ else:
653
+ missing = set(core_modules) - set(core_ok)
654
+ _check("Core deps", "FAIL", f"Missing: {', '.join(missing)}",
655
+ "pip install " + " ".join(core_modules[m] for m in missing))
656
+
657
+ # 3. Search deps
658
+ search_mods = {"sentence_transformers": "sentence-transformers", "torch": "torch",
659
+ "sklearn": "scikit-learn", "geoopt": "geoopt"}
660
+ search_ok = []
661
+ for mod, pkg in search_mods.items():
662
+ try:
663
+ __import__(mod)
664
+ search_ok.append(mod)
665
+ except ImportError:
666
+ pass
667
+ if len(search_ok) == len(search_mods):
668
+ _check("Search deps", "PASS", "sentence-transformers, torch, sklearn, geoopt")
669
+ else:
670
+ missing = set(search_mods) - set(search_ok)
671
+ _check("Search deps", "WARN", f"Missing: {', '.join(missing)}",
672
+ "pip install 'superlocalmemory[search]'")
673
+
674
+ # 4. Dashboard deps
675
+ dash_ok = True
676
+ for mod in ["fastapi", "uvicorn", "websockets"]:
677
+ try:
678
+ __import__(mod)
679
+ except ImportError:
680
+ dash_ok = False
681
+ break
682
+ if dash_ok:
683
+ _check("Dashboard deps", "PASS", "fastapi, uvicorn, websockets")
684
+ else:
685
+ _check("Dashboard deps", "WARN", "Missing dashboard deps",
686
+ "pip install 'fastapi[all]' uvicorn websockets")
687
+
688
+ # 5. Learning deps
689
+ try:
690
+ import lightgbm
691
+ _check("Learning deps", "PASS", f"lightgbm {lightgbm.__version__}")
692
+ except ImportError:
693
+ _check("Learning deps", "WARN", "lightgbm not installed",
694
+ "pip install lightgbm")
695
+ except OSError as exc:
696
+ _check("Learning deps", "WARN", f"lightgbm installed but broken: {exc}",
697
+ "brew install libomp && pip install --force-reinstall lightgbm")
698
+
699
+ # 6. Performance deps
700
+ perf_ok = []
701
+ for mod in ["diskcache", "orjson"]:
702
+ try:
703
+ __import__(mod)
704
+ perf_ok.append(mod)
705
+ except ImportError:
706
+ pass
707
+ if len(perf_ok) == 2:
708
+ _check("Performance deps", "PASS", "diskcache, orjson")
709
+ else:
710
+ missing = {"diskcache", "orjson"} - set(perf_ok)
711
+ _check("Performance deps", "WARN", f"Missing: {', '.join(missing)}",
712
+ "pip install diskcache orjson")
713
+
714
+ # 7. Embedding worker functional test
715
+ try:
716
+ import subprocess as _sp
717
+ import json as _json
718
+
719
+ env = {
720
+ **__import__("os").environ,
721
+ "CUDA_VISIBLE_DEVICES": "",
722
+ "PYTORCH_MPS_HIGH_WATERMARK_RATIO": "0.0",
723
+ "TOKENIZERS_PARALLELISM": "false",
724
+ "TORCH_DEVICE": "cpu",
725
+ }
726
+ proc = _sp.Popen(
727
+ [sys.executable, "-m", "superlocalmemory.core.embedding_worker"],
728
+ stdin=_sp.PIPE, stdout=_sp.PIPE, stderr=_sp.DEVNULL,
729
+ text=True, bufsize=1, env=env,
730
+ )
731
+ proc.stdin.write(_json.dumps({"cmd": "ping"}) + "\n")
732
+ proc.stdin.flush()
733
+
734
+ import select as _sel
735
+ ready, _, _ = _sel.select([proc.stdout], [], [], 30)
736
+ if ready:
737
+ resp = _json.loads(proc.stdout.readline())
738
+ if resp.get("ok"):
739
+ _check("Embedding worker", "PASS",
740
+ f"responsive (PID {proc.pid}, Python {sys.executable})")
741
+ else:
742
+ _check("Embedding worker", "FAIL",
743
+ f"error: {resp.get('error', 'unknown')}",
744
+ "pip install sentence-transformers einops torch")
745
+ else:
746
+ _check("Embedding worker", "FAIL", "timed out (30s)",
747
+ "slm warmup")
748
+ proc.stdin.write(_json.dumps({"cmd": "quit"}) + "\n")
749
+ proc.stdin.flush()
750
+ proc.wait(timeout=5)
751
+ except FileNotFoundError:
752
+ _check("Embedding worker", "FAIL", "embedding_worker module not found",
753
+ "Reinstall: npm install -g superlocalmemory")
754
+ except Exception as exc:
755
+ _check("Embedding worker", "FAIL", str(exc),
756
+ "slm warmup")
757
+
758
+ # 8. Ollama connectivity (Mode B only)
759
+ try:
760
+ from superlocalmemory.core.config import SLMConfig
761
+ config = SLMConfig.load()
762
+ if config.mode.value == "b":
763
+ import httpx
764
+ try:
765
+ resp = httpx.get(
766
+ f"{config.llm.api_base}/api/tags", timeout=5.0,
767
+ )
768
+ if resp.status_code == 200:
769
+ models = [m["name"].split(":")[0] for m in resp.json().get("models", [])]
770
+ has_llm = config.llm.model.split(":")[0] in models
771
+ if has_llm:
772
+ _check("Ollama", "PASS",
773
+ f"running, {len(models)} models, '{config.llm.model}' available")
774
+ else:
775
+ _check("Ollama", "WARN",
776
+ f"running but '{config.llm.model}' not pulled",
777
+ f"ollama pull {config.llm.model}")
778
+ else:
779
+ _check("Ollama", "WARN", f"HTTP {resp.status_code}",
780
+ "brew services start ollama")
781
+ except Exception:
782
+ _check("Ollama", "WARN", "not reachable at " + config.llm.api_base,
783
+ "brew services start ollama")
784
+ elif config.mode.value == "c":
785
+ # Mode C — check API key
786
+ if config.llm.api_key:
787
+ _check("API key", "PASS",
788
+ f"provider={config.llm.provider}, key=***{config.llm.api_key[-4:]}")
789
+ else:
790
+ _check("API key", "WARN", "no API key configured",
791
+ "slm provider set")
792
+ except Exception:
793
+ pass # Config load failed — already caught above
794
+
795
+ # 9. Disk space
796
+ slm_home = Path.home() / ".superlocalmemory"
797
+ try:
798
+ usage = shutil.disk_usage(slm_home if slm_home.exists() else Path.home())
799
+ free_gb = usage.free / (1024 ** 3)
800
+ if free_gb >= 2.0:
801
+ _check("Disk space", "PASS", f"{free_gb:.1f} GB free")
802
+ else:
803
+ _check("Disk space", "WARN", f"{free_gb:.1f} GB free (< 2 GB)",
804
+ "Free up disk space")
805
+ except Exception:
806
+ pass
807
+
808
+ # 10. Database integrity
809
+ db_path = slm_home / "memory.db"
810
+ if db_path.exists():
811
+ try:
812
+ import sqlite3
813
+ conn = sqlite3.connect(str(db_path))
814
+ result = conn.execute("PRAGMA integrity_check").fetchone()
815
+ conn.close()
816
+ if result and result[0] == "ok":
817
+ size_mb = db_path.stat().st_size / (1024 * 1024)
818
+ _check("Database", "PASS", f"OK ({size_mb:.2f} MB)")
819
+ else:
820
+ _check("Database", "FAIL", f"integrity check: {result}",
821
+ "Backup and recreate database")
822
+ except Exception as exc:
823
+ _check("Database", "FAIL", str(exc))
824
+ else:
825
+ _check("Database", "PASS", "not yet created (will initialize on first use)")
826
+
827
+ # Summary
828
+ if use_json:
829
+ from superlocalmemory.cli.json_output import json_print
830
+ next_actions = []
831
+ for c in checks:
832
+ if c["fix"]:
833
+ next_actions.append({"command": c["fix"], "description": f"Fix {c['name']}"})
834
+ json_print("doctor", data={
835
+ "checks": checks,
836
+ "summary": {"passed": passed, "warned": warned, "failed": failed},
837
+ }, next_actions=next_actions)
838
+ else:
839
+ print(f"\nSummary: {passed} passed, {warned} warnings, {failed} failed")
840
+ if failed > 0:
841
+ print("Run the suggested fix commands above, then re-run: slm doctor")
842
+
843
+
595
844
  def cmd_trace(args: Namespace) -> None:
596
845
  """Recall with per-channel score breakdown."""
597
846
  from superlocalmemory.core.engine import MemoryEngine
@@ -654,35 +903,74 @@ def cmd_mcp(_args: Namespace) -> None:
654
903
 
655
904
  def cmd_warmup(_args: Namespace) -> None:
656
905
  """Pre-download the embedding model so first use is instant."""
657
- print("Downloading embedding model (nomic-ai/nomic-embed-text-v1.5)...")
658
- print("This is ~500MB and only needed once.\n")
906
+ import superlocalmemory.core.embeddings as _emb_mod
907
+
908
+ print("SuperLocalMemory V3 — Embedding Model Warmup")
909
+ print("=" * 50)
910
+ print(f" Python: {sys.executable}")
911
+ print(f" Model: nomic-ai/nomic-embed-text-v1.5 (~500MB)")
912
+ print()
913
+
914
+ # Increase timeout for first-time download
915
+ original_timeout = _emb_mod._SUBPROCESS_RESPONSE_TIMEOUT
916
+ _emb_mod._SUBPROCESS_RESPONSE_TIMEOUT = 180 # 3 min for cold start
659
917
 
660
918
  try:
661
919
  from superlocalmemory.core.config import EmbeddingConfig
662
920
  from superlocalmemory.core.embeddings import EmbeddingService
663
921
 
664
922
  config = EmbeddingConfig()
923
+
924
+ print("Step 1/3: Spawning embedding worker subprocess...")
665
925
  svc = EmbeddingService(config)
666
926
 
667
- # Force model load (triggers download)
668
- if svc.is_available:
669
- # Verify it works
670
- emb = svc.embed("warmup test")
671
- if emb and len(emb) == config.dimension:
672
- print(f"\nModel ready: {config.model_name} ({config.dimension}-dim)")
673
- print("Semantic search is fully operational.")
674
- else:
675
- print("\nModel loaded but embedding verification failed.")
676
- print("Run: pip install sentence-transformers einops")
927
+ if not svc.is_available:
928
+ print("\n[FAIL] Embedding service not available.")
929
+ _warmup_diagnose()
930
+ return
931
+
932
+ print("Step 2/3: Loading model (may download ~500MB on first run)...")
933
+ emb = svc.embed("warmup test")
934
+
935
+ if emb and len(emb) == config.dimension:
936
+ print("Step 3/3: Verifying embedding output...")
937
+ print(f"\n[PASS] Model ready: {config.model_name} ({config.dimension}-dim)")
938
+ print("Semantic search is fully operational.")
677
939
  else:
678
- print("\nModel could not load.")
679
- print("Install dependencies: pip install sentence-transformers einops torch")
940
+ print("\n[FAIL] Model loaded but embedding verification failed.")
941
+ _warmup_diagnose()
942
+
680
943
  except ImportError as exc:
681
- print(f"\nMissing dependency: {exc}")
682
- print("Install with: pip install sentence-transformers einops torch")
944
+ print(f"\n[FAIL] Missing dependency: {exc}")
945
+ print("Fix: pip install sentence-transformers einops torch")
683
946
  except Exception as exc:
684
- print(f"\nWarmup failed: {exc}")
685
- print("Check your internet connection and try again.")
947
+ print(f"\n[FAIL] Warmup failed: {exc}")
948
+ _warmup_diagnose()
949
+ finally:
950
+ _emb_mod._SUBPROCESS_RESPONSE_TIMEOUT = original_timeout
951
+
952
+
953
+ def _warmup_diagnose() -> None:
954
+ """Diagnostic helper when warmup fails."""
955
+ print("\nDiagnosing...")
956
+ print(f" Python executable: {sys.executable}")
957
+ try:
958
+ from sentence_transformers import SentenceTransformer
959
+ print(" sentence-transformers: importable")
960
+ m = SentenceTransformer(
961
+ "nomic-ai/nomic-embed-text-v1.5", trust_remote_code=True, device="cpu",
962
+ )
963
+ v = m.encode(["test"], normalize_embeddings=True)
964
+ print(f" Direct embed: OK (dim={v.shape[1]})")
965
+ print("\n Issue: Subprocess worker failed but direct import works.")
966
+ print(" This is likely a Python path mismatch between Node.js wrapper")
967
+ print(" and your current shell. Run: slm doctor")
968
+ except ImportError as ie:
969
+ print(f" sentence-transformers: NOT importable ({ie})")
970
+ print(" Fix: pip install sentence-transformers einops torch")
971
+ except Exception as de:
972
+ print(f" Direct embed failed: {de}")
973
+ print(" Run: slm doctor")
686
974
 
687
975
 
688
976
  def cmd_dashboard(args: Namespace) -> None:
@@ -690,7 +978,8 @@ def cmd_dashboard(args: Namespace) -> None:
690
978
  try:
691
979
  import uvicorn
692
980
  except ImportError:
693
- print("Dashboard requires: pip install 'fastapi[all]' uvicorn")
981
+ print("Dashboard requires additional deps. Run: slm doctor")
982
+ print("Or install manually: pip install 'fastapi[all]' uvicorn")
694
983
  sys.exit(1)
695
984
 
696
985
  import socket
@@ -153,6 +153,10 @@ def main() -> None:
153
153
  trace_p.add_argument("query", help="Search query")
154
154
  trace_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
155
155
 
156
+ # -- Diagnostics (continued) ----------------------------------------
157
+ doctor_p = sub.add_parser("doctor", help="Pre-flight check: deps, embedding worker, connectivity")
158
+ doctor_p.add_argument("--json", action="store_true", help="Output structured JSON (agent-native)")
159
+
156
160
  # -- Services ------------------------------------------------------
157
161
  sub.add_parser("mcp", help="Start MCP server (stdio transport for IDE integration)")
158
162
  sub.add_parser("warmup", help="Pre-download embedding model (~500MB, one-time)")
@@ -366,7 +366,7 @@ class SLMConfig:
366
366
  ),
367
367
  llm=LLMConfig(
368
368
  provider=llm_provider or "ollama",
369
- model=llm_model or "phi3:mini",
369
+ model=llm_model or "llama3.2",
370
370
  api_base=llm_api_base or "http://localhost:11434",
371
371
  api_key=llm_api_key or "",
372
372
  ),
@@ -164,7 +164,12 @@ class EmbeddingService:
164
164
  _SUBPROCESS_RESPONSE_TIMEOUT,
165
165
  )
166
166
  if not resp_line:
167
- logger.warning("Worker returned empty or timed out, restarting")
167
+ logger.warning(
168
+ "Embedding worker timed out after %ds. On first run, model "
169
+ "download can take several minutes. Run 'slm doctor' to "
170
+ "diagnose or 'slm warmup' to pre-download the model.",
171
+ _SUBPROCESS_RESPONSE_TIMEOUT,
172
+ )
168
173
  self._kill_worker()
169
174
  return None
170
175
  resp = json.loads(resp_line)
@@ -174,7 +179,11 @@ class EmbeddingService:
174
179
  self._reset_idle_timer()
175
180
  return resp["vectors"]
176
181
  except (BrokenPipeError, OSError, json.JSONDecodeError) as exc:
177
- logger.warning("Worker communication failed: %s", exc)
182
+ logger.warning(
183
+ "Embedding worker communication failed: %s. "
184
+ "Run 'slm doctor' to check dependencies and Python version.",
185
+ exc,
186
+ )
178
187
  self._kill_worker()
179
188
  return None
180
189
 
@@ -231,7 +240,12 @@ class EmbeddingService:
231
240
  logger.info("Embedding worker spawned (PID %d)", self._worker_proc.pid)
232
241
  self._worker_ready = True
233
242
  except Exception as exc:
234
- logger.warning("Failed to spawn embedding worker: %s", exc)
243
+ logger.warning(
244
+ "Failed to spawn embedding worker: %s. "
245
+ "Run 'slm doctor' to verify your Python environment. "
246
+ "Using Python: %s",
247
+ exc, sys.executable,
248
+ )
235
249
  self._available = False
236
250
  self._worker_proc = None
237
251
 
@@ -175,9 +175,9 @@ class MemoryEngine:
175
175
  emb = cls(emb_cfg)
176
176
  if emb.is_available:
177
177
  return emb
178
- logger.warning("EmbeddingService not available. BM25-only mode.")
178
+ logger.warning("EmbeddingService not available. BM25-only mode. Run 'slm doctor' to diagnose.")
179
179
  except Exception as exc:
180
- logger.warning("Embeddings unavailable (%s). BM25-only mode.", exc)
180
+ logger.warning("Embeddings unavailable (%s). BM25-only mode. Run 'slm doctor' to diagnose.", exc)
181
181
  return None
182
182
 
183
183
  def store(
@@ -169,7 +169,7 @@ class WorkerPool:
169
169
 
170
170
  resp_line = self._proc.stdout.readline()
171
171
  if not resp_line:
172
- logger.warning("Worker returned empty, restarting")
172
+ logger.warning("Worker returned empty, restarting. Run 'slm doctor' to diagnose.")
173
173
  self._kill()
174
174
  return {"ok": False, "error": "Worker died"}
175
175
 
@@ -177,7 +177,7 @@ class WorkerPool:
177
177
  return json.loads(resp_line)
178
178
 
179
179
  except (BrokenPipeError, OSError, json.JSONDecodeError) as exc:
180
- logger.warning("Worker communication failed: %s", exc)
180
+ logger.warning("Worker communication failed: %s. Run 'slm doctor' to diagnose.", exc)
181
181
  self._kill()
182
182
  return {"ok": False, "error": str(exc)}
183
183
 
@@ -207,7 +207,7 @@ class WorkerPool:
207
207
  )
208
208
  logger.info("Recall worker spawned (PID %d)", self._proc.pid)
209
209
  except Exception as exc:
210
- logger.error("Failed to spawn recall worker: %s", exc)
210
+ logger.error("Failed to spawn recall worker: %s. Run 'slm doctor' to diagnose. Python: %s", exc, sys.executable)
211
211
  self._proc = None
212
212
 
213
213
  def _kill(self) -> None: