claude-memory-layer 1.0.28 → 1.0.29
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/README.md
CHANGED
|
@@ -16,37 +16,80 @@ Claude Memory Layer는 Claude Code에서 사용자와 AI 간의 모든 대화를
|
|
|
16
16
|
|
|
17
17
|
### 0) 최초 1회(머신 전체) 설치
|
|
18
18
|
|
|
19
|
+
권장 설치 방식은 npm에 배포된 패키지를 **전역 설치**하는 것입니다. `install`은 Claude Code hook 파일 경로를 `~/.claude/settings.json`에 저장하므로, 일회성 `npx claude-memory-layer install`보다 전역 설치 또는 고정된 로컬 checkout을 쓰는 것이 안전합니다.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g claude-memory-layer@latest
|
|
23
|
+
claude-memory-layer install
|
|
24
|
+
claude-memory-layer status
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
로컬 개발 checkout에서 설치할 때:
|
|
28
|
+
|
|
19
29
|
```bash
|
|
20
|
-
|
|
30
|
+
git clone https://github.com/buzzni/claude-memory-layer.git
|
|
31
|
+
cd claude-memory-layer
|
|
21
32
|
npm install
|
|
22
33
|
npm run build
|
|
23
34
|
npx claude-memory-layer install
|
|
35
|
+
npx claude-memory-layer status
|
|
24
36
|
```
|
|
25
37
|
|
|
26
38
|
- `install`은 **한 번만** 하면 됩니다(Claude Code hooks 등록).
|
|
39
|
+
- Linux x64 + CUDA 11 환경에서는 설치 중 optional embedding backend를 CPU-only ONNX Runtime으로 자동 복구합니다.
|
|
27
40
|
- 이후 프로젝트별로 메모리 저장소가 자동 분리됩니다.
|
|
41
|
+
- `install` / `uninstall`은 `~/.claude/settings.json`을 수정합니다.
|
|
42
|
+
|
|
43
|
+
#### CUDA 11 / `onnxruntime-node` 설치 에러
|
|
44
|
+
|
|
45
|
+
Linux x64 서버에 CUDA 11이 설치되어 있으면 `@huggingface/transformers`의 하위 의존성인 `onnxruntime-node`가 `nvcc --version`을 감지한 뒤 CUDA 11용 GPU 바이너리를 자동 설치하려고 합니다. 현재 해당 install script는 CUDA 11 자동 설치를 지원하지 않아 다음 오류로 `npm install`이 실패할 수 있습니다.
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
Error: CUDA 11 binaries are not supported by this script yet.
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Claude Memory Layer는 설치 시 Linux x64 + CUDA 11 환경을 감지하면 `@huggingface/transformers`를 optional dependency로 처리한 뒤 CPU-only ONNX Runtime 설정으로 자동 복구합니다. 그래서 일반적으로 사용자가 환경변수를 직접 지정할 필요는 없습니다.
|
|
52
|
+
|
|
53
|
+
만약 구버전 패키지를 설치 중이거나 postinstall 복구가 실패하면 아래처럼 수동으로 CUDA 바이너리 다운로드만 건너뛰어 재설치할 수 있습니다.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# 실패한 전역 설치가 일부 남아 있으면 먼저 제거
|
|
57
|
+
npm uninstall -g claude-memory-layer || true
|
|
58
|
+
|
|
59
|
+
# 수동 fallback: CPU-only ONNX Runtime으로 재설치
|
|
60
|
+
ONNXRUNTIME_NODE_INSTALL_CUDA=skip npm install -g claude-memory-layer@latest
|
|
61
|
+
claude-memory-layer --version
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
로컬 checkout 개발 환경에서 구버전 의존성 설치가 같은 오류를 내면 아래처럼 수동 fallback을 사용할 수 있습니다.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
ONNXRUNTIME_NODE_INSTALL_CUDA=skip npm install
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`npm warn deprecated ...` 경고는 하위 의존성 경고이며 설치 실패 원인이 아닙니다.
|
|
28
71
|
|
|
29
72
|
### 1) 새 프로젝트에서 초기 메모리 생성
|
|
30
73
|
|
|
31
74
|
```bash
|
|
32
75
|
cd /path/to/your-project
|
|
33
|
-
|
|
76
|
+
claude-memory-layer import
|
|
34
77
|
```
|
|
35
78
|
|
|
36
|
-
- 현재 프로젝트의 기존 Claude 세션(`~/.claude/projects/...`)을 읽어와 메모리로 적재합니다.
|
|
79
|
+
- 현재 프로젝트의 기존 Claude Code 세션(`~/.claude/projects/...`)을 읽어와 메모리로 적재합니다.
|
|
37
80
|
- 벡터 임베딩까지 한 번에 처리됩니다.
|
|
38
81
|
|
|
39
82
|
### 2) 사용 중 확인
|
|
40
83
|
|
|
41
84
|
```bash
|
|
42
85
|
# 프로젝트 메모리 검색
|
|
43
|
-
|
|
86
|
+
claude-memory-layer search "인증 구조"
|
|
44
87
|
|
|
45
88
|
# 통계 확인
|
|
46
|
-
|
|
89
|
+
claude-memory-layer stats
|
|
47
90
|
|
|
48
91
|
# 대시보드 실행
|
|
49
|
-
|
|
92
|
+
claude-memory-layer dashboard --no-open
|
|
50
93
|
```
|
|
51
94
|
|
|
52
95
|
### 3) 다른 프로젝트에도 동일하게 적용?
|
|
@@ -55,8 +98,8 @@ npx claude-memory-layer dashboard
|
|
|
55
98
|
|
|
56
99
|
```bash
|
|
57
100
|
cd /path/to/another-project
|
|
58
|
-
|
|
59
|
-
|
|
101
|
+
claude-memory-layer import
|
|
102
|
+
claude-memory-layer search "배포 이슈"
|
|
60
103
|
```
|
|
61
104
|
|
|
62
105
|
프로젝트마다 내부적으로 별도 저장소(`~/.claude-code/memory/projects/<hash>`)를 사용하므로,
|
|
@@ -66,6 +109,7 @@ npx claude-memory-layer search "배포 이슈"
|
|
|
66
109
|
|
|
67
110
|
- 특정 프로젝트를 명시하고 싶으면 대부분 명령에 `--project <path>` 사용 가능
|
|
68
111
|
- 대규모 리임포트가 필요하면 `import --force` 사용
|
|
112
|
+
- 최근 일부만 가져오고 싶으면 `--session-limit <n>` 또는 `--limit <n>` 사용
|
|
69
113
|
- 백그라운드 worker가 못 처리한 임베딩은 `process`로 수동 처리
|
|
70
114
|
- 상태 점검:
|
|
71
115
|
- `GET /health` (서버 헬스)
|
|
@@ -79,6 +123,144 @@ npx claude-memory-layer search "배포 이슈"
|
|
|
79
123
|
|
|
80
124
|
---
|
|
81
125
|
|
|
126
|
+
## 다른 서버 초기 세팅 & 이전 대화 ingest
|
|
127
|
+
|
|
128
|
+
새 서버에서 Claude Memory Layer를 설치하고 기존 Claude Code/Codex/Hermes 대화 기록을 처음 적재할 때는 아래 체크리스트를 따르세요.
|
|
129
|
+
|
|
130
|
+
### 1) 새 서버 준비
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
node --version # Node.js >= 18 필수
|
|
134
|
+
npm --version
|
|
135
|
+
npm install -g claude-memory-layer@latest
|
|
136
|
+
claude-memory-layer install
|
|
137
|
+
claude-memory-layer status
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> `claude-memory-layer install`은 Claude Code hooks를 등록합니다. 이미 Claude Code가 실행 중이면 재시작해야 hook이 반영됩니다.
|
|
141
|
+
|
|
142
|
+
### 2) 이전 서버의 원본 대화 기록 가져오기
|
|
143
|
+
|
|
144
|
+
가장 안전한 방식은 **원본 대화 기록을 새 서버로 복사한 뒤 새 서버에서 다시 import**하는 것입니다. 필요한 것만 선택해서 복사하세요. 이 디렉토리/DB에는 민감한 대화와 경로가 포함될 수 있으므로 공개 저장소에 커밋하거나 공유하지 말고, SSH/사설망 등 신뢰할 수 있는 경로로만 복사하세요.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Claude Code 원본 세션(JSONL)
|
|
148
|
+
mkdir -p ~/.claude/projects
|
|
149
|
+
rsync -a OLD_HOST:~/.claude/projects/ ~/.claude/projects/
|
|
150
|
+
|
|
151
|
+
# Codex CLI 원본 세션(JSONL) - Codex 기록도 가져올 때만
|
|
152
|
+
mkdir -p ~/.codex/sessions
|
|
153
|
+
rsync -a OLD_HOST:~/.codex/sessions/ ~/.codex/sessions/
|
|
154
|
+
|
|
155
|
+
# Hermes Agent SessionDB - Hermes 기록도 가져올 때만
|
|
156
|
+
# 권장: OLD_HOST에서 Hermes/gateway/agent 프로세스를 먼저 멈춘 뒤 SQLite sidecar까지 함께 복사
|
|
157
|
+
mkdir -p ~/.hermes
|
|
158
|
+
rsync -a OLD_HOST:'~/.hermes/state.db*' ~/.hermes/
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Hermes를 멈출 수 없는 운영 서버라면, `state.db` 파일을 직접 복사하는 대신 OLD_HOST에서 SQLite `.backup`으로 일관된 스냅샷을 만든 뒤 가져오세요.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
ssh OLD_HOST 'sqlite3 ~/.hermes/state.db ".backup /tmp/hermes-state.db.backup"'
|
|
165
|
+
mkdir -p ~/.hermes
|
|
166
|
+
rsync -a OLD_HOST:/tmp/hermes-state.db.backup ~/.hermes/state.db
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
이미 처리된 CML 저장소를 그대로 옮기고 싶다면, 이전/새 서버의 관련 agent, dashboard, semantic daemon/worker 등 모든 CML writer를 멈춘 뒤 아래처럼 복사할 수도 있습니다. 단, 재현성과 모델/버전 migration을 위해서는 위의 원본 기록 re-ingest 방식을 우선 권장합니다.
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
mkdir -p ~/.claude-code
|
|
173
|
+
rsync -a OLD_HOST:~/.claude-code/memory/ ~/.claude-code/memory/
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 3) 프로젝트별로 read-only 검증 후 import
|
|
177
|
+
|
|
178
|
+
프로젝트 메모리는 프로젝트 경로 기준으로 분리됩니다. 새 서버의 실제 repo 경로를 `PROJECT`에 넣고, 필요한 importer만 실행하세요.
|
|
179
|
+
|
|
180
|
+
중요: Codex/Hermes/Claude Code 원본 세션의 project filter는 **대화가 생성될 당시의 절대 경로**를 기준으로 매칭합니다. 새 서버에서도 repo 절대 경로가 이전 서버와 같으면 아래 project import를 그대로 쓰면 됩니다.
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
export PROJECT=/path/to/your-project
|
|
184
|
+
cd "$PROJECT"
|
|
185
|
+
|
|
186
|
+
# Claude Code 기록: import 가능한 세션 확인 후 프로젝트 메모리로 적재
|
|
187
|
+
claude-memory-layer list --project "$PROJECT"
|
|
188
|
+
claude-memory-layer import --project "$PROJECT" --verbose
|
|
189
|
+
|
|
190
|
+
# Codex 기록: 먼저 읽기 전용 리포트로 확인한 뒤 명시적으로 import
|
|
191
|
+
claude-memory-layer codex validate --project "$PROJECT" --format markdown
|
|
192
|
+
claude-memory-layer codex import --project "$PROJECT" --verbose
|
|
193
|
+
|
|
194
|
+
# Hermes 기록: 먼저 읽기 전용 리포트로 확인한 뒤 명시적으로 import
|
|
195
|
+
claude-memory-layer hermes validate --project "$PROJECT" --format markdown
|
|
196
|
+
claude-memory-layer hermes import --project "$PROJECT" --verbose
|
|
197
|
+
|
|
198
|
+
# pending embedding이 남아 있으면 수동 처리
|
|
199
|
+
claude-memory-layer process --project "$PROJECT"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
이전 서버와 새 서버의 repo 절대 경로가 다르면, `OLD_PROJECT`로 원본 세션을 찾고 특정 session 파일/id를 새 프로젝트 저장소(`NEW_PROJECT`)로 가져오세요.
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
export OLD_PROJECT=/old/server/path/to/your-project
|
|
206
|
+
export NEW_PROJECT=/path/to/your-project
|
|
207
|
+
cd "$NEW_PROJECT"
|
|
208
|
+
|
|
209
|
+
# Claude Code: list 출력의 JSONL session file path를 확인해서 사용
|
|
210
|
+
claude-memory-layer list --project "$OLD_PROJECT"
|
|
211
|
+
claude-memory-layer import --project "$NEW_PROJECT" --session /path/to/claude-session.jsonl --verbose
|
|
212
|
+
|
|
213
|
+
# Codex: validate 결과에서 session JSONL 파일을 확인한 뒤 import
|
|
214
|
+
claude-memory-layer codex validate --project "$OLD_PROJECT" --format markdown
|
|
215
|
+
claude-memory-layer codex import --project "$NEW_PROJECT" --session /path/to/codex-session.jsonl --verbose
|
|
216
|
+
|
|
217
|
+
# Hermes: validate 결과에서 Hermes session id를 확인한 뒤 import
|
|
218
|
+
claude-memory-layer hermes validate --project "$OLD_PROJECT" --format markdown
|
|
219
|
+
claude-memory-layer hermes import --project "$NEW_PROJECT" --session 20260505_010203_abcd1234 --verbose
|
|
220
|
+
|
|
221
|
+
claude-memory-layer process --project "$NEW_PROJECT"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
주의:
|
|
225
|
+
|
|
226
|
+
- `claude-memory-layer import --all`은 모든 Claude Code 세션을 전역 저장소로 가져옵니다. 프로젝트별 컨텍스트 품질이 중요하면 각 repo에서 `--project <path>` 방식으로 반복하는 것을 권장합니다.
|
|
227
|
+
- `codex import --all`, `hermes import --all`도 의도적으로 전역 메모리를 만들 때만 사용하세요.
|
|
228
|
+
- import/validate/list 결과에는 대화 내용 일부나 로컬 경로가 포함될 수 있습니다. 외부 공유 전에는 민감정보와 경로를 제거하고, Codex validate 리포트는 필요하면 `--anonymize-projects`를 함께 사용하세요.
|
|
229
|
+
- import는 콘텐츠 해시 기반으로 중복을 건너뛰므로 여러 번 실행해도 같은 내용이 중복 저장되지 않습니다. 단, `--force`는 기존 import 이벤트를 지우고 재적재하므로 신중히 사용하세요.
|
|
230
|
+
|
|
231
|
+
### 4) 검증
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
export VERIFY_PROJECT=/path/to/your-project # 위에서 쓴 PROJECT 또는 NEW_PROJECT
|
|
235
|
+
claude-memory-layer stats --project "$VERIFY_PROJECT"
|
|
236
|
+
claude-memory-layer search "최근에 하던 작업" --project "$VERIFY_PROJECT" --top-k 5
|
|
237
|
+
claude-memory-layer dashboard --no-open --port 37777
|
|
238
|
+
# 다른 터미널에서: curl http://localhost:37777/api/health
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### 5) MCP/다른 agent에 연결
|
|
242
|
+
|
|
243
|
+
Claude Desktop은 CLI로 자동 등록할 수 있습니다. GUI 앱에서 shell `PATH`가 다를 수 있으면 stdio binary의 절대 경로를 command로 넣는 방식이 더 견고합니다.
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
claude-memory-layer mcp install --command "$(command -v claude-memory-layer-mcp)"
|
|
247
|
+
# Claude Desktop 재시작
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Codex/Hermes 등 MCP client에도 전역 설치된 stdio binary를 등록하면 됩니다.
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# Codex 예시
|
|
254
|
+
codex mcp add claude-memory-layer -- claude-memory-layer-mcp
|
|
255
|
+
|
|
256
|
+
# Hermes 예시
|
|
257
|
+
hermes mcp add claude-memory-layer --command claude-memory-layer-mcp
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
MCP client가 환경에 따라 PATH를 못 찾으면 `command -v claude-memory-layer-mcp`로 절대 경로를 확인해서 command에 넣으세요.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
82
264
|
## Features
|
|
83
265
|
|
|
84
266
|
### Core Features
|
package/dist/cli/index.js
CHANGED
|
@@ -12904,7 +12904,7 @@ async function runMarketContextCommand(options) {
|
|
|
12904
12904
|
}
|
|
12905
12905
|
}
|
|
12906
12906
|
var program = new Command();
|
|
12907
|
-
program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.
|
|
12907
|
+
program.name("claude-memory-layer").description("Claude Code Memory Plugin CLI").version("1.0.29");
|
|
12908
12908
|
program.command("market-context").description("Fetch read-only DART/FRED/Finnhub context with structured MarketContextSnapshot bull/bear/risk/catalyst analysis").option("--company <name>", "Company name for DART fallback search and report subject").option("--dart-corp-code <code>", "Exact DART corp_code for issuer-specific filings").option("--symbol <ticker>", "Listed ticker for Finnhub company profile").option("--providers <list>", "Comma-separated providers: dart,fred,finnhub").option("--fred-series <list>", "Comma-separated FRED series IDs").option("--json", "Print structured JSON including analysis.marketSnapshot").option("--no-snapshot", "Disable MarketContextSnapshot and DART company snapshot analysis").action(async (options) => {
|
|
12909
12909
|
try {
|
|
12910
12910
|
await runMarketContextCommand(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-memory-layer",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.29",
|
|
4
4
|
"description": "Claude Code plugin that learns from conversations to provide personalized assistance",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"ops:health": "npm run ops:sync-gap:report && npm run ops:sync-gap:heal && npm run ops:review:resolve",
|
|
24
24
|
"ops:projects:clean-unknown": "node scripts/delete-unknown-projects.js",
|
|
25
25
|
"benchmark:replay": "tsx scripts/replay-retrieval-benchmark.ts",
|
|
26
|
-
"benchmark:qrels": "tsx scripts/generate-session-qrels.ts"
|
|
26
|
+
"benchmark:qrels": "tsx scripts/generate-session-qrels.ts",
|
|
27
|
+
"postinstall": "node scripts/postinstall-embedding-backend.cjs"
|
|
27
28
|
},
|
|
28
29
|
"keywords": [
|
|
29
30
|
"claude-code",
|
|
@@ -40,7 +41,6 @@
|
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
43
|
"@hono/node-server": "^1.13.0",
|
|
43
|
-
"@huggingface/transformers": "^3.8.1",
|
|
44
44
|
"@lancedb/lancedb": "^0.5.0",
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
46
46
|
"better-sqlite3": "^12.6.2",
|
|
@@ -49,6 +49,9 @@
|
|
|
49
49
|
"mongodb": "^6.14.0",
|
|
50
50
|
"zod": "^3.22.0"
|
|
51
51
|
},
|
|
52
|
+
"optionalDependencies": {
|
|
53
|
+
"@huggingface/transformers": "^3.8.1"
|
|
54
|
+
},
|
|
52
55
|
"devDependencies": {
|
|
53
56
|
"@types/better-sqlite3": "^7.6.13",
|
|
54
57
|
"@types/node": "^20.11.0",
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { execFileSync, spawnSync } = require('node:child_process');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { createRequire } = require('node:module');
|
|
7
|
+
|
|
8
|
+
const EMBEDDING_BACKEND_PACKAGE_NAME = '@huggingface/transformers';
|
|
9
|
+
const EMBEDDING_BACKEND_VERSION = '3.8.1';
|
|
10
|
+
const EMBEDDING_BACKEND_PACKAGE = `${EMBEDDING_BACKEND_PACKAGE_NAME}@${EMBEDDING_BACKEND_VERSION}`;
|
|
11
|
+
const REPAIR_GUARD_ENV = 'CLAUDE_MEMORY_LAYER_EMBEDDING_POSTINSTALL_REPAIR';
|
|
12
|
+
const SKIP_ENV = 'CLAUDE_MEMORY_LAYER_SKIP_EMBEDDING_POSTINSTALL';
|
|
13
|
+
|
|
14
|
+
function parseCudaMajor(output) {
|
|
15
|
+
const releaseMatch = String(output).match(/release\s+(\d+)(?:\.\d+)?/i);
|
|
16
|
+
if (releaseMatch) return Number(releaseMatch[1]);
|
|
17
|
+
|
|
18
|
+
const versionMatch = String(output).match(/\bV(\d+)\.\d+/i);
|
|
19
|
+
if (versionMatch) return Number(versionMatch[1]);
|
|
20
|
+
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseCudaMajorFromEnv(env = process.env) {
|
|
25
|
+
const value = env.ONNXRUNTIME_NODE_INSTALL_CUDA || env.npm_config_onnxruntime_node_install_cuda;
|
|
26
|
+
if (!value) return null;
|
|
27
|
+
if (value === 'v11' || value === '11') return 11;
|
|
28
|
+
if (value === 'v12' || value === '12') return 12;
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function detectCudaMajor({ env = process.env, execFileSyncImpl = execFileSync } = {}) {
|
|
33
|
+
const envMajor = parseCudaMajorFromEnv(env);
|
|
34
|
+
if (envMajor) return envMajor;
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
return parseCudaMajor(execFileSyncImpl('nvcc', ['--version'], {
|
|
38
|
+
encoding: 'utf8',
|
|
39
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
40
|
+
}));
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isSkipRequested(env = process.env) {
|
|
47
|
+
if (env[SKIP_ENV] === '1' || env[REPAIR_GUARD_ENV] === '1') return true;
|
|
48
|
+
if (env.npm_config_optional === 'false') return true;
|
|
49
|
+
if (String(env.npm_config_omit || '').split(',').map((item) => item.trim()).includes('optional')) return true;
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isEmbeddingBackendAvailable(rootDir = process.cwd()) {
|
|
54
|
+
try {
|
|
55
|
+
const requireFromRoot = createRequire(path.join(rootDir, 'package.json'));
|
|
56
|
+
requireFromRoot.resolve(EMBEDDING_BACKEND_PACKAGE_NAME);
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function shouldAttemptAutoInstall({ platform, arch, cudaMajor, transformersAvailable, skipRequested }) {
|
|
64
|
+
return platform === 'linux' &&
|
|
65
|
+
arch === 'x64' &&
|
|
66
|
+
cudaMajor === 11 &&
|
|
67
|
+
!transformersAvailable &&
|
|
68
|
+
!skipRequested;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createRepairEnv(env = process.env) {
|
|
72
|
+
return {
|
|
73
|
+
...env,
|
|
74
|
+
ONNXRUNTIME_NODE_INSTALL_CUDA: 'skip',
|
|
75
|
+
[REPAIR_GUARD_ENV]: '1'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function createNpmInstallArgs() {
|
|
80
|
+
return [
|
|
81
|
+
'install',
|
|
82
|
+
'--no-save',
|
|
83
|
+
'--no-package-lock',
|
|
84
|
+
'--omit=dev',
|
|
85
|
+
EMBEDDING_BACKEND_PACKAGE
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function runPostinstall({
|
|
90
|
+
rootDir = process.cwd(),
|
|
91
|
+
env = process.env,
|
|
92
|
+
platform = process.platform,
|
|
93
|
+
arch = process.arch,
|
|
94
|
+
execFileSyncImpl = execFileSync,
|
|
95
|
+
spawnSyncImpl = spawnSync,
|
|
96
|
+
log = console.log,
|
|
97
|
+
warn = console.warn
|
|
98
|
+
} = {}) {
|
|
99
|
+
const transformersAvailable = isEmbeddingBackendAvailable(rootDir);
|
|
100
|
+
const skipRequested = isSkipRequested(env);
|
|
101
|
+
const cudaMajor = detectCudaMajor({ env, execFileSyncImpl });
|
|
102
|
+
|
|
103
|
+
if (!shouldAttemptAutoInstall({ platform, arch, cudaMajor, transformersAvailable, skipRequested })) {
|
|
104
|
+
return { attempted: false, cudaMajor, transformersAvailable, skipRequested };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
log('[claude-memory-layer] CUDA 11 detected and optional embedding backend is missing. Installing CPU-only embedding backend...');
|
|
108
|
+
|
|
109
|
+
const npmCommand = platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
110
|
+
const result = spawnSyncImpl(npmCommand, createNpmInstallArgs(), {
|
|
111
|
+
cwd: rootDir,
|
|
112
|
+
env: createRepairEnv(env),
|
|
113
|
+
stdio: 'inherit'
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (result.error || result.status !== 0) {
|
|
117
|
+
warn('[claude-memory-layer] Optional embedding backend repair failed. Claude Memory Layer is installed, but semantic/vector embeddings may be unavailable until you run:');
|
|
118
|
+
warn(` ONNXRUNTIME_NODE_INSTALL_CUDA=skip npm install -g claude-memory-layer@latest`);
|
|
119
|
+
if (result.error) warn(` ${result.error.message}`);
|
|
120
|
+
return { attempted: true, success: false, cudaMajor, transformersAvailable, skipRequested };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
log('[claude-memory-layer] Optional embedding backend installed with CPU-only ONNX Runtime.');
|
|
124
|
+
return { attempted: true, success: true, cudaMajor, transformersAvailable, skipRequested };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (require.main === module) {
|
|
128
|
+
runPostinstall();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
EMBEDDING_BACKEND_PACKAGE,
|
|
133
|
+
parseCudaMajor,
|
|
134
|
+
parseCudaMajorFromEnv,
|
|
135
|
+
detectCudaMajor,
|
|
136
|
+
isSkipRequested,
|
|
137
|
+
isEmbeddingBackendAvailable,
|
|
138
|
+
shouldAttemptAutoInstall,
|
|
139
|
+
createRepairEnv,
|
|
140
|
+
createNpmInstallArgs,
|
|
141
|
+
runPostinstall
|
|
142
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
type SpawnCall = {
|
|
11
|
+
cmd: string;
|
|
12
|
+
args: string[];
|
|
13
|
+
env: NodeJS.ProcessEnv;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type PostinstallEmbeddingBackend = {
|
|
17
|
+
EMBEDDING_BACKEND_PACKAGE: string;
|
|
18
|
+
parseCudaMajor(output: string): number | null;
|
|
19
|
+
shouldAttemptAutoInstall(input: {
|
|
20
|
+
platform: NodeJS.Platform;
|
|
21
|
+
arch: string;
|
|
22
|
+
cudaMajor: number | null;
|
|
23
|
+
transformersAvailable: boolean;
|
|
24
|
+
skipRequested: boolean;
|
|
25
|
+
}): boolean;
|
|
26
|
+
createRepairEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
27
|
+
createNpmInstallArgs(): string[];
|
|
28
|
+
runPostinstall(input?: {
|
|
29
|
+
rootDir?: string;
|
|
30
|
+
env?: NodeJS.ProcessEnv;
|
|
31
|
+
platform?: NodeJS.Platform;
|
|
32
|
+
arch?: string;
|
|
33
|
+
execFileSyncImpl?: () => string;
|
|
34
|
+
spawnSyncImpl?: (cmd: string, args: string[], options: { env: NodeJS.ProcessEnv }) => { status: number };
|
|
35
|
+
log?: () => void;
|
|
36
|
+
warn?: () => void;
|
|
37
|
+
}): { attempted: boolean; success?: boolean; cudaMajor: number | null; transformersAvailable: boolean; skipRequested: boolean };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function loadPostinstallModule(): PostinstallEmbeddingBackend {
|
|
41
|
+
return require('../../scripts/postinstall-embedding-backend.cjs') as PostinstallEmbeddingBackend;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('embedding backend postinstall repair', () => {
|
|
45
|
+
it('keeps the install-time embedding backend optional and registers postinstall repair', () => {
|
|
46
|
+
const pkg = JSON.parse(readFileSync('package.json', 'utf-8')) as {
|
|
47
|
+
scripts: Record<string, string>;
|
|
48
|
+
dependencies?: Record<string, string>;
|
|
49
|
+
optionalDependencies?: Record<string, string>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
expect(pkg.dependencies).not.toHaveProperty('@huggingface/transformers');
|
|
53
|
+
expect(pkg.optionalDependencies).toMatchObject({
|
|
54
|
+
'@huggingface/transformers': '^3.8.1'
|
|
55
|
+
});
|
|
56
|
+
expect(pkg.scripts.postinstall).toBe('node scripts/postinstall-embedding-backend.cjs');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('detects CUDA major version from nvcc output', () => {
|
|
60
|
+
const postinstall = loadPostinstallModule();
|
|
61
|
+
|
|
62
|
+
expect(postinstall.parseCudaMajor('Cuda compilation tools, release 11.8, V11.8.89')).toBe(11);
|
|
63
|
+
expect(postinstall.parseCudaMajor('Cuda compilation tools, release 12.4, V12.4.131')).toBe(12);
|
|
64
|
+
expect(postinstall.parseCudaMajor('nvcc: NVIDIA (R) Cuda compiler driver')).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('only auto-installs the embedding backend for Linux x64 CUDA 11 when it is missing', () => {
|
|
68
|
+
const postinstall = loadPostinstallModule();
|
|
69
|
+
|
|
70
|
+
expect(postinstall.shouldAttemptAutoInstall({
|
|
71
|
+
platform: 'linux',
|
|
72
|
+
arch: 'x64',
|
|
73
|
+
cudaMajor: 11,
|
|
74
|
+
transformersAvailable: false,
|
|
75
|
+
skipRequested: false
|
|
76
|
+
})).toBe(true);
|
|
77
|
+
|
|
78
|
+
expect(postinstall.shouldAttemptAutoInstall({
|
|
79
|
+
platform: 'linux',
|
|
80
|
+
arch: 'x64',
|
|
81
|
+
cudaMajor: 11,
|
|
82
|
+
transformersAvailable: true,
|
|
83
|
+
skipRequested: false
|
|
84
|
+
})).toBe(false);
|
|
85
|
+
|
|
86
|
+
expect(postinstall.shouldAttemptAutoInstall({
|
|
87
|
+
platform: 'linux',
|
|
88
|
+
arch: 'arm64',
|
|
89
|
+
cudaMajor: 11,
|
|
90
|
+
transformersAvailable: false,
|
|
91
|
+
skipRequested: false
|
|
92
|
+
})).toBe(false);
|
|
93
|
+
|
|
94
|
+
expect(postinstall.shouldAttemptAutoInstall({
|
|
95
|
+
platform: 'darwin',
|
|
96
|
+
arch: 'x64',
|
|
97
|
+
cudaMajor: 11,
|
|
98
|
+
transformersAvailable: false,
|
|
99
|
+
skipRequested: false
|
|
100
|
+
})).toBe(false);
|
|
101
|
+
|
|
102
|
+
expect(postinstall.shouldAttemptAutoInstall({
|
|
103
|
+
platform: 'linux',
|
|
104
|
+
arch: 'x64',
|
|
105
|
+
cudaMajor: 12,
|
|
106
|
+
transformersAvailable: false,
|
|
107
|
+
skipRequested: false
|
|
108
|
+
})).toBe(false);
|
|
109
|
+
|
|
110
|
+
expect(postinstall.shouldAttemptAutoInstall({
|
|
111
|
+
platform: 'linux',
|
|
112
|
+
arch: 'x64',
|
|
113
|
+
cudaMajor: 11,
|
|
114
|
+
transformersAvailable: false,
|
|
115
|
+
skipRequested: true
|
|
116
|
+
})).toBe(false);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('repairs missing transformers with CPU-only onnxruntime install settings', () => {
|
|
120
|
+
const postinstall = loadPostinstallModule();
|
|
121
|
+
|
|
122
|
+
expect(postinstall.createRepairEnv({})).toMatchObject({
|
|
123
|
+
ONNXRUNTIME_NODE_INSTALL_CUDA: 'skip',
|
|
124
|
+
CLAUDE_MEMORY_LAYER_EMBEDDING_POSTINSTALL_REPAIR: '1'
|
|
125
|
+
});
|
|
126
|
+
expect(postinstall.createNpmInstallArgs()).toEqual([
|
|
127
|
+
'install',
|
|
128
|
+
'--no-save',
|
|
129
|
+
'--no-package-lock',
|
|
130
|
+
'--omit=dev',
|
|
131
|
+
postinstall.EMBEDDING_BACKEND_PACKAGE
|
|
132
|
+
]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('runs the automatic repair command when Linux x64 CUDA 11 skipped the optional backend', () => {
|
|
136
|
+
const postinstall = loadPostinstallModule();
|
|
137
|
+
const rootDir = mkdtempSync(join(tmpdir(), 'cml-postinstall-test-'));
|
|
138
|
+
const calls: SpawnCall[] = [];
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
writeFileSync(join(rootDir, 'package.json'), JSON.stringify({ name: 'claude-memory-layer-install-root' }));
|
|
142
|
+
|
|
143
|
+
const result = postinstall.runPostinstall({
|
|
144
|
+
rootDir,
|
|
145
|
+
env: {},
|
|
146
|
+
platform: 'linux',
|
|
147
|
+
arch: 'x64',
|
|
148
|
+
execFileSyncImpl: () => 'Cuda compilation tools, release 11.8, V11.8.89',
|
|
149
|
+
spawnSyncImpl: (cmd, args, options) => {
|
|
150
|
+
calls.push({ cmd, args, env: options.env });
|
|
151
|
+
return { status: 0 };
|
|
152
|
+
},
|
|
153
|
+
log: () => undefined,
|
|
154
|
+
warn: () => undefined
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(result).toMatchObject({ attempted: true, success: true, cudaMajor: 11, transformersAvailable: false });
|
|
158
|
+
expect(calls).toHaveLength(1);
|
|
159
|
+
expect(calls[0]?.cmd).toBe('npm');
|
|
160
|
+
expect(calls[0]?.args).toEqual(postinstall.createNpmInstallArgs());
|
|
161
|
+
expect(calls[0]?.env.ONNXRUNTIME_NODE_INSTALL_CUDA).toBe('skip');
|
|
162
|
+
expect(calls[0]?.env.CLAUDE_MEMORY_LAYER_EMBEDDING_POSTINSTALL_REPAIR).toBe('1');
|
|
163
|
+
} finally {
|
|
164
|
+
rmSync(rootDir, { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
});
|