superlocalmemory 4.1.19 → 4.1.21

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 (45) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +189 -0
  3. package/README.md +227 -601
  4. package/package.json +1 -1
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/.mcp.json +7 -2
  7. package/plugin/CLAUDE.md +3 -3
  8. package/plugin/agents/slm-governance-advisor.md +1 -1
  9. package/plugin/agents/slm-loop-runner.md +1 -1
  10. package/plugin/agents/slm-memory-advisor.md +1 -1
  11. package/plugin/agents/slm-optimize-advisor.md +1 -1
  12. package/plugin/hooks/hooks.json +9 -9
  13. package/plugin/requirements.txt +1 -1
  14. package/plugin/scripts/ensure-venv.sh +23 -1
  15. package/plugin/scripts/slm-launch +60 -68
  16. package/plugin/scripts/slm-launch.bat +10 -1
  17. package/plugin/scripts/slm-resolve.sh +128 -0
  18. package/plugin/scripts/slm-run +76 -0
  19. package/plugin/skills/slm-cache/SKILL.md +1 -1
  20. package/plugin/skills/slm-compress/SKILL.md +1 -1
  21. package/plugin/skills/slm-governance/SKILL.md +1 -1
  22. package/plugin/skills/slm-graph/SKILL.md +1 -1
  23. package/plugin/skills/slm-loop/SKILL.md +1 -1
  24. package/plugin/skills/slm-mesh/SKILL.md +1 -1
  25. package/plugin/skills/slm-profile/SKILL.md +3 -3
  26. package/plugin/skills/slm-recall/SKILL.md +1 -1
  27. package/plugin/skills/slm-remember/SKILL.md +1 -1
  28. package/plugin/skills/slm-scope/SKILL.md +1 -1
  29. package/plugin/skills/slm-session/SKILL.md +1 -1
  30. package/plugin/skills/slm-status/SKILL.md +1 -1
  31. package/plugin-src/agents/slm-memory-advisor.md +1 -1
  32. package/plugin-src/agents/slm-optimize-advisor.md +1 -1
  33. package/plugin-src/rules/AGENTS.md +3 -3
  34. package/plugin-src/skills/slm-cache/SKILL.md +1 -1
  35. package/plugin-src/skills/slm-compress/SKILL.md +1 -1
  36. package/plugin-src/skills/slm-governance/SKILL.md +1 -1
  37. package/plugin-src/skills/slm-graph/SKILL.md +1 -1
  38. package/plugin-src/skills/slm-loop/SKILL.md +1 -1
  39. package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
  40. package/plugin-src/skills/slm-profile/SKILL.md +3 -3
  41. package/plugin-src/skills/slm-recall/SKILL.md +1 -1
  42. package/plugin-src/skills/slm-remember/SKILL.md +1 -1
  43. package/plugin-src/skills/slm-scope/SKILL.md +1 -1
  44. package/plugin-src/skills/slm-session/SKILL.md +1 -1
  45. package/plugin-src/skills/slm-status/SKILL.md +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "superlocalmemory",
3
- "version": "4.1.19",
3
+ "version": "4.1.21",
4
4
  "description": "Local-first agent memory with MCP and an agent-native CLI. Documented clients include Claude Code, Cursor, and Windsurf.",
5
5
  "keywords": [
6
6
  "ai-memory",
@@ -15,5 +15,5 @@
15
15
  "mcpServers": "./.mcp.json",
16
16
  "name": "superlocalmemory",
17
17
  "repository": "https://github.com/qualixar/superlocalmemory",
18
- "version": "4.1.19"
18
+ "version": "4.1.21"
19
19
  }
package/plugin/.mcp.json CHANGED
@@ -1,8 +1,13 @@
1
1
  {
2
2
  "mcpServers": {
3
3
  "superlocalmemory": {
4
- "command": "${CLAUDE_PLUGIN_ROOT}/scripts/slm-launch",
5
- "args": [],
4
+ "command": "${ComSpec:-${CLAUDE_PLUGIN_ROOT}/scripts/slm-launch}",
5
+ "args": [
6
+ "/d",
7
+ "/s",
8
+ "/c",
9
+ "where.exe /q slm || (echo SLM plugin: slm is not on PATH. On Windows the plugin runs the SuperLocalMemory you installed. Install it first with: pipx install superlocalmemory or: pip install superlocalmemory then restart your editor. 1>&2 & exit 1) & slm serve start 1>&2 & slm mcp"
10
+ ],
6
11
  "env": {
7
12
  "SLM_AGENT_ID": "claude_code"
8
13
  }
package/plugin/CLAUDE.md CHANGED
@@ -1,4 +1,4 @@
1
- <!-- BEGIN SuperLocalMemory v4.1.19 -->
1
+ <!-- BEGIN SuperLocalMemory v4.1.21 -->
2
2
 
3
3
  ## SuperLocalMemory (SLM) — Agent Rules
4
4
 
@@ -40,6 +40,6 @@ slm-recall · slm-remember · slm-session · slm-status · slm-cache · slm-comp
40
40
  ### Subagents
41
41
  slm-memory-advisor (memory decisions, session hygiene, scope/profile guidance) · slm-optimize-advisor (context compression + KV cache) · slm-governance-advisor (scope/roles/compliance/GDPR)
42
42
 
43
- <!-- END SuperLocalMemory v4.1.19 -->
43
+ <!-- END SuperLocalMemory v4.1.21 -->
44
44
 
45
- SuperLocalMemory v4.1.19 · Qualixar · AGPL-3.0-or-later
45
+ SuperLocalMemory v4.1.21 · Qualixar · AGPL-3.0-or-later
@@ -77,4 +77,4 @@ slm-scope · slm-governance · slm-profile · slm-remember · slm-recall
77
77
  # What NOT to do
78
78
  Never session_init twice; never forget without dry-run preview; never store secrets; never bypass role checks; never claim an erasure succeeded without verifying via recall.
79
79
 
80
- SuperLocalMemory v4.1.19 · Qualixar · AGPL-3.0-or-later
80
+ SuperLocalMemory v4.1.21 · Qualixar · AGPL-3.0-or-later
@@ -68,4 +68,4 @@ assessment. The gate is the authority.
68
68
 
69
69
  ---
70
70
 
71
- SuperLocalMemory v4.1.19 · Qualixar · AGPL-3.0-or-later
71
+ SuperLocalMemory v4.1.21 · Qualixar · AGPL-3.0-or-later
@@ -47,4 +47,4 @@ slm-recall · slm-remember · slm-session · slm-scope · slm-profile · slm-gov
47
47
  # What NOT to do
48
48
  Never session_init twice; never forget dry_run=False without reporting preview; never dump a whole file into remember; never invent a memory; never claim "saved" without success:true / clean CLI exit; never bypass scope or governance restrictions.
49
49
 
50
- SuperLocalMemory v4.1.19 · Qualixar · AGPL-3.0-or-later
50
+ SuperLocalMemory v4.1.21 · Qualixar · AGPL-3.0-or-later
@@ -41,4 +41,4 @@ slm-compress · slm-cache · slm-status · slm-profile
41
41
  # What NOT to do
42
42
  Never compress code-for-edit/JSON-to-parse/<500 chars; never store secrets/ccr_ids; never let optimize failure block/alter the task; never claim a specific savings %; never carry ccr_ids across profile switches.
43
43
 
44
- SuperLocalMemory v4.1.19 · Qualixar · AGPL-3.0-or-later
44
+ SuperLocalMemory v4.1.21 · Qualixar · AGPL-3.0-or-later
@@ -5,7 +5,7 @@
5
5
  "hooks": [
6
6
  {
7
7
  "type": "command",
8
- "command": "slm hook mandate 2>/dev/null || true",
8
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/slm-run\" hook mandate || true",
9
9
  "timeout": 5000
10
10
  }
11
11
  ]
@@ -22,7 +22,7 @@
22
22
  "hooks": [
23
23
  {
24
24
  "type": "command",
25
- "command": "slm hook start 2>/dev/null || true",
25
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/slm-run\" hook start || true",
26
26
  "timeout": 15000
27
27
  }
28
28
  ]
@@ -34,7 +34,7 @@
34
34
  "hooks": [
35
35
  {
36
36
  "type": "command",
37
- "command": "slm hook checkpoint 2>/dev/null || true",
37
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/slm-run\" hook checkpoint || true",
38
38
  "timeout": 5000
39
39
  }
40
40
  ]
@@ -44,7 +44,7 @@
44
44
  "hooks": [
45
45
  {
46
46
  "type": "command",
47
- "command": "slm hook post_tool_outcome 2>/dev/null || true",
47
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/slm-run\" hook post_tool_outcome || true",
48
48
  "timeout": 5000
49
49
  }
50
50
  ]
@@ -55,7 +55,7 @@
55
55
  "hooks": [
56
56
  {
57
57
  "type": "command",
58
- "command": "slm hook user_prompt_rehash 2>/dev/null || true",
58
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/slm-run\" hook user_prompt_rehash || true",
59
59
  "timeout": 5000
60
60
  }
61
61
  ]
@@ -64,7 +64,7 @@
64
64
  "hooks": [
65
65
  {
66
66
  "type": "command",
67
- "command": "slm hook topic_shift 2>/dev/null || true",
67
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/slm-run\" hook topic_shift || true",
68
68
  "timeout": 3000
69
69
  }
70
70
  ]
@@ -75,12 +75,12 @@
75
75
  "hooks": [
76
76
  {
77
77
  "type": "command",
78
- "command": "slm hook stop 2>/dev/null || true",
78
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/slm-run\" hook stop || true",
79
79
  "timeout": 10000
80
80
  },
81
81
  {
82
82
  "type": "command",
83
- "command": "slm hook stop_outcome 2>/dev/null || true",
83
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/slm-run\" hook stop_outcome || true",
84
84
  "timeout": 10000
85
85
  }
86
86
  ]
@@ -92,7 +92,7 @@
92
92
  "hooks": [
93
93
  {
94
94
  "type": "command",
95
- "command": "slm hook before_web 2>/dev/null || true",
95
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/slm-run\" hook before_web || true",
96
96
  "timeout": 5000
97
97
  }
98
98
  ]
@@ -1 +1 @@
1
- superlocalmemory==4.1.19
1
+ superlocalmemory==4.1.21
@@ -8,7 +8,7 @@
8
8
  # CLAUDE_PLUGIN_ROOT — plugin installation dir (ephemeral, contains scripts/ + requirements.txt)
9
9
  # CLAUDE_PLUGIN_DATA — persistent data dir (venv lives here, survives plugin updates)
10
10
  #
11
- # Exit codes: 0 = venv ready non-0 = failure (logged to stderr)
11
+ # Exit codes: 0 = venv ready, or not needed non-0 = failure (logged to stderr)
12
12
  # All output goes to stderr only (stdout reserved for MCP stdio protocol).
13
13
 
14
14
  set -euo pipefail
@@ -24,6 +24,28 @@ set -euo pipefail
24
24
  # ---------------------------------------------------------------------------
25
25
  exec 1>&2
26
26
 
27
+ # ---------------------------------------------------------------------------
28
+ # Only build a venv that will be used.
29
+ #
30
+ # slm-launch prefers an slm that is already installed, and SLM_LAUNCHER can name
31
+ # one outright. In those cases the plugin venv is never run, so neither its
32
+ # Python requirement nor its install cost should land on this session. The
33
+ # question is answered by slm-resolve.sh, the same code the launcher and the
34
+ # hooks use, so the three can never disagree about which slm is in play.
35
+ # ---------------------------------------------------------------------------
36
+ # shellcheck source=slm-resolve.sh
37
+ . "$(dirname "${BASH_SOURCE[0]}")/slm-resolve.sh"
38
+ if ! slm_choose; then
39
+ # A venv cannot fix a launcher that is configured to use something else.
40
+ # Report it, and leave the session alone: the MCP server reports it again.
41
+ echo "SLM plugin: venv not needed — ${SLM_RESOLVE_ERROR}" >&2
42
+ exit 0
43
+ fi
44
+ if [ "${SLM_CHOICE}" != "plugin" ]; then
45
+ echo "SLM plugin: venv not needed — using ${SLM_CHOICE_BIN} (${SLM_CHOICE})." >&2
46
+ exit 0
47
+ fi
48
+
27
49
  # ---------------------------------------------------------------------------
28
50
  # Python >= 3.12 guard
29
51
  # ---------------------------------------------------------------------------
@@ -31,7 +31,12 @@
31
31
  # user's decision, and a plugin that narrows it takes away tools they configured.
32
32
 
33
33
  _SLM_DATA="${SLM_DATA_DIR:-$HOME/.superlocalmemory}"
34
- _PLUGIN_BIN="${CLAUDE_PLUGIN_DATA:+${CLAUDE_PLUGIN_DATA}/venv/bin/slm}"
34
+
35
+ # Which slm to run is decided in ONE place, shared with the hooks and the venv
36
+ # bootstrap, so the three can never disagree about which install is in play.
37
+ # shellcheck source=slm-resolve.sh
38
+ . "$(dirname "${BASH_SOURCE[0]}")/slm-resolve.sh"
39
+ _PLUGIN_BIN="$(slm_plugin_bin)"
35
40
 
36
41
  # Is a daemon already up for this data dir? If so we join it rather than boot a
37
42
  # second one — one writer for every session that shares the store.
@@ -41,91 +46,78 @@ if [ -f "${_SLM_DATA}/daemon.pid" ] \
41
46
  _daemon_running=1
42
47
  fi
43
48
 
44
- # Expand a leading ~ WITHOUT eval — eval on an environment value is a
45
- # command-injection foot-gun. Only tilde expansion; pass expanded paths for vars.
46
- _expand_tilde() {
47
- case "$1" in
48
- "~"*) printf '%s' "${HOME}${1#\~}" ;;
49
- *) printf '%s' "$1" ;;
50
- esac
51
- }
52
-
53
49
  # Start a daemon with the chosen binary unless one is already serving.
54
50
  _ensure_daemon() {
55
51
  [ "${_daemon_running}" -eq 1 ] && return 0
56
52
  "$1" serve start >&2
57
53
  }
58
54
 
59
- case "${SLM_LAUNCHER:-auto}" in
60
- auto)
61
- # 1. An slm already on PATH — the pip, pipx or npm install the user has.
62
- # This is the common case and the one that must not be forked.
63
- if command -v slm >/dev/null 2>&1; then
64
- # Both orderings end up here, and both must converge on ONE
65
- # environment:
66
- # installed first, plugin second -> nothing was ever forked;
67
- # plugin first, installed second -> the plugin venv is now the
68
- # older copy and must stop being used.
69
- # They already share one store, because this launcher sets no
70
- # SLM_DATA_DIR and both resolve ~/.superlocalmemory. What is left to
71
- # go wrong is two DIFFERENT VERSIONS serving that one store: whoever
72
- # starts the daemon first decides which code the other one talks to.
73
- # So when a leftover plugin venv is found beside a real install, say
74
- # so, with both versions named. Silent duplication is how a store
75
- # ends up served by a release nobody thinks they are running.
76
- if [ -n "${_PLUGIN_BIN}" ] && [ -x "${_PLUGIN_BIN}" ]; then
77
- _sys_v="$(slm --version 2>/dev/null | awk '{print $NF}')"
78
- _plg_v="$("${_PLUGIN_BIN}" --version 2>/dev/null | awk '{print $NF}')"
79
- if [ -n "${_plg_v}" ] && [ "${_sys_v}" != "${_plg_v}" ]; then
80
- echo "SLM plugin: using the installed slm ${_sys_v:-?} and IGNORING a leftover plugin venv at ${_PLUGIN_BIN} (${_plg_v})." >&2
81
- echo " One store, one version. Remove the venv to stop it drifting: rm -rf \"${CLAUDE_PLUGIN_DATA}/venv\"" >&2
82
- fi
83
- fi
84
- _ensure_daemon "$(command -v slm)" || \
85
- echo "SLM plugin: daemon did not start; continuing as a direct MCP writer." >&2
86
- exec slm mcp
87
- fi
88
- # 2. No system install. Use the plugin's own venv if this host gave us
89
- # somewhere to keep one.
55
+ if ! slm_choose; then
56
+ echo "${SLM_RESOLVE_ERROR}" >&2
57
+ exit 1
58
+ fi
59
+
60
+ case "${SLM_LAUNCHER:-auto}:${SLM_CHOICE}" in
61
+ auto:system)
62
+ # An slm already on PATH — the pip, pipx or npm install the user has.
63
+ # This is the common case and the one that must not be forked.
64
+ #
65
+ # Both orderings end up here, and both must converge on ONE
66
+ # environment:
67
+ # installed first, plugin second -> nothing was ever forked;
68
+ # plugin first, installed second -> the plugin venv is now the
69
+ # older copy and must stop being used.
70
+ # They already share one store, because this launcher sets no
71
+ # SLM_DATA_DIR and both resolve ~/.superlocalmemory. What is left to
72
+ # go wrong is two DIFFERENT VERSIONS serving that one store: whoever
73
+ # starts the daemon first decides which code the other one talks to.
74
+ # So when a leftover plugin venv is found beside a real install, say
75
+ # so, with both versions named. Silent duplication is how a store
76
+ # ends up served by a release nobody thinks they are running.
90
77
  if [ -n "${_PLUGIN_BIN}" ] && [ -x "${_PLUGIN_BIN}" ]; then
91
- if ! _ensure_daemon "${_PLUGIN_BIN}"; then
92
- echo "SLM plugin: unable to start the owned daemon; refusing a direct MCP writer." >&2
93
- exit 1
78
+ _sys_v="$(slm --version 2>/dev/null | awk '{print $NF}')"
79
+ _plg_v="$("${_PLUGIN_BIN}" --version 2>/dev/null | awk '{print $NF}')"
80
+ if [ -n "${_plg_v}" ] && [ "${_sys_v}" != "${_plg_v}" ]; then
81
+ echo "SLM plugin: using the installed slm ${_sys_v:-?} and IGNORING a leftover plugin venv at ${_PLUGIN_BIN} (${_plg_v})." >&2
82
+ echo " One store, one version. Remove the venv to stop it drifting: rm -rf \"${CLAUDE_PLUGIN_DATA}/venv\"" >&2
94
83
  fi
95
- exec "${_PLUGIN_BIN}" mcp
96
84
  fi
97
- # 3. Neither. Say which two things were looked for, so the fix is obvious.
98
- echo "SLM plugin: no SuperLocalMemory found." >&2
99
- echo " Looked for: 'slm' on PATH, and ${_PLUGIN_BIN:-a plugin venv (this host sets no plugin data dir)}." >&2
100
- echo " Install it with: pipx install superlocalmemory" >&2
101
- exit 1
102
- ;;
103
- system)
104
- if ! command -v slm >/dev/null 2>&1; then
105
- echo "SLM_LAUNCHER=system but no 'slm' on PATH." >&2
106
- exit 1
107
- fi
108
- _ensure_daemon "$(command -v slm)" || true
85
+ _ensure_daemon "${SLM_CHOICE_BIN}" || \
86
+ echo "SLM plugin: daemon did not start; continuing as a direct MCP writer." >&2
109
87
  exec slm mcp
110
88
  ;;
111
- plugin)
112
- if [ -z "${_PLUGIN_BIN}" ]; then
113
- echo "SLM_LAUNCHER=plugin but this host sets no CLAUDE_PLUGIN_DATA." >&2
89
+ auto:plugin)
90
+ # No system install. Use the plugin's own venv, once SessionStart has
91
+ # built it. If it is not there either, say which two things were looked
92
+ # for, so the fix is obvious.
93
+ if [ "${SLM_CHOICE_READY}" -ne 1 ]; then
94
+ slm_not_found_message >&2
95
+ echo >&2
114
96
  exit 1
115
97
  fi
116
- if ! _ensure_daemon "${_PLUGIN_BIN}"; then
98
+ if ! _ensure_daemon "${SLM_CHOICE_BIN}"; then
117
99
  echo "SLM plugin: unable to start the owned daemon; refusing a direct MCP writer." >&2
118
100
  exit 1
119
101
  fi
120
- exec "${_PLUGIN_BIN}" mcp
102
+ exec "${SLM_CHOICE_BIN}" mcp
121
103
  ;;
122
- *)
123
- _SLM_BIN="$(_expand_tilde "${SLM_LAUNCHER}")"
124
- if [ ! -x "${_SLM_BIN}" ]; then
125
- echo "SLM_LAUNCHER is not an executable slm binary: ${_SLM_BIN}" >&2
104
+ system:system)
105
+ _ensure_daemon "${SLM_CHOICE_BIN}" || true
106
+ exec slm mcp
107
+ ;;
108
+ plugin:plugin)
109
+ if ! _ensure_daemon "${SLM_CHOICE_BIN}"; then
110
+ echo "SLM plugin: unable to start the owned daemon; refusing a direct MCP writer." >&2
126
111
  exit 1
127
112
  fi
128
- _ensure_daemon "${_SLM_BIN}" || true
129
- exec "${_SLM_BIN}" mcp
113
+ exec "${SLM_CHOICE_BIN}" mcp
114
+ ;;
115
+ *:explicit)
116
+ _ensure_daemon "${SLM_CHOICE_BIN}" || true
117
+ exec "${SLM_CHOICE_BIN}" mcp
118
+ ;;
119
+ *)
120
+ echo "SLM plugin: internal error: unexpected launcher choice '${SLM_LAUNCHER:-auto}:${SLM_CHOICE}'." >&2
121
+ exit 1
130
122
  ;;
131
123
  esac
@@ -2,7 +2,16 @@
2
2
  :: slm-launch.bat — WP-F SuperLocalMemory MCP launcher (Windows)
3
3
  ::
4
4
  :: Cross-platform counterpart: slm-launch (POSIX bash)
5
- :: Referenced by plugin/.mcp.json as the MCP server command (Windows picks .bat automatically).
5
+ ::
6
+ :: NOT reached through plugin/.mcp.json. That file names the extensionless
7
+ :: "${CLAUDE_PLUGIN_ROOT}/scripts/slm-launch", and a host that spawns it without
8
+ :: a shell (CreateProcess, Node spawn without shell) never resolves it to this
9
+ :: .bat: CreateProcess cannot start a batch file and only appends .exe to a
10
+ :: name without an extension. This file runs only when started through cmd.exe
11
+ :: (for example a hand-written MCP entry using "cmd /c"). It does not read
12
+ :: SLM_LAUNCHER; see MCP_JSON_NOTES.md and issue #139. Since 4.1.20 the plugin's
13
+ :: .mcp.json starts the INSTALLED slm on Windows through %ComSpec% instead.
14
+ :: Proven on the Windows CI runner: tests/test_plugin/test_windows_mcp_spawn_premise.py
6
15
  ::
7
16
  :: Resolves the correct venv binary for Windows and joins the namespace daemon
8
17
  :: before opening MCP, preserving one writer for parallel Claude sessions.
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env bash
2
+ # slm-resolve.sh — which SuperLocalMemory install does this plugin use?
3
+ #
4
+ # Sourced, never run. ONE answer to that question, shared by every script that
5
+ # needs it:
6
+ # slm-launch the MCP server
7
+ # slm-run every lifecycle hook
8
+ # ensure-venv.sh the plugin venv bootstrap (it only builds what will be used)
9
+ # Before this file each of them decided on its own, and they disagreed: the
10
+ # launcher fell back to the plugin venv, the hooks only ever tried a bare `slm`,
11
+ # and the bootstrap built a venv even when nothing would run it.
12
+ #
13
+ # Environment (all optional):
14
+ # SLM_LAUNCHER auto (default) — prefer an installed slm, else the plugin venv
15
+ # system — require the slm on PATH
16
+ # plugin — require the plugin's own venv
17
+ # <path> — an explicit slm binary (leading ~ expanded)
18
+ # CLAUDE_PLUGIN_DATA where a plugin-owned venv lives, when there is one
19
+ #
20
+ # After slm_choose returns 0:
21
+ # SLM_CHOICE system | plugin | explicit
22
+ # SLM_CHOICE_BIN the binary that choice names
23
+ # SLM_CHOICE_READY 1 when SLM_CHOICE_BIN is executable now, else 0
24
+ # (a plugin venv that SessionStart has not built yet is 0)
25
+ # After slm_choose returns 1:
26
+ # SLM_RESOLVE_ERROR what was asked for and why it cannot be honoured
27
+ #
28
+ # Safe under `set -euo pipefail`: every variable read has a default.
29
+
30
+ # Expand a leading ~ WITHOUT eval — eval on an environment value is a
31
+ # command-injection foot-gun. Only tilde expansion; pass expanded paths for vars.
32
+ _slm_expand_tilde() {
33
+ case "$1" in
34
+ "~"*) printf '%s' "${HOME}${1#\~}" ;;
35
+ *) printf '%s' "$1" ;;
36
+ esac
37
+ }
38
+
39
+ # Where the plugin venv keeps slm. Empty when the host gives the plugin no data
40
+ # directory (only Claude Code sets CLAUDE_PLUGIN_DATA). A venv built on Windows
41
+ # keeps entry points in Scripts\ with an .exe suffix, and Git Bash is where these
42
+ # scripts run there.
43
+ #
44
+ # _slm_plugin_bin_var sets SLM_PLUGIN_BIN without a subshell (hooks run this on
45
+ # every tool call); slm_plugin_bin prints it, for callers that want a value.
46
+ _slm_plugin_bin_var() {
47
+ SLM_PLUGIN_BIN=""
48
+ [ -n "${CLAUDE_PLUGIN_DATA:-}" ] || return 0
49
+ case "${OSTYPE:-}" in
50
+ msys*|cygwin*|win32*) SLM_PLUGIN_BIN="${CLAUDE_PLUGIN_DATA}/venv/Scripts/slm.exe" ;;
51
+ *) SLM_PLUGIN_BIN="${CLAUDE_PLUGIN_DATA}/venv/bin/slm" ;;
52
+ esac
53
+ }
54
+
55
+ slm_plugin_bin() {
56
+ _slm_plugin_bin_var
57
+ printf '%s' "${SLM_PLUGIN_BIN}"
58
+ }
59
+
60
+ # The message for "nothing to run", naming both places that were looked in.
61
+ slm_not_found_message() {
62
+ local _pbin
63
+ _pbin="$(slm_plugin_bin)"
64
+ printf '%s\n' "SLM plugin: no SuperLocalMemory found."
65
+ printf '%s\n' " Looked for: 'slm' on PATH, and ${_pbin:-a plugin venv (this host sets no plugin data dir)}."
66
+ printf '%s' " Install it with: pipx install superlocalmemory"
67
+ }
68
+
69
+ _slm_set_choice() {
70
+ SLM_CHOICE="$1"
71
+ SLM_CHOICE_BIN="$2"
72
+ if [ -n "$2" ] && [ -x "$2" ]; then SLM_CHOICE_READY=1; else SLM_CHOICE_READY=0; fi
73
+ }
74
+
75
+ slm_choose() {
76
+ SLM_CHOICE=""
77
+ SLM_CHOICE_BIN=""
78
+ SLM_CHOICE_READY=0
79
+ SLM_RESOLVE_ERROR=""
80
+ local _pbin _sys
81
+ _slm_plugin_bin_var
82
+ _pbin="${SLM_PLUGIN_BIN}"
83
+
84
+ case "${SLM_LAUNCHER:-auto}" in
85
+ auto)
86
+ # 1. An slm already on PATH — the pip, pipx or npm install the user
87
+ # has. The common case, and the one that must not be forked.
88
+ if _sys="$(command -v slm 2>/dev/null)" && [ -n "${_sys}" ]; then
89
+ _slm_set_choice system "${_sys}"
90
+ return 0
91
+ fi
92
+ # 2. No system install: the plugin's own venv, if this host gives the
93
+ # plugin somewhere to keep one. It may not be built yet.
94
+ if [ -n "${_pbin}" ]; then
95
+ _slm_set_choice plugin "${_pbin}"
96
+ return 0
97
+ fi
98
+ # 3. Neither.
99
+ SLM_RESOLVE_ERROR="$(slm_not_found_message)"
100
+ return 1
101
+ ;;
102
+ system)
103
+ if _sys="$(command -v slm 2>/dev/null)" && [ -n "${_sys}" ]; then
104
+ _slm_set_choice system "${_sys}"
105
+ return 0
106
+ fi
107
+ SLM_RESOLVE_ERROR="SLM_LAUNCHER=system but no 'slm' on PATH."
108
+ return 1
109
+ ;;
110
+ plugin)
111
+ if [ -z "${_pbin}" ]; then
112
+ SLM_RESOLVE_ERROR="SLM_LAUNCHER=plugin but this host sets no CLAUDE_PLUGIN_DATA."
113
+ return 1
114
+ fi
115
+ _slm_set_choice plugin "${_pbin}"
116
+ return 0
117
+ ;;
118
+ *)
119
+ _sys="$(_slm_expand_tilde "${SLM_LAUNCHER}")"
120
+ if [ ! -x "${_sys}" ]; then
121
+ SLM_RESOLVE_ERROR="SLM_LAUNCHER is not an executable slm binary: ${_sys}"
122
+ return 1
123
+ fi
124
+ _slm_set_choice explicit "${_sys}"
125
+ return 0
126
+ ;;
127
+ esac
128
+ }
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bash
2
+ # slm-run — run slm for a plugin hook, using the slm the MCP server uses.
3
+ #
4
+ # "${CLAUDE_PLUGIN_ROOT}/scripts/slm-run" hook <name>
5
+ #
6
+ # Hooks used to call a bare `slm`, so on a machine where SuperLocalMemory lives
7
+ # only in the plugin venv every hook was "command not found" — and the error was
8
+ # thrown away, so memory capture stopped with nothing to say why. This wrapper
9
+ # resolves slm with the launcher's own code (slm-resolve.sh), honours the same
10
+ # SLM_LAUNCHER, and passes stdin, stdout and the arguments straight through.
11
+ #
12
+ # A hook must never break the session that runs it, so this ALWAYS exits 0
13
+ # (Claude Code reads exit 2 as "block"). When slm cannot be found, or fails, the
14
+ # reason goes to
15
+ # ${SLM_DATA_DIR:-~/.superlocalmemory}/logs/plugin-hooks.log
16
+ # and one line naming that file goes to stderr. A hook that succeeds is silent:
17
+ # whatever slm wrote to stderr is discarded, exactly as before.
18
+
19
+ _slm_args="$*"
20
+ _log_dir="${SLM_DATA_DIR:-${HOME}/.superlocalmemory}/logs"
21
+ _log="${_log_dir}/plugin-hooks.log"
22
+ _LOG_MAX_BYTES=1048576
23
+
24
+ # Append one entry. Best effort: an unwritable log must not fail the hook.
25
+ _log_entry() {
26
+ {
27
+ mkdir -p "${_log_dir}" || return 1
28
+ if [ -f "${_log}" ] && [ "$(wc -c < "${_log}")" -gt "${_LOG_MAX_BYTES}" ]; then
29
+ mv -f "${_log}" "${_log}.1"
30
+ fi
31
+ printf '%s slm %s: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "${_slm_args}" "$1" >> "${_log}"
32
+ } 2>/dev/null
33
+ }
34
+
35
+ # Record a failure, tell stderr where to look, and leave the session alone.
36
+ _give_up() {
37
+ if _log_entry "$1"; then
38
+ echo "SLM plugin: hook 'slm ${_slm_args}' did not run cleanly; see ${_log}" >&2
39
+ else
40
+ echo "SLM plugin: hook 'slm ${_slm_args}' did not run cleanly: $1" >&2
41
+ fi
42
+ exit 0
43
+ }
44
+
45
+ _here="${BASH_SOURCE[0]%/*}"
46
+ [ "${_here}" = "${BASH_SOURCE[0]}" ] && _here=.
47
+ # shellcheck source=slm-resolve.sh
48
+ if ! . "${_here}/slm-resolve.sh" 2>/dev/null; then
49
+ _give_up "the plugin is incomplete: ${_here}/slm-resolve.sh is missing"
50
+ fi
51
+
52
+ if ! slm_choose; then
53
+ _give_up "$(printf '%s' "${SLM_RESOLVE_ERROR}" | tr '\n' ' ')"
54
+ fi
55
+
56
+ if [ "${SLM_CHOICE_READY}" -ne 1 ]; then
57
+ if [ "${SLM_LAUNCHER:-auto}" = "auto" ]; then
58
+ _give_up "$(slm_not_found_message | tr '\n' ' ') (A plugin venv is built by SessionStart; the first session can run its hooks before it exists.)"
59
+ fi
60
+ _give_up "${SLM_CHOICE} slm is not executable: ${SLM_CHOICE_BIN}"
61
+ fi
62
+
63
+ # slm's stderr is kept only long enough to report a failure. The trap removes
64
+ # it however this script ends, including a host timeout killing the hook.
65
+ _err="$(mktemp "${TMPDIR:-/tmp}/slm-hook.XXXXXX" 2>/dev/null)" || _err=/dev/null
66
+ trap '[ "${_err}" = /dev/null ] || rm -f "${_err}"' EXIT
67
+ trap 'exit 0' HUP INT TERM
68
+
69
+ "${SLM_CHOICE_BIN}" "$@" 2>"${_err}"
70
+ _rc=$?
71
+ if [ "${_rc}" -ne 0 ]; then
72
+ _detail=""
73
+ [ "${_err}" = /dev/null ] || _detail="$(tail -n 5 "${_err}" 2>/dev/null | tr '\n' ' ')"
74
+ _give_up "exit ${_rc} from ${SLM_CHOICE_BIN} (${SLM_CHOICE})${_detail:+: ${_detail}}"
75
+ fi
76
+ exit 0
@@ -145,4 +145,4 @@ These subcommands control daemon-level cache settings. They do not read or write
145
145
 
146
146
  ---
147
147
 
148
- SuperLocalMemory v4.1.19 · Qualixar · AGPL-3.0-or-later
148
+ SuperLocalMemory v4.1.21 · Qualixar · AGPL-3.0-or-later
@@ -147,4 +147,4 @@ Content over 1 MB (1 000 000 bytes UTF-8) is processed but `reversible` is force
147
147
 
148
148
  ---
149
149
 
150
- SuperLocalMemory v4.1.19 · Qualixar · AGPL-3.0-or-later
150
+ SuperLocalMemory v4.1.21 · Qualixar · AGPL-3.0-or-later
@@ -245,4 +245,4 @@ Before running any destructive operation (`forget`, `compact_memories`):
245
245
 
246
246
  ---
247
247
 
248
- *SuperLocalMemory v4.1.19 · Qualixar · AGPL-3.0-or-later*
248
+ *SuperLocalMemory v4.1.21 · Qualixar · AGPL-3.0-or-later*
@@ -312,4 +312,4 @@ profile. See `slm-profile` for the full profile switching workflow.
312
312
 
313
313
  ---
314
314
 
315
- SuperLocalMemory v4.1.19 · Qualixar · AGPL-3.0-or-later
315
+ SuperLocalMemory v4.1.21 · Qualixar · AGPL-3.0-or-later
@@ -96,4 +96,4 @@ paused, name the approval needed; when errored, quote the short detail.
96
96
 
97
97
  ---
98
98
 
99
- SuperLocalMemory v4.1.19 · Qualixar · AGPL-3.0-or-later
99
+ SuperLocalMemory v4.1.21 · Qualixar · AGPL-3.0-or-later
@@ -279,4 +279,4 @@ mesh availability.
279
279
 
280
280
  ---
281
281
 
282
- *SuperLocalMemory v4.1.19 · Qualixar · AGPL-3.0-or-later*
282
+ *SuperLocalMemory v4.1.21 · Qualixar · AGPL-3.0-or-later*
@@ -32,8 +32,8 @@ project, a client engagement, a production vs staging environment.
32
32
  |---------|-------|-------------|
33
33
  | `core` | 18 tools — remember, recall, search, session, optimize | Minimal footprint, no code tools |
34
34
  | `code` | 38 tools — core + portable Brain evidence + code graph + profile switching + bounded loops + memory-kind management | Default for IDE/coding agents |
35
- | `full` | 54 tools — code + all memory ops + mesh + bounded loops | Multi-session, team workflows |
36
- | `power` | 66 tools — full + governance + behavioral tools | Enterprise, admin, audit use cases |
35
+ | `full` | 56 tools — code + all memory ops + mesh + bounded loops | Multi-session, team workflows |
36
+ | `power` | 68 tools — full + governance + behavioral tools | Enterprise, admin, audit use cases |
37
37
  | `mesh` | 8 tools — mesh coordination only | Lightweight cross-session signalling |
38
38
 
39
39
  The profile is set at MCP server startup via `SLM_MCP_PROFILE` in the MCP config.
@@ -146,4 +146,4 @@ Name them differently in your MCP config (e.g. `superlocalmemory-personal` and
146
146
 
147
147
  ---
148
148
 
149
- *SuperLocalMemory v4.1.19 · Qualixar · AGPL-3.0-or-later*
149
+ *SuperLocalMemory v4.1.21 · Qualixar · AGPL-3.0-or-later*