bluera-knowledge 0.16.2 → 0.16.4
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/plugin.json +1 -9
- package/CHANGELOG.md +20 -0
- package/commands/test-plugin.md +260 -187
- package/dist/mcp/bootstrap.js +1 -105
- package/dist/mcp/bootstrap.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bluera-knowledge",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.4",
|
|
4
4
|
"description": "Clone repos, crawl docs, search locally. Fast, authoritative answers for AI coding agents.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Bluera Inc",
|
|
7
7
|
"url": "https://bluera.ai"
|
|
8
|
-
},
|
|
9
|
-
"prebuiltBinaries": {
|
|
10
|
-
"manifestUrl": "https://github.com/blueraai/bluera-knowledge/releases/latest/download/manifest.json",
|
|
11
|
-
"platforms": [
|
|
12
|
-
"darwin-arm64",
|
|
13
|
-
"darwin-x64",
|
|
14
|
-
"linux-x64"
|
|
15
|
-
]
|
|
16
8
|
}
|
|
17
9
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [0.16.4](https://github.com/blueraai/bluera-knowledge/compare/v0.16.3...v0.16.4) (2026-01-17)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **test:** expand test-plugin to cover full API surface ([e473f09](https://github.com/blueraai/bluera-knowledge/commit/e473f09003f311ca8648d60ccf56fbd7f99bd480))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **bootstrap:** use system tar for extraction ([3a17b33](https://github.com/blueraai/bluera-knowledge/commit/3a17b33b99aa6ae0d35b2f2a4a96daa444336e99))
|
|
16
|
+
|
|
17
|
+
## [0.16.3](https://github.com/blueraai/bluera-knowledge/compare/v0.16.1...v0.16.3) (2026-01-17)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **plugin:** add author field to manifest ([9bf15c2](https://github.com/blueraai/bluera-knowledge/commit/9bf15c243b735d4a1763de9b3601781c6e4aeaf6))
|
|
23
|
+
* **plugin:** remove unrecognized prebuiltBinaries field ([329ac08](https://github.com/blueraai/bluera-knowledge/commit/329ac08b4f794e016bbfedc0d24c63491e9a7dfb))
|
|
24
|
+
|
|
5
25
|
## [0.16.2](https://github.com/blueraai/bluera-knowledge/compare/v0.16.1...v0.16.2) (2026-01-17)
|
|
6
26
|
|
|
7
27
|
|
package/commands/test-plugin.md
CHANGED
|
@@ -6,22 +6,29 @@ context: fork
|
|
|
6
6
|
|
|
7
7
|
# Test Plugin
|
|
8
8
|
|
|
9
|
-
Comprehensive test of all Bluera Knowledge plugin functionality
|
|
9
|
+
Comprehensive test of all Bluera Knowledge plugin functionality covering the **full API surface**.
|
|
10
|
+
|
|
11
|
+
## API Coverage
|
|
12
|
+
|
|
13
|
+
| Category | Tests |
|
|
14
|
+
|----------|-------|
|
|
15
|
+
| MCP execute commands | help, commands, stores, store:create, store:info, store:index, store:delete, jobs, job:status, job:cancel |
|
|
16
|
+
| MCP tools | search, get_full_context |
|
|
17
|
+
| Slash commands | stores, search, suggest, check-status, skill-activation, index, add-folder |
|
|
18
|
+
| Web crawling | crawl (start job, verify status, cancel) |
|
|
19
|
+
| Hooks | PostToolUse, UserPromptSubmit, SessionStart |
|
|
10
20
|
|
|
11
21
|
## When to Use Each Mode
|
|
12
22
|
|
|
13
23
|
| Scenario | Command | Tests |
|
|
14
24
|
|----------|---------|-------|
|
|
15
|
-
| Claude running with `--plugin-dir .` | `/test-plugin` |
|
|
16
|
-
| Plugin installed from marketplace | `/test-plugin` |
|
|
17
|
-
| Claude running WITHOUT plugin loaded | `/test-plugin --dev` |
|
|
18
|
-
| CI/CD or scripted testing (no Claude) | `--dev` via bash | 16/19 (no slash cmds) |
|
|
25
|
+
| Claude running with `--plugin-dir .` | `/test-plugin` | 30/30 (full suite) |
|
|
26
|
+
| Plugin installed from marketplace | `/test-plugin` | 30/30 (full suite) |
|
|
27
|
+
| Claude running WITHOUT plugin loaded | `/test-plugin --dev` | 24/30 (no slash cmds) |
|
|
19
28
|
|
|
20
29
|
**Modes:**
|
|
21
|
-
- **Default mode**: Uses Claude's MCP tools.
|
|
22
|
-
- **`--dev` mode**: Spawns MCP server directly
|
|
23
|
-
|
|
24
|
-
**Recommendation:** If developing with `claude --plugin-dir .`, use default mode (no `--dev` flag) for full test coverage.
|
|
30
|
+
- **Default mode**: Uses Claude's MCP tools. Runs all 30 tests including slash commands.
|
|
31
|
+
- **`--dev` mode**: Spawns MCP server directly. Skips slash command tests since they require Claude's skill router.
|
|
25
32
|
|
|
26
33
|
## Context
|
|
27
34
|
|
|
@@ -31,24 +38,26 @@ Comprehensive test of all Bluera Knowledge plugin functionality.
|
|
|
31
38
|
|
|
32
39
|
Check if `--dev` flag is present in: $ARGUMENTS
|
|
33
40
|
|
|
34
|
-
- If `--dev` is present: Use **Development Mode**
|
|
35
|
-
- Otherwise: Use **Production Mode**
|
|
41
|
+
- If `--dev` is present: Use **Development Mode**
|
|
42
|
+
- Otherwise: Use **Production Mode**
|
|
36
43
|
|
|
37
44
|
---
|
|
38
45
|
|
|
39
46
|
## Pre-Test Cleanup
|
|
40
47
|
|
|
41
|
-
|
|
48
|
+
Clean up any leftover artifacts from previous test runs (ignore errors):
|
|
42
49
|
|
|
43
50
|
**In `--dev` mode:**
|
|
44
51
|
```bash
|
|
45
52
|
node scripts/test-mcp-dev.js call execute '{"command":"store:delete","args":{"store":"bk-test-store"}}' 2>/dev/null || true
|
|
53
|
+
node scripts/test-mcp-dev.js call execute '{"command":"store:delete","args":{"store":"bk-crawl-test"}}' 2>/dev/null || true
|
|
46
54
|
rm -rf .bluera/bluera-knowledge/test-content
|
|
47
55
|
```
|
|
48
56
|
|
|
49
57
|
**In production mode:**
|
|
50
|
-
1. Call MCP
|
|
51
|
-
2.
|
|
58
|
+
1. Call MCP `execute` with `{ command: "store:delete", args: { store: "bk-test-store" } }` - ignore errors
|
|
59
|
+
2. Call MCP `execute` with `{ command: "store:delete", args: { store: "bk-crawl-test" } }` - ignore errors
|
|
60
|
+
3. Run: `rm -rf .bluera/bluera-knowledge/test-content`
|
|
52
61
|
|
|
53
62
|
## Test Content Setup
|
|
54
63
|
|
|
@@ -70,253 +79,318 @@ Keywords: bluera-knowledge-test, plugin-validation, mcp-test
|
|
|
70
79
|
EOF
|
|
71
80
|
```
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
---
|
|
74
83
|
|
|
75
|
-
|
|
84
|
+
# PRODUCTION MODE (default)
|
|
76
85
|
|
|
77
|
-
|
|
86
|
+
Use this section if `--dev` flag is NOT present.
|
|
78
87
|
|
|
79
|
-
|
|
88
|
+
## Part 1: MCP Execute Commands
|
|
80
89
|
|
|
81
|
-
|
|
90
|
+
1. **help**: Call `execute` with `{ command: "help" }`
|
|
91
|
+
- PASS if response contains "Available commands"
|
|
82
92
|
|
|
83
|
-
|
|
93
|
+
2. **commands**: Call `execute` with `{ command: "commands" }`
|
|
94
|
+
- PASS if response contains array of commands with "stores" and "store:create"
|
|
84
95
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
node scripts/test-mcp-dev.js call execute '{"command":"help"}'
|
|
88
|
-
```
|
|
89
|
-
- PASS if output contains "Available commands"
|
|
96
|
+
3. **stores**: Call `execute` with `{ command: "stores" }`
|
|
97
|
+
- PASS if no error (may return empty array)
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
```
|
|
93
|
-
|
|
99
|
+
4. **store:create**: Call `execute` with:
|
|
100
|
+
```json
|
|
101
|
+
{ "command": "store:create", "args": { "name": "bk-test-store", "type": "file", "source": ".bluera/bluera-knowledge/test-content" } }
|
|
94
102
|
```
|
|
95
|
-
- PASS if
|
|
103
|
+
- PASS if response indicates success and contains store id
|
|
96
104
|
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
|
|
105
|
+
5. **store:info**: Call `execute` with:
|
|
106
|
+
```json
|
|
107
|
+
{ "command": "store:info", "args": { "store": "bk-test-store" } }
|
|
100
108
|
```
|
|
101
|
-
- PASS if response
|
|
109
|
+
- PASS if response contains store name and type
|
|
102
110
|
|
|
103
|
-
|
|
104
|
-
```
|
|
105
|
-
|
|
111
|
+
6. **store:index**: Call `execute` with:
|
|
112
|
+
```json
|
|
113
|
+
{ "command": "store:index", "args": { "store": "bk-test-store" } }
|
|
106
114
|
```
|
|
107
|
-
- PASS if response
|
|
115
|
+
- PASS if response indicates indexing started (job created)
|
|
108
116
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
7. **jobs**: Call `execute` with `{ command: "jobs" }`
|
|
118
|
+
- PASS if response contains jobs array (may be empty if indexing completed)
|
|
119
|
+
|
|
120
|
+
8. **job:status** (if job from step 6 available): Call `execute` with:
|
|
121
|
+
```json
|
|
122
|
+
{ "command": "job:status", "args": { "jobId": "<job-id-from-step-6>" } }
|
|
112
123
|
```
|
|
113
|
-
- PASS if
|
|
124
|
+
- PASS if response contains job status (pending, running, or completed)
|
|
125
|
+
- SKIP if no job id available (indexing was synchronous)
|
|
114
126
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
EOF
|
|
127
|
+
## Part 2: MCP Search Tools
|
|
128
|
+
|
|
129
|
+
9. **search**: Call MCP tool `search` with:
|
|
130
|
+
```json
|
|
131
|
+
{ "query": "validateBKPlugin", "stores": ["bk-test-store"] }
|
|
121
132
|
```
|
|
122
|
-
- PASS if
|
|
123
|
-
|
|
133
|
+
- PASS if results array is non-empty
|
|
134
|
+
|
|
135
|
+
10. **get_full_context**: Call MCP tool `get_full_context` with resultId from step 9
|
|
136
|
+
- PASS if response contains code or documentation content
|
|
137
|
+
|
|
138
|
+
## Part 3: Slash Commands
|
|
139
|
+
|
|
140
|
+
11. **/stores**: Run `/bluera-knowledge:stores`
|
|
141
|
+
- PASS if bk-test-store is listed
|
|
142
|
+
|
|
143
|
+
12. **/search**: Run `/bluera-knowledge:search "bluera-knowledge-test"`
|
|
144
|
+
- PASS if results are shown
|
|
124
145
|
|
|
125
|
-
|
|
146
|
+
13. **/suggest**: Run `/bluera-knowledge:suggest`
|
|
147
|
+
- PASS if no error thrown
|
|
148
|
+
|
|
149
|
+
14. **/check-status**: Run `/bluera-knowledge:check-status`
|
|
150
|
+
- PASS if shows job status (may show "no active jobs")
|
|
151
|
+
|
|
152
|
+
15. **/skill-activation**: Run `/bluera-knowledge:skill-activation`
|
|
153
|
+
- PASS if shows activation configuration or prompts for setup
|
|
154
|
+
|
|
155
|
+
16. **/index**: Run `/bluera-knowledge:index bk-test-store`
|
|
156
|
+
- PASS if indexing completes or starts successfully
|
|
157
|
+
|
|
158
|
+
## Part 4: Web Crawl Testing
|
|
159
|
+
|
|
160
|
+
17. **Create crawl store**: Call `execute` with:
|
|
161
|
+
```json
|
|
162
|
+
{ "command": "store:create", "args": { "name": "bk-crawl-test", "type": "web", "source": "https://httpbin.org/html", "options": { "maxPages": 1 } } }
|
|
163
|
+
```
|
|
164
|
+
- PASS if store creation initiates and returns job id
|
|
165
|
+
|
|
166
|
+
18. **Verify crawl job**: Call `execute` with `{ command: "jobs" }`
|
|
167
|
+
- PASS if bk-crawl-test job appears in list (or completed)
|
|
168
|
+
|
|
169
|
+
19. **job:cancel** (if crawl still running): Call `execute` with:
|
|
170
|
+
```json
|
|
171
|
+
{ "command": "job:cancel", "args": { "jobId": "<crawl-job-id>" } }
|
|
172
|
+
```
|
|
173
|
+
- PASS if job cancelled or already completed
|
|
174
|
+
- SKIP if job already completed
|
|
175
|
+
|
|
176
|
+
20. **Delete crawl store**: Call `execute` with:
|
|
177
|
+
```json
|
|
178
|
+
{ "command": "store:delete", "args": { "store": "bk-crawl-test" } }
|
|
179
|
+
```
|
|
180
|
+
- PASS if deletion succeeds
|
|
181
|
+
|
|
182
|
+
## Part 5: Hook Tests
|
|
183
|
+
|
|
184
|
+
21. **Hook Registration**:
|
|
185
|
+
```bash
|
|
186
|
+
cat hooks/hooks.json | jq -e '.hooks.PostToolUse and .hooks.UserPromptSubmit and .hooks.SessionStart'
|
|
187
|
+
```
|
|
188
|
+
- PASS if returns `true`
|
|
126
189
|
|
|
127
|
-
|
|
190
|
+
22. **PostToolUse Hook - Library Detection**:
|
|
191
|
+
```bash
|
|
192
|
+
echo '{"tool_name": "Read", "tool_input": {"file_path": "/project/node_modules/express/index.js"}}' | python3 hooks/posttooluse-bk-reminder.py
|
|
193
|
+
```
|
|
194
|
+
- PASS if output contains "BLUERA-KNOWLEDGE REMINDER"
|
|
128
195
|
|
|
129
|
-
|
|
196
|
+
23. **PostToolUse Hook - Non-Library**:
|
|
197
|
+
```bash
|
|
198
|
+
echo '{"tool_name": "Read", "tool_input": {"file_path": "/project/src/index.ts"}}' | python3 hooks/posttooluse-bk-reminder.py
|
|
199
|
+
```
|
|
200
|
+
- PASS if output is empty
|
|
130
201
|
|
|
131
|
-
|
|
202
|
+
24. **Skill Activation Hook - Matching**:
|
|
203
|
+
```bash
|
|
204
|
+
export CLAUDE_PLUGIN_ROOT="$(pwd)" && echo '{"prompt": "why does the express package throw this error?"}' | python3 hooks/skill-activation.py
|
|
205
|
+
```
|
|
206
|
+
- PASS if output contains "MANDATORY EVALUATION"
|
|
132
207
|
|
|
133
|
-
|
|
208
|
+
25. **Skill Activation Hook - Excluded**:
|
|
209
|
+
```bash
|
|
210
|
+
export CLAUDE_PLUGIN_ROOT="$(pwd)" && echo '{"prompt": "/bluera-knowledge:search express"}' | python3 hooks/skill-activation.py
|
|
211
|
+
```
|
|
212
|
+
- PASS if output is empty
|
|
134
213
|
|
|
135
|
-
|
|
214
|
+
26. **Skill Rules File**:
|
|
136
215
|
```bash
|
|
137
|
-
|
|
216
|
+
jq -e '(.skills | length) > 0 and (.globalExclusions | length) > 0' hooks/skill-rules.json
|
|
217
|
+
```
|
|
218
|
+
- PASS if returns `true`
|
|
219
|
+
|
|
220
|
+
## Part 6: Cleanup
|
|
221
|
+
|
|
222
|
+
27. **Delete test store**: Call `execute` with:
|
|
223
|
+
```json
|
|
224
|
+
{ "command": "store:delete", "args": { "store": "bk-test-store" } }
|
|
138
225
|
```
|
|
139
226
|
- PASS if deletion succeeds
|
|
140
227
|
|
|
141
|
-
|
|
228
|
+
28. **Remove test content**:
|
|
142
229
|
```bash
|
|
143
230
|
rm -rf .bluera/bluera-knowledge/test-content
|
|
144
231
|
```
|
|
145
232
|
- PASS if command succeeds
|
|
146
233
|
|
|
147
|
-
|
|
234
|
+
29. **Verify store cleanup**: Call `execute` with `{ command: "stores" }`
|
|
235
|
+
- PASS if bk-test-store is NOT in the list
|
|
236
|
+
|
|
237
|
+
30. **Verify no test artifacts**:
|
|
148
238
|
```bash
|
|
149
|
-
|
|
239
|
+
test ! -d .bluera/bluera-knowledge/test-content && echo "clean"
|
|
150
240
|
```
|
|
151
|
-
- PASS if
|
|
241
|
+
- PASS if outputs "clean"
|
|
152
242
|
|
|
153
243
|
---
|
|
154
244
|
|
|
155
|
-
#
|
|
156
|
-
|
|
157
|
-
Use this section if `--dev` flag is NOT present. Uses Claude's built-in MCP tools.
|
|
158
|
-
|
|
159
|
-
### Part 1: MCP Tools
|
|
245
|
+
# DEVELOPMENT MODE (`--dev`)
|
|
160
246
|
|
|
161
|
-
|
|
162
|
-
- PASS if response contains "Available commands"
|
|
247
|
+
Use this section if `--dev` flag is present. Uses `scripts/test-mcp-dev.js`.
|
|
163
248
|
|
|
164
|
-
|
|
165
|
-
- PASS if no error
|
|
249
|
+
### Part 1: MCP Commands (via dev script)
|
|
166
250
|
|
|
167
|
-
|
|
168
|
-
```
|
|
169
|
-
|
|
251
|
+
1. **help**:
|
|
252
|
+
```bash
|
|
253
|
+
node scripts/test-mcp-dev.js call execute '{"command":"help"}'
|
|
170
254
|
```
|
|
171
|
-
- PASS if
|
|
255
|
+
- PASS if contains "Available commands"
|
|
172
256
|
|
|
173
|
-
|
|
174
|
-
```
|
|
175
|
-
|
|
257
|
+
2. **commands**:
|
|
258
|
+
```bash
|
|
259
|
+
node scripts/test-mcp-dev.js call execute '{"command":"commands"}'
|
|
176
260
|
```
|
|
177
|
-
- PASS if
|
|
261
|
+
- PASS if contains commands array
|
|
178
262
|
|
|
179
|
-
|
|
263
|
+
3. **stores**:
|
|
180
264
|
```bash
|
|
181
|
-
node
|
|
265
|
+
node scripts/test-mcp-dev.js call execute '{"command":"stores"}'
|
|
182
266
|
```
|
|
183
|
-
- PASS if
|
|
267
|
+
- PASS if no error
|
|
184
268
|
|
|
185
|
-
|
|
186
|
-
```
|
|
187
|
-
{
|
|
269
|
+
4. **store:create**:
|
|
270
|
+
```bash
|
|
271
|
+
node scripts/test-mcp-dev.js call execute '{"command":"store:create","args":{"name":"bk-test-store","type":"file","source":".bluera/bluera-knowledge/test-content"}}'
|
|
188
272
|
```
|
|
189
|
-
- PASS if
|
|
273
|
+
- PASS if success
|
|
190
274
|
|
|
191
|
-
|
|
192
|
-
|
|
275
|
+
5. **store:info**:
|
|
276
|
+
```bash
|
|
277
|
+
node scripts/test-mcp-dev.js call execute '{"command":"store:info","args":{"store":"bk-test-store"}}'
|
|
278
|
+
```
|
|
279
|
+
- PASS if contains store details
|
|
193
280
|
|
|
194
|
-
|
|
281
|
+
6. **store:index**:
|
|
282
|
+
```bash
|
|
283
|
+
node scripts/test-mcp-dev.js call execute '{"command":"store:index","args":{"store":"bk-test-store"}}'
|
|
284
|
+
```
|
|
285
|
+
- PASS if indexing starts
|
|
195
286
|
|
|
196
|
-
|
|
197
|
-
|
|
287
|
+
7. **jobs**:
|
|
288
|
+
```bash
|
|
289
|
+
node scripts/test-mcp-dev.js call execute '{"command":"jobs"}'
|
|
290
|
+
```
|
|
291
|
+
- PASS if returns jobs array
|
|
198
292
|
|
|
199
|
-
|
|
200
|
-
|
|
293
|
+
8. **job:status** (if job available):
|
|
294
|
+
```bash
|
|
295
|
+
node scripts/test-mcp-dev.js call execute '{"command":"job:status","args":{"jobId":"<job-id>"}}'
|
|
296
|
+
```
|
|
297
|
+
- PASS if returns status
|
|
201
298
|
|
|
202
|
-
|
|
203
|
-
- PASS if no error thrown
|
|
299
|
+
### Part 2: Search (via dev script)
|
|
204
300
|
|
|
205
|
-
|
|
301
|
+
9-10. **Search + Get Full Context**:
|
|
302
|
+
```bash
|
|
303
|
+
node scripts/test-mcp-dev.js session << 'EOF'
|
|
304
|
+
search {"query":"validateBKPlugin","stores":["bk-test-store"]}
|
|
305
|
+
get_full_context {"resultId":"$LAST_ID"}
|
|
306
|
+
EOF
|
|
307
|
+
```
|
|
308
|
+
- PASS if search returns results and context contains content
|
|
206
309
|
|
|
207
|
-
|
|
310
|
+
### Part 3: Slash Commands (SKIPPED in --dev mode)
|
|
208
311
|
|
|
209
|
-
|
|
312
|
+
Tests 11-16 are skipped - slash commands require Claude's skill router.
|
|
210
313
|
|
|
211
|
-
|
|
212
|
-
```json
|
|
213
|
-
{ "command": "store:delete", "args": { "store": "bk-test-store" } }
|
|
214
|
-
```
|
|
215
|
-
- PASS if deletion succeeds
|
|
314
|
+
### Part 4: Web Crawl (via dev script)
|
|
216
315
|
|
|
217
|
-
|
|
316
|
+
17. **Create crawl store**:
|
|
218
317
|
```bash
|
|
219
|
-
|
|
318
|
+
node scripts/test-mcp-dev.js call execute '{"command":"store:create","args":{"name":"bk-crawl-test","type":"web","source":"https://httpbin.org/html","options":{"maxPages":1}}}'
|
|
220
319
|
```
|
|
221
|
-
- PASS if
|
|
222
|
-
|
|
223
|
-
19. **Verify Cleanup**: Call MCP tool `execute` with `{ command: "stores" }`
|
|
224
|
-
- PASS if bk-test-store is NOT in the list
|
|
225
|
-
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
# Part 3: Hook Tests (both modes)
|
|
229
|
-
|
|
230
|
-
These tests verify that plugin hooks work correctly by running hook scripts directly.
|
|
320
|
+
- PASS if returns job id
|
|
231
321
|
|
|
232
|
-
|
|
322
|
+
18. **Verify crawl job**:
|
|
233
323
|
```bash
|
|
234
|
-
|
|
324
|
+
node scripts/test-mcp-dev.js call execute '{"command":"jobs"}'
|
|
235
325
|
```
|
|
236
|
-
- PASS if
|
|
326
|
+
- PASS if job appears
|
|
237
327
|
|
|
238
|
-
|
|
328
|
+
19. **job:cancel** (if running):
|
|
239
329
|
```bash
|
|
240
|
-
|
|
330
|
+
node scripts/test-mcp-dev.js call execute '{"command":"job:cancel","args":{"jobId":"<crawl-job-id>"}}'
|
|
241
331
|
```
|
|
242
|
-
- PASS if
|
|
332
|
+
- PASS if cancelled or completed
|
|
243
333
|
|
|
244
|
-
|
|
334
|
+
20. **Delete crawl store**:
|
|
245
335
|
```bash
|
|
246
|
-
|
|
336
|
+
node scripts/test-mcp-dev.js call execute '{"command":"store:delete","args":{"store":"bk-crawl-test"}}'
|
|
247
337
|
```
|
|
248
|
-
- PASS if
|
|
338
|
+
- PASS if deleted
|
|
249
339
|
|
|
250
|
-
|
|
251
|
-
```bash
|
|
252
|
-
export CLAUDE_PLUGIN_ROOT="$(pwd)" && echo '{"prompt": "why does the express package throw this error?"}' | python3 hooks/skill-activation.py
|
|
253
|
-
```
|
|
254
|
-
- PASS if output contains "MANDATORY EVALUATION"
|
|
340
|
+
### Part 5: Hook Tests (same as production)
|
|
255
341
|
|
|
256
|
-
|
|
257
|
-
```bash
|
|
258
|
-
export CLAUDE_PLUGIN_ROOT="$(pwd)" && echo '{"prompt": "/bluera-knowledge:search express"}' | python3 hooks/skill-activation.py
|
|
259
|
-
```
|
|
260
|
-
- PASS if output is empty
|
|
342
|
+
[Tests 21-26 - same bash commands as production mode]
|
|
261
343
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
```
|
|
266
|
-
- PASS if returns `true`
|
|
344
|
+
### Part 6: Cleanup (via dev script)
|
|
345
|
+
|
|
346
|
+
27-30: Same cleanup steps using dev script for MCP calls.
|
|
267
347
|
|
|
268
348
|
---
|
|
269
349
|
|
|
270
350
|
## Output Format
|
|
271
351
|
|
|
272
|
-
###
|
|
352
|
+
### Production mode (30 tests):
|
|
273
353
|
|
|
274
354
|
| # | Test | Status |
|
|
275
355
|
|---|------|--------|
|
|
276
|
-
| 1 | MCP
|
|
277
|
-
| 2 |
|
|
278
|
-
| 3 |
|
|
279
|
-
| 4 |
|
|
280
|
-
| 5 |
|
|
281
|
-
| 6
|
|
282
|
-
|
|
|
283
|
-
|
|
|
284
|
-
|
|
|
285
|
-
|
|
|
286
|
-
|
|
|
287
|
-
|
|
|
288
|
-
|
|
|
289
|
-
|
|
|
290
|
-
|
|
|
291
|
-
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
|
298
|
-
|
|
299
|
-
|
|
|
300
|
-
|
|
|
301
|
-
|
|
|
302
|
-
|
|
|
303
|
-
|
|
|
304
|
-
|
|
|
305
|
-
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
| 16 | Skill Rules File | ? |
|
|
315
|
-
| 17 | Delete Store | ? |
|
|
316
|
-
| 18 | Remove Test Content | ? |
|
|
317
|
-
| 19 | Verify Cleanup | ? |
|
|
318
|
-
|
|
319
|
-
**Result: X/19 tests passed**
|
|
356
|
+
| 1 | MCP help | ? |
|
|
357
|
+
| 2 | MCP commands | ? |
|
|
358
|
+
| 3 | MCP stores | ? |
|
|
359
|
+
| 4 | MCP store:create | ? |
|
|
360
|
+
| 5 | MCP store:info | ? |
|
|
361
|
+
| 6 | MCP store:index | ? |
|
|
362
|
+
| 7 | MCP jobs | ? |
|
|
363
|
+
| 8 | MCP job:status | ? |
|
|
364
|
+
| 9 | MCP search | ? |
|
|
365
|
+
| 10 | MCP get_full_context | ? |
|
|
366
|
+
| 11 | /stores | ? |
|
|
367
|
+
| 12 | /search | ? |
|
|
368
|
+
| 13 | /suggest | ? |
|
|
369
|
+
| 14 | /check-status | ? |
|
|
370
|
+
| 15 | /skill-activation | ? |
|
|
371
|
+
| 16 | /index | ? |
|
|
372
|
+
| 17 | Crawl store create | ? |
|
|
373
|
+
| 18 | Crawl job verify | ? |
|
|
374
|
+
| 19 | Crawl job:cancel | ? |
|
|
375
|
+
| 20 | Crawl store delete | ? |
|
|
376
|
+
| 21 | Hook registration | ? |
|
|
377
|
+
| 22 | PostToolUse - library | ? |
|
|
378
|
+
| 23 | PostToolUse - non-library | ? |
|
|
379
|
+
| 24 | Skill activation - match | ? |
|
|
380
|
+
| 25 | Skill activation - exclude | ? |
|
|
381
|
+
| 26 | Skill rules file | ? |
|
|
382
|
+
| 27 | Delete test store | ? |
|
|
383
|
+
| 28 | Remove test content | ? |
|
|
384
|
+
| 29 | Verify store cleanup | ? |
|
|
385
|
+
| 30 | Verify no artifacts | ? |
|
|
386
|
+
|
|
387
|
+
**Result: X/30 tests passed**
|
|
388
|
+
|
|
389
|
+
### Development mode (24 tests):
|
|
390
|
+
|
|
391
|
+
Same table but tests 11-16 show "SKIPPED (--dev mode)"
|
|
392
|
+
|
|
393
|
+
**Result: X/24 tests passed (6 skipped)**
|
|
320
394
|
|
|
321
395
|
---
|
|
322
396
|
|
|
@@ -324,12 +398,11 @@ These tests verify that plugin hooks work correctly by running hook scripts dire
|
|
|
324
398
|
|
|
325
399
|
If tests fail partway through, clean up manually:
|
|
326
400
|
|
|
327
|
-
**In --dev mode:**
|
|
328
401
|
```bash
|
|
402
|
+
# Delete test stores
|
|
329
403
|
node scripts/test-mcp-dev.js call execute '{"command":"store:delete","args":{"store":"bk-test-store"}}' 2>/dev/null || true
|
|
404
|
+
node scripts/test-mcp-dev.js call execute '{"command":"store:delete","args":{"store":"bk-crawl-test"}}' 2>/dev/null || true
|
|
405
|
+
|
|
406
|
+
# Remove test content
|
|
330
407
|
rm -rf .bluera/bluera-knowledge/test-content
|
|
331
408
|
```
|
|
332
|
-
|
|
333
|
-
**In production mode:**
|
|
334
|
-
1. Call MCP tool `execute` with `{ command: "store:delete", args: { store: "bk-test-store" } }`
|
|
335
|
-
2. Run: `rm -rf .bluera/bluera-knowledge/test-content`
|
package/dist/mcp/bootstrap.js
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import {
|
|
6
6
|
appendFileSync,
|
|
7
|
-
chmodSync,
|
|
8
|
-
createReadStream,
|
|
9
7
|
createWriteStream,
|
|
10
8
|
existsSync,
|
|
11
9
|
mkdirSync,
|
|
@@ -16,7 +14,6 @@ import { get } from "https";
|
|
|
16
14
|
import { arch, homedir, platform } from "os";
|
|
17
15
|
import { dirname, join } from "path";
|
|
18
16
|
import { fileURLToPath } from "url";
|
|
19
|
-
import { createGunzip } from "zlib";
|
|
20
17
|
var logDir = join(homedir(), ".bluera", "bluera-knowledge", "logs");
|
|
21
18
|
var logFile = join(logDir, "app.log");
|
|
22
19
|
var log = (level, msg, data) => {
|
|
@@ -115,107 +112,6 @@ function downloadFile(url, destPath) {
|
|
|
115
112
|
request(url);
|
|
116
113
|
});
|
|
117
114
|
}
|
|
118
|
-
function extractTar(tarPath, destDir) {
|
|
119
|
-
return new Promise((resolve, reject) => {
|
|
120
|
-
const gunzip = createGunzip();
|
|
121
|
-
const input = createReadStream(tarPath);
|
|
122
|
-
let buffer = Buffer.alloc(0);
|
|
123
|
-
let currentFile = null;
|
|
124
|
-
let bytesRemaining = 0;
|
|
125
|
-
let fileStream = null;
|
|
126
|
-
const parseHeader = (header) => {
|
|
127
|
-
if (header.every((b) => b === 0)) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
const name = header.subarray(0, 100).toString("utf-8").replace(/\0/g, "");
|
|
131
|
-
const mode = parseInt(header.subarray(100, 108).toString("utf-8").trim(), 8);
|
|
132
|
-
const size = parseInt(header.subarray(124, 136).toString("utf-8").trim(), 8);
|
|
133
|
-
const typeByte = header[156];
|
|
134
|
-
const typeFlag = typeByte !== void 0 ? String.fromCharCode(typeByte) : "0";
|
|
135
|
-
let type = "file";
|
|
136
|
-
if (typeFlag === "5") type = "directory";
|
|
137
|
-
else if (typeFlag === "2") type = "symlink";
|
|
138
|
-
return { name, size, type, mode: isNaN(mode) ? 420 : mode };
|
|
139
|
-
};
|
|
140
|
-
const processChunk = (chunk) => {
|
|
141
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
142
|
-
while (buffer.length > 0) {
|
|
143
|
-
if (currentFile === null) {
|
|
144
|
-
if (buffer.length < 512) break;
|
|
145
|
-
const header = buffer.subarray(0, 512);
|
|
146
|
-
buffer = buffer.subarray(512);
|
|
147
|
-
const parsed = parseHeader(header);
|
|
148
|
-
if (parsed === null) {
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
currentFile = parsed;
|
|
152
|
-
bytesRemaining = currentFile.size;
|
|
153
|
-
let filePath = currentFile.name;
|
|
154
|
-
if (filePath.startsWith("./")) {
|
|
155
|
-
filePath = filePath.slice(2);
|
|
156
|
-
}
|
|
157
|
-
if (filePath.length === 0 || filePath === ".") {
|
|
158
|
-
currentFile = null;
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
const fullPath = join(destDir, filePath);
|
|
162
|
-
if (currentFile.type === "directory") {
|
|
163
|
-
mkdirSync(fullPath, { recursive: true });
|
|
164
|
-
currentFile = null;
|
|
165
|
-
} else if (currentFile.type === "file" && bytesRemaining > 0) {
|
|
166
|
-
mkdirSync(dirname(fullPath), { recursive: true });
|
|
167
|
-
fileStream = createWriteStream(fullPath, {
|
|
168
|
-
mode: currentFile.mode
|
|
169
|
-
});
|
|
170
|
-
} else if (currentFile.type === "file" && bytesRemaining === 0) {
|
|
171
|
-
mkdirSync(dirname(fullPath), { recursive: true });
|
|
172
|
-
createWriteStream(fullPath, { mode: currentFile.mode }).close();
|
|
173
|
-
currentFile = null;
|
|
174
|
-
} else {
|
|
175
|
-
currentFile = null;
|
|
176
|
-
}
|
|
177
|
-
} else {
|
|
178
|
-
const toRead = Math.min(bytesRemaining, buffer.length);
|
|
179
|
-
if (fileStream !== null && toRead > 0) {
|
|
180
|
-
fileStream.write(buffer.subarray(0, toRead));
|
|
181
|
-
}
|
|
182
|
-
buffer = buffer.subarray(toRead);
|
|
183
|
-
bytesRemaining -= toRead;
|
|
184
|
-
if (bytesRemaining === 0) {
|
|
185
|
-
if (fileStream !== null) {
|
|
186
|
-
const stream = fileStream;
|
|
187
|
-
const file = currentFile;
|
|
188
|
-
stream.end(() => {
|
|
189
|
-
if ((file.mode & 73) !== 0) {
|
|
190
|
-
try {
|
|
191
|
-
const fullPath = join(
|
|
192
|
-
destDir,
|
|
193
|
-
file.name.startsWith("./") ? file.name.slice(2) : file.name
|
|
194
|
-
);
|
|
195
|
-
chmodSync(fullPath, file.mode);
|
|
196
|
-
} catch {
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
fileStream = null;
|
|
201
|
-
}
|
|
202
|
-
currentFile = null;
|
|
203
|
-
const padding = (512 - toRead % 512) % 512;
|
|
204
|
-
if (buffer.length >= padding) {
|
|
205
|
-
buffer = buffer.subarray(padding);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
input.pipe(gunzip).on("data", processChunk).on("end", () => {
|
|
212
|
-
if (fileStream !== null) {
|
|
213
|
-
fileStream.end();
|
|
214
|
-
}
|
|
215
|
-
resolve();
|
|
216
|
-
}).on("error", reject);
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
115
|
function isManifest(value) {
|
|
220
116
|
return typeof value === "object" && value !== null && "version" in value && "platforms" in value && typeof value.platforms === "object";
|
|
221
117
|
}
|
|
@@ -241,7 +137,7 @@ async function downloadPrebuilt() {
|
|
|
241
137
|
const tarPath = join(tmpDir, `bluera-knowledge-${platformKey}.tar.gz`);
|
|
242
138
|
await downloadFile(platformInfo.url, tarPath);
|
|
243
139
|
log("info", "Download complete, extracting", { tarPath, destDir: pluginRoot });
|
|
244
|
-
|
|
140
|
+
execSync(`tar -xzf "${tarPath}" -C "${pluginRoot}"`, { stdio: "pipe" });
|
|
245
141
|
try {
|
|
246
142
|
unlinkSync(tarPath);
|
|
247
143
|
} catch {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/mcp/bootstrap.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * MCP Server Bootstrap - installs dependencies before starting server.\n *\n * Uses only Node.js built-ins (no external dependencies required).\n * Self-locates plugin root via import.meta.url (doesn't rely on CLAUDE_PLUGIN_ROOT).\n *\n * Dependency installation strategy:\n * 1. Fast path: node_modules already exists → skip\n * 2. Try prebuilt: Download platform-specific tarball from GitHub release\n * 3. Package manager: Run bun install or npm ci if prebuilt unavailable\n *\n * IMPORTANT: MCP servers must NOT log to stderr - Claude Code treats stderr output\n * as an error and may mark the MCP server as failed. All logging goes to file.\n */\nimport { execSync } from 'node:child_process';\nimport {\n appendFileSync,\n chmodSync,\n createReadStream,\n createWriteStream,\n existsSync,\n mkdirSync,\n readFileSync,\n unlinkSync,\n} from 'node:fs';\nimport { get } from 'node:https';\nimport { arch, homedir, platform } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { createGunzip } from 'node:zlib';\n\n// Logging helper - writes to file since MCP servers must NOT use stderr\n// (Claude Code treats stderr as error and may fail the server)\n// JSON format matches pino output for consistency\nconst logDir = join(homedir(), '.bluera', 'bluera-knowledge', 'logs');\nconst logFile = join(logDir, 'app.log');\n\nconst log = (\n level: 'info' | 'error' | 'debug',\n msg: string,\n data?: Record<string, unknown>\n): void => {\n try {\n mkdirSync(logDir, { recursive: true });\n const entry = {\n time: new Date().toISOString(),\n level,\n module: 'bootstrap',\n msg,\n ...data,\n };\n appendFileSync(logFile, `${JSON.stringify(entry)}\\n`);\n } catch {\n // Silently fail - we cannot use stderr for MCP servers\n }\n};\n\n// Self-locate plugin root from this file's path\n// dist/mcp/bootstrap.js -> plugin root (two directories up)\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst pluginRoot = join(__dirname, '..', '..');\n\n// Get version from package.json\nconst getVersion = (): string => {\n try {\n const pkg: unknown = JSON.parse(readFileSync(join(pluginRoot, 'package.json'), 'utf-8'));\n if (\n typeof pkg === 'object' &&\n pkg !== null &&\n 'version' in pkg &&\n typeof pkg.version === 'string'\n ) {\n return `v${pkg.version}`;\n }\n return 'unknown';\n } catch {\n return 'unknown';\n }\n};\n\nconst VERSION = getVersion();\nconst MANIFEST_URL = `https://github.com/blueraai/bluera-knowledge/releases/download/${VERSION}/manifest.json`;\n\n/**\n * Fetch JSON from URL with redirect handling.\n * Uses only Node.js built-ins.\n */\nfunction fetchJSON(url: string): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const request = (targetUrl: string, redirectCount = 0): void => {\n if (redirectCount > 5) {\n reject(new Error('Too many redirects'));\n return;\n }\n\n get(targetUrl, { headers: { 'User-Agent': 'bluera-knowledge' } }, (res) => {\n // Follow redirects\n const location = res.headers.location;\n if (\n (res.statusCode === 302 || res.statusCode === 301) &&\n typeof location === 'string' &&\n location.length > 0\n ) {\n request(location, redirectCount + 1);\n return;\n }\n const statusCode = res.statusCode ?? 0;\n if (statusCode !== 200) {\n reject(new Error(`HTTP ${String(statusCode)}`));\n return;\n }\n let data = '';\n res.on('data', (chunk: Buffer) => (data += chunk.toString()));\n res.on('end', () => {\n try {\n resolve(JSON.parse(data));\n } catch (e) {\n reject(e instanceof Error ? e : new Error(String(e)));\n }\n });\n }).on('error', reject);\n };\n request(url);\n });\n}\n\n/**\n * Download file from URL to destination path.\n * Uses only Node.js built-ins.\n */\nfunction downloadFile(url: string, destPath: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const request = (targetUrl: string, redirectCount = 0): void => {\n if (redirectCount > 10) {\n reject(new Error('Too many redirects'));\n return;\n }\n\n get(targetUrl, { headers: { 'User-Agent': 'bluera-knowledge' } }, (res) => {\n const location = res.headers.location;\n if (\n (res.statusCode === 302 || res.statusCode === 301) &&\n typeof location === 'string' &&\n location.length > 0\n ) {\n request(location, redirectCount + 1);\n return;\n }\n const statusCode = res.statusCode ?? 0;\n if (statusCode !== 200) {\n reject(new Error(`HTTP ${String(statusCode)}`));\n return;\n }\n const file = createWriteStream(destPath);\n res.pipe(file);\n file.on('finish', () => {\n file.close();\n resolve();\n });\n file.on('error', (err) => {\n file.close();\n reject(err);\n });\n }).on('error', reject);\n };\n request(url);\n });\n}\n\n/**\n * Simple tar parser using Node.js built-ins.\n *\n * TAR format:\n * - 512-byte header blocks\n * - File data follows, padded to 512-byte boundary\n * - Two empty 512-byte blocks at end\n */\nfunction extractTar(tarPath: string, destDir: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const gunzip = createGunzip();\n const input = createReadStream(tarPath);\n\n let buffer = Buffer.alloc(0);\n let currentFile: {\n name: string;\n size: number;\n type: string;\n mode: number;\n } | null = null;\n let bytesRemaining = 0;\n let fileStream: ReturnType<typeof createWriteStream> | null = null;\n\n const parseHeader = (\n header: Buffer\n ): { name: string; size: number; type: string; mode: number } | null => {\n // Check for empty block (end of archive)\n if (header.every((b) => b === 0)) {\n return null;\n }\n\n // Parse tar header fields\n const name = header.subarray(0, 100).toString('utf-8').replace(/\\0/g, '');\n const mode = parseInt(header.subarray(100, 108).toString('utf-8').trim(), 8);\n const size = parseInt(header.subarray(124, 136).toString('utf-8').trim(), 8);\n const typeByte = header[156];\n const typeFlag = typeByte !== undefined ? String.fromCharCode(typeByte) : '0';\n\n // Type: '0' or '' = file, '5' = directory, '2' = symlink\n let type = 'file';\n if (typeFlag === '5') type = 'directory';\n else if (typeFlag === '2') type = 'symlink';\n\n return { name, size, type, mode: isNaN(mode) ? 0o644 : mode };\n };\n\n const processChunk = (chunk: Buffer): void => {\n buffer = Buffer.concat([buffer, chunk]);\n\n while (buffer.length > 0) {\n if (currentFile === null) {\n // Need to read a header\n if (buffer.length < 512) break;\n\n const header = buffer.subarray(0, 512);\n buffer = buffer.subarray(512);\n\n const parsed = parseHeader(header);\n if (parsed === null) {\n // End of archive\n continue;\n }\n\n currentFile = parsed;\n bytesRemaining = currentFile.size;\n\n // Skip ./ prefix and handle path\n let filePath = currentFile.name;\n if (filePath.startsWith('./')) {\n filePath = filePath.slice(2);\n }\n if (filePath.length === 0 || filePath === '.') {\n currentFile = null;\n continue;\n }\n\n const fullPath = join(destDir, filePath);\n\n if (currentFile.type === 'directory') {\n mkdirSync(fullPath, { recursive: true });\n currentFile = null;\n } else if (currentFile.type === 'file' && bytesRemaining > 0) {\n mkdirSync(dirname(fullPath), { recursive: true });\n fileStream = createWriteStream(fullPath, {\n mode: currentFile.mode,\n });\n } else if (currentFile.type === 'file' && bytesRemaining === 0) {\n // Empty file\n mkdirSync(dirname(fullPath), { recursive: true });\n createWriteStream(fullPath, { mode: currentFile.mode }).close();\n currentFile = null;\n } else {\n // Skip symlinks and other types\n currentFile = null;\n }\n } else {\n // Reading file content\n const toRead = Math.min(bytesRemaining, buffer.length);\n if (fileStream !== null && toRead > 0) {\n fileStream.write(buffer.subarray(0, toRead));\n }\n buffer = buffer.subarray(toRead);\n bytesRemaining -= toRead;\n\n if (bytesRemaining === 0) {\n if (fileStream !== null) {\n const stream = fileStream;\n const file = currentFile;\n stream.end(() => {\n // Set executable bit for scripts\n if ((file.mode & 0o111) !== 0) {\n try {\n const fullPath = join(\n destDir,\n file.name.startsWith('./') ? file.name.slice(2) : file.name\n );\n chmodSync(fullPath, file.mode);\n } catch {\n // Ignore chmod errors\n }\n }\n });\n fileStream = null;\n }\n currentFile = null;\n\n // Skip padding to 512-byte boundary\n const padding = (512 - (toRead % 512)) % 512;\n if (buffer.length >= padding) {\n buffer = buffer.subarray(padding);\n }\n }\n }\n }\n };\n\n input\n .pipe(gunzip)\n .on('data', processChunk)\n .on('end', () => {\n if (fileStream !== null) {\n fileStream.end();\n }\n resolve();\n })\n .on('error', reject);\n });\n}\n\ninterface PlatformInfo {\n url: string;\n sha256: string;\n}\n\ninterface Manifest {\n version: string;\n platforms: Record<string, PlatformInfo>;\n}\n\nfunction isManifest(value: unknown): value is Manifest {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'version' in value &&\n 'platforms' in value &&\n typeof value.platforms === 'object'\n );\n}\n\n/**\n * Try to download and extract prebuilt binary for current platform.\n * Returns true if successful, false if prebuilt is not available.\n */\nasync function downloadPrebuilt(): Promise<boolean> {\n const plat = platform(); // 'darwin', 'linux', 'win32'\n const ar = arch(); // 'arm64', 'x64'\n const platformKey = `${plat}-${ar}`;\n\n try {\n log('info', 'Checking for prebuilt binary', { platformKey, version: VERSION });\n\n const manifestData = await fetchJSON(MANIFEST_URL);\n if (!isManifest(manifestData)) {\n log('info', 'Invalid manifest format');\n return false;\n }\n\n const platformInfo = manifestData.platforms[platformKey];\n if (platformInfo === undefined) {\n log('info', 'No prebuilt binary available for platform', { platformKey });\n return false;\n }\n\n log('info', 'Downloading prebuilt binary', { url: platformInfo.url });\n const tmpDir = join(homedir(), '.bluera', 'tmp');\n mkdirSync(tmpDir, { recursive: true });\n const tarPath = join(tmpDir, `bluera-knowledge-${platformKey}.tar.gz`);\n\n await downloadFile(platformInfo.url, tarPath);\n log('info', 'Download complete, extracting', { tarPath, destDir: pluginRoot });\n\n await extractTar(tarPath, pluginRoot);\n\n // Cleanup temp file\n try {\n unlinkSync(tarPath);\n } catch {\n // Ignore cleanup errors\n }\n\n log('info', 'Prebuilt binary installed successfully');\n return true;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n log('debug', 'Prebuilt download failed, will use package manager', {\n error: message,\n });\n return false;\n }\n}\n\n/**\n * Install dependencies using bun or npm.\n */\nfunction installWithPackageManager(): void {\n const hasBun = ((): boolean => {\n try {\n execSync('which bun', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n })();\n\n const cmd = hasBun ? 'bun install --frozen-lockfile' : 'npm ci --silent';\n log('info', 'Installing dependencies with package manager', { hasBun, cmd });\n execSync(cmd, { cwd: pluginRoot, stdio: 'inherit' });\n log('info', 'Dependencies installed via package manager');\n}\n\n/**\n * Ensure dependencies are available.\n * Tries prebuilt first, falls back to package manager.\n */\nasync function ensureDependencies(): Promise<void> {\n // Fast path: already installed\n if (existsSync(join(pluginRoot, 'node_modules'))) {\n log('info', 'Dependencies already installed');\n return;\n }\n\n // Try prebuilt binary first (faster, no npm install needed)\n const prebuiltSuccess = await downloadPrebuilt();\n if (prebuiltSuccess) {\n return;\n }\n\n // Prebuilt not available, use package manager instead\n installWithPackageManager();\n}\n\n// Main entry point\nlog('info', 'Bootstrap starting', { pluginRoot, version: VERSION });\n\nawait ensureDependencies();\n\n// Now that dependencies are installed, import and run the server\n// Dynamic import required because @modelcontextprotocol/sdk wouldn't be available before install\nlog('info', 'Loading server module');\nconst { runMCPServer } = await import('./server.js');\n\nconst projectRoot = process.env['PROJECT_ROOT'];\nif (projectRoot === undefined) {\n throw new Error('PROJECT_ROOT environment variable is required');\n}\n\nlog('info', 'Starting MCP server', {\n projectRoot,\n dataDir: process.env['DATA_DIR'],\n});\n\nawait runMCPServer({\n dataDir: process.env['DATA_DIR'],\n config: process.env['CONFIG_PATH'],\n projectRoot,\n});\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/bootstrap.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * MCP Server Bootstrap - installs dependencies before starting server.\n *\n * Uses only Node.js built-ins (no external dependencies required).\n * Self-locates plugin root via import.meta.url (doesn't rely on CLAUDE_PLUGIN_ROOT).\n *\n * Dependency installation strategy:\n * 1. Fast path: node_modules already exists → skip\n * 2. Try prebuilt: Download platform-specific tarball from GitHub release\n * 3. Package manager: Run bun install or npm ci if prebuilt unavailable\n *\n * IMPORTANT: MCP servers must NOT log to stderr - Claude Code treats stderr output\n * as an error and may mark the MCP server as failed. All logging goes to file.\n */\nimport { execSync } from 'node:child_process';\nimport {\n appendFileSync,\n createWriteStream,\n existsSync,\n mkdirSync,\n readFileSync,\n unlinkSync,\n} from 'node:fs';\nimport { get } from 'node:https';\nimport { arch, homedir, platform } from 'node:os';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// Logging helper - writes to file since MCP servers must NOT use stderr\n// (Claude Code treats stderr as error and may fail the server)\n// JSON format matches pino output for consistency\nconst logDir = join(homedir(), '.bluera', 'bluera-knowledge', 'logs');\nconst logFile = join(logDir, 'app.log');\n\nconst log = (\n level: 'info' | 'error' | 'debug',\n msg: string,\n data?: Record<string, unknown>\n): void => {\n try {\n mkdirSync(logDir, { recursive: true });\n const entry = {\n time: new Date().toISOString(),\n level,\n module: 'bootstrap',\n msg,\n ...data,\n };\n appendFileSync(logFile, `${JSON.stringify(entry)}\\n`);\n } catch {\n // Silently fail - we cannot use stderr for MCP servers\n }\n};\n\n// Self-locate plugin root from this file's path\n// dist/mcp/bootstrap.js -> plugin root (two directories up)\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\nconst pluginRoot = join(__dirname, '..', '..');\n\n// Get version from package.json\nconst getVersion = (): string => {\n try {\n const pkg: unknown = JSON.parse(readFileSync(join(pluginRoot, 'package.json'), 'utf-8'));\n if (\n typeof pkg === 'object' &&\n pkg !== null &&\n 'version' in pkg &&\n typeof pkg.version === 'string'\n ) {\n return `v${pkg.version}`;\n }\n return 'unknown';\n } catch {\n return 'unknown';\n }\n};\n\nconst VERSION = getVersion();\nconst MANIFEST_URL = `https://github.com/blueraai/bluera-knowledge/releases/download/${VERSION}/manifest.json`;\n\n/**\n * Fetch JSON from URL with redirect handling.\n * Uses only Node.js built-ins.\n */\nfunction fetchJSON(url: string): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const request = (targetUrl: string, redirectCount = 0): void => {\n if (redirectCount > 5) {\n reject(new Error('Too many redirects'));\n return;\n }\n\n get(targetUrl, { headers: { 'User-Agent': 'bluera-knowledge' } }, (res) => {\n // Follow redirects\n const location = res.headers.location;\n if (\n (res.statusCode === 302 || res.statusCode === 301) &&\n typeof location === 'string' &&\n location.length > 0\n ) {\n request(location, redirectCount + 1);\n return;\n }\n const statusCode = res.statusCode ?? 0;\n if (statusCode !== 200) {\n reject(new Error(`HTTP ${String(statusCode)}`));\n return;\n }\n let data = '';\n res.on('data', (chunk: Buffer) => (data += chunk.toString()));\n res.on('end', () => {\n try {\n resolve(JSON.parse(data));\n } catch (e) {\n reject(e instanceof Error ? e : new Error(String(e)));\n }\n });\n }).on('error', reject);\n };\n request(url);\n });\n}\n\n/**\n * Download file from URL to destination path.\n * Uses only Node.js built-ins.\n */\nfunction downloadFile(url: string, destPath: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const request = (targetUrl: string, redirectCount = 0): void => {\n if (redirectCount > 10) {\n reject(new Error('Too many redirects'));\n return;\n }\n\n get(targetUrl, { headers: { 'User-Agent': 'bluera-knowledge' } }, (res) => {\n const location = res.headers.location;\n if (\n (res.statusCode === 302 || res.statusCode === 301) &&\n typeof location === 'string' &&\n location.length > 0\n ) {\n request(location, redirectCount + 1);\n return;\n }\n const statusCode = res.statusCode ?? 0;\n if (statusCode !== 200) {\n reject(new Error(`HTTP ${String(statusCode)}`));\n return;\n }\n const file = createWriteStream(destPath);\n res.pipe(file);\n file.on('finish', () => {\n file.close();\n resolve();\n });\n file.on('error', (err) => {\n file.close();\n reject(err);\n });\n }).on('error', reject);\n };\n request(url);\n });\n}\n\ninterface PlatformInfo {\n url: string;\n sha256: string;\n}\n\ninterface Manifest {\n version: string;\n platforms: Record<string, PlatformInfo>;\n}\n\nfunction isManifest(value: unknown): value is Manifest {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'version' in value &&\n 'platforms' in value &&\n typeof value.platforms === 'object'\n );\n}\n\n/**\n * Try to download and extract prebuilt binary for current platform.\n * Returns true if successful, false if prebuilt is not available.\n */\nasync function downloadPrebuilt(): Promise<boolean> {\n const plat = platform(); // 'darwin', 'linux', 'win32'\n const ar = arch(); // 'arm64', 'x64'\n const platformKey = `${plat}-${ar}`;\n\n try {\n log('info', 'Checking for prebuilt binary', { platformKey, version: VERSION });\n\n const manifestData = await fetchJSON(MANIFEST_URL);\n if (!isManifest(manifestData)) {\n log('info', 'Invalid manifest format');\n return false;\n }\n\n const platformInfo = manifestData.platforms[platformKey];\n if (platformInfo === undefined) {\n log('info', 'No prebuilt binary available for platform', { platformKey });\n return false;\n }\n\n log('info', 'Downloading prebuilt binary', { url: platformInfo.url });\n const tmpDir = join(homedir(), '.bluera', 'tmp');\n mkdirSync(tmpDir, { recursive: true });\n const tarPath = join(tmpDir, `bluera-knowledge-${platformKey}.tar.gz`);\n\n await downloadFile(platformInfo.url, tarPath);\n log('info', 'Download complete, extracting', { tarPath, destDir: pluginRoot });\n\n // Use system tar - handles all formats (PAX, GNU, USTAR) correctly\n execSync(`tar -xzf \"${tarPath}\" -C \"${pluginRoot}\"`, { stdio: 'pipe' });\n\n // Cleanup temp file\n try {\n unlinkSync(tarPath);\n } catch {\n // Ignore cleanup errors\n }\n\n log('info', 'Prebuilt binary installed successfully');\n return true;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n log('debug', 'Prebuilt download failed, will use package manager', {\n error: message,\n });\n return false;\n }\n}\n\n/**\n * Install dependencies using bun or npm.\n */\nfunction installWithPackageManager(): void {\n const hasBun = ((): boolean => {\n try {\n execSync('which bun', { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n })();\n\n const cmd = hasBun ? 'bun install --frozen-lockfile' : 'npm ci --silent';\n log('info', 'Installing dependencies with package manager', { hasBun, cmd });\n execSync(cmd, { cwd: pluginRoot, stdio: 'inherit' });\n log('info', 'Dependencies installed via package manager');\n}\n\n/**\n * Ensure dependencies are available.\n * Tries prebuilt first, falls back to package manager.\n */\nasync function ensureDependencies(): Promise<void> {\n // Fast path: already installed\n if (existsSync(join(pluginRoot, 'node_modules'))) {\n log('info', 'Dependencies already installed');\n return;\n }\n\n // Try prebuilt binary first (faster, no npm install needed)\n const prebuiltSuccess = await downloadPrebuilt();\n if (prebuiltSuccess) {\n return;\n }\n\n // Prebuilt not available, use package manager instead\n installWithPackageManager();\n}\n\n// Main entry point\nlog('info', 'Bootstrap starting', { pluginRoot, version: VERSION });\n\nawait ensureDependencies();\n\n// Now that dependencies are installed, import and run the server\n// Dynamic import required because @modelcontextprotocol/sdk wouldn't be available before install\nlog('info', 'Loading server module');\nconst { runMCPServer } = await import('./server.js');\n\nconst projectRoot = process.env['PROJECT_ROOT'];\nif (projectRoot === undefined) {\n throw new Error('PROJECT_ROOT environment variable is required');\n}\n\nlog('info', 'Starting MCP server', {\n projectRoot,\n dataDir: process.env['DATA_DIR'],\n});\n\nawait runMCPServer({\n dataDir: process.env['DATA_DIR'],\n config: process.env['CONFIG_PATH'],\n projectRoot,\n});\n"],"mappings":"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","names":[]}
|