mcp-vector-search 0.0.3__py3-none-any.whl
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.
Potentially problematic release.
This version of mcp-vector-search might be problematic. Click here for more details.
- mcp_vector_search/__init__.py +9 -0
- mcp_vector_search/cli/__init__.py +1 -0
- mcp_vector_search/cli/commands/__init__.py +1 -0
- mcp_vector_search/cli/commands/config.py +303 -0
- mcp_vector_search/cli/commands/index.py +304 -0
- mcp_vector_search/cli/commands/init.py +212 -0
- mcp_vector_search/cli/commands/search.py +395 -0
- mcp_vector_search/cli/commands/status.py +340 -0
- mcp_vector_search/cli/commands/watch.py +288 -0
- mcp_vector_search/cli/main.py +117 -0
- mcp_vector_search/cli/output.py +242 -0
- mcp_vector_search/config/__init__.py +1 -0
- mcp_vector_search/config/defaults.py +175 -0
- mcp_vector_search/config/settings.py +108 -0
- mcp_vector_search/core/__init__.py +1 -0
- mcp_vector_search/core/database.py +431 -0
- mcp_vector_search/core/embeddings.py +250 -0
- mcp_vector_search/core/exceptions.py +66 -0
- mcp_vector_search/core/indexer.py +310 -0
- mcp_vector_search/core/models.py +174 -0
- mcp_vector_search/core/project.py +304 -0
- mcp_vector_search/core/search.py +324 -0
- mcp_vector_search/core/watcher.py +320 -0
- mcp_vector_search/mcp/__init__.py +1 -0
- mcp_vector_search/parsers/__init__.py +1 -0
- mcp_vector_search/parsers/base.py +180 -0
- mcp_vector_search/parsers/javascript.py +238 -0
- mcp_vector_search/parsers/python.py +407 -0
- mcp_vector_search/parsers/registry.py +187 -0
- mcp_vector_search/py.typed +1 -0
- mcp_vector_search-0.0.3.dist-info/METADATA +333 -0
- mcp_vector_search-0.0.3.dist-info/RECORD +35 -0
- mcp_vector_search-0.0.3.dist-info/WHEEL +4 -0
- mcp_vector_search-0.0.3.dist-info/entry_points.txt +2 -0
- mcp_vector_search-0.0.3.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""Status command for MCP Vector Search CLI."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Dict, Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
from ...core.database import ChromaVectorDatabase
|
|
11
|
+
from ...core.embeddings import create_embedding_function
|
|
12
|
+
from ...core.exceptions import ProjectNotFoundError
|
|
13
|
+
from ...core.indexer import SemanticIndexer
|
|
14
|
+
from ...core.project import ProjectManager
|
|
15
|
+
from ..output import (
|
|
16
|
+
console,
|
|
17
|
+
print_config,
|
|
18
|
+
print_dependency_status,
|
|
19
|
+
print_error,
|
|
20
|
+
print_index_stats,
|
|
21
|
+
print_info,
|
|
22
|
+
print_json,
|
|
23
|
+
print_project_info,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Create status subcommand app
|
|
27
|
+
status_app = typer.Typer(help="Show project status and statistics")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@status_app.command()
|
|
31
|
+
def main(
|
|
32
|
+
ctx: typer.Context,
|
|
33
|
+
verbose: bool = typer.Option(
|
|
34
|
+
False,
|
|
35
|
+
"--verbose",
|
|
36
|
+
"-v",
|
|
37
|
+
help="Show detailed information",
|
|
38
|
+
),
|
|
39
|
+
health_check: bool = typer.Option(
|
|
40
|
+
False,
|
|
41
|
+
"--health-check",
|
|
42
|
+
help="Perform health check of all components",
|
|
43
|
+
),
|
|
44
|
+
json_output: bool = typer.Option(
|
|
45
|
+
False,
|
|
46
|
+
"--json",
|
|
47
|
+
help="Output status in JSON format",
|
|
48
|
+
),
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Show project status and indexing statistics.
|
|
51
|
+
|
|
52
|
+
This command displays comprehensive information about your MCP Vector Search
|
|
53
|
+
project including configuration, indexing status, and system health.
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
mcp-vector-search status
|
|
57
|
+
mcp-vector-search status --verbose
|
|
58
|
+
mcp-vector-search status --health-check --json
|
|
59
|
+
"""
|
|
60
|
+
try:
|
|
61
|
+
project_root = ctx.obj.get("project_root") or Path.cwd()
|
|
62
|
+
|
|
63
|
+
asyncio.run(show_status(
|
|
64
|
+
project_root=project_root,
|
|
65
|
+
verbose=verbose,
|
|
66
|
+
health_check=health_check,
|
|
67
|
+
json_output=json_output,
|
|
68
|
+
))
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f"Status check failed: {e}")
|
|
72
|
+
print_error(f"Status check failed: {e}")
|
|
73
|
+
raise typer.Exit(1)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def show_status(
|
|
77
|
+
project_root: Path,
|
|
78
|
+
verbose: bool = False,
|
|
79
|
+
health_check: bool = False,
|
|
80
|
+
json_output: bool = False,
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Show comprehensive project status."""
|
|
83
|
+
status_data = {}
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
# Check if project is initialized
|
|
87
|
+
project_manager = ProjectManager(project_root)
|
|
88
|
+
|
|
89
|
+
if not project_manager.is_initialized():
|
|
90
|
+
if json_output:
|
|
91
|
+
status_data = {
|
|
92
|
+
"initialized": False,
|
|
93
|
+
"project_root": str(project_root),
|
|
94
|
+
"error": "Project not initialized"
|
|
95
|
+
}
|
|
96
|
+
print_json(status_data)
|
|
97
|
+
else:
|
|
98
|
+
print_error(f"Project not initialized at {project_root}")
|
|
99
|
+
print_info("Run 'mcp-vector-search init' to initialize the project")
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
# Get project information
|
|
103
|
+
project_info = project_manager.get_project_info()
|
|
104
|
+
config = project_manager.load_config()
|
|
105
|
+
|
|
106
|
+
# Get indexing statistics
|
|
107
|
+
embedding_function, _ = create_embedding_function(config.embedding_model)
|
|
108
|
+
database = ChromaVectorDatabase(
|
|
109
|
+
persist_directory=config.index_path,
|
|
110
|
+
embedding_function=embedding_function,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
indexer = SemanticIndexer(
|
|
114
|
+
database=database,
|
|
115
|
+
project_root=project_root,
|
|
116
|
+
file_extensions=config.file_extensions,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
async with database:
|
|
120
|
+
index_stats = await indexer.get_indexing_stats()
|
|
121
|
+
db_stats = await database.get_stats()
|
|
122
|
+
|
|
123
|
+
# Compile status data
|
|
124
|
+
status_data = {
|
|
125
|
+
"project": {
|
|
126
|
+
"name": project_info.name,
|
|
127
|
+
"root_path": str(project_info.root_path),
|
|
128
|
+
"initialized": project_info.is_initialized,
|
|
129
|
+
"languages": project_info.languages,
|
|
130
|
+
"file_count": project_info.file_count,
|
|
131
|
+
},
|
|
132
|
+
"configuration": {
|
|
133
|
+
"embedding_model": config.embedding_model,
|
|
134
|
+
"similarity_threshold": config.similarity_threshold,
|
|
135
|
+
"file_extensions": config.file_extensions,
|
|
136
|
+
"max_chunk_size": config.max_chunk_size,
|
|
137
|
+
"cache_embeddings": config.cache_embeddings,
|
|
138
|
+
"watch_files": config.watch_files,
|
|
139
|
+
},
|
|
140
|
+
"index": {
|
|
141
|
+
"total_files": index_stats.get("total_indexable_files", 0),
|
|
142
|
+
"indexed_files": index_stats.get("indexed_files", 0),
|
|
143
|
+
"total_chunks": index_stats.get("total_chunks", 0),
|
|
144
|
+
"languages": index_stats.get("languages", {}),
|
|
145
|
+
"index_size_mb": db_stats.index_size_mb,
|
|
146
|
+
"last_updated": db_stats.last_updated,
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
# Add health check if requested
|
|
151
|
+
if health_check:
|
|
152
|
+
health_status = await perform_health_check(project_root, config)
|
|
153
|
+
status_data["health"] = health_status
|
|
154
|
+
|
|
155
|
+
# Add verbose information
|
|
156
|
+
if verbose:
|
|
157
|
+
status_data["verbose"] = {
|
|
158
|
+
"config_path": str(project_info.config_path),
|
|
159
|
+
"index_path": str(project_info.index_path),
|
|
160
|
+
"ignore_patterns": list(indexer.get_ignore_patterns()),
|
|
161
|
+
"parser_info": index_stats.get("parser_info", {}),
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Output results
|
|
165
|
+
if json_output:
|
|
166
|
+
print_json(status_data)
|
|
167
|
+
else:
|
|
168
|
+
_display_status(status_data, verbose)
|
|
169
|
+
|
|
170
|
+
except ProjectNotFoundError:
|
|
171
|
+
if json_output:
|
|
172
|
+
print_json({"initialized": False, "error": "Project not initialized"})
|
|
173
|
+
else:
|
|
174
|
+
print_error("Project not initialized")
|
|
175
|
+
print_info("Run 'mcp-vector-search init' to initialize the project")
|
|
176
|
+
except Exception as e:
|
|
177
|
+
if json_output:
|
|
178
|
+
print_json({"error": str(e)})
|
|
179
|
+
else:
|
|
180
|
+
print_error(f"Failed to get status: {e}")
|
|
181
|
+
raise
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _display_status(status_data: Dict[str, Any], verbose: bool) -> None:
|
|
185
|
+
"""Display status in human-readable format."""
|
|
186
|
+
project_data = status_data["project"]
|
|
187
|
+
config_data = status_data["configuration"]
|
|
188
|
+
index_data = status_data["index"]
|
|
189
|
+
|
|
190
|
+
# Project information
|
|
191
|
+
console.print("[bold blue]Project Information[/bold blue]")
|
|
192
|
+
console.print(f" Name: {project_data['name']}")
|
|
193
|
+
console.print(f" Root: {project_data['root_path']}")
|
|
194
|
+
console.print(f" Languages: {', '.join(project_data['languages']) if project_data['languages'] else 'None detected'}")
|
|
195
|
+
console.print(f" Indexable Files: {project_data['file_count']}")
|
|
196
|
+
console.print()
|
|
197
|
+
|
|
198
|
+
# Configuration
|
|
199
|
+
console.print("[bold blue]Configuration[/bold blue]")
|
|
200
|
+
console.print(f" Embedding Model: {config_data['embedding_model']}")
|
|
201
|
+
console.print(f" Similarity Threshold: {config_data['similarity_threshold']}")
|
|
202
|
+
console.print(f" File Extensions: {', '.join(config_data['file_extensions'])}")
|
|
203
|
+
console.print(f" Cache Embeddings: {'ā' if config_data['cache_embeddings'] else 'ā'}")
|
|
204
|
+
console.print()
|
|
205
|
+
|
|
206
|
+
# Index statistics
|
|
207
|
+
console.print("[bold blue]Index Statistics[/bold blue]")
|
|
208
|
+
console.print(f" Indexed Files: {index_data['indexed_files']}/{index_data['total_files']}")
|
|
209
|
+
console.print(f" Total Chunks: {index_data['total_chunks']}")
|
|
210
|
+
console.print(f" Index Size: {index_data['index_size_mb']:.2f} MB")
|
|
211
|
+
|
|
212
|
+
if index_data['languages']:
|
|
213
|
+
console.print(" Language Distribution:")
|
|
214
|
+
for lang, count in index_data['languages'].items():
|
|
215
|
+
console.print(f" {lang}: {count} chunks")
|
|
216
|
+
console.print()
|
|
217
|
+
|
|
218
|
+
# Health check results
|
|
219
|
+
if "health" in status_data:
|
|
220
|
+
health_data = status_data["health"]
|
|
221
|
+
console.print("[bold blue]Health Check[/bold blue]")
|
|
222
|
+
|
|
223
|
+
overall_health = health_data.get("overall", "unknown")
|
|
224
|
+
if overall_health == "healthy":
|
|
225
|
+
console.print("[green]ā System is healthy[/green]")
|
|
226
|
+
elif overall_health == "warning":
|
|
227
|
+
console.print("[yellow]ā System has warnings[/yellow]")
|
|
228
|
+
else:
|
|
229
|
+
console.print("[red]ā System has issues[/red]")
|
|
230
|
+
|
|
231
|
+
for component, status in health_data.get("components", {}).items():
|
|
232
|
+
if status == "ok":
|
|
233
|
+
console.print(f" [green]ā[/green] {component}")
|
|
234
|
+
elif status == "warning":
|
|
235
|
+
console.print(f" [yellow]ā [/yellow] {component}")
|
|
236
|
+
else:
|
|
237
|
+
console.print(f" [red]ā[/red] {component}")
|
|
238
|
+
console.print()
|
|
239
|
+
|
|
240
|
+
# Verbose information
|
|
241
|
+
if verbose and "verbose" in status_data:
|
|
242
|
+
verbose_data = status_data["verbose"]
|
|
243
|
+
console.print("[bold blue]Detailed Information[/bold blue]")
|
|
244
|
+
console.print(f" Config Path: {verbose_data['config_path']}")
|
|
245
|
+
console.print(f" Index Path: {verbose_data['index_path']}")
|
|
246
|
+
console.print(f" Ignore Patterns: {', '.join(verbose_data['ignore_patterns'])}")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
async def perform_health_check(project_root: Path, config) -> Dict[str, Any]:
|
|
250
|
+
"""Perform comprehensive health check."""
|
|
251
|
+
health_status = {
|
|
252
|
+
"overall": "healthy",
|
|
253
|
+
"components": {},
|
|
254
|
+
"issues": [],
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
# Check dependencies
|
|
259
|
+
deps_ok = check_dependencies()
|
|
260
|
+
health_status["components"]["dependencies"] = "ok" if deps_ok else "error"
|
|
261
|
+
if not deps_ok:
|
|
262
|
+
health_status["issues"].append("Missing dependencies")
|
|
263
|
+
|
|
264
|
+
# Check configuration
|
|
265
|
+
config_ok = True
|
|
266
|
+
try:
|
|
267
|
+
# Validate embedding model
|
|
268
|
+
embedding_function, _ = create_embedding_function(config.embedding_model)
|
|
269
|
+
health_status["components"]["embedding_model"] = "ok"
|
|
270
|
+
except Exception as e:
|
|
271
|
+
config_ok = False
|
|
272
|
+
health_status["components"]["embedding_model"] = "error"
|
|
273
|
+
health_status["issues"].append(f"Embedding model error: {e}")
|
|
274
|
+
|
|
275
|
+
# Check database
|
|
276
|
+
try:
|
|
277
|
+
database = ChromaVectorDatabase(
|
|
278
|
+
persist_directory=config.index_path,
|
|
279
|
+
embedding_function=embedding_function,
|
|
280
|
+
)
|
|
281
|
+
async with database:
|
|
282
|
+
await database.get_stats()
|
|
283
|
+
health_status["components"]["database"] = "ok"
|
|
284
|
+
except Exception as e:
|
|
285
|
+
health_status["components"]["database"] = "error"
|
|
286
|
+
health_status["issues"].append(f"Database error: {e}")
|
|
287
|
+
|
|
288
|
+
# Check file system permissions
|
|
289
|
+
try:
|
|
290
|
+
config.index_path.mkdir(parents=True, exist_ok=True)
|
|
291
|
+
test_file = config.index_path / ".test_write"
|
|
292
|
+
test_file.write_text("test")
|
|
293
|
+
test_file.unlink()
|
|
294
|
+
health_status["components"]["file_permissions"] = "ok"
|
|
295
|
+
except Exception as e:
|
|
296
|
+
health_status["components"]["file_permissions"] = "error"
|
|
297
|
+
health_status["issues"].append(f"File permission error: {e}")
|
|
298
|
+
|
|
299
|
+
# Determine overall health
|
|
300
|
+
if any(status == "error" for status in health_status["components"].values()):
|
|
301
|
+
health_status["overall"] = "error"
|
|
302
|
+
elif any(status == "warning" for status in health_status["components"].values()):
|
|
303
|
+
health_status["overall"] = "warning"
|
|
304
|
+
|
|
305
|
+
except Exception as e:
|
|
306
|
+
health_status["overall"] = "error"
|
|
307
|
+
health_status["issues"].append(f"Health check failed: {e}")
|
|
308
|
+
|
|
309
|
+
return health_status
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def check_dependencies() -> bool:
|
|
313
|
+
"""Check if all required dependencies are available."""
|
|
314
|
+
dependencies = [
|
|
315
|
+
("chromadb", "ChromaDB"),
|
|
316
|
+
("sentence_transformers", "Sentence Transformers"),
|
|
317
|
+
("tree_sitter", "Tree-sitter"),
|
|
318
|
+
("tree_sitter_languages", "Tree-sitter Languages"),
|
|
319
|
+
("typer", "Typer"),
|
|
320
|
+
("rich", "Rich"),
|
|
321
|
+
("pydantic", "Pydantic"),
|
|
322
|
+
("watchdog", "Watchdog"),
|
|
323
|
+
("loguru", "Loguru"),
|
|
324
|
+
]
|
|
325
|
+
|
|
326
|
+
all_available = True
|
|
327
|
+
|
|
328
|
+
for module_name, display_name in dependencies:
|
|
329
|
+
try:
|
|
330
|
+
__import__(module_name)
|
|
331
|
+
print_dependency_status(display_name, True)
|
|
332
|
+
except ImportError:
|
|
333
|
+
print_dependency_status(display_name, False)
|
|
334
|
+
all_available = False
|
|
335
|
+
|
|
336
|
+
return all_available
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
if __name__ == "__main__":
|
|
340
|
+
status_app()
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Watch command for MCP Vector Search CLI."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import signal
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import typer
|
|
10
|
+
from loguru import logger
|
|
11
|
+
|
|
12
|
+
from ...core.database import ChromaVectorDatabase
|
|
13
|
+
from ...core.embeddings import create_embedding_function
|
|
14
|
+
from ...core.exceptions import ProjectNotFoundError
|
|
15
|
+
from ...core.indexer import SemanticIndexer
|
|
16
|
+
from ...core.project import ProjectManager
|
|
17
|
+
from ...core.watcher import FileWatcher
|
|
18
|
+
from ..output import (
|
|
19
|
+
console,
|
|
20
|
+
print_error,
|
|
21
|
+
print_info,
|
|
22
|
+
print_success,
|
|
23
|
+
print_warning,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
app = typer.Typer(name="watch", help="Watch for file changes and update index")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command("main")
|
|
30
|
+
def watch_main(
|
|
31
|
+
project_root: Path = typer.Argument(
|
|
32
|
+
Path.cwd(),
|
|
33
|
+
help="Project root directory to watch",
|
|
34
|
+
exists=True,
|
|
35
|
+
file_okay=False,
|
|
36
|
+
dir_okay=True,
|
|
37
|
+
readable=True,
|
|
38
|
+
),
|
|
39
|
+
config: Optional[Path] = typer.Option(
|
|
40
|
+
None,
|
|
41
|
+
"--config",
|
|
42
|
+
"-c",
|
|
43
|
+
help="Configuration file to use",
|
|
44
|
+
exists=True,
|
|
45
|
+
file_okay=True,
|
|
46
|
+
dir_okay=False,
|
|
47
|
+
readable=True,
|
|
48
|
+
),
|
|
49
|
+
verbose: bool = typer.Option(
|
|
50
|
+
False,
|
|
51
|
+
"--verbose",
|
|
52
|
+
"-v",
|
|
53
|
+
help="Enable verbose output",
|
|
54
|
+
),
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Watch for file changes and automatically update the search index.
|
|
57
|
+
|
|
58
|
+
This command starts a file watcher that monitors your project directory
|
|
59
|
+
for changes to code files. When files are created, modified, or deleted,
|
|
60
|
+
the search index is automatically updated to reflect the changes.
|
|
61
|
+
|
|
62
|
+
The watcher will:
|
|
63
|
+
- Monitor all files with configured extensions
|
|
64
|
+
- Debounce rapid changes to avoid excessive indexing
|
|
65
|
+
- Update the index incrementally for better performance
|
|
66
|
+
- Ignore common build/cache directories
|
|
67
|
+
|
|
68
|
+
Press Ctrl+C to stop watching.
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
mcp-vector-search watch
|
|
72
|
+
mcp-vector-search watch /path/to/project --verbose
|
|
73
|
+
mcp-vector-search watch --config custom-config.json
|
|
74
|
+
"""
|
|
75
|
+
if verbose:
|
|
76
|
+
logger.remove()
|
|
77
|
+
logger.add(sys.stderr, level="DEBUG")
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
asyncio.run(_watch_async(project_root, config))
|
|
81
|
+
except KeyboardInterrupt:
|
|
82
|
+
print_info("Watch stopped by user")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print_error(f"Watch failed: {e}")
|
|
85
|
+
raise typer.Exit(1)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def _watch_async(project_root: Path, config_path: Optional[Path]) -> None:
|
|
89
|
+
"""Async implementation of watch command."""
|
|
90
|
+
# Load project configuration
|
|
91
|
+
try:
|
|
92
|
+
project_manager = ProjectManager(project_root)
|
|
93
|
+
if not project_manager.is_initialized():
|
|
94
|
+
print_error(
|
|
95
|
+
f"Project not initialized at {project_root}. "
|
|
96
|
+
"Run 'mcp-vector-search init' first."
|
|
97
|
+
)
|
|
98
|
+
raise typer.Exit(1)
|
|
99
|
+
|
|
100
|
+
config = project_manager.load_config()
|
|
101
|
+
print_info(f"Loaded configuration from {project_root}")
|
|
102
|
+
|
|
103
|
+
except ProjectNotFoundError:
|
|
104
|
+
print_error(
|
|
105
|
+
f"No MCP Vector Search project found at {project_root}. "
|
|
106
|
+
"Run 'mcp-vector-search init' to initialize."
|
|
107
|
+
)
|
|
108
|
+
raise typer.Exit(1)
|
|
109
|
+
|
|
110
|
+
# Setup database and indexer
|
|
111
|
+
try:
|
|
112
|
+
embedding_function, _ = create_embedding_function(config.embedding_model)
|
|
113
|
+
database = ChromaVectorDatabase(
|
|
114
|
+
persist_directory=config.index_path,
|
|
115
|
+
embedding_function=embedding_function,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
indexer = SemanticIndexer(
|
|
119
|
+
database=database,
|
|
120
|
+
project_root=project_root,
|
|
121
|
+
file_extensions=config.file_extensions,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
print_info(f"Initialized database at {config.index_path}")
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
print_error(f"Failed to initialize database: {e}")
|
|
128
|
+
raise typer.Exit(1)
|
|
129
|
+
|
|
130
|
+
# Start watching
|
|
131
|
+
try:
|
|
132
|
+
async with database:
|
|
133
|
+
watcher = FileWatcher(
|
|
134
|
+
project_root=project_root,
|
|
135
|
+
config=config,
|
|
136
|
+
indexer=indexer,
|
|
137
|
+
database=database,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
print_success("š Starting file watcher...")
|
|
141
|
+
print_info(f"š Watching: {project_root}")
|
|
142
|
+
print_info(f"š Extensions: {', '.join(config.file_extensions)}")
|
|
143
|
+
print_info("Press Ctrl+C to stop watching")
|
|
144
|
+
|
|
145
|
+
async with watcher:
|
|
146
|
+
# Set up signal handlers for graceful shutdown
|
|
147
|
+
stop_event = asyncio.Event()
|
|
148
|
+
|
|
149
|
+
def signal_handler():
|
|
150
|
+
print_info("\nā¹ļø Stopping file watcher...")
|
|
151
|
+
stop_event.set()
|
|
152
|
+
|
|
153
|
+
# Handle SIGINT (Ctrl+C) and SIGTERM
|
|
154
|
+
if sys.platform != "win32":
|
|
155
|
+
loop = asyncio.get_running_loop()
|
|
156
|
+
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
157
|
+
loop.add_signal_handler(sig, signal_handler)
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
# Wait for stop signal
|
|
161
|
+
await stop_event.wait()
|
|
162
|
+
except KeyboardInterrupt:
|
|
163
|
+
signal_handler()
|
|
164
|
+
|
|
165
|
+
print_success("ā
File watcher stopped")
|
|
166
|
+
|
|
167
|
+
except Exception as e:
|
|
168
|
+
print_error(f"File watching failed: {e}")
|
|
169
|
+
raise typer.Exit(1)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@app.command("status")
|
|
173
|
+
def watch_status(
|
|
174
|
+
project_root: Path = typer.Argument(
|
|
175
|
+
Path.cwd(),
|
|
176
|
+
help="Project root directory",
|
|
177
|
+
exists=True,
|
|
178
|
+
file_okay=False,
|
|
179
|
+
dir_okay=True,
|
|
180
|
+
readable=True,
|
|
181
|
+
),
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Check if file watching is enabled for a project.
|
|
184
|
+
|
|
185
|
+
This command checks the project configuration to see if file watching
|
|
186
|
+
is enabled and provides information about the watch settings.
|
|
187
|
+
"""
|
|
188
|
+
try:
|
|
189
|
+
project_manager = ProjectManager(project_root)
|
|
190
|
+
if not project_manager.is_initialized():
|
|
191
|
+
print_error(f"Project not initialized at {project_root}")
|
|
192
|
+
raise typer.Exit(1)
|
|
193
|
+
|
|
194
|
+
config = project_manager.load_config()
|
|
195
|
+
|
|
196
|
+
console.print("\n[bold]File Watch Status[/bold]")
|
|
197
|
+
console.print(f"Project: {project_root}")
|
|
198
|
+
console.print(f"Watch Files: {'ā' if config.watch_files else 'ā'}")
|
|
199
|
+
|
|
200
|
+
if config.watch_files:
|
|
201
|
+
console.print(f"Extensions: {', '.join(config.file_extensions)}")
|
|
202
|
+
print_info("File watching is enabled for this project")
|
|
203
|
+
else:
|
|
204
|
+
print_warning("File watching is disabled for this project")
|
|
205
|
+
print_info("Enable with: mcp-vector-search config set watch_files true")
|
|
206
|
+
|
|
207
|
+
except ProjectNotFoundError:
|
|
208
|
+
print_error(f"No MCP Vector Search project found at {project_root}")
|
|
209
|
+
raise typer.Exit(1)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
print_error(f"Failed to check watch status: {e}")
|
|
212
|
+
raise typer.Exit(1)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@app.command("enable")
|
|
216
|
+
def watch_enable(
|
|
217
|
+
project_root: Path = typer.Argument(
|
|
218
|
+
Path.cwd(),
|
|
219
|
+
help="Project root directory",
|
|
220
|
+
exists=True,
|
|
221
|
+
file_okay=False,
|
|
222
|
+
dir_okay=True,
|
|
223
|
+
readable=True,
|
|
224
|
+
),
|
|
225
|
+
) -> None:
|
|
226
|
+
"""Enable file watching for a project.
|
|
227
|
+
|
|
228
|
+
This command enables the watch_files setting in the project configuration.
|
|
229
|
+
After enabling, you can use 'mcp-vector-search watch' to start monitoring.
|
|
230
|
+
"""
|
|
231
|
+
try:
|
|
232
|
+
project_manager = ProjectManager(project_root)
|
|
233
|
+
if not project_manager.is_initialized():
|
|
234
|
+
print_error(f"Project not initialized at {project_root}")
|
|
235
|
+
raise typer.Exit(1)
|
|
236
|
+
|
|
237
|
+
config = project_manager.load_config()
|
|
238
|
+
config.watch_files = True
|
|
239
|
+
project_manager.save_config(config)
|
|
240
|
+
|
|
241
|
+
print_success("ā
File watching enabled")
|
|
242
|
+
print_info("Start watching with: mcp-vector-search watch")
|
|
243
|
+
|
|
244
|
+
except ProjectNotFoundError:
|
|
245
|
+
print_error(f"No MCP Vector Search project found at {project_root}")
|
|
246
|
+
raise typer.Exit(1)
|
|
247
|
+
except Exception as e:
|
|
248
|
+
print_error(f"Failed to enable file watching: {e}")
|
|
249
|
+
raise typer.Exit(1)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@app.command("disable")
|
|
253
|
+
def watch_disable(
|
|
254
|
+
project_root: Path = typer.Argument(
|
|
255
|
+
Path.cwd(),
|
|
256
|
+
help="Project root directory",
|
|
257
|
+
exists=True,
|
|
258
|
+
file_okay=False,
|
|
259
|
+
dir_okay=True,
|
|
260
|
+
readable=True,
|
|
261
|
+
),
|
|
262
|
+
) -> None:
|
|
263
|
+
"""Disable file watching for a project.
|
|
264
|
+
|
|
265
|
+
This command disables the watch_files setting in the project configuration.
|
|
266
|
+
"""
|
|
267
|
+
try:
|
|
268
|
+
project_manager = ProjectManager(project_root)
|
|
269
|
+
if not project_manager.is_initialized():
|
|
270
|
+
print_error(f"Project not initialized at {project_root}")
|
|
271
|
+
raise typer.Exit(1)
|
|
272
|
+
|
|
273
|
+
config = project_manager.load_config()
|
|
274
|
+
config.watch_files = False
|
|
275
|
+
project_manager.save_config(config)
|
|
276
|
+
|
|
277
|
+
print_success("ā
File watching disabled")
|
|
278
|
+
|
|
279
|
+
except ProjectNotFoundError:
|
|
280
|
+
print_error(f"No MCP Vector Search project found at {project_root}")
|
|
281
|
+
raise typer.Exit(1)
|
|
282
|
+
except Exception as e:
|
|
283
|
+
print_error(f"Failed to disable file watching: {e}")
|
|
284
|
+
raise typer.Exit(1)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
if __name__ == "__main__":
|
|
288
|
+
app()
|