superlocalmemory 4.2.4 → 4.2.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/.claude-plugin/marketplace.json +3 -3
- package/.cursor-plugin/marketplace.json +29 -0
- package/CHANGELOG.md +165 -0
- package/README.md +48 -7
- package/antigravity-plugin/_GENERATED.md +1 -1
- package/antigravity-plugin/agents/slm-governance-advisor.md +1 -1
- package/antigravity-plugin/agents/slm-loop-runner.md +1 -1
- package/antigravity-plugin/agents/slm-memory-advisor.md +1 -1
- package/antigravity-plugin/agents/slm-optimize-advisor.md +1 -1
- package/antigravity-plugin/mcp_config.json +1 -2
- package/antigravity-plugin/plugin.json +1 -1
- package/antigravity-plugin/skills/slm-bot-memory/SKILL.md +1 -1
- package/antigravity-plugin/skills/slm-cache/SKILL.md +1 -1
- package/antigravity-plugin/skills/slm-compress/SKILL.md +1 -1
- package/antigravity-plugin/skills/slm-getting-started-bot/SKILL.md +1 -1
- package/antigravity-plugin/skills/slm-governance/SKILL.md +4 -2
- package/antigravity-plugin/skills/slm-graph/SKILL.md +7 -13
- package/antigravity-plugin/skills/slm-loop/SKILL.md +1 -1
- package/antigravity-plugin/skills/slm-media/SKILL.md +1 -1
- package/antigravity-plugin/skills/slm-mesh/SKILL.md +1 -1
- package/antigravity-plugin/skills/slm-profile/SKILL.md +21 -18
- package/antigravity-plugin/skills/slm-recall/SKILL.md +1 -1
- package/antigravity-plugin/skills/slm-remember/SKILL.md +1 -1
- package/antigravity-plugin/skills/slm-scope/SKILL.md +1 -1
- package/antigravity-plugin/skills/slm-session/SKILL.md +1 -1
- package/antigravity-plugin/skills/slm-status/SKILL.md +1 -1
- package/codex-plugin/.claude-plugin/plugin.json +1 -1
- package/codex-plugin/.codex-plugin/plugin.json +7 -3
- package/codex-plugin/AGENTS.md +1 -1
- package/codex-plugin/README.md +2 -2
- package/codex-plugin/_GENERATED.md +2 -1
- package/codex-plugin/agents/slm-governance-advisor.md +1 -1
- package/codex-plugin/agents/slm-loop-runner.md +1 -1
- package/codex-plugin/agents/slm-memory-advisor.md +1 -1
- package/codex-plugin/agents/slm-optimize-advisor.md +1 -1
- package/codex-plugin/skills/slm-bot-memory/SKILL.md +1 -1
- package/codex-plugin/skills/slm-cache/SKILL.md +1 -1
- package/codex-plugin/skills/slm-compress/SKILL.md +1 -1
- package/codex-plugin/skills/slm-getting-started-bot/SKILL.md +1 -1
- package/codex-plugin/skills/slm-governance/SKILL.md +4 -2
- package/codex-plugin/skills/slm-graph/SKILL.md +7 -13
- package/codex-plugin/skills/slm-loop/SKILL.md +1 -1
- package/codex-plugin/skills/slm-media/SKILL.md +1 -1
- package/codex-plugin/skills/slm-mesh/SKILL.md +1 -1
- package/codex-plugin/skills/slm-profile/SKILL.md +21 -18
- package/codex-plugin/skills/slm-recall/SKILL.md +1 -1
- package/codex-plugin/skills/slm-remember/SKILL.md +1 -1
- package/codex-plugin/skills/slm-scope/SKILL.md +1 -1
- package/codex-plugin/skills/slm-session/SKILL.md +1 -1
- package/codex-plugin/skills/slm-status/SKILL.md +1 -1
- package/copilot-plugin/.github/agents/slm-governance-advisor.agent.md +2 -2
- package/copilot-plugin/.github/agents/slm-loop-runner.agent.md +2 -2
- package/copilot-plugin/.github/agents/slm-memory-advisor.agent.md +2 -2
- package/copilot-plugin/.github/agents/slm-optimize-advisor.agent.md +2 -2
- package/copilot-plugin/.github/copilot-instructions.md +4 -4
- package/copilot-plugin/.github/prompts/slm-bot-memory.prompt.md +2 -2
- package/copilot-plugin/.github/prompts/slm-cache.prompt.md +2 -2
- package/copilot-plugin/.github/prompts/slm-compress.prompt.md +2 -2
- package/copilot-plugin/.github/prompts/slm-getting-started-bot.prompt.md +2 -2
- package/copilot-plugin/.github/prompts/slm-governance.prompt.md +5 -3
- package/copilot-plugin/.github/prompts/slm-graph.prompt.md +8 -14
- package/copilot-plugin/.github/prompts/slm-loop.prompt.md +2 -2
- package/copilot-plugin/.github/prompts/slm-media.prompt.md +2 -2
- package/copilot-plugin/.github/prompts/slm-mesh.prompt.md +2 -2
- package/copilot-plugin/.github/prompts/slm-profile.prompt.md +22 -19
- package/copilot-plugin/.github/prompts/slm-recall.prompt.md +2 -2
- package/copilot-plugin/.github/prompts/slm-remember.prompt.md +2 -2
- package/copilot-plugin/.github/prompts/slm-scope.prompt.md +2 -2
- package/copilot-plugin/.github/prompts/slm-session.prompt.md +2 -2
- package/copilot-plugin/.github/prompts/slm-status.prompt.md +2 -2
- package/copilot-plugin/.github/prompts/slm-web-access.prompt.md +1 -1
- package/copilot-plugin/README.md +2 -2
- package/copilot-plugin/_GENERATED.md +3 -3
- package/{plugin → cursor-plugin}/.cursor-plugin/plugin.json +3 -3
- package/cursor-plugin/LICENSE +24 -0
- package/cursor-plugin/assets/logo.svg +36 -0
- package/{plugin → cursor-plugin}/cursor-skills/slm-bot-memory/SKILL.md +1 -1
- package/cursor-plugin/cursor-skills/slm-cache/SKILL.md +147 -0
- package/cursor-plugin/cursor-skills/slm-compress/SKILL.md +150 -0
- package/{plugin → cursor-plugin}/cursor-skills/slm-getting-started-bot/SKILL.md +1 -1
- package/cursor-plugin/cursor-skills/slm-governance/SKILL.md +279 -0
- package/cursor-plugin/cursor-skills/slm-graph/SKILL.md +321 -0
- package/cursor-plugin/cursor-skills/slm-loop/SKILL.md +174 -0
- package/cursor-plugin/cursor-skills/slm-media/SKILL.md +340 -0
- package/cursor-plugin/cursor-skills/slm-mesh/SKILL.md +389 -0
- package/cursor-plugin/cursor-skills/slm-profile/SKILL.md +167 -0
- package/{plugin → cursor-plugin}/cursor-skills/slm-recall/SKILL.md +1 -1
- package/{plugin → cursor-plugin}/cursor-skills/slm-remember/SKILL.md +1 -1
- package/{plugin → cursor-plugin}/cursor-skills/slm-scope/SKILL.md +1 -1
- package/{plugin → cursor-plugin}/cursor-skills/slm-session/SKILL.md +1 -1
- package/cursor-plugin/cursor-skills/slm-status/SKILL.md +231 -0
- package/cursor-plugin/cursor-skills/slm-web-access/SKILL.md +179 -0
- package/{plugin → cursor-plugin}/mcp.cursor.json +1 -2
- package/hermes-plugin/__init__.py +39 -7
- package/hermes-plugin/agents/slm-governance-advisor.md +1 -1
- package/hermes-plugin/agents/slm-loop-runner.md +1 -1
- package/hermes-plugin/agents/slm-memory-advisor.md +1 -1
- package/hermes-plugin/agents/slm-optimize-advisor.md +1 -1
- package/hermes-plugin/command-inventory.json +2 -2
- package/hermes-plugin/plugin.yaml +2 -2
- package/hermes-plugin/skills/slm-bot-memory/SKILL.md +1 -1
- package/hermes-plugin/skills/slm-cache/SKILL.md +1 -1
- package/hermes-plugin/skills/slm-compress/SKILL.md +1 -1
- package/hermes-plugin/skills/slm-getting-started-bot/SKILL.md +1 -1
- package/hermes-plugin/skills/slm-governance/SKILL.md +4 -2
- package/hermes-plugin/skills/slm-graph/SKILL.md +7 -13
- package/hermes-plugin/skills/slm-loop/SKILL.md +1 -1
- package/hermes-plugin/skills/slm-media/SKILL.md +1 -1
- package/hermes-plugin/skills/slm-mesh/SKILL.md +1 -1
- package/hermes-plugin/skills/slm-profile/SKILL.md +21 -18
- package/hermes-plugin/skills/slm-recall/SKILL.md +1 -1
- package/hermes-plugin/skills/slm-remember/SKILL.md +1 -1
- package/hermes-plugin/skills/slm-scope/SKILL.md +1 -1
- package/hermes-plugin/skills/slm-session/SKILL.md +1 -1
- package/hermes-plugin/skills/slm-status/SKILL.md +1 -1
- package/ide/configs/copilot-cli-mcp.json +17 -0
- package/ide/configs/grok-cli-mcp.toml +11 -0
- package/ide/configs/muse-mcp.json +16 -0
- package/ide/configs/openclaw-mcp.json +15 -0
- package/muse-plugin/.claude-plugin/plugin.json +20 -0
- package/muse-plugin/LICENSE +663 -0
- package/muse-plugin/README.md +44 -0
- package/muse-plugin/_GENERATED.md +3 -0
- package/muse-plugin/agents/slm-governance-advisor.md +84 -0
- package/muse-plugin/agents/slm-loop-runner.md +82 -0
- package/muse-plugin/agents/slm-memory-advisor.md +54 -0
- package/muse-plugin/agents/slm-optimize-advisor.md +44 -0
- package/muse-plugin/commands/slm-loop.md +33 -0
- package/muse-plugin/hooks/hooks.json +102 -0
- package/muse-plugin/requirements-cpu-torch.txt +13 -0
- package/muse-plugin/requirements.txt +1 -0
- package/muse-plugin/scripts/ensure-venv.bat +122 -0
- package/muse-plugin/scripts/ensure-venv.sh +169 -0
- package/muse-plugin/scripts/slm-launch +207 -0
- package/muse-plugin/scripts/slm-launch.bat +32 -0
- package/muse-plugin/scripts/slm-resolve.sh +128 -0
- package/muse-plugin/scripts/slm-run +76 -0
- package/muse-plugin/scripts/torch-cpu-resolve.sh +86 -0
- package/muse-plugin/skills/slm-bot-memory/SKILL.md +180 -0
- package/muse-plugin/skills/slm-cache/SKILL.md +146 -0
- package/muse-plugin/skills/slm-compress/SKILL.md +149 -0
- package/muse-plugin/skills/slm-getting-started-bot/SKILL.md +196 -0
- package/muse-plugin/skills/slm-governance/SKILL.md +278 -0
- package/muse-plugin/skills/slm-graph/SKILL.md +320 -0
- package/muse-plugin/skills/slm-loop/SKILL.md +173 -0
- package/muse-plugin/skills/slm-media/SKILL.md +339 -0
- package/muse-plugin/skills/slm-mesh/SKILL.md +388 -0
- package/muse-plugin/skills/slm-profile/SKILL.md +166 -0
- package/muse-plugin/skills/slm-recall/SKILL.md +517 -0
- package/muse-plugin/skills/slm-remember/SKILL.md +374 -0
- package/muse-plugin/skills/slm-scope/SKILL.md +182 -0
- package/muse-plugin/skills/slm-session/SKILL.md +290 -0
- package/muse-plugin/skills/slm-status/SKILL.md +230 -0
- package/muse-plugin/skills/slm-web-access/SKILL.md +178 -0
- package/package.json +6 -2
- package/plugin/.claude-plugin/plugin.json +4 -2
- package/plugin/LICENSE +663 -0
- package/plugin/README.md +44 -0
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/hooks/hooks.json +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/skills/slm-bot-memory/SKILL.md +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-getting-started-bot/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +4 -2
- package/plugin/skills/slm-graph/SKILL.md +7 -13
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-media/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +21 -18
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +1 -1
- package/plugin-src/agents/slm-governance-advisor.md +1 -1
- package/plugin-src/agents/slm-loop-runner.md +1 -1
- package/plugin-src/agents/slm-memory-advisor.md +1 -1
- package/plugin-src/agents/slm-optimize-advisor.md +1 -1
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/rules/CLAUDE.md.fragment +3 -3
- package/plugin-src/skills/slm-bot-memory/SKILL.md +1 -1
- package/plugin-src/skills/slm-cache/SKILL.md +1 -1
- package/plugin-src/skills/slm-compress/SKILL.md +1 -1
- package/plugin-src/skills/slm-getting-started-bot/SKILL.md +1 -1
- package/plugin-src/skills/slm-governance/SKILL.md +4 -2
- package/plugin-src/skills/slm-graph/SKILL.md +7 -13
- package/plugin-src/skills/slm-loop/SKILL.md +1 -1
- package/plugin-src/skills/slm-media/SKILL.md +1 -1
- package/plugin-src/skills/slm-mesh/SKILL.md +1 -1
- package/plugin-src/skills/slm-profile/SKILL.md +21 -18
- package/plugin-src/skills/slm-recall/SKILL.md +1 -1
- package/plugin-src/skills/slm-remember/SKILL.md +1 -1
- package/plugin-src/skills/slm-scope/SKILL.md +1 -1
- package/plugin-src/skills/slm-session/SKILL.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +1 -1
- package/plugin/CLAUDE.md +0 -56
- /package/{plugin/assets/logo.svg → codex-plugin/assets/icon.svg} +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ensure-venv.sh — SuperLocalMemory plugin venv bootstrap
|
|
3
|
+
#
|
|
4
|
+
# Called by Claude Code SessionStart hook. Idempotent: fast-path exits in <100ms
|
|
5
|
+
# on repeat invocations when requirements.txt is unchanged.
|
|
6
|
+
#
|
|
7
|
+
# Environment (set by Claude Code plugin runtime):
|
|
8
|
+
# CLAUDE_PLUGIN_ROOT — plugin installation dir (ephemeral, contains scripts/ + requirements.txt)
|
|
9
|
+
# CLAUDE_PLUGIN_DATA — persistent data dir (venv lives here, survives plugin updates)
|
|
10
|
+
#
|
|
11
|
+
# Exit codes: 0 = venv ready, or not needed non-0 = failure (logged to stderr)
|
|
12
|
+
# All output goes to stderr only (stdout reserved for MCP stdio protocol).
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# :? guard — fail loudly if required env vars are unset or empty
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
: "${CLAUDE_PLUGIN_ROOT:?CLAUDE_PLUGIN_ROOT must be set (plugin installation directory)}"
|
|
20
|
+
: "${CLAUDE_PLUGIN_DATA:?CLAUDE_PLUGIN_DATA must be set (plugin persistent data directory)}"
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Redirect all output to stderr (MCP uses stdout for protocol messages)
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
exec 1>&2
|
|
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
|
+
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
# Python >= 3.12 guard
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
if ! python3 -c "import sys; sys.exit(0 if sys.version_info >= (3, 12) else 1)" 2>/dev/null; then
|
|
53
|
+
PY_VER=$(python3 --version 2>&1 || echo "unknown")
|
|
54
|
+
echo "ERROR: SuperLocalMemory plugin requires Python >= 3.12, found: ${PY_VER}" >&2
|
|
55
|
+
echo "Install Python 3.12+ and ensure it is first on PATH." >&2
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Paths
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
REQ="${CLAUDE_PLUGIN_ROOT}/requirements.txt"
|
|
63
|
+
TORCH_CPU_PIN_FILE="${CLAUDE_PLUGIN_ROOT}/requirements-cpu-torch.txt"
|
|
64
|
+
VENV="${CLAUDE_PLUGIN_DATA}/venv"
|
|
65
|
+
SENTINEL="${CLAUDE_PLUGIN_DATA}/.venv-reqs.sha256"
|
|
66
|
+
VENV_TMP="${CLAUDE_PLUGIN_DATA}/venv.tmp"
|
|
67
|
+
|
|
68
|
+
# ---------------------------------------------------------------------------
|
|
69
|
+
# Compute sha256 of requirements.txt, and of the CPU-torch pin file when it
|
|
70
|
+
# exists (a pin bump alone must also trigger a rebuild).
|
|
71
|
+
# (cross-platform: prefer sha256sum, fall back to shasum -a 256)
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
_sha256_of() {
|
|
74
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
75
|
+
sha256sum "$1" | awk '{print $1}'
|
|
76
|
+
else
|
|
77
|
+
shasum -a 256 "$1" | awk '{print $1}'
|
|
78
|
+
fi
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
hash_req() {
|
|
82
|
+
local combined
|
|
83
|
+
combined="$(_sha256_of "${REQ}")"
|
|
84
|
+
if [ -f "${TORCH_CPU_PIN_FILE}" ]; then
|
|
85
|
+
combined="${combined}:$(_sha256_of "${TORCH_CPU_PIN_FILE}")"
|
|
86
|
+
fi
|
|
87
|
+
# Fold the (possibly combined) digest down to one hash again so the
|
|
88
|
+
# sentinel stays a single opaque token either way.
|
|
89
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
90
|
+
printf '%s' "${combined}" | sha256sum | awk '{print $1}'
|
|
91
|
+
else
|
|
92
|
+
printf '%s' "${combined}" | shasum -a 256 | awk '{print $1}'
|
|
93
|
+
fi
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
NEW_HASH=$(hash_req)
|
|
97
|
+
|
|
98
|
+
# ---------------------------------------------------------------------------
|
|
99
|
+
# Fast-path: venv python exists AND sentinel matches current requirements hash
|
|
100
|
+
# venv/bin/python3 (or python) is always present after `python3 -m venv`;
|
|
101
|
+
# the sentinel guards against stale requirements.
|
|
102
|
+
# In production, superlocalmemory also installs venv/bin/slm; both checks pass.
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
VENV_PYTHON="${VENV}/bin/python3"
|
|
105
|
+
if [ ! -x "${VENV_PYTHON}" ]; then
|
|
106
|
+
VENV_PYTHON="${VENV}/bin/python"
|
|
107
|
+
fi
|
|
108
|
+
if [ -x "${VENV_PYTHON}" ] && [ -f "${SENTINEL}" ] && [ "$(cat "${SENTINEL}")" = "${NEW_HASH}" ]; then
|
|
109
|
+
echo "SLM plugin: venv up-to-date (sha256=${NEW_HASH:0:12}…), skipping install." >&2
|
|
110
|
+
exit 0
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
# Rebuild venv atomically: install to venv.tmp, rename to venv, write sentinel LAST
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
echo "SLM plugin: bootstrapping Python venv at ${VENV} …" >&2
|
|
117
|
+
echo " requirements: ${REQ}" >&2
|
|
118
|
+
echo " python3: $(python3 --version 2>&1)" >&2
|
|
119
|
+
|
|
120
|
+
# Clean up any partial previous attempt
|
|
121
|
+
rm -rf "${VENV_TMP}"
|
|
122
|
+
|
|
123
|
+
# Create fresh venv
|
|
124
|
+
python3 -m venv "${VENV_TMP}"
|
|
125
|
+
|
|
126
|
+
# Upgrade pip first (prefer binary to avoid source builds)
|
|
127
|
+
"${VENV_TMP}/bin/pip" install --upgrade pip --prefer-binary --quiet
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# CPU-only torch on CPU-only Linux. Plain PyPI resolution of the
|
|
131
|
+
# `torch==X.Y.Z` requirement that requirements.txt pulls in transitively
|
|
132
|
+
# installs the CUDA build on Linux (torch + triton + ~18 nvidia-* packages,
|
|
133
|
+
# about 2.6 GiB) even when there is no GPU to use it. Pre-installing the
|
|
134
|
+
# pinned version from the official CPU index first means the later
|
|
135
|
+
# `pip install -r requirements.txt` below finds it already satisfied and
|
|
136
|
+
# never touches the CUDA wheels. macOS, Windows, a host with a GPU, and a
|
|
137
|
+
# user who already set SLM_TORCH_BACKEND or their own pip index are all left
|
|
138
|
+
# exactly as before (see torch-cpu-resolve.sh).
|
|
139
|
+
# shellcheck source=torch-cpu-resolve.sh
|
|
140
|
+
. "$(dirname "${BASH_SOURCE[0]}")/torch-cpu-resolve.sh"
|
|
141
|
+
if torch_cpu_should_force; then
|
|
142
|
+
if TORCH_PIN="$(torch_cpu_pin "${TORCH_CPU_PIN_FILE}")"; then
|
|
143
|
+
echo "SLM plugin: Linux, no GPU detected — installing ${TORCH_PIN} from ${TORCH_CPU_INDEX_URL} (set SLM_TORCH_BACKEND=cuda to opt out)." >&2
|
|
144
|
+
"${VENV_TMP}/bin/pip" install \
|
|
145
|
+
--require-virtualenv \
|
|
146
|
+
--prefer-binary \
|
|
147
|
+
--quiet \
|
|
148
|
+
--index-url "${TORCH_CPU_INDEX_URL}" \
|
|
149
|
+
"${TORCH_PIN}"
|
|
150
|
+
fi
|
|
151
|
+
fi
|
|
152
|
+
|
|
153
|
+
# Install requirements
|
|
154
|
+
"${VENV_TMP}/bin/pip" install \
|
|
155
|
+
--require-virtualenv \
|
|
156
|
+
--prefer-binary \
|
|
157
|
+
--quiet \
|
|
158
|
+
-r "${REQ}"
|
|
159
|
+
|
|
160
|
+
echo "SLM plugin: install complete, activating venv." >&2
|
|
161
|
+
|
|
162
|
+
# Atomic swap: remove old venv (if any), rename tmp into place
|
|
163
|
+
rm -rf "${VENV}"
|
|
164
|
+
mv "${VENV_TMP}" "${VENV}"
|
|
165
|
+
|
|
166
|
+
# Write sentinel LAST — guarantees that a crash before this line triggers rebuild
|
|
167
|
+
echo "${NEW_HASH}" > "${SENTINEL}"
|
|
168
|
+
|
|
169
|
+
echo "SLM plugin: venv ready at ${VENV}/bin/slm" >&2
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# slm-launch — SuperLocalMemory MCP launcher (POSIX)
|
|
3
|
+
#
|
|
4
|
+
# Cross-platform counterpart: slm-launch.bat (Windows)
|
|
5
|
+
# Referenced by .mcp.json as the MCP server command.
|
|
6
|
+
#
|
|
7
|
+
# WHAT THIS RESOLVES, AND WHY THE DEFAULT CHANGED
|
|
8
|
+
#
|
|
9
|
+
# It used to default to a venv the plugin owns:
|
|
10
|
+
# "${CLAUDE_PLUGIN_DATA}/venv/bin/slm". So anyone who already had SuperLocalMemory
|
|
11
|
+
# — from pip, pipx or npm — got a SECOND installation the moment they installed
|
|
12
|
+
# the plugin, on its own schedule, upgraded separately, and easy to leave behind
|
|
13
|
+
# a release. Installing a plugin should not fork your installation.
|
|
14
|
+
#
|
|
15
|
+
# It was also Claude-Code-only. ${CLAUDE_PLUGIN_DATA} is set by Claude Code and
|
|
16
|
+
# by nothing else, so on Codex or Antigravity the path collapsed to
|
|
17
|
+
# "/venv/bin/slm" and the server simply did not start.
|
|
18
|
+
#
|
|
19
|
+
# The default is now: USE THE SLM THAT IS ALREADY INSTALLED. A plugin-owned venv
|
|
20
|
+
# is the fallback for a machine that has none, not the first choice.
|
|
21
|
+
#
|
|
22
|
+
# Environment (all optional):
|
|
23
|
+
# SLM_LAUNCHER auto (default) — prefer an installed slm, else the plugin venv
|
|
24
|
+
# system — require the slm on PATH
|
|
25
|
+
# plugin — require the plugin's own venv
|
|
26
|
+
# <path> — an explicit slm binary (leading ~ expanded)
|
|
27
|
+
# SLM_DATA_DIR SLM data dir (default: ~/.superlocalmemory)
|
|
28
|
+
# CLAUDE_PLUGIN_DATA where a plugin-owned venv lives (then CURSOR_PLUGIN_DATA,
|
|
29
|
+
# PLUGIN_DATA, then ~/.local/share/superlocalmemory-plugin)
|
|
30
|
+
# SLM_LAUNCHER_BOOTSTRAP=1 build that venv here when nothing else can run
|
|
31
|
+
#
|
|
32
|
+
# HOSTS THAT DO NOT EXPAND THEIR VARIABLES
|
|
33
|
+
#
|
|
34
|
+
# Some hosts (Grok Bot's plugin computer) pass ${CLAUDE_PLUGIN_ROOT} and
|
|
35
|
+
# ${CLAUDE_PLUGIN_DATA} through literally. So this script finds itself from
|
|
36
|
+
# BASH_SOURCE, and treats any value still containing "${" as unset.
|
|
37
|
+
#
|
|
38
|
+
# Which slm runs, in order: SLM_LAUNCHER; an slm on PATH; the plugin venv, if
|
|
39
|
+
# built; uvx, pinned to this plugin's release (CPU-only torch on Linux); and,
|
|
40
|
+
# only with SLM_LAUNCHER_BOOTSTRAP=1, a plugin venv built on the spot.
|
|
41
|
+
# The daemon is started first and `slm serve start` waits (up to 60 s) for its
|
|
42
|
+
# /health, so the first remember after a cold start is not "daemon unavailable".
|
|
43
|
+
#
|
|
44
|
+
# Deliberately NOT set here: the MCP profile. Which tools a host exposes is the
|
|
45
|
+
# user's decision, and a plugin that narrows it takes away tools they configured.
|
|
46
|
+
|
|
47
|
+
# Where this script lives. Never from ${CLAUDE_PLUGIN_ROOT}: not every host sets it.
|
|
48
|
+
_slm_self="${BASH_SOURCE[0]}"
|
|
49
|
+
while [ -h "${_slm_self}" ]; do
|
|
50
|
+
_slm_link="$(readlink "${_slm_self}")"
|
|
51
|
+
case "${_slm_link}" in
|
|
52
|
+
/*) _slm_self="${_slm_link}" ;;
|
|
53
|
+
*) _slm_self="$(dirname "${_slm_self}")/${_slm_link}" ;;
|
|
54
|
+
esac
|
|
55
|
+
done
|
|
56
|
+
_SLM_SCRIPTS="$(cd "$(dirname "${_slm_self}")" && pwd)"
|
|
57
|
+
_SLM_PLUGIN_ROOT="$(dirname "${_SLM_SCRIPTS}")"
|
|
58
|
+
|
|
59
|
+
# A value that is empty, or a placeholder the host never replaced, is not a value.
|
|
60
|
+
_slm_usable() {
|
|
61
|
+
case "$1" in
|
|
62
|
+
''|*'${'*) return 1 ;;
|
|
63
|
+
esac
|
|
64
|
+
return 0
|
|
65
|
+
}
|
|
66
|
+
[ -n "${HOME:-}" ] || { HOME="$(cd ~ 2>/dev/null && pwd)"; export HOME; }
|
|
67
|
+
_slm_usable "${SLM_DATA_DIR:-}" || unset SLM_DATA_DIR
|
|
68
|
+
_slm_usable "${SLM_LAUNCHER:-}" || unset SLM_LAUNCHER
|
|
69
|
+
if ! _slm_usable "${CLAUDE_PLUGIN_DATA:-}"; then
|
|
70
|
+
unset CLAUDE_PLUGIN_DATA
|
|
71
|
+
for _slm_d in "${CURSOR_PLUGIN_DATA:-}" "${PLUGIN_DATA:-}" \
|
|
72
|
+
"${XDG_DATA_HOME:-${HOME}/.local/share}/superlocalmemory-plugin"; do
|
|
73
|
+
if _slm_usable "${_slm_d}"; then CLAUDE_PLUGIN_DATA="${_slm_d}"; break; fi
|
|
74
|
+
done
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
_SLM_DATA="${SLM_DATA_DIR:-$HOME/.superlocalmemory}"
|
|
78
|
+
|
|
79
|
+
# Which slm to run is decided in ONE place, shared with the hooks and the venv
|
|
80
|
+
# bootstrap, so the three can never disagree about which install is in play.
|
|
81
|
+
# shellcheck source=slm-resolve.sh
|
|
82
|
+
. "${_SLM_SCRIPTS}/slm-resolve.sh"
|
|
83
|
+
_PLUGIN_BIN="$(slm_plugin_bin)"
|
|
84
|
+
|
|
85
|
+
# Is a daemon already up for this data dir? If so we join it rather than boot a
|
|
86
|
+
# second one — one writer for every session that shares the store.
|
|
87
|
+
_daemon_running=0
|
|
88
|
+
if [ -f "${_SLM_DATA}/daemon.pid" ] \
|
|
89
|
+
&& kill -0 "$(cat "${_SLM_DATA}/daemon.pid" 2>/dev/null)" 2>/dev/null; then
|
|
90
|
+
_daemon_running=1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# Start a daemon with the chosen command unless one is already serving.
|
|
94
|
+
_ensure_daemon() {
|
|
95
|
+
[ "${_daemon_running}" -eq 1 ] && return 0
|
|
96
|
+
"$@" serve start >&2
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# The release this plugin tree ships (requirements.txt is stamped every release).
|
|
100
|
+
_slm_pin() {
|
|
101
|
+
sed -n 's/^\(superlocalmemory==[0-9][0-9A-Za-z.+!-]*\)[[:space:]]*$/\1/p' \
|
|
102
|
+
"${_SLM_PLUGIN_ROOT}/requirements.txt" 2>/dev/null | head -n 1
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# No installed slm and no plugin venv: run this release through uvx. Returns
|
|
106
|
+
# only when it cannot (no uvx, or no pin); otherwise it becomes the server.
|
|
107
|
+
_slm_run_uvx() {
|
|
108
|
+
local _pin
|
|
109
|
+
command -v uvx >/dev/null 2>&1 || return 1
|
|
110
|
+
_pin="$(_slm_pin)"
|
|
111
|
+
if [ -z "${_pin}" ]; then
|
|
112
|
+
echo "SLM plugin: not using uvx: no superlocalmemory pin in ${_SLM_PLUGIN_ROOT}/requirements.txt." >&2
|
|
113
|
+
return 1
|
|
114
|
+
fi
|
|
115
|
+
# PyPI's Linux torch is the CUDA build; macOS and Windows get CPU wheels anyway.
|
|
116
|
+
if [ "$(uname -s 2>/dev/null)" = "Linux" ]; then
|
|
117
|
+
export UV_TORCH_BACKEND="${UV_TORCH_BACKEND:-cpu}"
|
|
118
|
+
fi
|
|
119
|
+
export TOKENIZERS_PARALLELISM="${TOKENIZERS_PARALLELISM:-false}"
|
|
120
|
+
echo "SLM plugin: no installed slm; running ${_pin} through uvx (downloaded once, then cached)." >&2
|
|
121
|
+
_ensure_daemon uvx --from "${_pin}" slm || \
|
|
122
|
+
echo "SLM plugin: daemon did not start; the MCP server will retry on first use." >&2
|
|
123
|
+
exec uvx --from "${_pin}" slm mcp
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Opt-in: build the plugin venv now, with the same bootstrap SessionStart runs.
|
|
127
|
+
_slm_bootstrap() {
|
|
128
|
+
[ "${SLM_LAUNCHER_BOOTSTRAP:-0}" = "1" ] || return 1
|
|
129
|
+
CLAUDE_PLUGIN_ROOT="${_SLM_PLUGIN_ROOT}" CLAUDE_PLUGIN_DATA="${CLAUDE_PLUGIN_DATA}" \
|
|
130
|
+
bash "${_SLM_SCRIPTS}/ensure-venv.sh" >&2 || return 1
|
|
131
|
+
[ -x "${SLM_CHOICE_BIN}" ]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if ! slm_choose; then
|
|
135
|
+
echo "${SLM_RESOLVE_ERROR}" >&2
|
|
136
|
+
exit 1
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
case "${SLM_LAUNCHER:-auto}:${SLM_CHOICE}" in
|
|
140
|
+
auto:system)
|
|
141
|
+
# An slm already on PATH — the pip, pipx or npm install the user has.
|
|
142
|
+
# This is the common case and the one that must not be forked.
|
|
143
|
+
#
|
|
144
|
+
# Both orderings end up here, and both must converge on ONE
|
|
145
|
+
# environment:
|
|
146
|
+
# installed first, plugin second -> nothing was ever forked;
|
|
147
|
+
# plugin first, installed second -> the plugin venv is now the
|
|
148
|
+
# older copy and must stop being used.
|
|
149
|
+
# They already share one store, because this launcher sets no
|
|
150
|
+
# SLM_DATA_DIR and both resolve ~/.superlocalmemory. What is left to
|
|
151
|
+
# go wrong is two DIFFERENT VERSIONS serving that one store: whoever
|
|
152
|
+
# starts the daemon first decides which code the other one talks to.
|
|
153
|
+
# So when a leftover plugin venv is found beside a real install, say
|
|
154
|
+
# so, with both versions named. Silent duplication is how a store
|
|
155
|
+
# ends up served by a release nobody thinks they are running.
|
|
156
|
+
if [ -n "${_PLUGIN_BIN}" ] && [ -x "${_PLUGIN_BIN}" ]; then
|
|
157
|
+
_sys_v="$(slm --version 2>/dev/null | awk '{print $NF}')"
|
|
158
|
+
_plg_v="$("${_PLUGIN_BIN}" --version 2>/dev/null | awk '{print $NF}')"
|
|
159
|
+
if [ -n "${_plg_v}" ] && [ "${_sys_v}" != "${_plg_v}" ]; then
|
|
160
|
+
echo "SLM plugin: using the installed slm ${_sys_v:-?} and IGNORING a leftover plugin venv at ${_PLUGIN_BIN} (${_plg_v})." >&2
|
|
161
|
+
echo " One store, one version. Remove the venv to stop it drifting: rm -rf \"${CLAUDE_PLUGIN_DATA}/venv\"" >&2
|
|
162
|
+
fi
|
|
163
|
+
fi
|
|
164
|
+
_ensure_daemon "${SLM_CHOICE_BIN}" || \
|
|
165
|
+
echo "SLM plugin: daemon did not start; continuing as a direct MCP writer." >&2
|
|
166
|
+
exec slm mcp
|
|
167
|
+
;;
|
|
168
|
+
auto:plugin)
|
|
169
|
+
# No system install. Use the plugin's own venv, once SessionStart has
|
|
170
|
+
# built it. A host that runs no hooks never builds it: then uvx, then
|
|
171
|
+
# (opt-in) a venv built here. If none of that works, say what was
|
|
172
|
+
# looked for, so the fix is obvious.
|
|
173
|
+
if [ "${SLM_CHOICE_READY}" -ne 1 ]; then
|
|
174
|
+
_slm_run_uvx
|
|
175
|
+
if ! _slm_bootstrap; then
|
|
176
|
+
slm_not_found_message >&2
|
|
177
|
+
echo >&2
|
|
178
|
+
echo " Or install uv (https://docs.astral.sh/uv/): the plugin then runs SuperLocalMemory through uvx." >&2
|
|
179
|
+
exit 1
|
|
180
|
+
fi
|
|
181
|
+
fi
|
|
182
|
+
if ! _ensure_daemon "${SLM_CHOICE_BIN}"; then
|
|
183
|
+
echo "SLM plugin: unable to start the owned daemon; refusing a direct MCP writer." >&2
|
|
184
|
+
exit 1
|
|
185
|
+
fi
|
|
186
|
+
exec "${SLM_CHOICE_BIN}" mcp
|
|
187
|
+
;;
|
|
188
|
+
system:system)
|
|
189
|
+
_ensure_daemon "${SLM_CHOICE_BIN}" || true
|
|
190
|
+
exec slm mcp
|
|
191
|
+
;;
|
|
192
|
+
plugin:plugin)
|
|
193
|
+
if ! _ensure_daemon "${SLM_CHOICE_BIN}"; then
|
|
194
|
+
echo "SLM plugin: unable to start the owned daemon; refusing a direct MCP writer." >&2
|
|
195
|
+
exit 1
|
|
196
|
+
fi
|
|
197
|
+
exec "${SLM_CHOICE_BIN}" mcp
|
|
198
|
+
;;
|
|
199
|
+
*:explicit)
|
|
200
|
+
_ensure_daemon "${SLM_CHOICE_BIN}" || true
|
|
201
|
+
exec "${SLM_CHOICE_BIN}" mcp
|
|
202
|
+
;;
|
|
203
|
+
*)
|
|
204
|
+
echo "SLM plugin: internal error: unexpected launcher choice '${SLM_LAUNCHER:-auto}:${SLM_CHOICE}'." >&2
|
|
205
|
+
exit 1
|
|
206
|
+
;;
|
|
207
|
+
esac
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
:: slm-launch.bat — WP-F SuperLocalMemory MCP launcher (Windows)
|
|
3
|
+
::
|
|
4
|
+
:: Cross-platform counterpart: slm-launch (POSIX bash)
|
|
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
|
|
15
|
+
::
|
|
16
|
+
:: Resolves the correct venv binary for Windows and joins the namespace daemon
|
|
17
|
+
:: before opening MCP, preserving one writer for parallel Claude sessions.
|
|
18
|
+
::
|
|
19
|
+
:: On Windows, Python venv places entry points in Scripts\ (not bin\ like POSIX).
|
|
20
|
+
:: This launcher bridges the path difference so ONE .mcp.json command field works
|
|
21
|
+
:: cross-platform.
|
|
22
|
+
::
|
|
23
|
+
:: Environment:
|
|
24
|
+
:: CLAUDE_PLUGIN_DATA — persistent data dir where venv lives
|
|
25
|
+
|
|
26
|
+
"%CLAUDE_PLUGIN_DATA%\venv\Scripts\slm.exe" serve start 1>&2
|
|
27
|
+
if errorlevel 1 (
|
|
28
|
+
echo SLM plugin: unable to start the owned daemon; refusing a direct MCP writer. 1>&2
|
|
29
|
+
exit /b 1
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
"%CLAUDE_PLUGIN_DATA%\venv\Scripts\slm.exe" mcp
|
|
@@ -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
|