bluera-knowledge 0.16.6 → 0.17.1
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/CHANGELOG.md +19 -0
- package/dist/{chunk-OMC3RAZT.js → chunk-RAXRD23K.js} +2 -2
- package/dist/{chunk-WYZQUKUD.js → chunk-VKTVMW45.js} +28 -18
- package/dist/chunk-VKTVMW45.js.map +1 -0
- package/dist/{chunk-3EQRQOXD.js → chunk-ZGEQCLOZ.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/mcp/bootstrap.js +19 -2
- package/dist/mcp/bootstrap.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +2 -2
- package/dist/workers/background-worker-cli.js +2 -2
- package/package.json +1 -5
- package/.claude-plugin/plugin.json +0 -9
- package/commands/add-folder.md +0 -48
- package/commands/add-repo.md +0 -50
- package/commands/cancel.md +0 -63
- package/commands/check-status.md +0 -78
- package/commands/crawl.md +0 -54
- package/commands/index.md +0 -48
- package/commands/remove-store.md +0 -52
- package/commands/search.md +0 -86
- package/commands/search.sh +0 -63
- package/commands/skill-activation.md +0 -130
- package/commands/stores.md +0 -54
- package/commands/suggest.md +0 -82
- package/commands/sync.md +0 -96
- package/commands/test-plugin.md +0 -408
- package/commands/uninstall.md +0 -65
- package/dist/chunk-WYZQUKUD.js.map +0 -1
- package/hooks/check-dependencies.sh +0 -145
- package/hooks/format-search-results.py +0 -132
- package/hooks/hooks.json +0 -54
- package/hooks/job-status-hook.sh +0 -51
- package/hooks/posttooluse-bk-reminder.py +0 -166
- package/hooks/skill-activation.py +0 -194
- package/hooks/skill-rules.json +0 -122
- package/skills/advanced-workflows/SKILL.md +0 -273
- package/skills/knowledge-search/SKILL.md +0 -110
- package/skills/search-optimization/SKILL.md +0 -396
- package/skills/store-lifecycle/SKILL.md +0 -470
- package/skills/when-to-query/SKILL.md +0 -160
- /package/dist/{chunk-OMC3RAZT.js.map → chunk-RAXRD23K.js.map} +0 -0
- /package/dist/{chunk-3EQRQOXD.js.map → chunk-ZGEQCLOZ.js.map} +0 -0
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Bluera Knowledge Plugin - Dependency Checker
|
|
3
|
-
# Automatically checks and installs dependencies for the plugin
|
|
4
|
-
#
|
|
5
|
-
# Environment variables:
|
|
6
|
-
# BK_SKIP_AUTO_INSTALL=1 - Skip automatic installation of crawl4ai
|
|
7
|
-
# Set this if you prefer to manage Python packages manually
|
|
8
|
-
#
|
|
9
|
-
# What this script auto-installs (if missing):
|
|
10
|
-
# - Node.js dependencies (via bun or npm, from package.json)
|
|
11
|
-
# - Python virtual environment with crawl4ai (isolated from system Python)
|
|
12
|
-
# - Playwright Chromium browser (via playwright CLI, for headless crawling)
|
|
13
|
-
|
|
14
|
-
set -e
|
|
15
|
-
|
|
16
|
-
# Get the plugin root directory
|
|
17
|
-
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(dirname "$(dirname "$0")")}"
|
|
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
|
-
|
|
24
|
-
# Colors for output
|
|
25
|
-
RED='\033[0;31m'
|
|
26
|
-
GREEN='\033[0;32m'
|
|
27
|
-
YELLOW='\033[1;33m'
|
|
28
|
-
NC='\033[0m' # No Color
|
|
29
|
-
|
|
30
|
-
# =====================
|
|
31
|
-
# Helper Functions
|
|
32
|
-
# =====================
|
|
33
|
-
|
|
34
|
-
# Install Playwright browser using specified python
|
|
35
|
-
# Args: $1 = python path to use
|
|
36
|
-
install_playwright_browser() {
|
|
37
|
-
local PYTHON_CMD="${1:-python3}"
|
|
38
|
-
|
|
39
|
-
# Check if Playwright Chromium is already installed by testing if browser can be launched
|
|
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
|
|
41
|
-
echo -e "${GREEN}[bluera-knowledge] Playwright Chromium ready ✓${NC}"
|
|
42
|
-
return 0
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
echo -e "${YELLOW}[bluera-knowledge] Installing Playwright browser (one-time setup)...${NC}"
|
|
46
|
-
if "$PYTHON_CMD" -m playwright install chromium 2>/dev/null; then
|
|
47
|
-
echo -e "${GREEN}[bluera-knowledge] Playwright Chromium installed ✓${NC}"
|
|
48
|
-
return 0
|
|
49
|
-
else
|
|
50
|
-
echo -e "${YELLOW}[bluera-knowledge] Playwright browser install failed.${NC}"
|
|
51
|
-
echo -e "${YELLOW}Manual fix: $PYTHON_CMD -m playwright install chromium${NC}"
|
|
52
|
-
return 1
|
|
53
|
-
fi
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
# =====================
|
|
57
|
-
# Node.js Dependencies
|
|
58
|
-
# =====================
|
|
59
|
-
|
|
60
|
-
# Check if node_modules exists
|
|
61
|
-
if [ ! -d "$PLUGIN_ROOT/node_modules" ]; then
|
|
62
|
-
echo -e "${YELLOW}[bluera-knowledge] Installing Node.js dependencies...${NC}"
|
|
63
|
-
|
|
64
|
-
# Try bun first (faster), fall back to npm
|
|
65
|
-
if command -v bun &> /dev/null; then
|
|
66
|
-
(cd "$PLUGIN_ROOT" && bun install --frozen-lockfile 2>/dev/null) && \
|
|
67
|
-
echo -e "${GREEN}[bluera-knowledge] Node.js dependencies installed ✓${NC}" || \
|
|
68
|
-
echo -e "${RED}[bluera-knowledge] Failed to install Node.js dependencies${NC}"
|
|
69
|
-
elif command -v npm &> /dev/null; then
|
|
70
|
-
(cd "$PLUGIN_ROOT" && npm ci --silent 2>/dev/null) && \
|
|
71
|
-
echo -e "${GREEN}[bluera-knowledge] Node.js dependencies installed ✓${NC}" || \
|
|
72
|
-
echo -e "${RED}[bluera-knowledge] Failed to install Node.js dependencies${NC}"
|
|
73
|
-
else
|
|
74
|
-
echo -e "${RED}[bluera-knowledge] Neither bun nor npm found. Please install Node.js dependencies manually.${NC}"
|
|
75
|
-
fi
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
|
-
# =====================
|
|
79
|
-
# Python Dependencies (using venv)
|
|
80
|
-
# =====================
|
|
81
|
-
|
|
82
|
-
# Check if Python3 is installed
|
|
83
|
-
if ! command -v python3 &> /dev/null; then
|
|
84
|
-
echo -e "${RED}[bluera-knowledge] Python3 is not installed${NC}"
|
|
85
|
-
echo -e "${YELLOW}Web crawling features require Python 3.x${NC}"
|
|
86
|
-
echo -e "${YELLOW}Install Python3: https://www.python.org/downloads/${NC}"
|
|
87
|
-
exit 0 # Don't block the session, just warn
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
# Check Python version (require 3.8+)
|
|
91
|
-
python_version=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
|
|
92
|
-
|
|
93
|
-
if ! python3 -c "import sys; exit(0 if sys.version_info >= (3, 8) else 1)"; then
|
|
94
|
-
echo -e "${YELLOW}[bluera-knowledge] Python ${python_version} detected. Python 3.8+ recommended for crawl4ai${NC}"
|
|
95
|
-
fi
|
|
96
|
-
|
|
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}"
|
|
101
|
-
# Ensure Playwright browser is installed for headless crawling
|
|
102
|
-
install_playwright_browser "$VENV_PYTHON"
|
|
103
|
-
exit 0
|
|
104
|
-
fi
|
|
105
|
-
|
|
106
|
-
# Check if auto-install is disabled
|
|
107
|
-
if [ "${BK_SKIP_AUTO_INSTALL:-}" = "1" ]; then
|
|
108
|
-
echo -e "${YELLOW}[bluera-knowledge] Auto-install disabled (BK_SKIP_AUTO_INSTALL=1)${NC}"
|
|
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}"
|
|
113
|
-
exit 0
|
|
114
|
-
fi
|
|
115
|
-
|
|
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}"
|
|
121
|
-
else
|
|
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
|
|
125
|
-
fi
|
|
126
|
-
fi
|
|
127
|
-
|
|
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"
|
|
137
|
-
else
|
|
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}"
|
|
142
|
-
fi
|
|
143
|
-
|
|
144
|
-
# Always exit 0 to not block the session
|
|
145
|
-
exit 0
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
PostToolUse hook to format mcp__bluera-knowledge__search results
|
|
4
|
-
into a fixed-width table with deterministic output.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import json
|
|
8
|
-
import sys
|
|
9
|
-
import os
|
|
10
|
-
|
|
11
|
-
# Column widths (content only, not including | separators)
|
|
12
|
-
SCORE_WIDTH = 6 # Right-aligned: " 1.00 "
|
|
13
|
-
STORE_WIDTH = 12 # Left-aligned: "store "
|
|
14
|
-
FILE_WIDTH = 45 # Left-aligned: "path/to/file..."
|
|
15
|
-
PURPOSE_WIDTH = 48 # Left-aligned: "Purpose text..."
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def truncate(text: str, max_len: int) -> str:
|
|
19
|
-
"""Truncate text with ellipsis if needed."""
|
|
20
|
-
if len(text) <= max_len:
|
|
21
|
-
return text
|
|
22
|
-
return text[:max_len - 3] + "..."
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def format_score(score: float) -> str:
|
|
26
|
-
"""Format score as right-aligned fixed-width string."""
|
|
27
|
-
return f"{score:5.2f}".rjust(SCORE_WIDTH)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def format_store(store_name: str) -> str:
|
|
31
|
-
"""Format store name as left-aligned fixed-width string."""
|
|
32
|
-
truncated = truncate(store_name, STORE_WIDTH)
|
|
33
|
-
return truncated.ljust(STORE_WIDTH)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def format_file(location: str, repo_root: str) -> str:
|
|
37
|
-
"""Format file path as left-aligned fixed-width string."""
|
|
38
|
-
# Strip repo root prefix
|
|
39
|
-
if repo_root and location.startswith(repo_root):
|
|
40
|
-
path = location[len(repo_root):].lstrip("/")
|
|
41
|
-
else:
|
|
42
|
-
path = location
|
|
43
|
-
truncated = truncate(path, FILE_WIDTH)
|
|
44
|
-
return truncated.ljust(FILE_WIDTH)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def format_purpose(purpose: str) -> str:
|
|
48
|
-
"""Format purpose as left-aligned fixed-width string."""
|
|
49
|
-
# Clean up purpose text
|
|
50
|
-
clean = purpose.replace("\n", " ").strip()
|
|
51
|
-
truncated = truncate(clean, PURPOSE_WIDTH)
|
|
52
|
-
return truncated.ljust(PURPOSE_WIDTH)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def format_table(results: list, query: str) -> str:
|
|
56
|
-
"""Format search results into a fixed-width markdown table."""
|
|
57
|
-
lines = []
|
|
58
|
-
|
|
59
|
-
# Header
|
|
60
|
-
lines.append(f"## Search Results for \"{query}\"")
|
|
61
|
-
lines.append("")
|
|
62
|
-
|
|
63
|
-
if not results:
|
|
64
|
-
lines.append(f"No results found for \"{query}\"")
|
|
65
|
-
lines.append("")
|
|
66
|
-
lines.append("Try:")
|
|
67
|
-
lines.append("- Broadening your search terms")
|
|
68
|
-
lines.append("- Checking if the relevant stores are indexed")
|
|
69
|
-
lines.append("- Using /bluera-knowledge:stores to see available stores")
|
|
70
|
-
return "\n".join(lines)
|
|
71
|
-
|
|
72
|
-
# Table header
|
|
73
|
-
header = f"| {'Score'.rjust(SCORE_WIDTH)} | {'Store'.ljust(STORE_WIDTH)} | {'File'.ljust(FILE_WIDTH)} | {'Purpose'.ljust(PURPOSE_WIDTH)} |"
|
|
74
|
-
# Separator: Score is right-aligned (colon at end), others are left-aligned
|
|
75
|
-
# The +2 accounts for the spaces around content, -1 for the colon on Score column
|
|
76
|
-
separator = f"|{'-' * (SCORE_WIDTH + 1)}:|{'-' * (STORE_WIDTH + 2)}|{'-' * (FILE_WIDTH + 2)}|{'-' * (PURPOSE_WIDTH + 2)}|"
|
|
77
|
-
|
|
78
|
-
lines.append(header)
|
|
79
|
-
lines.append(separator)
|
|
80
|
-
|
|
81
|
-
# Data rows
|
|
82
|
-
for result in results:
|
|
83
|
-
score = result.get("score", 0)
|
|
84
|
-
summary = result.get("summary", {})
|
|
85
|
-
store_name = summary.get("storeName", "unknown")
|
|
86
|
-
location = summary.get("location", "")
|
|
87
|
-
repo_root = summary.get("repoRoot", "")
|
|
88
|
-
purpose = summary.get("purpose", "")
|
|
89
|
-
|
|
90
|
-
row = f"| {format_score(score)} | {format_store(store_name)} | {format_file(location, repo_root)} | {format_purpose(purpose)} |"
|
|
91
|
-
lines.append(row)
|
|
92
|
-
|
|
93
|
-
lines.append("")
|
|
94
|
-
lines.append(f"**Found**: {len(results)} results")
|
|
95
|
-
|
|
96
|
-
return "\n".join(lines)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def main():
|
|
100
|
-
try:
|
|
101
|
-
# Read hook input from stdin
|
|
102
|
-
input_data = json.load(sys.stdin)
|
|
103
|
-
|
|
104
|
-
tool_name = input_data.get("tool_name", "")
|
|
105
|
-
tool_input = input_data.get("tool_input", {})
|
|
106
|
-
tool_result = input_data.get("tool_result", {})
|
|
107
|
-
|
|
108
|
-
# Only process search results
|
|
109
|
-
if tool_name != "mcp__bluera-knowledge__search":
|
|
110
|
-
sys.exit(0)
|
|
111
|
-
|
|
112
|
-
# Extract results and query
|
|
113
|
-
results = tool_result.get("results", [])
|
|
114
|
-
query = tool_input.get("query", "")
|
|
115
|
-
|
|
116
|
-
# Format the table
|
|
117
|
-
formatted = format_table(results, query)
|
|
118
|
-
|
|
119
|
-
# Output the formatted table
|
|
120
|
-
# For PostToolUse, stdout is shown in the transcript
|
|
121
|
-
print(formatted)
|
|
122
|
-
|
|
123
|
-
except json.JSONDecodeError as e:
|
|
124
|
-
print(f"Error parsing hook input: {e}", file=sys.stderr)
|
|
125
|
-
sys.exit(1)
|
|
126
|
-
except Exception as e:
|
|
127
|
-
print(f"Error formatting results: {e}", file=sys.stderr)
|
|
128
|
-
sys.exit(1)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if __name__ == "__main__":
|
|
132
|
-
main()
|
package/hooks/hooks.json
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"description": "bluera-knowledge plugin hooks - dependency checking, job monitoring, and BK suggestions",
|
|
3
|
-
"hooks": {
|
|
4
|
-
"PostToolUse": [
|
|
5
|
-
{
|
|
6
|
-
"matcher": "Grep",
|
|
7
|
-
"hooks": [
|
|
8
|
-
{
|
|
9
|
-
"type": "command",
|
|
10
|
-
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-bk-reminder.py",
|
|
11
|
-
"timeout": 3
|
|
12
|
-
}
|
|
13
|
-
]
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"matcher": "Read",
|
|
17
|
-
"hooks": [
|
|
18
|
-
{
|
|
19
|
-
"type": "command",
|
|
20
|
-
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/posttooluse-bk-reminder.py",
|
|
21
|
-
"timeout": 3
|
|
22
|
-
}
|
|
23
|
-
]
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
"SessionStart": [
|
|
27
|
-
{
|
|
28
|
-
"hooks": [
|
|
29
|
-
{
|
|
30
|
-
"type": "command",
|
|
31
|
-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/check-dependencies.sh",
|
|
32
|
-
"timeout": 30
|
|
33
|
-
}
|
|
34
|
-
]
|
|
35
|
-
}
|
|
36
|
-
],
|
|
37
|
-
"UserPromptSubmit": [
|
|
38
|
-
{
|
|
39
|
-
"hooks": [
|
|
40
|
-
{
|
|
41
|
-
"type": "command",
|
|
42
|
-
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/job-status-hook.sh",
|
|
43
|
-
"timeout": 2
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"type": "command",
|
|
47
|
-
"command": "python3 ${CLAUDE_PLUGIN_ROOT}/hooks/skill-activation.py",
|
|
48
|
-
"timeout": 2
|
|
49
|
-
}
|
|
50
|
-
]
|
|
51
|
-
}
|
|
52
|
-
]
|
|
53
|
-
}
|
|
54
|
-
}
|
package/hooks/job-status-hook.sh
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Show active jobs in context when user submits a prompt
|
|
3
|
-
#
|
|
4
|
-
# This hook runs on UserPromptSubmit events and injects
|
|
5
|
-
# information about active background jobs into the context.
|
|
6
|
-
|
|
7
|
-
JOBS_DIR="$HOME/.local/share/bluera-knowledge/jobs"
|
|
8
|
-
|
|
9
|
-
# Exit silently if jobs directory doesn't exist
|
|
10
|
-
if [ ! -d "$JOBS_DIR" ]; then
|
|
11
|
-
exit 0
|
|
12
|
-
fi
|
|
13
|
-
|
|
14
|
-
# Find active jobs (modified in last 60 minutes)
|
|
15
|
-
active_jobs=$(find "$JOBS_DIR" -name "*.json" -type f -not -name "*.pid" -mmin -60 2>/dev/null | while read -r file; do
|
|
16
|
-
# Skip if file doesn't exist or isn't readable
|
|
17
|
-
if [ ! -r "$file" ]; then
|
|
18
|
-
continue
|
|
19
|
-
fi
|
|
20
|
-
|
|
21
|
-
# Extract job details using jq (if available) or grep fallback
|
|
22
|
-
if command -v jq >/dev/null 2>&1; then
|
|
23
|
-
status=$(jq -r '.status' "$file" 2>/dev/null || echo "unknown")
|
|
24
|
-
if [ "$status" = "running" ] || [ "$status" = "pending" ]; then
|
|
25
|
-
job_id=$(basename "$file" .json)
|
|
26
|
-
type=$(jq -r '.type' "$file" 2>/dev/null || echo "unknown")
|
|
27
|
-
progress=$(jq -r '.progress' "$file" 2>/dev/null || echo "0")
|
|
28
|
-
message=$(jq -r '.message' "$file" 2>/dev/null || echo "No message")
|
|
29
|
-
echo "- $type job ($job_id): ${progress}% - $message"
|
|
30
|
-
fi
|
|
31
|
-
else
|
|
32
|
-
# Fallback using grep if jq not available
|
|
33
|
-
status=$(grep -o '"status"[[:space:]]*:[[:space:]]*"[^"]*"' "$file" | cut -d'"' -f4)
|
|
34
|
-
if [ "$status" = "running" ] || [ "$status" = "pending" ]; then
|
|
35
|
-
job_id=$(basename "$file" .json)
|
|
36
|
-
type=$(grep -o '"type"[[:space:]]*:[[:space:]]*"[^"]*"' "$file" | cut -d'"' -f4)
|
|
37
|
-
progress=$(grep -o '"progress"[[:space:]]*:[[:space:]]*[0-9.]*' "$file" | awk '{print $NF}')
|
|
38
|
-
message=$(grep -o '"message"[[:space:]]*:[[:space:]]*"[^"]*"' "$file" | cut -d'"' -f4)
|
|
39
|
-
echo "- $type job ($job_id): ${progress}% - $message"
|
|
40
|
-
fi
|
|
41
|
-
fi
|
|
42
|
-
done)
|
|
43
|
-
|
|
44
|
-
# Output active jobs if any found
|
|
45
|
-
if [ -n "$active_jobs" ]; then
|
|
46
|
-
echo ""
|
|
47
|
-
echo "Active background jobs:"
|
|
48
|
-
echo "$active_jobs"
|
|
49
|
-
echo ""
|
|
50
|
-
echo "Check status with: /bluera-knowledge:check-status"
|
|
51
|
-
fi
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
PostToolUse hook for bluera-knowledge plugin.
|
|
4
|
-
Fires after Claude reads/greps in dependency directories,
|
|
5
|
-
reminding to consider using BK for similar future queries.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import json
|
|
9
|
-
import re
|
|
10
|
-
import sys
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
# Fast string checks - if none of these are in the path, skip regex entirely
|
|
14
|
-
# This avoids regex overhead for the vast majority of file accesses
|
|
15
|
-
LIBRARY_QUICK_CHECKS = frozenset([
|
|
16
|
-
"node_modules",
|
|
17
|
-
"vendor",
|
|
18
|
-
"site-packages",
|
|
19
|
-
"venv",
|
|
20
|
-
"bower_components",
|
|
21
|
-
"packages",
|
|
22
|
-
".npm",
|
|
23
|
-
".cargo",
|
|
24
|
-
"go/pkg",
|
|
25
|
-
])
|
|
26
|
-
|
|
27
|
-
# Patterns indicating library/dependency code
|
|
28
|
-
LIBRARY_PATH_PATTERNS = [
|
|
29
|
-
r"node_modules/",
|
|
30
|
-
r"vendor/",
|
|
31
|
-
r"site-packages/",
|
|
32
|
-
r"\.venv/",
|
|
33
|
-
r"venv/",
|
|
34
|
-
r"bower_components/",
|
|
35
|
-
r"packages/.*/node_modules/",
|
|
36
|
-
r"\.npm/",
|
|
37
|
-
r"\.cargo/registry/",
|
|
38
|
-
r"go/pkg/mod/",
|
|
39
|
-
]
|
|
40
|
-
|
|
41
|
-
# Compile patterns for efficiency
|
|
42
|
-
LIBRARY_PATTERNS_RE = re.compile("|".join(LIBRARY_PATH_PATTERNS), re.IGNORECASE)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def quick_path_check(path: str) -> bool:
|
|
46
|
-
"""Fast check if path might contain library code. Avoids regex for most paths."""
|
|
47
|
-
path_lower = path.lower()
|
|
48
|
-
return any(keyword in path_lower for keyword in LIBRARY_QUICK_CHECKS)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def extract_library_name(path: str) -> str | None:
|
|
52
|
-
"""Extract library name from dependency path."""
|
|
53
|
-
# node_modules/package-name/...
|
|
54
|
-
match = re.search(r"node_modules/(@[^/]+/[^/]+|[^/]+)", path)
|
|
55
|
-
if match:
|
|
56
|
-
return match.group(1)
|
|
57
|
-
|
|
58
|
-
# site-packages/package_name/...
|
|
59
|
-
match = re.search(r"site-packages/([^/]+)", path)
|
|
60
|
-
if match:
|
|
61
|
-
return match.group(1)
|
|
62
|
-
|
|
63
|
-
# vendor/package/...
|
|
64
|
-
match = re.search(r"vendor/([^/]+)", path)
|
|
65
|
-
if match:
|
|
66
|
-
return match.group(1)
|
|
67
|
-
|
|
68
|
-
# .cargo/registry/.../package-name-version/...
|
|
69
|
-
match = re.search(r"\.cargo/registry/[^/]+/([^/]+)-\d", path)
|
|
70
|
-
if match:
|
|
71
|
-
return match.group(1)
|
|
72
|
-
|
|
73
|
-
# go/pkg/mod/package@version/...
|
|
74
|
-
match = re.search(r"go/pkg/mod/([^@]+)@", path)
|
|
75
|
-
if match:
|
|
76
|
-
return match.group(1)
|
|
77
|
-
|
|
78
|
-
return None
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def check_grep_tool(tool_input: dict[str, Any]) -> tuple[str | None, str | None]:
|
|
82
|
-
"""Check if Grep targeted library code. Returns (action, library_name)."""
|
|
83
|
-
path = tool_input.get("path", "")
|
|
84
|
-
|
|
85
|
-
# Fast path: skip regex if no library keywords present
|
|
86
|
-
if not path or not quick_path_check(path):
|
|
87
|
-
return None, None
|
|
88
|
-
|
|
89
|
-
if LIBRARY_PATTERNS_RE.search(path):
|
|
90
|
-
lib_name = extract_library_name(path)
|
|
91
|
-
return f"grepped in `{path}`", lib_name
|
|
92
|
-
|
|
93
|
-
return None, None
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def check_read_tool(tool_input: dict[str, Any]) -> tuple[str | None, str | None]:
|
|
97
|
-
"""Check if Read targeted library code. Returns (action, library_name)."""
|
|
98
|
-
file_path = tool_input.get("file_path", "")
|
|
99
|
-
|
|
100
|
-
# Fast path: skip regex if no library keywords present
|
|
101
|
-
if not file_path or not quick_path_check(file_path):
|
|
102
|
-
return None, None
|
|
103
|
-
|
|
104
|
-
if LIBRARY_PATTERNS_RE.search(file_path):
|
|
105
|
-
lib_name = extract_library_name(file_path)
|
|
106
|
-
return f"read `{file_path}`", lib_name
|
|
107
|
-
|
|
108
|
-
return None, None
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def main() -> int:
|
|
112
|
-
try:
|
|
113
|
-
stdin_data = sys.stdin.read()
|
|
114
|
-
if not stdin_data.strip():
|
|
115
|
-
return 0
|
|
116
|
-
hook_input = json.loads(stdin_data)
|
|
117
|
-
except json.JSONDecodeError:
|
|
118
|
-
return 0
|
|
119
|
-
|
|
120
|
-
tool_name = hook_input.get("tool_name", "")
|
|
121
|
-
tool_input = hook_input.get("tool_input", {})
|
|
122
|
-
|
|
123
|
-
action = None
|
|
124
|
-
lib_name = None
|
|
125
|
-
|
|
126
|
-
if tool_name == "Grep":
|
|
127
|
-
action, lib_name = check_grep_tool(tool_input)
|
|
128
|
-
elif tool_name == "Read":
|
|
129
|
-
action, lib_name = check_read_tool(tool_input)
|
|
130
|
-
|
|
131
|
-
if not action:
|
|
132
|
-
return 0
|
|
133
|
-
|
|
134
|
-
# Build context-aware reminder
|
|
135
|
-
lib_hint = f" ({lib_name})" if lib_name else ""
|
|
136
|
-
add_suggestion = (
|
|
137
|
-
f"If {lib_name} is not indexed, consider: /bluera-knowledge:add-repo"
|
|
138
|
-
if lib_name
|
|
139
|
-
else "Consider indexing frequently-used libraries with /bluera-knowledge:add-repo"
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
reminder_text = f"""BLUERA-KNOWLEDGE REMINDER
|
|
143
|
-
|
|
144
|
-
You just {action} - this is dependency/library code{lib_hint}.
|
|
145
|
-
|
|
146
|
-
For FUTURE queries about this library, use Bluera Knowledge instead:
|
|
147
|
-
- MCP tool: search(query="your question about {lib_name or 'the library'}")
|
|
148
|
-
- Slash command: /bluera-knowledge:search <query>
|
|
149
|
-
|
|
150
|
-
BK provides indexed, semantic search across library sources - significantly faster
|
|
151
|
-
and more context-efficient than reading through dependency directories.
|
|
152
|
-
|
|
153
|
-
{add_suggestion}"""
|
|
154
|
-
|
|
155
|
-
output = {
|
|
156
|
-
"hookSpecificOutput": {
|
|
157
|
-
"hookEventName": "PostToolUse",
|
|
158
|
-
"additionalContext": reminder_text,
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
print(json.dumps(output))
|
|
162
|
-
return 0
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if __name__ == "__main__":
|
|
166
|
-
raise SystemExit(main())
|