claude-local-docs 1.0.2 → 1.0.12
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 +25 -5
- package/.claude-plugin/plugin.json +25 -4
- package/.mcp.json +9 -3
- package/README.md +139 -77
- package/commands/fetch-docs.md +146 -33
- package/dist/discovery.d.ts +46 -0
- package/dist/discovery.js +357 -0
- package/dist/discovery.js.map +1 -0
- package/dist/fetcher.d.ts +6 -1
- package/dist/fetcher.js +8 -5
- package/dist/fetcher.js.map +1 -1
- package/dist/index.js +85 -5
- package/dist/index.js.map +1 -1
- package/dist/indexer.d.ts +4 -4
- package/dist/indexer.js +95 -37
- package/dist/indexer.js.map +1 -1
- package/dist/integration.test.d.ts +8 -0
- package/dist/integration.test.js +114 -0
- package/dist/integration.test.js.map +1 -0
- package/dist/reranker.d.ts +2 -4
- package/dist/reranker.js +14 -42
- package/dist/reranker.js.map +1 -1
- package/dist/search.js +5 -4
- package/dist/search.js.map +1 -1
- package/docker-compose.nvidia.yml +14 -0
- package/docker-compose.yml +20 -0
- package/hooks/hooks.json +16 -0
- package/package.json +14 -3
- package/scripts/ensure-tei.sh +71 -0
- package/start-tei.sh +239 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-local-docs",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Local-first Context7 alternative — indexes JS/TS dependency docs with a 4-stage RAG pipeline.
|
|
3
|
+
"version": "1.0.12",
|
|
4
|
+
"description": "Local-first Context7 alternative — indexes JS/TS dependency docs with a 4-stage RAG pipeline. Uses TEI (Text Embeddings Inference) Docker containers for embeddings and reranking.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -10,14 +10,20 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"commands",
|
|
13
|
+
"hooks",
|
|
14
|
+
"scripts",
|
|
13
15
|
".claude-plugin",
|
|
14
16
|
".mcp.json",
|
|
17
|
+
"docker-compose.yml",
|
|
18
|
+
"docker-compose.nvidia.yml",
|
|
19
|
+
"start-tei.sh",
|
|
15
20
|
"README.md",
|
|
16
21
|
"LICENSE"
|
|
17
22
|
],
|
|
18
23
|
"scripts": {
|
|
19
24
|
"build": "tsc",
|
|
20
25
|
"dev": "tsc --watch",
|
|
26
|
+
"test": "tsc && node --test dist/integration.test.js",
|
|
21
27
|
"prepublishOnly": "tsc"
|
|
22
28
|
},
|
|
23
29
|
"keywords": [
|
|
@@ -34,18 +40,23 @@
|
|
|
34
40
|
],
|
|
35
41
|
"author": "matthew",
|
|
36
42
|
"license": "MIT",
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
37
46
|
"repository": {
|
|
38
47
|
"type": "git",
|
|
39
48
|
"url": "https://github.com/matteodante/claude-local-docs.git"
|
|
40
49
|
},
|
|
41
50
|
"dependencies": {
|
|
42
|
-
"@huggingface/transformers": "^3.4.1",
|
|
43
51
|
"@lancedb/lancedb": "^0.17.0",
|
|
44
52
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
53
|
+
"turndown": "^7.2.2",
|
|
54
|
+
"turndown-plugin-gfm": "^1.0.2",
|
|
45
55
|
"zod": "^3.24.0"
|
|
46
56
|
},
|
|
47
57
|
"devDependencies": {
|
|
48
58
|
"@types/node": "^22.0.0",
|
|
59
|
+
"@types/turndown": "^5.0.6",
|
|
49
60
|
"esbuild": "^0.27.3",
|
|
50
61
|
"typescript": "^5.7.0"
|
|
51
62
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ensure-tei.sh — SessionStart hook: check if TEI containers are running, start if not.
|
|
3
|
+
# This script is idempotent and fast when containers are already up.
|
|
4
|
+
#
|
|
5
|
+
# Output: JSON with additionalContext for Claude's context window.
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
PLUGIN_DIR="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
|
|
10
|
+
|
|
11
|
+
# ── Check if Docker is available ─────────────────────────────────────
|
|
12
|
+
if ! command -v docker &>/dev/null; then
|
|
13
|
+
cat <<'EOF'
|
|
14
|
+
{"additionalContext": "WARNING: Docker is not installed. TEI inference containers are not running. The search_docs and store_and_index_doc tools will fail. Install Docker Desktop from https://www.docker.com/products/docker-desktop/ and run ./start-tei.sh to start the TEI containers."}
|
|
15
|
+
EOF
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
if ! docker info &>/dev/null 2>&1; then
|
|
20
|
+
cat <<'EOF'
|
|
21
|
+
{"additionalContext": "WARNING: Docker daemon is not running. TEI inference containers are not available. Start Docker Desktop, then run ./start-tei.sh to start the TEI containers."}
|
|
22
|
+
EOF
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# ── Check if TEI containers are already healthy ──────────────────────
|
|
27
|
+
embed_ok=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:39281/health 2>/dev/null || echo "000")
|
|
28
|
+
rerank_ok=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:39282/health 2>/dev/null || echo "000")
|
|
29
|
+
|
|
30
|
+
if [ "$embed_ok" = "200" ] && [ "$rerank_ok" = "200" ]; then
|
|
31
|
+
cat <<'EOF'
|
|
32
|
+
{"additionalContext": "TEI inference containers are running and healthy (embed :39281, rerank :39282)."}
|
|
33
|
+
EOF
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# ── Check if native TEI (Metal) is running via PID file ──────────────
|
|
38
|
+
if [ -f "$PLUGIN_DIR/.tei-pids" ]; then
|
|
39
|
+
all_alive=true
|
|
40
|
+
while IFS= read -r pid; do
|
|
41
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
42
|
+
all_alive=false
|
|
43
|
+
break
|
|
44
|
+
fi
|
|
45
|
+
done < "$PLUGIN_DIR/.tei-pids"
|
|
46
|
+
|
|
47
|
+
if $all_alive; then
|
|
48
|
+
# PIDs alive but health check failed — still starting up, don't restart
|
|
49
|
+
cat <<'EOF'
|
|
50
|
+
{"additionalContext": "TEI native processes are running but not yet healthy. They may still be loading models. Wait a moment and try again."}
|
|
51
|
+
EOF
|
|
52
|
+
exit 0
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# ── Try to start containers ──────────────────────────────────────────
|
|
57
|
+
if [ -f "$PLUGIN_DIR/start-tei.sh" ]; then
|
|
58
|
+
echo "Starting TEI containers..." >&2
|
|
59
|
+
bash "$PLUGIN_DIR/start-tei.sh" >&2 2>&1 && {
|
|
60
|
+
cat <<'EOF'
|
|
61
|
+
{"additionalContext": "TEI inference containers started successfully (embed :39281, rerank :39282). Ready for indexing and search."}
|
|
62
|
+
EOF
|
|
63
|
+
exit 0
|
|
64
|
+
}
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# ── Fallback: containers not running, can't auto-start ───────────────
|
|
68
|
+
cat <<'EOF'
|
|
69
|
+
{"additionalContext": "WARNING: TEI inference containers are not running. Run ./start-tei.sh in the claude-local-docs plugin directory to start them. Without TEI, search_docs and store_and_index_doc will fail."}
|
|
70
|
+
EOF
|
|
71
|
+
exit 0
|
package/start-tei.sh
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# start-tei.sh — Auto-detect GPU and start TEI with the optimal backend.
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# ./start-tei.sh # auto-detect (NVIDIA GPU → Docker, Apple Silicon → Metal native, else CPU Docker)
|
|
6
|
+
# ./start-tei.sh --metal # force native Metal build (macOS Apple Silicon)
|
|
7
|
+
# ./start-tei.sh --cpu # force CPU Docker
|
|
8
|
+
# ./start-tei.sh --tag 89-1.9 # force a specific TEI Docker image tag
|
|
9
|
+
# ./start-tei.sh --stop # stop all running TEI (Docker + native)
|
|
10
|
+
#
|
|
11
|
+
# NVIDIA architecture → optimized TEI Docker image:
|
|
12
|
+
# 12.x Blackwell RTX 50x0 → 120-1.9
|
|
13
|
+
# 10.x Blackwell B200 → 100-1.9 (experimental)
|
|
14
|
+
# 9.x Hopper H100/H200 → hopper-1.9
|
|
15
|
+
# 8.9 Ada RTX 40x0 / L4 → 89-1.9
|
|
16
|
+
# 8.6 Ampere RTX 30x0 / A10 → 86-1.9
|
|
17
|
+
# 8.0 Ampere A100 → 1.9
|
|
18
|
+
# 7.5 Turing T4 / RTX 20x0 → turing-1.9 (experimental)
|
|
19
|
+
# * fallback → cuda-1.9
|
|
20
|
+
#
|
|
21
|
+
# Apple Silicon → native Metal build via cargo (no Docker)
|
|
22
|
+
|
|
23
|
+
set -euo pipefail
|
|
24
|
+
cd "$(dirname "$0")"
|
|
25
|
+
|
|
26
|
+
PID_FILE=".tei-pids"
|
|
27
|
+
TEI_REPO="https://github.com/huggingface/text-embeddings-inference"
|
|
28
|
+
TEI_CLONE_DIR="${TEI_CLONE_DIR:-/tmp/text-embeddings-inference}"
|
|
29
|
+
|
|
30
|
+
# ── Arg parsing ──────────────────────────────────────────────────────
|
|
31
|
+
MODE="" # auto | metal | cpu | tag | stop
|
|
32
|
+
FORCE_TAG=""
|
|
33
|
+
|
|
34
|
+
while [[ $# -gt 0 ]]; do
|
|
35
|
+
case "$1" in
|
|
36
|
+
--metal) MODE="metal"; shift ;;
|
|
37
|
+
--cpu) MODE="cpu"; shift ;;
|
|
38
|
+
--tag) MODE="tag"; FORCE_TAG="$2"; shift 2 ;;
|
|
39
|
+
--stop) MODE="stop"; shift ;;
|
|
40
|
+
*) echo "Unknown option: $1"; exit 1 ;;
|
|
41
|
+
esac
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
[ -z "$MODE" ] && MODE="auto"
|
|
45
|
+
|
|
46
|
+
# ── Stop all TEI processes ───────────────────────────────────────────
|
|
47
|
+
stop_tei() {
|
|
48
|
+
local stopped=false
|
|
49
|
+
|
|
50
|
+
# Stop native processes
|
|
51
|
+
if [ -f "$PID_FILE" ]; then
|
|
52
|
+
echo "Stopping native TEI processes..."
|
|
53
|
+
while IFS= read -r pid; do
|
|
54
|
+
if kill -0 "$pid" 2>/dev/null; then
|
|
55
|
+
kill "$pid" 2>/dev/null || true
|
|
56
|
+
stopped=true
|
|
57
|
+
fi
|
|
58
|
+
done < "$PID_FILE"
|
|
59
|
+
rm -f "$PID_FILE"
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# Stop Docker containers
|
|
63
|
+
if docker compose -f docker-compose.yml -f docker-compose.nvidia.yml ps -q 2>/dev/null | grep -q .; then
|
|
64
|
+
echo "Stopping Docker TEI containers..."
|
|
65
|
+
docker compose -f docker-compose.yml -f docker-compose.nvidia.yml down 2>/dev/null || true
|
|
66
|
+
stopped=true
|
|
67
|
+
elif docker compose ps -q 2>/dev/null | grep -q .; then
|
|
68
|
+
echo "Stopping Docker TEI containers..."
|
|
69
|
+
docker compose down 2>/dev/null || true
|
|
70
|
+
stopped=true
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
if $stopped; then
|
|
74
|
+
echo "Stopped."
|
|
75
|
+
else
|
|
76
|
+
echo "No running TEI processes found."
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if [ "$MODE" = "stop" ]; then
|
|
81
|
+
stop_tei
|
|
82
|
+
exit 0
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
# Stop any existing TEI before starting new ones
|
|
86
|
+
stop_tei 2>/dev/null || true
|
|
87
|
+
|
|
88
|
+
# ── Detect best TEI tag for NVIDIA GPU ───────────────────────────────
|
|
89
|
+
detect_nvidia_tag() {
|
|
90
|
+
local cc major minor
|
|
91
|
+
cc=$(nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>/dev/null | head -1 | tr -d '[:space:]') || return 1
|
|
92
|
+
[ -z "$cc" ] && return 1
|
|
93
|
+
|
|
94
|
+
major="${cc%%.*}"
|
|
95
|
+
minor="${cc#*.}"
|
|
96
|
+
|
|
97
|
+
case "$major" in
|
|
98
|
+
12) echo "120-1.9" ;; # Blackwell RTX 50x0
|
|
99
|
+
10) echo "100-1.9" ;; # Blackwell B200
|
|
100
|
+
9) echo "hopper-1.9" ;; # Hopper
|
|
101
|
+
8) case "$minor" in
|
|
102
|
+
9) echo "89-1.9" ;; # Ada Lovelace
|
|
103
|
+
6) echo "86-1.9" ;; # Ampere A10/A40
|
|
104
|
+
0) echo "1.9" ;; # Ampere A100
|
|
105
|
+
*) echo "cuda-1.9" ;;
|
|
106
|
+
esac ;;
|
|
107
|
+
7) echo "turing-1.9" ;; # Turing
|
|
108
|
+
*) echo "cuda-1.9" ;; # Unknown — generic CUDA
|
|
109
|
+
esac
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
# ── Metal: native macOS build ────────────────────────────────────────
|
|
113
|
+
start_metal() {
|
|
114
|
+
local TEI_BIN
|
|
115
|
+
TEI_BIN=$(command -v text-embeddings-router 2>/dev/null || echo "")
|
|
116
|
+
|
|
117
|
+
if [ -z "$TEI_BIN" ]; then
|
|
118
|
+
echo "text-embeddings-router not found. Building with Metal support..."
|
|
119
|
+
|
|
120
|
+
if ! command -v cargo &>/dev/null; then
|
|
121
|
+
echo "Error: Rust is required. Install from https://rustup.rs"
|
|
122
|
+
exit 1
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
if [ -d "$TEI_CLONE_DIR" ]; then
|
|
126
|
+
echo "Updating TEI source..."
|
|
127
|
+
git -C "$TEI_CLONE_DIR" pull --ff-only
|
|
128
|
+
else
|
|
129
|
+
echo "Cloning TEI..."
|
|
130
|
+
git clone "$TEI_REPO" "$TEI_CLONE_DIR"
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
echo "Building text-embeddings-router with Metal (this may take a few minutes)..."
|
|
134
|
+
cargo install --path "$TEI_CLONE_DIR/router" --features metal
|
|
135
|
+
TEI_BIN=$(command -v text-embeddings-router)
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
echo "┌─────────────────────────────────────────────┐"
|
|
139
|
+
echo "│ Backend: Metal (native macOS)"
|
|
140
|
+
echo "│ Binary: $TEI_BIN"
|
|
141
|
+
echo "└─────────────────────────────────────────────┘"
|
|
142
|
+
|
|
143
|
+
rm -f "$PID_FILE"
|
|
144
|
+
|
|
145
|
+
echo "Starting TEI embed (Metal) on :39281..."
|
|
146
|
+
"$TEI_BIN" --model-id nomic-ai/nomic-embed-text-v1.5 --port 39281 --max-client-batch-size 64 &
|
|
147
|
+
echo $! >> "$PID_FILE"
|
|
148
|
+
|
|
149
|
+
echo "Starting TEI rerank (Metal) on :39282..."
|
|
150
|
+
"$TEI_BIN" --model-id cross-encoder/ms-marco-MiniLM-L-6-v2 --port 39282 &
|
|
151
|
+
echo $! >> "$PID_FILE"
|
|
152
|
+
|
|
153
|
+
wait_for_health
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# ── Docker: NVIDIA or CPU ────────────────────────────────────────────
|
|
157
|
+
start_docker() {
|
|
158
|
+
local COMPOSE_FILES=("-f" "docker-compose.yml")
|
|
159
|
+
local GPU_NAME=""
|
|
160
|
+
local TEI_TAG
|
|
161
|
+
|
|
162
|
+
if [ "$MODE" = "tag" ]; then
|
|
163
|
+
TEI_TAG="$FORCE_TAG"
|
|
164
|
+
if [[ "$TEI_TAG" != cpu-* ]]; then
|
|
165
|
+
COMPOSE_FILES+=("-f" "docker-compose.nvidia.yml")
|
|
166
|
+
fi
|
|
167
|
+
elif [ "$MODE" = "cpu" ]; then
|
|
168
|
+
TEI_TAG="cpu-1.9"
|
|
169
|
+
elif command -v nvidia-smi &>/dev/null; then
|
|
170
|
+
local TAG
|
|
171
|
+
TAG=$(detect_nvidia_tag) || TAG=""
|
|
172
|
+
if [ -n "$TAG" ]; then
|
|
173
|
+
TEI_TAG="$TAG"
|
|
174
|
+
GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 | xargs)
|
|
175
|
+
COMPOSE_FILES+=("-f" "docker-compose.nvidia.yml")
|
|
176
|
+
else
|
|
177
|
+
TEI_TAG="cpu-1.9"
|
|
178
|
+
fi
|
|
179
|
+
else
|
|
180
|
+
TEI_TAG="cpu-1.9"
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
export TEI_TAG
|
|
184
|
+
|
|
185
|
+
echo "┌─────────────────────────────────────────────┐"
|
|
186
|
+
if [ -n "$GPU_NAME" ]; then
|
|
187
|
+
echo "│ GPU: $GPU_NAME"
|
|
188
|
+
fi
|
|
189
|
+
echo "│ Backend: Docker"
|
|
190
|
+
echo "│ Tag: $TEI_TAG"
|
|
191
|
+
echo "│ Compose: ${COMPOSE_FILES[*]}"
|
|
192
|
+
echo "└─────────────────────────────────────────────┘"
|
|
193
|
+
|
|
194
|
+
docker compose "${COMPOSE_FILES[@]}" up -d
|
|
195
|
+
|
|
196
|
+
wait_for_health
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# ── Health check loop ────────────────────────────────────────────────
|
|
200
|
+
wait_for_health() {
|
|
201
|
+
echo ""
|
|
202
|
+
echo "Waiting for TEI to be ready..."
|
|
203
|
+
for i in $(seq 1 120); do
|
|
204
|
+
embed=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:39281/health 2>/dev/null || true)
|
|
205
|
+
rerank=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:39282/health 2>/dev/null || true)
|
|
206
|
+
if [ "$embed" = "200" ] && [ "$rerank" = "200" ]; then
|
|
207
|
+
printf "\n"
|
|
208
|
+
echo "Ready! embed=OK rerank=OK"
|
|
209
|
+
return 0
|
|
210
|
+
fi
|
|
211
|
+
printf "\r [%3d] embed=%s rerank=%s" "$i" "$embed" "$rerank"
|
|
212
|
+
sleep 3
|
|
213
|
+
done
|
|
214
|
+
|
|
215
|
+
printf "\n"
|
|
216
|
+
echo "Warning: TEI did not become healthy within 6 minutes."
|
|
217
|
+
return 1
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# ── Main ─────────────────────────────────────────────────────────────
|
|
221
|
+
case "$MODE" in
|
|
222
|
+
metal)
|
|
223
|
+
start_metal
|
|
224
|
+
;;
|
|
225
|
+
auto)
|
|
226
|
+
# Auto-detect: NVIDIA → Docker GPU, Apple Silicon → Metal native, else → Docker CPU
|
|
227
|
+
if command -v nvidia-smi &>/dev/null && nvidia-smi &>/dev/null; then
|
|
228
|
+
start_docker
|
|
229
|
+
elif [[ "$(uname -s)" == "Darwin" ]] && [[ "$(uname -m)" == "arm64" ]]; then
|
|
230
|
+
echo "Detected Apple Silicon — using native Metal backend"
|
|
231
|
+
start_metal
|
|
232
|
+
else
|
|
233
|
+
start_docker
|
|
234
|
+
fi
|
|
235
|
+
;;
|
|
236
|
+
*)
|
|
237
|
+
start_docker
|
|
238
|
+
;;
|
|
239
|
+
esac
|