bluera-knowledge 0.13.2 → 0.14.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.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +14 -0
- package/commands/uninstall.md +65 -0
- package/dist/{chunk-GCUKVV33.js → chunk-AIS5S77C.js} +2 -2
- package/dist/{chunk-H5AKKHY7.js → chunk-UAWKTJWN.js} +134 -15
- package/dist/chunk-UAWKTJWN.js.map +1 -0
- package/dist/{chunk-6ZVW2P2F.js → chunk-Y24ZJRZP.js} +72 -50
- package/dist/{chunk-6ZVW2P2F.js.map → chunk-Y24ZJRZP.js.map} +1 -1
- package/dist/index.js +3 -3
- package/dist/mcp/server.js +2 -2
- package/dist/workers/background-worker-cli.js +2 -2
- package/hooks/check-dependencies.sh +46 -57
- package/package.json +1 -1
- package/src/crawl/bridge.test.ts +1 -1
- package/src/crawl/bridge.ts +35 -2
- package/src/mcp/commands/index.ts +2 -0
- package/src/mcp/commands/uninstall.commands.test.ts +37 -0
- package/src/mcp/commands/uninstall.commands.ts +29 -0
- package/src/mcp/handlers/uninstall.handler.test.ts +194 -0
- package/src/mcp/handlers/uninstall.handler.ts +142 -0
- package/dist/chunk-H5AKKHY7.js.map +0 -1
- /package/dist/{chunk-GCUKVV33.js.map → chunk-AIS5S77C.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
isWebStoreDefinition,
|
|
8
8
|
runMCPServer,
|
|
9
9
|
spawnBackgroundWorker
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-UAWKTJWN.js";
|
|
11
11
|
import {
|
|
12
12
|
IntelligentCrawler
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-AIS5S77C.js";
|
|
14
14
|
import {
|
|
15
15
|
ASTParser,
|
|
16
16
|
AdapterRegistry,
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
err,
|
|
25
25
|
extractRepoName,
|
|
26
26
|
ok
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-Y24ZJRZP.js";
|
|
28
28
|
import "./chunk-HRQD3MPH.js";
|
|
29
29
|
|
|
30
30
|
// src/index.ts
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
IntelligentCrawler
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-AIS5S77C.js";
|
|
5
5
|
import {
|
|
6
6
|
JobService,
|
|
7
7
|
createDocumentId,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
createStoreId,
|
|
11
11
|
destroyServices,
|
|
12
12
|
shutdownLogger
|
|
13
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-Y24ZJRZP.js";
|
|
14
14
|
import "../chunk-HRQD3MPH.js";
|
|
15
15
|
|
|
16
16
|
// src/workers/background-worker-cli.ts
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
# Automatically checks and installs dependencies for the plugin
|
|
4
4
|
#
|
|
5
5
|
# Environment variables:
|
|
6
|
-
# BK_SKIP_AUTO_INSTALL=1 - Skip automatic
|
|
6
|
+
# BK_SKIP_AUTO_INSTALL=1 - Skip automatic installation of crawl4ai
|
|
7
7
|
# Set this if you prefer to manage Python packages manually
|
|
8
8
|
#
|
|
9
9
|
# What this script auto-installs (if missing):
|
|
10
10
|
# - Node.js dependencies (via bun or npm, from package.json)
|
|
11
|
-
# -
|
|
11
|
+
# - Python virtual environment with crawl4ai (isolated from system Python)
|
|
12
12
|
# - Playwright Chromium browser (via playwright CLI, for headless crawling)
|
|
13
13
|
|
|
14
14
|
set -e
|
|
@@ -16,6 +16,11 @@ set -e
|
|
|
16
16
|
# Get the plugin root directory
|
|
17
17
|
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$0")")}"
|
|
18
18
|
|
|
19
|
+
# Venv location within plugin (isolated from system Python)
|
|
20
|
+
VENV_DIR="$PLUGIN_ROOT/.venv"
|
|
21
|
+
VENV_PYTHON="$VENV_DIR/bin/python3"
|
|
22
|
+
VENV_PIP="$VENV_DIR/bin/pip"
|
|
23
|
+
|
|
19
24
|
# Colors for output
|
|
20
25
|
RED='\033[0;31m'
|
|
21
26
|
GREEN='\033[0;32m'
|
|
@@ -26,21 +31,24 @@ NC='\033[0m' # No Color
|
|
|
26
31
|
# Helper Functions
|
|
27
32
|
# =====================
|
|
28
33
|
|
|
29
|
-
# Install Playwright browser
|
|
34
|
+
# Install Playwright browser using specified python
|
|
35
|
+
# Args: $1 = python path to use
|
|
30
36
|
install_playwright_browser() {
|
|
37
|
+
local PYTHON_CMD="${1:-python3}"
|
|
38
|
+
|
|
31
39
|
# Check if Playwright Chromium is already installed by testing if browser can be launched
|
|
32
|
-
if
|
|
40
|
+
if "$PYTHON_CMD" -c "from playwright.sync_api import sync_playwright; p = sync_playwright().start(); b = p.chromium.launch(); b.close(); p.stop()" 2>/dev/null; then
|
|
33
41
|
echo -e "${GREEN}[bluera-knowledge] Playwright Chromium ready ✓${NC}"
|
|
34
42
|
return 0
|
|
35
43
|
fi
|
|
36
44
|
|
|
37
45
|
echo -e "${YELLOW}[bluera-knowledge] Installing Playwright browser (one-time setup)...${NC}"
|
|
38
|
-
if
|
|
46
|
+
if "$PYTHON_CMD" -m playwright install chromium 2>/dev/null; then
|
|
39
47
|
echo -e "${GREEN}[bluera-knowledge] Playwright Chromium installed ✓${NC}"
|
|
40
48
|
return 0
|
|
41
49
|
else
|
|
42
50
|
echo -e "${YELLOW}[bluera-knowledge] Playwright browser install failed.${NC}"
|
|
43
|
-
echo -e "${YELLOW}Manual fix:
|
|
51
|
+
echo -e "${YELLOW}Manual fix: $PYTHON_CMD -m playwright install chromium${NC}"
|
|
44
52
|
return 1
|
|
45
53
|
fi
|
|
46
54
|
}
|
|
@@ -68,7 +76,7 @@ if [ ! -d "$PLUGIN_ROOT/node_modules" ]; then
|
|
|
68
76
|
fi
|
|
69
77
|
|
|
70
78
|
# =====================
|
|
71
|
-
# Python Dependencies
|
|
79
|
+
# Python Dependencies (using venv)
|
|
72
80
|
# =====================
|
|
73
81
|
|
|
74
82
|
# Check if Python3 is installed
|
|
@@ -81,75 +89,56 @@ fi
|
|
|
81
89
|
|
|
82
90
|
# Check Python version (require 3.8+)
|
|
83
91
|
python_version=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
|
|
84
|
-
required_version="3.8"
|
|
85
92
|
|
|
86
93
|
if ! python3 -c "import sys; exit(0 if sys.version_info >= (3, 8) else 1)"; then
|
|
87
94
|
echo -e "${YELLOW}[bluera-knowledge] Python ${python_version} detected. Python 3.8+ recommended for crawl4ai${NC}"
|
|
88
95
|
fi
|
|
89
96
|
|
|
90
|
-
# Check if crawl4ai
|
|
91
|
-
if
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
echo -e "${GREEN}[bluera-knowledge] crawl4ai ${crawl4ai_version} is installed ✓${NC}"
|
|
97
|
+
# Check if venv exists and has crawl4ai installed
|
|
98
|
+
if [ -f "$VENV_PYTHON" ] && "$VENV_PYTHON" -c "import crawl4ai" 2>/dev/null; then
|
|
99
|
+
crawl4ai_version=$("$VENV_PYTHON" -c "import crawl4ai; print(crawl4ai.__version__)" 2>/dev/null || echo "unknown")
|
|
100
|
+
echo -e "${GREEN}[bluera-knowledge] crawl4ai ${crawl4ai_version} ready (venv) ✓${NC}"
|
|
95
101
|
# Ensure Playwright browser is installed for headless crawling
|
|
96
|
-
install_playwright_browser
|
|
102
|
+
install_playwright_browser "$VENV_PYTHON"
|
|
97
103
|
exit 0
|
|
98
104
|
fi
|
|
99
105
|
|
|
100
|
-
# crawl4ai not installed - attempt installation
|
|
101
|
-
echo -e "${YELLOW}[bluera-knowledge] crawl4ai not found. Web crawling features will be unavailable.${NC}"
|
|
102
|
-
echo ""
|
|
103
|
-
echo -e "${YELLOW}To enable web crawling, install crawl4ai:${NC}"
|
|
104
|
-
echo -e " ${GREEN}pip install crawl4ai${NC}"
|
|
105
|
-
echo ""
|
|
106
|
-
|
|
107
106
|
# Check if auto-install is disabled
|
|
108
107
|
if [ "${BK_SKIP_AUTO_INSTALL:-}" = "1" ]; then
|
|
109
108
|
echo -e "${YELLOW}[bluera-knowledge] Auto-install disabled (BK_SKIP_AUTO_INSTALL=1)${NC}"
|
|
110
|
-
echo -e "${YELLOW}
|
|
109
|
+
echo -e "${YELLOW}To enable web crawling, create venv manually:${NC}"
|
|
110
|
+
echo -e " ${GREEN}python3 -m venv $VENV_DIR${NC}"
|
|
111
|
+
echo -e " ${GREEN}$VENV_PIP install crawl4ai${NC}"
|
|
112
|
+
echo -e " ${GREEN}$VENV_PYTHON -m playwright install chromium${NC}"
|
|
111
113
|
exit 0
|
|
112
114
|
fi
|
|
113
115
|
|
|
114
|
-
#
|
|
115
|
-
if
|
|
116
|
-
echo -e "${YELLOW}[bluera-knowledge]
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
# Try to install using pip3 or pip
|
|
120
|
-
if command -v pip3 &> /dev/null; then
|
|
121
|
-
PIP_CMD="pip3"
|
|
116
|
+
# Create venv if it doesn't exist
|
|
117
|
+
if [ ! -d "$VENV_DIR" ]; then
|
|
118
|
+
echo -e "${YELLOW}[bluera-knowledge] Creating Python virtual environment...${NC}"
|
|
119
|
+
if python3 -m venv "$VENV_DIR" 2>/dev/null; then
|
|
120
|
+
echo -e "${GREEN}[bluera-knowledge] Virtual environment created ✓${NC}"
|
|
122
121
|
else
|
|
123
|
-
|
|
122
|
+
echo -e "${RED}[bluera-knowledge] Failed to create virtual environment${NC}"
|
|
123
|
+
echo -e "${YELLOW}Manual fix: python3 -m venv $VENV_DIR${NC}"
|
|
124
|
+
exit 0
|
|
124
125
|
fi
|
|
126
|
+
fi
|
|
125
127
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
# Fallback: try without --break-system-packages for older Python
|
|
136
|
-
if $PIP_CMD install --quiet --user crawl4ai 2>/dev/null; then
|
|
137
|
-
echo -e "${GREEN}[bluera-knowledge] Successfully installed crawl4ai ✓${NC}"
|
|
138
|
-
crawl4ai_version=$(python3 -c "import crawl4ai; print(crawl4ai.__version__)" 2>/dev/null || echo "installed")
|
|
139
|
-
echo -e "${GREEN}[bluera-knowledge] crawl4ai ${crawl4ai_version} ready${NC}"
|
|
140
|
-
# Install Playwright browser for headless crawling
|
|
141
|
-
install_playwright_browser
|
|
142
|
-
else
|
|
143
|
-
echo -e "${RED}[bluera-knowledge] Auto-installation failed${NC}"
|
|
144
|
-
echo ""
|
|
145
|
-
echo -e "${YELLOW}For Python 3.11+ (externally-managed), install manually:${NC}"
|
|
146
|
-
echo -e " ${GREEN}pip install --break-system-packages crawl4ai${NC}"
|
|
147
|
-
echo -e "${YELLOW}Or use a virtual environment:${NC}"
|
|
148
|
-
echo -e " ${GREEN}python3 -m venv venv && source venv/bin/activate && pip install crawl4ai${NC}"
|
|
149
|
-
fi
|
|
150
|
-
fi
|
|
128
|
+
# Install crawl4ai into venv
|
|
129
|
+
echo -e "${YELLOW}[bluera-knowledge] Installing crawl4ai into virtual environment...${NC}"
|
|
130
|
+
echo -e "${YELLOW}(Set BK_SKIP_AUTO_INSTALL=1 to disable auto-install)${NC}"
|
|
131
|
+
|
|
132
|
+
if "$VENV_PIP" install --quiet crawl4ai 2>/dev/null; then
|
|
133
|
+
crawl4ai_version=$("$VENV_PYTHON" -c "import crawl4ai; print(crawl4ai.__version__)" 2>/dev/null || echo "installed")
|
|
134
|
+
echo -e "${GREEN}[bluera-knowledge] crawl4ai ${crawl4ai_version} installed (venv) ✓${NC}"
|
|
135
|
+
# Install Playwright browser for headless crawling
|
|
136
|
+
install_playwright_browser "$VENV_PYTHON"
|
|
151
137
|
else
|
|
152
|
-
echo -e "${
|
|
138
|
+
echo -e "${RED}[bluera-knowledge] Failed to install crawl4ai${NC}"
|
|
139
|
+
echo -e "${YELLOW}Manual fix:${NC}"
|
|
140
|
+
echo -e " ${GREEN}$VENV_PIP install crawl4ai${NC}"
|
|
141
|
+
echo -e " ${GREEN}$VENV_PYTHON -m playwright install chromium${NC}"
|
|
153
142
|
fi
|
|
154
143
|
|
|
155
144
|
# Always exit 0 to not block the session
|
package/package.json
CHANGED
package/src/crawl/bridge.test.ts
CHANGED
package/src/crawl/bridge.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from 'node:child_process';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
3
5
|
import { createInterface, type Interface as ReadlineInterface } from 'node:readline';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
4
7
|
import { ZodError } from 'zod';
|
|
5
8
|
import {
|
|
6
9
|
type CrawlResult,
|
|
@@ -37,9 +40,39 @@ export class PythonBridge {
|
|
|
37
40
|
start(): Promise<void> {
|
|
38
41
|
if (this.process) return Promise.resolve();
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
// Compute absolute path to Python worker using import.meta.url
|
|
44
|
+
// This works both in development (src/) and production (dist/)
|
|
45
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
46
|
+
const isProduction = currentFilePath.includes('/dist/');
|
|
47
|
+
|
|
48
|
+
let pythonWorkerPath: string;
|
|
49
|
+
let pythonPath: string;
|
|
50
|
+
|
|
51
|
+
if (isProduction) {
|
|
52
|
+
// Production: Find dist dir and go to sibling python/ directory
|
|
53
|
+
const distIndex = currentFilePath.indexOf('/dist/');
|
|
54
|
+
const pluginRoot = currentFilePath.substring(0, distIndex);
|
|
55
|
+
pythonWorkerPath = path.join(pluginRoot, 'python', 'crawl_worker.py');
|
|
56
|
+
|
|
57
|
+
// Use venv python if available (installed by check-dependencies.sh hook)
|
|
58
|
+
const venvPython = path.join(pluginRoot, '.venv', 'bin', 'python3');
|
|
59
|
+
pythonPath = existsSync(venvPython) ? venvPython : 'python3';
|
|
60
|
+
} else {
|
|
61
|
+
// Development: Go up from src/crawl to find python/
|
|
62
|
+
const srcDir = path.dirname(path.dirname(currentFilePath));
|
|
63
|
+
const projectRoot = path.dirname(srcDir);
|
|
64
|
+
pythonWorkerPath = path.join(projectRoot, 'python', 'crawl_worker.py');
|
|
65
|
+
|
|
66
|
+
// Development: Use system python (user manages their own environment)
|
|
67
|
+
pythonPath = 'python3';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
logger.debug(
|
|
71
|
+
{ pythonWorkerPath, pythonPath, currentFilePath, isProduction },
|
|
72
|
+
'Starting Python bridge process'
|
|
73
|
+
);
|
|
41
74
|
|
|
42
|
-
this.process = spawn(
|
|
75
|
+
this.process = spawn(pythonPath, [pythonWorkerPath], {
|
|
43
76
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
44
77
|
});
|
|
45
78
|
|
|
@@ -10,12 +10,14 @@ import { metaCommands } from './meta.commands.js';
|
|
|
10
10
|
import { commandRegistry } from './registry.js';
|
|
11
11
|
import { storeCommands } from './store.commands.js';
|
|
12
12
|
import { syncCommands } from './sync.commands.js';
|
|
13
|
+
import { uninstallCommands } from './uninstall.commands.js';
|
|
13
14
|
|
|
14
15
|
// Register all commands
|
|
15
16
|
commandRegistry.registerAll(storeCommands);
|
|
16
17
|
commandRegistry.registerAll(jobCommands);
|
|
17
18
|
commandRegistry.registerAll(metaCommands);
|
|
18
19
|
commandRegistry.registerAll(syncCommands);
|
|
20
|
+
commandRegistry.registerAll(uninstallCommands);
|
|
19
21
|
|
|
20
22
|
// Re-export for convenience
|
|
21
23
|
export { commandRegistry, executeCommand, generateHelp } from './registry.js';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { uninstallCommands } from './uninstall.commands.js';
|
|
3
|
+
|
|
4
|
+
describe('uninstall.commands', () => {
|
|
5
|
+
it('should export uninstall command', () => {
|
|
6
|
+
expect(uninstallCommands).toHaveLength(1);
|
|
7
|
+
expect(uninstallCommands[0].name).toBe('uninstall');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should have correct command definition', () => {
|
|
11
|
+
const cmd = uninstallCommands[0];
|
|
12
|
+
expect(cmd.description).toContain('Remove Bluera Knowledge data');
|
|
13
|
+
expect(cmd.argsSchema).toBeDefined();
|
|
14
|
+
expect(cmd.handler).toBeTypeOf('function');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should have valid schema for global and keepDefinitions options', () => {
|
|
18
|
+
const cmd = uninstallCommands[0];
|
|
19
|
+
const schema = cmd.argsSchema;
|
|
20
|
+
|
|
21
|
+
// Validate the schema accepts expected args
|
|
22
|
+
const result = schema?.safeParse({
|
|
23
|
+
global: true,
|
|
24
|
+
keepDefinitions: false,
|
|
25
|
+
});
|
|
26
|
+
expect(result?.success).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should have optional args in schema', () => {
|
|
30
|
+
const cmd = uninstallCommands[0];
|
|
31
|
+
const schema = cmd.argsSchema;
|
|
32
|
+
|
|
33
|
+
// Empty args should be valid
|
|
34
|
+
const result = schema?.safeParse({});
|
|
35
|
+
expect(result?.success).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { handleUninstall } from '../handlers/uninstall.handler.js';
|
|
3
|
+
import type { CommandDefinition } from './registry.js';
|
|
4
|
+
import type { UninstallArgs } from '../handlers/uninstall.handler.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Uninstall commands for removing Bluera Knowledge data
|
|
8
|
+
*
|
|
9
|
+
* Provides cleanup functionality for testing fresh installs or removing the plugin.
|
|
10
|
+
*/
|
|
11
|
+
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
|
12
|
+
export const uninstallCommands: CommandDefinition[] = [
|
|
13
|
+
{
|
|
14
|
+
name: 'uninstall',
|
|
15
|
+
description: 'Remove Bluera Knowledge data from project (and optionally global data)',
|
|
16
|
+
argsSchema: z.object({
|
|
17
|
+
global: z
|
|
18
|
+
.boolean()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('Also remove global data (~/.local/share/bluera-knowledge)'),
|
|
21
|
+
keepDefinitions: z
|
|
22
|
+
.boolean()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Keep stores.config.json for team sharing (default: true)'),
|
|
25
|
+
}),
|
|
26
|
+
handler: (args, context) => handleUninstall(args as unknown as UninstallArgs, context),
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
/* eslint-enable @typescript-eslint/consistent-type-assertions */
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { handleUninstall } from './uninstall.handler.js';
|
|
6
|
+
import type { HandlerContext } from '../types.js';
|
|
7
|
+
import type { UninstallArgs } from './uninstall.handler.js';
|
|
8
|
+
|
|
9
|
+
// Mock os module to control homedir for testing global data deletion
|
|
10
|
+
vi.mock('node:os', async () => {
|
|
11
|
+
const actual = await vi.importActual<typeof import('node:os')>('node:os');
|
|
12
|
+
return {
|
|
13
|
+
...actual,
|
|
14
|
+
homedir: vi.fn(() => actual.homedir()),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('uninstall.handler', () => {
|
|
19
|
+
let tempDir: string;
|
|
20
|
+
let projectRoot: string;
|
|
21
|
+
let mockContext: HandlerContext;
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
tempDir = mkdtempSync(join(tmpdir(), 'uninstall-handler-test-'));
|
|
25
|
+
projectRoot = tempDir;
|
|
26
|
+
|
|
27
|
+
// Create minimal mock context - uninstall handler doesn't use services
|
|
28
|
+
mockContext = {
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
30
|
+
services: {} as HandlerContext['services'],
|
|
31
|
+
options: { projectRoot },
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
37
|
+
vi.restoreAllMocks();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('handleUninstall', () => {
|
|
41
|
+
it('should delete project data when it exists', async () => {
|
|
42
|
+
// Create project data structure
|
|
43
|
+
const bkDir = join(projectRoot, '.bluera', 'bluera-knowledge');
|
|
44
|
+
const dataDir = join(bkDir, 'data');
|
|
45
|
+
mkdirSync(dataDir, { recursive: true });
|
|
46
|
+
writeFileSync(join(bkDir, 'config.json'), '{}');
|
|
47
|
+
writeFileSync(join(dataDir, 'stores.json'), '[]');
|
|
48
|
+
|
|
49
|
+
const args: UninstallArgs = {};
|
|
50
|
+
const result = await handleUninstall(args, mockContext);
|
|
51
|
+
|
|
52
|
+
// Verify deletion
|
|
53
|
+
expect(existsSync(join(bkDir, 'data'))).toBe(false);
|
|
54
|
+
expect(existsSync(join(bkDir, 'config.json'))).toBe(false);
|
|
55
|
+
|
|
56
|
+
// Verify response
|
|
57
|
+
expect(result.content[0].type).toBe('text');
|
|
58
|
+
expect(result.content[0].text).toContain('Deleted:');
|
|
59
|
+
expect(result.content[0].text).toContain('plugin cache');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should preserve stores.config.json by default', async () => {
|
|
63
|
+
// Create project data structure
|
|
64
|
+
const bkDir = join(projectRoot, '.bluera', 'bluera-knowledge');
|
|
65
|
+
const dataDir = join(bkDir, 'data');
|
|
66
|
+
mkdirSync(dataDir, { recursive: true });
|
|
67
|
+
writeFileSync(join(bkDir, 'config.json'), '{}');
|
|
68
|
+
writeFileSync(join(bkDir, 'stores.config.json'), '{"stores":[]}');
|
|
69
|
+
writeFileSync(join(dataDir, 'stores.json'), '[]');
|
|
70
|
+
|
|
71
|
+
const args: UninstallArgs = { keepDefinitions: true };
|
|
72
|
+
const result = await handleUninstall(args, mockContext);
|
|
73
|
+
|
|
74
|
+
// Verify stores.config.json is preserved
|
|
75
|
+
expect(existsSync(join(bkDir, 'stores.config.json'))).toBe(true);
|
|
76
|
+
expect(existsSync(join(bkDir, 'data'))).toBe(false);
|
|
77
|
+
expect(existsSync(join(bkDir, 'config.json'))).toBe(false);
|
|
78
|
+
|
|
79
|
+
// Verify response mentions preserved files
|
|
80
|
+
expect(result.content[0].text).toContain('Preserved:');
|
|
81
|
+
expect(result.content[0].text).toContain('stores.config.json');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should delete stores.config.json when keepDefinitions is false', async () => {
|
|
85
|
+
// Create project data structure
|
|
86
|
+
const bkDir = join(projectRoot, '.bluera', 'bluera-knowledge');
|
|
87
|
+
mkdirSync(bkDir, { recursive: true });
|
|
88
|
+
writeFileSync(join(bkDir, 'stores.config.json'), '{"stores":[]}');
|
|
89
|
+
|
|
90
|
+
const args: UninstallArgs = { keepDefinitions: false };
|
|
91
|
+
const result = await handleUninstall(args, mockContext);
|
|
92
|
+
|
|
93
|
+
// Verify entire directory is gone
|
|
94
|
+
expect(existsSync(bkDir)).toBe(false);
|
|
95
|
+
|
|
96
|
+
// Verify response
|
|
97
|
+
expect(result.content[0].text).toContain('Deleted:');
|
|
98
|
+
expect(result.content[0].text).not.toContain('Preserved:');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should handle missing project data gracefully', async () => {
|
|
102
|
+
const args: UninstallArgs = {};
|
|
103
|
+
const result = await handleUninstall(args, mockContext);
|
|
104
|
+
|
|
105
|
+
// Verify response indicates nothing to delete
|
|
106
|
+
expect(result.content[0].text).toContain('No data found to delete');
|
|
107
|
+
expect(result.content[0].text).toContain('plugin cache');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should always include plugin cache instructions', async () => {
|
|
111
|
+
const args: UninstallArgs = {};
|
|
112
|
+
const result = await handleUninstall(args, mockContext);
|
|
113
|
+
|
|
114
|
+
expect(result.content[0].text).toContain('To fully uninstall (clear plugin cache):');
|
|
115
|
+
expect(result.content[0].text).toContain('~/.claude/plugins/cache/bluera-knowledge-*');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should mention venv is cleaned with plugin cache', async () => {
|
|
119
|
+
const args: UninstallArgs = {};
|
|
120
|
+
const result = await handleUninstall(args, mockContext);
|
|
121
|
+
|
|
122
|
+
expect(result.content[0].text).toContain('Python venv');
|
|
123
|
+
expect(result.content[0].text).toContain('all dependencies');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should handle subdirectories in data folder', async () => {
|
|
127
|
+
// Create nested structure
|
|
128
|
+
const bkDir = join(projectRoot, '.bluera', 'bluera-knowledge');
|
|
129
|
+
const reposDir = join(bkDir, 'data', 'repos', 'test-repo');
|
|
130
|
+
mkdirSync(reposDir, { recursive: true });
|
|
131
|
+
writeFileSync(join(reposDir, 'file.txt'), 'test');
|
|
132
|
+
|
|
133
|
+
const args: UninstallArgs = {};
|
|
134
|
+
await handleUninstall(args, mockContext);
|
|
135
|
+
|
|
136
|
+
// Verify everything is deleted
|
|
137
|
+
expect(existsSync(join(bkDir, 'data'))).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should delete global data when global flag is true', async () => {
|
|
141
|
+
// Mock homedir to return our temp directory
|
|
142
|
+
const { homedir } = await import('node:os');
|
|
143
|
+
vi.mocked(homedir).mockReturnValue(tempDir);
|
|
144
|
+
|
|
145
|
+
// Create fake global data directory
|
|
146
|
+
const globalDir = join(tempDir, '.local', 'share', 'bluera-knowledge');
|
|
147
|
+
mkdirSync(globalDir, { recursive: true });
|
|
148
|
+
writeFileSync(join(globalDir, 'jobs.json'), '[]');
|
|
149
|
+
|
|
150
|
+
const args: UninstallArgs = { global: true };
|
|
151
|
+
const result = await handleUninstall(args, mockContext);
|
|
152
|
+
|
|
153
|
+
// Verify global data was deleted
|
|
154
|
+
expect(existsSync(globalDir)).toBe(false);
|
|
155
|
+
expect(result.content[0].text).toContain('Deleted:');
|
|
156
|
+
expect(result.content[0].text).toContain('.local/share/bluera-knowledge');
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should handle global flag when global data does not exist', async () => {
|
|
160
|
+
// Mock homedir to return our temp directory (no global data exists)
|
|
161
|
+
const { homedir } = await import('node:os');
|
|
162
|
+
vi.mocked(homedir).mockReturnValue(tempDir);
|
|
163
|
+
|
|
164
|
+
const args: UninstallArgs = { global: true };
|
|
165
|
+
const result = await handleUninstall(args, mockContext);
|
|
166
|
+
|
|
167
|
+
// The handler should still complete without errors
|
|
168
|
+
expect(result.content[0].text).toContain('plugin cache');
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should report global data deletion in result', async () => {
|
|
172
|
+
// Mock homedir to return our temp directory
|
|
173
|
+
const { homedir } = await import('node:os');
|
|
174
|
+
vi.mocked(homedir).mockReturnValue(tempDir);
|
|
175
|
+
|
|
176
|
+
// Create both project and global data
|
|
177
|
+
const bkDir = join(projectRoot, '.bluera', 'bluera-knowledge');
|
|
178
|
+
mkdirSync(bkDir, { recursive: true });
|
|
179
|
+
writeFileSync(join(bkDir, 'config.json'), '{}');
|
|
180
|
+
|
|
181
|
+
const globalDir = join(tempDir, '.local', 'share', 'bluera-knowledge');
|
|
182
|
+
mkdirSync(globalDir, { recursive: true });
|
|
183
|
+
writeFileSync(join(globalDir, 'jobs.json'), '[]');
|
|
184
|
+
|
|
185
|
+
const args: UninstallArgs = { global: true, keepDefinitions: false };
|
|
186
|
+
const result = await handleUninstall(args, mockContext);
|
|
187
|
+
|
|
188
|
+
// Verify both were deleted
|
|
189
|
+
expect(existsSync(bkDir)).toBe(false);
|
|
190
|
+
expect(existsSync(globalDir)).toBe(false);
|
|
191
|
+
expect(result.content[0].text).toContain('Deleted:');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
});
|