bluera-knowledge 0.31.0 → 0.33.0

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.
Files changed (70) hide show
  1. package/.claude-plugin/plugin.json +23 -0
  2. package/.mcp.json +13 -0
  3. package/CHANGELOG.md +42 -0
  4. package/NOTICE +47 -0
  5. package/README.md +2 -2
  6. package/bun.lock +1978 -0
  7. package/dist/{chunk-B335UOU7.js → chunk-3TB7TDVF.js} +24 -3
  8. package/dist/chunk-3TB7TDVF.js.map +1 -0
  9. package/dist/{chunk-KCI4U6FH.js → chunk-KDZDLJUY.js} +2 -2
  10. package/dist/{chunk-AEXFPA57.js → chunk-YDTTD53Y.js} +158 -26
  11. package/dist/chunk-YDTTD53Y.js.map +1 -0
  12. package/dist/index.js +3 -3
  13. package/dist/mcp/bootstrap.js +10 -0
  14. package/dist/mcp/bootstrap.js.map +1 -1
  15. package/dist/mcp/server.d.ts +5 -3
  16. package/dist/mcp/server.js +2 -2
  17. package/dist/workers/background-worker-cli.js +2 -2
  18. package/hooks/check-ready.sh +109 -0
  19. package/hooks/hooks.json +97 -0
  20. package/hooks/job-status-hook.sh +51 -0
  21. package/hooks/posttooluse-bk-reminder.py +126 -0
  22. package/hooks/posttooluse-web-research.py +209 -0
  23. package/hooks/posttooluse-websearch-bk.py +158 -0
  24. package/hooks/pretooluse-bk-suggest.py +296 -0
  25. package/hooks/skill-activation.py +221 -0
  26. package/hooks/skill-rules.json +131 -0
  27. package/package.json +9 -2
  28. package/scripts/CLAUDE.md +65 -0
  29. package/scripts/auto-setup.sh +65 -0
  30. package/scripts/bench-regression.sh +345 -0
  31. package/scripts/dev.sh +16 -0
  32. package/scripts/doctor.sh +103 -0
  33. package/scripts/download-models.ts +188 -0
  34. package/scripts/export-web-store.ts +142 -0
  35. package/scripts/lib/mock-server.sh +70 -0
  36. package/scripts/mcp-wrapper.sh +91 -0
  37. package/scripts/setup.sh +224 -0
  38. package/scripts/statusline-module.sh +29 -0
  39. package/scripts/test-mcp-dev.js +260 -0
  40. package/scripts/validate-local.sh +412 -0
  41. package/scripts/validate-npm-release.sh +406 -0
  42. package/skills/add-folder/SKILL.md +48 -0
  43. package/skills/add-repo/SKILL.md +50 -0
  44. package/skills/advanced-workflows/SKILL.md +273 -0
  45. package/skills/cancel/SKILL.md +63 -0
  46. package/skills/check-status/SKILL.md +130 -0
  47. package/skills/crawl/SKILL.md +61 -0
  48. package/skills/doctor/SKILL.md +27 -0
  49. package/skills/eval/SKILL.md +222 -0
  50. package/skills/health/SKILL.md +72 -0
  51. package/skills/index/SKILL.md +48 -0
  52. package/skills/knowledge-search/SKILL.md +110 -0
  53. package/skills/remove-store/SKILL.md +52 -0
  54. package/skills/search/SKILL.md +80 -0
  55. package/skills/search/search.sh +63 -0
  56. package/skills/search-optimization/SKILL.md +199 -0
  57. package/skills/search-optimization/references/mistakes.md +21 -0
  58. package/skills/search-optimization/references/strategies.md +80 -0
  59. package/skills/skill-activation/SKILL.md +131 -0
  60. package/skills/statusline/SKILL.md +19 -0
  61. package/skills/store-lifecycle/SKILL.md +470 -0
  62. package/skills/stores/SKILL.md +54 -0
  63. package/skills/suggest/SKILL.md +118 -0
  64. package/skills/sync/SKILL.md +96 -0
  65. package/skills/test-plugin/SKILL.md +547 -0
  66. package/skills/uninstall/SKILL.md +65 -0
  67. package/skills/when-to-query/SKILL.md +160 -0
  68. package/dist/chunk-AEXFPA57.js.map +0 -1
  69. package/dist/chunk-B335UOU7.js.map +0 -1
  70. /package/dist/{chunk-KCI4U6FH.js.map → chunk-KDZDLJUY.js.map} +0 -0
@@ -0,0 +1,224 @@
1
+ #!/bin/bash
2
+ # Bluera Knowledge Plugin - Setup Script
3
+ # Runs on: SessionStart (via auto-setup.sh) or manually
4
+ #
5
+ # Installs:
6
+ # - MCP wrapper script (bluera-knowledge-mcp)
7
+ # - Node.js dependencies (bun install / npm ci)
8
+ # - Playwright Chromium browser (for web crawling)
9
+ #
10
+ # Environment variables:
11
+ # NONINTERACTIVE=1 - Skip interactive prompts (for auto-setup)
12
+
13
+ set -e
14
+
15
+ # Get the plugin root directory
16
+ PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$0")")}"
17
+
18
+ # Non-interactive mode (set by auto-setup.sh)
19
+ NONINTERACTIVE="${NONINTERACTIVE:-}"
20
+
21
+ # Colors for output
22
+ GREEN='\033[0;32m'
23
+ YELLOW='\033[1;33m'
24
+ RED='\033[0;31m'
25
+ NC='\033[0m'
26
+
27
+ # Debug logging - writes JSON to same log file as bootstrap.ts
28
+ # Uses PROJECT_ROOT if available (set by Claude Code), else current dir
29
+ LOG_DIR="${PROJECT_ROOT:-.}/.bluera/bluera-knowledge/logs"
30
+ LOG_FILE="$LOG_DIR/app.log"
31
+
32
+ log_debug() {
33
+ local msg="$1"
34
+ mkdir -p "$LOG_DIR" 2>/dev/null || true
35
+ # macOS date doesn't support %3N, fallback to seconds-only
36
+ local timestamp
37
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z" 2>/dev/null || date -u +"%Y-%m-%dT%H:%M:%SZ")
38
+ echo "{\"time\":\"$timestamp\",\"level\":\"debug\",\"module\":\"setup.sh\",\"msg\":\"$msg\"}" >> "$LOG_FILE" 2>/dev/null || true
39
+ }
40
+
41
+ log_debug "Setup starting, PLUGIN_ROOT=$PLUGIN_ROOT, PROJECT_ROOT=${PROJECT_ROOT:-unset}"
42
+
43
+ echo -e "${YELLOW}[bluera-knowledge] Running setup...${NC}"
44
+
45
+ # =====================
46
+ # Node.js Dependencies
47
+ # =====================
48
+
49
+ log_debug "Checking node_modules at $PLUGIN_ROOT/node_modules"
50
+
51
+ # =====================
52
+ # Build Tools Check (required for native modules)
53
+ # =====================
54
+
55
+ log_debug "Checking build tools"
56
+
57
+ if command -v make &> /dev/null; then
58
+ log_debug "make found"
59
+ echo -e "${GREEN}[bluera-knowledge] Build tools available ✓${NC}"
60
+ else
61
+ log_debug "make not found, prompting for install"
62
+ echo -e "${YELLOW}[bluera-knowledge] Build tools (make) not found - required for native modules${NC}"
63
+
64
+ # Detect platform and set install command
65
+ if [ -f /etc/debian_version ]; then
66
+ INSTALL_CMD="sudo apt update && sudo apt install -y build-essential"
67
+ PLATFORM="Debian/Ubuntu"
68
+ elif [ -f /etc/fedora-release ] || [ -f /etc/redhat-release ]; then
69
+ INSTALL_CMD="sudo dnf groupinstall -y 'Development Tools'"
70
+ PLATFORM="Fedora/RHEL"
71
+ elif [ "$(uname)" = "Darwin" ]; then
72
+ INSTALL_CMD="xcode-select --install"
73
+ PLATFORM="macOS"
74
+ else
75
+ # Unknown platform - show manual instructions and exit
76
+ echo -e "${RED}[bluera-knowledge] Could not detect platform for auto-install${NC}"
77
+ echo -e "${YELLOW}Please install build tools manually:${NC}"
78
+ echo -e "${YELLOW} Debian/Ubuntu: sudo apt install build-essential${NC}"
79
+ echo -e "${YELLOW} Fedora/RHEL: sudo dnf groupinstall 'Development Tools'${NC}"
80
+ echo -e "${YELLOW} macOS: xcode-select --install${NC}"
81
+ exit 1
82
+ fi
83
+
84
+ echo -e "${YELLOW}Detected platform: ${PLATFORM}${NC}"
85
+ echo -e "${YELLOW}Install command: ${INSTALL_CMD}${NC}"
86
+ echo ""
87
+
88
+ # Non-interactive mode: cannot prompt for sudo, exit with instructions
89
+ if [[ -n "$NONINTERACTIVE" ]]; then
90
+ log_debug "Non-interactive mode, cannot prompt for build tools install"
91
+ echo -e "${RED}[bluera-knowledge] Build tools required but running non-interactively.${NC}"
92
+ echo -e "${YELLOW}Run manually: ${INSTALL_CMD}${NC}"
93
+ echo -e "${YELLOW}Then restart Claude Code.${NC}"
94
+ exit 1
95
+ fi
96
+
97
+ read -p "Install build tools now? [y/N] " -n 1 -r
98
+ echo ""
99
+
100
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
101
+ log_debug "User confirmed install, running: $INSTALL_CMD"
102
+ echo -e "${YELLOW}[bluera-knowledge] Installing build tools...${NC}"
103
+ if eval "$INSTALL_CMD"; then
104
+ log_debug "Build tools installed successfully"
105
+ echo -e "${GREEN}[bluera-knowledge] Build tools installed ✓${NC}"
106
+ else
107
+ log_debug "Build tools install failed"
108
+ echo -e "${RED}[bluera-knowledge] Build tools installation failed${NC}"
109
+ echo -e "${YELLOW}Please install manually and retry${NC}"
110
+ exit 1
111
+ fi
112
+ else
113
+ log_debug "User declined install"
114
+ echo -e "${YELLOW}[bluera-knowledge] Skipped. Please install build tools manually:${NC}"
115
+ echo -e "${YELLOW} ${INSTALL_CMD}${NC}"
116
+ exit 1
117
+ fi
118
+ fi
119
+
120
+ # =====================
121
+ # MCP Wrapper Script
122
+ # WORKAROUND: ${CLAUDE_PLUGIN_ROOT} not expanding in plugin .mcp.json
123
+ # Claude Code's ${CLAUDE_PLUGIN_ROOT} should work per docs, but doesn't in some environments.
124
+ # See: https://github.com/anthropics/claude-code/issues/9427
125
+ # See: https://code.claude.com/docs/en/mcp (Plugin MCP configuration)
126
+ # =====================
127
+
128
+ log_debug "Installing MCP wrapper script"
129
+
130
+ WRAPPER_DIR="$HOME/.local/bin"
131
+ WRAPPER_PATH="$WRAPPER_DIR/bluera-knowledge-mcp"
132
+
133
+ mkdir -p "$WRAPPER_DIR"
134
+ cp "$PLUGIN_ROOT/scripts/mcp-wrapper.sh" "$WRAPPER_PATH"
135
+ chmod +x "$WRAPPER_PATH"
136
+
137
+ # Check if ~/.local/bin is in PATH
138
+ if [[ ":$PATH:" != *":$WRAPPER_DIR:"* ]]; then
139
+ log_debug "Warning: $WRAPPER_DIR not in PATH"
140
+ echo -e "${YELLOW}[bluera-knowledge] Note: Add ~/.local/bin to your PATH if MCP server doesn't start${NC}"
141
+ echo -e "${YELLOW} export PATH=\"\$HOME/.local/bin:\$PATH\"${NC}"
142
+ fi
143
+
144
+ echo -e "${GREEN}[bluera-knowledge] MCP wrapper installed ✓${NC}"
145
+
146
+ # =====================
147
+ # Node.js Dependencies
148
+ # =====================
149
+
150
+ if [ -d "$PLUGIN_ROOT/node_modules" ]; then
151
+ log_debug "node_modules exists, skipping install"
152
+ echo -e "${GREEN}[bluera-knowledge] Node.js dependencies already installed ✓${NC}"
153
+ else
154
+ echo -e "${YELLOW}[bluera-knowledge] Installing Node.js dependencies...${NC}"
155
+
156
+ # Skip browser downloads during npm/bun install - browsers are installed separately below
157
+ export PUPPETEER_SKIP_DOWNLOAD=1
158
+ export PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1
159
+
160
+ if command -v bun &> /dev/null; then
161
+ log_debug "Starting bun install --frozen-lockfile"
162
+ (cd "$PLUGIN_ROOT" && bun install --frozen-lockfile) && \
163
+ { log_debug "bun install complete"; echo -e "${GREEN}[bluera-knowledge] Node.js dependencies installed ✓${NC}"; } || \
164
+ { log_debug "bun install FAILED"; echo -e "${RED}[bluera-knowledge] Failed to install Node.js dependencies${NC}"; exit 1; }
165
+ elif command -v npm &> /dev/null; then
166
+ # CRITICAL: --legacy-peer-deps required!
167
+ # tree-sitter-go@0.25 requires tree-sitter@^0.25
168
+ # tree-sitter-rust@0.24 requires tree-sitter@^0.22
169
+ # These peer deps conflict - no compatible version exists.
170
+ # Without --legacy-peer-deps, npm fails even though tree-sitter is optional.
171
+ log_debug "Starting npm install --legacy-peer-deps"
172
+ (cd "$PLUGIN_ROOT" && npm install --legacy-peer-deps) && \
173
+ { log_debug "npm install complete"; echo -e "${GREEN}[bluera-knowledge] Node.js dependencies installed ✓${NC}"; } || \
174
+ { log_debug "npm install FAILED"; echo -e "${RED}[bluera-knowledge] Failed to install Node.js dependencies${NC}"; exit 1; }
175
+ else
176
+ log_debug "Neither bun nor npm found"
177
+ echo -e "${RED}[bluera-knowledge] Neither bun nor npm found${NC}"
178
+ exit 1
179
+ fi
180
+ fi
181
+
182
+ # =====================
183
+ # Playwright Browser
184
+ # =====================
185
+
186
+ PLAYWRIGHT_BROWSERS_PATH="${PLAYWRIGHT_BROWSERS_PATH:-$HOME/.cache/ms-playwright}"
187
+
188
+ log_debug "Checking playwright at $PLAYWRIGHT_BROWSERS_PATH/chromium-*"
189
+
190
+ if ls "$PLAYWRIGHT_BROWSERS_PATH"/chromium-* 1>/dev/null 2>&1; then
191
+ log_debug "Playwright chromium exists, skipping install"
192
+ echo -e "${GREEN}[bluera-knowledge] Playwright Chromium already installed ✓${NC}"
193
+ else
194
+ echo -e "${YELLOW}[bluera-knowledge] Installing Playwright Chromium browser...${NC}"
195
+
196
+ log_debug "Starting npx playwright install chromium (this can take several minutes)"
197
+ if (cd "$PLUGIN_ROOT" && npx playwright install chromium); then
198
+ log_debug "Playwright install complete"
199
+ echo -e "${GREEN}[bluera-knowledge] Playwright Chromium installed ✓${NC}"
200
+ else
201
+ log_debug "Playwright install FAILED"
202
+ echo -e "${RED}[bluera-knowledge] Playwright browser install failed${NC}"
203
+ echo -e "${YELLOW}Manual fix: npx playwright install chromium${NC}"
204
+ exit 1
205
+ fi
206
+ fi
207
+
208
+ # =====================
209
+ # Python3 Check (Optional)
210
+ # =====================
211
+
212
+ log_debug "Checking python3"
213
+
214
+ if command -v python3 &> /dev/null; then
215
+ python_version=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
216
+ log_debug "Python $python_version found"
217
+ echo -e "${GREEN}[bluera-knowledge] Python ${python_version} available (for code-graph) ✓${NC}"
218
+ else
219
+ log_debug "Python3 not found"
220
+ echo -e "${YELLOW}[bluera-knowledge] Python3 not found - code-graph feature unavailable${NC}"
221
+ fi
222
+
223
+ log_debug "Setup complete"
224
+ echo -e "${GREEN}[bluera-knowledge] Setup complete ✓${NC}"
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+ # bluera-knowledge statusline module
3
+ # Injected into ~/.claude/statusline.sh between boundary comments:
4
+ # # --- bluera-knowledge ---
5
+ # # --- end bluera-knowledge ---
6
+ #
7
+ # Expected variables from parent script:
8
+ # PROJECT_DIR - project directory path (from statusline JSON input)
9
+ #
10
+ # Exports:
11
+ # BK_STATUS - formatted status string (e.g. " 📘●3")
12
+
13
+ get_bk_status() {
14
+ local project_dir="$1"
15
+ local store_count=""
16
+ local config_file="$project_dir/.bluera/bluera-knowledge/stores.config.json"
17
+ if [ -f "$config_file" ]; then
18
+ store_count=$(jq '.stores | length' "$config_file" 2>/dev/null)
19
+ [ "$store_count" = "0" ] && store_count=""
20
+ fi
21
+
22
+ if pgrep -f "bluera-knowledge.*bootstrap" >/dev/null 2>&1 || \
23
+ pgrep -f "bluera-knowledge-mcp" >/dev/null 2>&1; then
24
+ printf " 📘\033[32m●\033[0m%s" "$store_count"
25
+ else
26
+ printf " 📘\033[31m●\033[0m%s" "$store_count"
27
+ fi
28
+ }
29
+ BK_STATUS=$(get_bk_status "$PROJECT_DIR")
@@ -0,0 +1,260 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Development Test Helper
4
+ *
5
+ * Spawns the MCP server and communicates with it directly via JSON-RPC over stdio.
6
+ * Used by test-plugin --dev to test MCP functionality without needing the plugin installed.
7
+ *
8
+ * Usage:
9
+ * ./scripts/test-mcp-dev.mjs call <tool-name> '<json-args>'
10
+ * ./scripts/test-mcp-dev.mjs call execute '{"command":"help"}'
11
+ * ./scripts/test-mcp-dev.mjs call search '{"query":"test"}'
12
+ */
13
+
14
+ import { spawn } from 'node:child_process';
15
+ import { createInterface } from 'node:readline';
16
+ import { dirname, join } from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const PROJECT_ROOT = join(__dirname, '..');
21
+
22
+ class MCPTestClient {
23
+ constructor() {
24
+ this.server = null;
25
+ this.messageId = 0;
26
+ this.pendingRequests = new Map();
27
+ this.initialized = false;
28
+ }
29
+
30
+ async start() {
31
+ return new Promise((resolve, reject) => {
32
+ const serverPath = join(PROJECT_ROOT, 'dist', 'mcp', 'server.js');
33
+
34
+ this.server = spawn('node', [serverPath], {
35
+ cwd: PROJECT_ROOT,
36
+ env: {
37
+ ...process.env,
38
+ PROJECT_ROOT: PROJECT_ROOT,
39
+ DATA_DIR: '.bluera/bluera-knowledge/data',
40
+ CONFIG_PATH: '.bluera/bluera-knowledge/config.json',
41
+ },
42
+ stdio: ['pipe', 'pipe', 'pipe'],
43
+ });
44
+
45
+ // Handle stderr (logs)
46
+ this.server.stderr.on('data', (data) => {
47
+ // Suppress server logs in test output unless DEBUG
48
+ if (process.env.DEBUG) {
49
+ process.stderr.write(`[server] ${data}`);
50
+ }
51
+ });
52
+
53
+ // Handle stdout (JSON-RPC responses)
54
+ const rl = createInterface({ input: this.server.stdout });
55
+ rl.on('line', (line) => {
56
+ try {
57
+ const msg = JSON.parse(line);
58
+ if (msg.id !== undefined && this.pendingRequests.has(msg.id)) {
59
+ const { resolve, reject } = this.pendingRequests.get(msg.id);
60
+ this.pendingRequests.delete(msg.id);
61
+ if (msg.error) {
62
+ reject(new Error(msg.error.message || JSON.stringify(msg.error)));
63
+ } else {
64
+ resolve(msg.result);
65
+ }
66
+ }
67
+ } catch {
68
+ // Not JSON or parse error - ignore
69
+ }
70
+ });
71
+
72
+ this.server.on('error', reject);
73
+ this.server.on('spawn', () => {
74
+ // Give server a moment to initialize
75
+ setTimeout(() => resolve(), 100);
76
+ });
77
+ });
78
+ }
79
+
80
+ async initialize() {
81
+ if (this.initialized) return;
82
+
83
+ // Send initialize request
84
+ const initResult = await this.sendRequest('initialize', {
85
+ protocolVersion: '2024-11-05',
86
+ capabilities: {},
87
+ clientInfo: { name: 'test-mcp-dev', version: '1.0.0' },
88
+ });
89
+
90
+ // Send initialized notification
91
+ this.sendNotification('notifications/initialized', {});
92
+
93
+ this.initialized = true;
94
+ return initResult;
95
+ }
96
+
97
+ sendRequest(method, params) {
98
+ return new Promise((resolve, reject) => {
99
+ const id = ++this.messageId;
100
+ const request = { jsonrpc: '2.0', id, method, params };
101
+
102
+ this.pendingRequests.set(id, { resolve, reject });
103
+
104
+ // Set timeout for request
105
+ setTimeout(() => {
106
+ if (this.pendingRequests.has(id)) {
107
+ this.pendingRequests.delete(id);
108
+ reject(new Error(`Request timeout: ${method}`));
109
+ }
110
+ }, 30000);
111
+
112
+ this.server.stdin.write(JSON.stringify(request) + '\n');
113
+ });
114
+ }
115
+
116
+ sendNotification(method, params) {
117
+ const notification = { jsonrpc: '2.0', method, params };
118
+ this.server.stdin.write(JSON.stringify(notification) + '\n');
119
+ }
120
+
121
+ async callTool(name, args) {
122
+ if (!this.initialized) {
123
+ await this.initialize();
124
+ }
125
+
126
+ const result = await this.sendRequest('tools/call', {
127
+ name,
128
+ arguments: args,
129
+ });
130
+
131
+ return result;
132
+ }
133
+
134
+ async listTools() {
135
+ if (!this.initialized) {
136
+ await this.initialize();
137
+ }
138
+
139
+ return this.sendRequest('tools/list', {});
140
+ }
141
+
142
+ stop() {
143
+ if (this.server) {
144
+ this.server.kill('SIGTERM');
145
+ this.server = null;
146
+ }
147
+ }
148
+ }
149
+
150
+ // Session mode - reads multiple commands from stdin, maintains server across calls
151
+ async function sessionMode() {
152
+ const client = new MCPTestClient();
153
+ await client.start();
154
+
155
+ const rl = createInterface({ input: process.stdin });
156
+ const results = [];
157
+ let lastResultId = null;
158
+
159
+ for await (const line of rl) {
160
+ const trimmed = line.trim();
161
+ if (!trimmed || trimmed.startsWith('#')) continue;
162
+
163
+ const spaceIndex = trimmed.indexOf(' ');
164
+ const toolName = spaceIndex > 0 ? trimmed.slice(0, spaceIndex) : trimmed;
165
+ let argsStr = spaceIndex > 0 ? trimmed.slice(spaceIndex + 1) : '{}';
166
+
167
+ // Substitute $LAST_ID with actual result ID from previous search
168
+ if (lastResultId && argsStr.includes('$LAST_ID')) {
169
+ argsStr = argsStr.replace(/\$LAST_ID/g, lastResultId);
170
+ }
171
+
172
+ const args = JSON.parse(argsStr);
173
+ const result = await client.callTool(toolName, args);
174
+ results.push(result);
175
+
176
+ // Extract result ID for next call (search results have results[0].id)
177
+ // Search results have a header line before JSON, so find first '{'
178
+ if (result?.content?.[0]?.text) {
179
+ const text = result.content[0].text;
180
+ const jsonStart = text.indexOf('{');
181
+ if (jsonStart >= 0) {
182
+ try {
183
+ const parsed = JSON.parse(text.slice(jsonStart));
184
+ if (parsed.results?.[0]?.id) {
185
+ lastResultId = parsed.results[0].id;
186
+ }
187
+ } catch {
188
+ // Not valid JSON or no results - ignore
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ client.stop();
195
+ console.log(JSON.stringify(results, null, 2));
196
+ }
197
+
198
+ // CLI interface
199
+ async function main() {
200
+ const args = process.argv.slice(2);
201
+
202
+ if (args.length < 1) {
203
+ console.error('Usage: test-mcp-dev.js <command> [args...]');
204
+ console.error('Commands:');
205
+ console.error(' call <tool-name> <json-args> - Call an MCP tool (one-shot)');
206
+ console.error(' session - Read commands from stdin (persistent server)');
207
+ console.error(' list - List available tools');
208
+ console.error('');
209
+ console.error('Examples:');
210
+ console.error(' ./scripts/test-mcp-dev.js call execute \'{"command":"help"}\'');
211
+ console.error(' ./scripts/test-mcp-dev.js call search \'{"query":"test"}\'');
212
+ console.error(' ./scripts/test-mcp-dev.js list');
213
+ console.error('');
214
+ console.error('Session mode (maintains cache across calls):');
215
+ console.error(' echo -e \'search {"query":"test"}\\nget_full_context {"resultId":"$LAST_ID"}\' | ./scripts/test-mcp-dev.js session');
216
+ process.exit(1);
217
+ }
218
+
219
+ const command = args[0];
220
+
221
+ // Session mode handles its own client lifecycle
222
+ if (command === 'session') {
223
+ await sessionMode();
224
+ return;
225
+ }
226
+
227
+ const client = new MCPTestClient();
228
+
229
+ try {
230
+ await client.start();
231
+
232
+ if (command === 'call') {
233
+ if (args.length < 3) {
234
+ console.error('Usage: test-mcp-dev.js call <tool-name> <json-args>');
235
+ process.exit(1);
236
+ }
237
+
238
+ const toolName = args[1];
239
+ const toolArgs = JSON.parse(args[2]);
240
+
241
+ const result = await client.callTool(toolName, toolArgs);
242
+
243
+ // Output result as JSON for parsing by test-plugin
244
+ console.log(JSON.stringify(result, null, 2));
245
+ } else if (command === 'list') {
246
+ const result = await client.listTools();
247
+ console.log(JSON.stringify(result, null, 2));
248
+ } else {
249
+ console.error(`Unknown command: ${command}`);
250
+ process.exit(1);
251
+ }
252
+ } catch (error) {
253
+ console.error(`Error: ${error.message}`);
254
+ process.exit(1);
255
+ } finally {
256
+ client.stop();
257
+ }
258
+ }
259
+
260
+ main();