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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-local-docs",
3
- "version": "1.0.2",
4
- "description": "Local-first Context7 alternative — indexes JS/TS dependency docs with a 4-stage RAG pipeline. All models run locally via ONNX.",
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