mcp-vector-search 0.15.7__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.

Files changed (86) hide show
  1. mcp_vector_search/__init__.py +10 -0
  2. mcp_vector_search/cli/__init__.py +1 -0
  3. mcp_vector_search/cli/commands/__init__.py +1 -0
  4. mcp_vector_search/cli/commands/auto_index.py +397 -0
  5. mcp_vector_search/cli/commands/chat.py +534 -0
  6. mcp_vector_search/cli/commands/config.py +393 -0
  7. mcp_vector_search/cli/commands/demo.py +358 -0
  8. mcp_vector_search/cli/commands/index.py +762 -0
  9. mcp_vector_search/cli/commands/init.py +658 -0
  10. mcp_vector_search/cli/commands/install.py +869 -0
  11. mcp_vector_search/cli/commands/install_old.py +700 -0
  12. mcp_vector_search/cli/commands/mcp.py +1254 -0
  13. mcp_vector_search/cli/commands/reset.py +393 -0
  14. mcp_vector_search/cli/commands/search.py +796 -0
  15. mcp_vector_search/cli/commands/setup.py +1133 -0
  16. mcp_vector_search/cli/commands/status.py +584 -0
  17. mcp_vector_search/cli/commands/uninstall.py +404 -0
  18. mcp_vector_search/cli/commands/visualize/__init__.py +39 -0
  19. mcp_vector_search/cli/commands/visualize/cli.py +265 -0
  20. mcp_vector_search/cli/commands/visualize/exporters/__init__.py +12 -0
  21. mcp_vector_search/cli/commands/visualize/exporters/html_exporter.py +33 -0
  22. mcp_vector_search/cli/commands/visualize/exporters/json_exporter.py +29 -0
  23. mcp_vector_search/cli/commands/visualize/graph_builder.py +709 -0
  24. mcp_vector_search/cli/commands/visualize/layout_engine.py +469 -0
  25. mcp_vector_search/cli/commands/visualize/server.py +201 -0
  26. mcp_vector_search/cli/commands/visualize/state_manager.py +428 -0
  27. mcp_vector_search/cli/commands/visualize/templates/__init__.py +16 -0
  28. mcp_vector_search/cli/commands/visualize/templates/base.py +218 -0
  29. mcp_vector_search/cli/commands/visualize/templates/scripts.py +3670 -0
  30. mcp_vector_search/cli/commands/visualize/templates/styles.py +779 -0
  31. mcp_vector_search/cli/commands/visualize.py.original +2536 -0
  32. mcp_vector_search/cli/commands/watch.py +287 -0
  33. mcp_vector_search/cli/didyoumean.py +520 -0
  34. mcp_vector_search/cli/export.py +320 -0
  35. mcp_vector_search/cli/history.py +295 -0
  36. mcp_vector_search/cli/interactive.py +342 -0
  37. mcp_vector_search/cli/main.py +484 -0
  38. mcp_vector_search/cli/output.py +414 -0
  39. mcp_vector_search/cli/suggestions.py +375 -0
  40. mcp_vector_search/config/__init__.py +1 -0
  41. mcp_vector_search/config/constants.py +24 -0
  42. mcp_vector_search/config/defaults.py +200 -0
  43. mcp_vector_search/config/settings.py +146 -0
  44. mcp_vector_search/core/__init__.py +1 -0
  45. mcp_vector_search/core/auto_indexer.py +298 -0
  46. mcp_vector_search/core/config_utils.py +394 -0
  47. mcp_vector_search/core/connection_pool.py +360 -0
  48. mcp_vector_search/core/database.py +1237 -0
  49. mcp_vector_search/core/directory_index.py +318 -0
  50. mcp_vector_search/core/embeddings.py +294 -0
  51. mcp_vector_search/core/exceptions.py +89 -0
  52. mcp_vector_search/core/factory.py +318 -0
  53. mcp_vector_search/core/git_hooks.py +345 -0
  54. mcp_vector_search/core/indexer.py +1002 -0
  55. mcp_vector_search/core/llm_client.py +453 -0
  56. mcp_vector_search/core/models.py +294 -0
  57. mcp_vector_search/core/project.py +350 -0
  58. mcp_vector_search/core/scheduler.py +330 -0
  59. mcp_vector_search/core/search.py +952 -0
  60. mcp_vector_search/core/watcher.py +322 -0
  61. mcp_vector_search/mcp/__init__.py +5 -0
  62. mcp_vector_search/mcp/__main__.py +25 -0
  63. mcp_vector_search/mcp/server.py +752 -0
  64. mcp_vector_search/parsers/__init__.py +8 -0
  65. mcp_vector_search/parsers/base.py +296 -0
  66. mcp_vector_search/parsers/dart.py +605 -0
  67. mcp_vector_search/parsers/html.py +413 -0
  68. mcp_vector_search/parsers/javascript.py +643 -0
  69. mcp_vector_search/parsers/php.py +694 -0
  70. mcp_vector_search/parsers/python.py +502 -0
  71. mcp_vector_search/parsers/registry.py +223 -0
  72. mcp_vector_search/parsers/ruby.py +678 -0
  73. mcp_vector_search/parsers/text.py +186 -0
  74. mcp_vector_search/parsers/utils.py +265 -0
  75. mcp_vector_search/py.typed +1 -0
  76. mcp_vector_search/utils/__init__.py +42 -0
  77. mcp_vector_search/utils/gitignore.py +250 -0
  78. mcp_vector_search/utils/gitignore_updater.py +212 -0
  79. mcp_vector_search/utils/monorepo.py +339 -0
  80. mcp_vector_search/utils/timing.py +338 -0
  81. mcp_vector_search/utils/version.py +47 -0
  82. mcp_vector_search-0.15.7.dist-info/METADATA +884 -0
  83. mcp_vector_search-0.15.7.dist-info/RECORD +86 -0
  84. mcp_vector_search-0.15.7.dist-info/WHEEL +4 -0
  85. mcp_vector_search-0.15.7.dist-info/entry_points.txt +3 -0
  86. mcp_vector_search-0.15.7.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,404 @@
1
+ """Uninstall commands for MCP Vector Search CLI.
2
+
3
+ This module provides commands to remove MCP integrations from various platforms
4
+ using the py-mcp-installer library.
5
+
6
+ Examples:
7
+ # Remove from auto-detected platform
8
+ $ mcp-vector-search uninstall mcp
9
+
10
+ # Remove from specific platform
11
+ $ mcp-vector-search uninstall mcp --platform cursor
12
+
13
+ # Remove from all platforms
14
+ $ mcp-vector-search uninstall mcp --all
15
+
16
+ # Use alias
17
+ $ mcp-vector-search remove mcp
18
+ """
19
+
20
+ from pathlib import Path
21
+
22
+ import typer
23
+ from loguru import logger
24
+
25
+ # Import from py-mcp-installer library
26
+ from py_mcp_installer import MCPInstaller, Platform, PlatformDetector, PlatformInfo
27
+ from rich.console import Console
28
+ from rich.panel import Panel
29
+ from rich.table import Table
30
+
31
+ from ..didyoumean import create_enhanced_typer
32
+ from ..output import (
33
+ confirm_action,
34
+ print_error,
35
+ print_info,
36
+ print_success,
37
+ )
38
+
39
+ # Create console for rich output
40
+ console = Console()
41
+
42
+ # Create uninstall app with subcommands
43
+ uninstall_app = create_enhanced_typer(
44
+ help="""🗑️ Remove MCP integrations from platforms
45
+
46
+ [bold cyan]Usage Patterns:[/bold cyan]
47
+
48
+ [green]1. Remove from Auto-Detected Platform[/green]
49
+ Remove MCP integration from highest confidence platform:
50
+ [code]$ mcp-vector-search uninstall mcp[/code]
51
+
52
+ [green]2. Remove from Specific Platform[/green]
53
+ Remove from a specific platform:
54
+ [code]$ mcp-vector-search uninstall mcp --platform cursor[/code]
55
+
56
+ [green]3. Remove from All Platforms[/green]
57
+ Remove from all configured platforms:
58
+ [code]$ mcp-vector-search uninstall mcp --all[/code]
59
+
60
+ [green]4. List Current Installations[/green]
61
+ See what's currently configured:
62
+ [code]$ mcp-vector-search uninstall list[/code]
63
+
64
+ [bold cyan]Supported Platforms:[/bold cyan]
65
+ • [green]claude-code[/green] - Claude Code
66
+ • [green]claude-desktop[/green] - Claude Desktop
67
+ • [green]cursor[/green] - Cursor IDE
68
+ • [green]auggie[/green] - Auggie
69
+ • [green]codex[/green] - Codex
70
+ • [green]windsurf[/green] - Windsurf IDE
71
+ • [green]gemini-cli[/green] - Gemini CLI
72
+
73
+ [dim]💡 Alias: 'mcp-vector-search remove' works the same way[/dim]
74
+ """,
75
+ invoke_without_command=True,
76
+ no_args_is_help=True,
77
+ )
78
+
79
+
80
+ # ==============================================================================
81
+ # Helper Functions
82
+ # ==============================================================================
83
+
84
+
85
+ def detect_all_platforms() -> list[PlatformInfo]:
86
+ """Detect all available platforms on the system.
87
+
88
+ Returns:
89
+ List of detected platforms with confidence scores
90
+ """
91
+ detector = PlatformDetector()
92
+ detected_platforms = []
93
+
94
+ # Try to detect each platform
95
+ platform_detectors = {
96
+ Platform.CLAUDE_CODE: detector.detect_claude_code,
97
+ Platform.CLAUDE_DESKTOP: detector.detect_claude_desktop,
98
+ Platform.CURSOR: detector.detect_cursor,
99
+ Platform.AUGGIE: detector.detect_auggie,
100
+ Platform.CODEX: detector.detect_codex,
101
+ Platform.WINDSURF: detector.detect_windsurf,
102
+ Platform.GEMINI_CLI: detector.detect_gemini_cli,
103
+ }
104
+
105
+ for platform_enum, detector_func in platform_detectors.items():
106
+ try:
107
+ confidence, config_path = detector_func()
108
+ if confidence > 0.0 and config_path:
109
+ # Determine CLI availability
110
+ cli_available = False
111
+ from py_mcp_installer.utils import resolve_command_path
112
+
113
+ if platform_enum in (Platform.CLAUDE_CODE, Platform.CLAUDE_DESKTOP):
114
+ cli_available = resolve_command_path("claude") is not None
115
+ elif platform_enum == Platform.CURSOR:
116
+ cli_available = resolve_command_path("cursor") is not None
117
+
118
+ platform_info = PlatformInfo(
119
+ platform=platform_enum,
120
+ confidence=confidence,
121
+ config_path=config_path,
122
+ cli_available=cli_available,
123
+ )
124
+ detected_platforms.append(platform_info)
125
+ except Exception as e:
126
+ logger.debug(f"Failed to detect {platform_enum.value}: {e}")
127
+ continue
128
+
129
+ return detected_platforms
130
+
131
+
132
+ def platform_name_to_enum(name: str) -> Platform | None:
133
+ """Convert platform name to enum.
134
+
135
+ Args:
136
+ name: Platform name (e.g., "cursor", "claude-code")
137
+
138
+ Returns:
139
+ Platform enum or None if not found
140
+ """
141
+ name_map = {
142
+ "claude-code": Platform.CLAUDE_CODE,
143
+ "claude-desktop": Platform.CLAUDE_DESKTOP,
144
+ "cursor": Platform.CURSOR,
145
+ "auggie": Platform.AUGGIE,
146
+ "codex": Platform.CODEX,
147
+ "windsurf": Platform.WINDSURF,
148
+ "gemini-cli": Platform.GEMINI_CLI,
149
+ }
150
+ return name_map.get(name.lower())
151
+
152
+
153
+ def find_configured_platforms() -> list[PlatformInfo]:
154
+ """Find all platforms that have mcp-vector-search configured.
155
+
156
+ Returns:
157
+ List of platforms with mcp-vector-search installed
158
+ """
159
+ detected = detect_all_platforms()
160
+ configured = []
161
+
162
+ for platform_info in detected:
163
+ try:
164
+ installer = MCPInstaller(platform=platform_info.platform)
165
+ server = installer.get_server("mcp-vector-search")
166
+ if server:
167
+ configured.append(platform_info)
168
+ except Exception as e:
169
+ logger.debug(
170
+ f"Failed to check {platform_info.platform.value} configuration: {e}"
171
+ )
172
+ continue
173
+
174
+ return configured
175
+
176
+
177
+ def uninstall_from_platform(platform_info: PlatformInfo) -> bool:
178
+ """Uninstall from a specific platform.
179
+
180
+ Args:
181
+ platform_info: Platform information
182
+
183
+ Returns:
184
+ True if uninstallation succeeded
185
+ """
186
+ try:
187
+ installer = MCPInstaller(platform=platform_info.platform)
188
+
189
+ # Uninstall server
190
+ result = installer.uninstall_server("mcp-vector-search")
191
+
192
+ if result.success:
193
+ print_success(f" ✅ Removed from {platform_info.platform.value}")
194
+ return True
195
+ else:
196
+ print_error(
197
+ f" ❌ Failed to remove from {platform_info.platform.value}: {result.message}"
198
+ )
199
+ return False
200
+
201
+ except Exception as e:
202
+ logger.exception(f"Uninstallation from {platform_info.platform.value} failed")
203
+ print_error(f" ❌ Uninstallation failed: {e}")
204
+ return False
205
+
206
+
207
+ # ==============================================================================
208
+ # Main Uninstall Command
209
+ # ==============================================================================
210
+
211
+
212
+ @uninstall_app.command(name="mcp")
213
+ def uninstall_mcp(
214
+ ctx: typer.Context,
215
+ platform: str | None = typer.Option(
216
+ None,
217
+ "--platform",
218
+ "-p",
219
+ help="Specific platform to uninstall from (e.g., cursor, claude-code)",
220
+ ),
221
+ all_platforms: bool = typer.Option(
222
+ False,
223
+ "--all",
224
+ "-a",
225
+ help="Uninstall from all configured platforms",
226
+ ),
227
+ ) -> None:
228
+ """Remove MCP integration from platforms.
229
+
230
+ By default, uninstalls from the highest confidence platform. Use --all to
231
+ remove from all configured platforms.
232
+
233
+ [bold cyan]Examples:[/bold cyan]
234
+
235
+ [green]Remove from auto-detected platform:[/green]
236
+ $ mcp-vector-search uninstall mcp
237
+
238
+ [green]Remove from specific platform:[/green]
239
+ $ mcp-vector-search uninstall mcp --platform cursor
240
+
241
+ [green]Remove from all platforms:[/green]
242
+ $ mcp-vector-search uninstall mcp --all
243
+
244
+ [dim]💡 Use 'mcp-vector-search uninstall list' to see configured platforms[/dim]
245
+ """
246
+ project_root = ctx.obj.get("project_root") or Path.cwd()
247
+
248
+ console.print(
249
+ Panel.fit(
250
+ "[bold yellow]Removing MCP Integration[/bold yellow]\n"
251
+ f"📁 Project: {project_root}",
252
+ border_style="yellow",
253
+ )
254
+ )
255
+
256
+ try:
257
+ # Find configured platforms
258
+ print_info("🔍 Finding configured platforms...")
259
+ configured = find_configured_platforms()
260
+
261
+ if not configured:
262
+ print_info("No MCP integrations found to remove")
263
+ return
264
+
265
+ # Display configured platforms
266
+ table = Table(title="Configured MCP Platforms")
267
+ table.add_column("Platform", style="cyan")
268
+ table.add_column("Config Path", style="green")
269
+ table.add_column("Confidence", style="yellow")
270
+
271
+ for p in configured:
272
+ table.add_row(
273
+ p.platform.value,
274
+ str(p.config_path) if p.config_path else "N/A",
275
+ f"{p.confidence:.2f}",
276
+ )
277
+
278
+ console.print(table)
279
+
280
+ # Filter platforms
281
+ target_platforms = configured
282
+
283
+ if platform:
284
+ # Uninstall from specific platform
285
+ platform_enum = platform_name_to_enum(platform)
286
+ if not platform_enum:
287
+ print_error(f"Unknown platform: {platform}")
288
+ print_info(
289
+ "Supported: claude-code, claude-desktop, cursor, auggie, codex, windsurf, gemini-cli"
290
+ )
291
+ raise typer.Exit(1)
292
+
293
+ target_platforms = [p for p in configured if p.platform == platform_enum]
294
+
295
+ if not target_platforms:
296
+ print_error(
297
+ f"Platform '{platform}' does not have mcp-vector-search configured"
298
+ )
299
+ raise typer.Exit(1)
300
+
301
+ elif not all_platforms:
302
+ # By default, uninstall from highest confidence platform only
303
+ if configured:
304
+ max_confidence_platform = max(configured, key=lambda p: p.confidence)
305
+ target_platforms = [max_confidence_platform]
306
+ print_info(
307
+ f"Removing from highest confidence platform: {max_confidence_platform.platform.value}"
308
+ )
309
+ print_info("Use --all to remove from all configured platforms")
310
+
311
+ # Show what will be removed
312
+ console.print("\n[bold]Target platforms:[/bold]")
313
+ for p in target_platforms:
314
+ console.print(f" • {p.platform.value}")
315
+
316
+ # Confirm removal if multiple platforms
317
+ if len(target_platforms) > 1:
318
+ if not confirm_action("\nRemove from all these platforms?", default=False):
319
+ print_info("Cancelled")
320
+ raise typer.Exit(0)
321
+
322
+ # Uninstall from each platform
323
+ console.print("\n[bold]Removing integrations...[/bold]")
324
+ successful = 0
325
+ failed = 0
326
+
327
+ for platform_info in target_platforms:
328
+ if uninstall_from_platform(platform_info):
329
+ successful += 1
330
+ else:
331
+ failed += 1
332
+
333
+ # Summary
334
+ console.print("\n[bold green]✨ Removal Summary[/bold green]")
335
+ console.print(f" ✅ Successful: {successful}")
336
+ if failed > 0:
337
+ console.print(f" ❌ Failed: {failed}")
338
+
339
+ console.print("\n[dim]💡 Restart your AI coding tool to apply changes[/dim]")
340
+
341
+ except typer.Exit:
342
+ raise
343
+ except Exception as e:
344
+ logger.exception("MCP uninstallation failed")
345
+ print_error(f"Uninstallation failed: {e}")
346
+ raise typer.Exit(1)
347
+
348
+
349
+ # ==============================================================================
350
+ # List Configured Platforms Command
351
+ # ==============================================================================
352
+
353
+
354
+ @uninstall_app.command("list")
355
+ def list_integrations(ctx: typer.Context) -> None:
356
+ """List all currently configured MCP integrations."""
357
+ console.print(
358
+ Panel.fit(
359
+ "[bold cyan]Configured MCP Integrations[/bold cyan]", border_style="cyan"
360
+ )
361
+ )
362
+
363
+ try:
364
+ configured = find_configured_platforms()
365
+
366
+ if not configured:
367
+ console.print("\n[yellow]No MCP integrations configured[/yellow]")
368
+ console.print(
369
+ "\n[dim]Use 'mcp-vector-search install mcp' to add integrations[/dim]"
370
+ )
371
+ return
372
+
373
+ table = Table(show_header=True, header_style="bold cyan")
374
+ table.add_column("Platform", style="cyan")
375
+ table.add_column("Config Path")
376
+ table.add_column("Confidence", style="yellow")
377
+ table.add_column("Removal Command", style="dim")
378
+
379
+ for platform_info in configured:
380
+ table.add_row(
381
+ platform_info.platform.value,
382
+ str(platform_info.config_path) if platform_info.config_path else "N/A",
383
+ f"{platform_info.confidence:.2f}",
384
+ f"mcp-vector-search uninstall mcp --platform {platform_info.platform.value}",
385
+ )
386
+
387
+ console.print(table)
388
+
389
+ console.print("\n[bold blue]Removal Options:[/bold blue]")
390
+ console.print(
391
+ " • Remove specific: [code]mcp-vector-search uninstall mcp --platform <name>[/code]"
392
+ )
393
+ console.print(
394
+ " • Remove all: [code]mcp-vector-search uninstall mcp --all[/code]"
395
+ )
396
+
397
+ except Exception as e:
398
+ logger.exception("Failed to list configured platforms")
399
+ print_error(f"Failed to list configured platforms: {e}")
400
+ raise typer.Exit(1)
401
+
402
+
403
+ if __name__ == "__main__":
404
+ uninstall_app()
@@ -0,0 +1,39 @@
1
+ """Modular visualization package for code graphs.
2
+
3
+ This package provides D3.js-based interactive visualization of code relationships,
4
+ organized into modular components for maintainability.
5
+
6
+ Structure:
7
+ - graph_builder.py: Graph data construction logic
8
+ - server.py: HTTP server for serving visualization
9
+ - templates/: HTML, CSS, and JavaScript generation
10
+ - exporters/: Export functionality (JSON, HTML)
11
+ - cli.py: Typer CLI commands (imported for backwards compatibility)
12
+ """
13
+
14
+ from .cli import app
15
+ from .exporters import export_to_html, export_to_json
16
+ from .graph_builder import (
17
+ build_graph_data,
18
+ get_subproject_color,
19
+ parse_project_dependencies,
20
+ )
21
+ from .server import find_free_port, start_visualization_server
22
+ from .templates import generate_html_template
23
+
24
+ __all__ = [
25
+ # CLI
26
+ "app",
27
+ # Graph building
28
+ "build_graph_data",
29
+ "get_subproject_color",
30
+ "parse_project_dependencies",
31
+ # Server
32
+ "find_free_port",
33
+ "start_visualization_server",
34
+ # Templates
35
+ "generate_html_template",
36
+ # Exporters
37
+ "export_to_html",
38
+ "export_to_json",
39
+ ]
@@ -0,0 +1,265 @@
1
+ """Visualization commands for MCP Vector Search.
2
+
3
+ This module provides a backwards-compatible interface to the refactored
4
+ modular visualization components.
5
+ """
6
+
7
+ import asyncio
8
+ import shutil
9
+ from fnmatch import fnmatch
10
+ from pathlib import Path
11
+
12
+ import typer
13
+ from loguru import logger
14
+ from rich.console import Console
15
+ from rich.panel import Panel
16
+
17
+ from ....core.database import ChromaVectorDatabase
18
+ from ....core.embeddings import create_embedding_function
19
+ from ....core.project import ProjectManager
20
+
21
+ # Import from refactored modules (same directory)
22
+ from .exporters import export_to_html, export_to_json
23
+ from .graph_builder import build_graph_data
24
+ from .server import find_free_port, start_visualization_server
25
+
26
+ app = typer.Typer(
27
+ help="Visualize code chunk relationships",
28
+ no_args_is_help=True,
29
+ )
30
+ console = Console()
31
+
32
+
33
+ @app.command()
34
+ def export(
35
+ output: Path = typer.Option(
36
+ Path("chunk-graph.json"),
37
+ "--output",
38
+ "-o",
39
+ help="Output file for chunk relationship data",
40
+ ),
41
+ file_path: str | None = typer.Option(
42
+ None,
43
+ "--file",
44
+ "-f",
45
+ help="Export only chunks from specific file (supports wildcards)",
46
+ ),
47
+ code_only: bool = typer.Option(
48
+ False,
49
+ "--code-only",
50
+ help="Exclude documentation chunks (text, comment, docstring)",
51
+ ),
52
+ ) -> None:
53
+ """Export chunk relationships as JSON for D3.js visualization.
54
+
55
+ Examples:
56
+ # Export all chunks
57
+ mcp-vector-search visualize export
58
+
59
+ # Export from specific file
60
+ mcp-vector-search visualize export --file src/main.py
61
+
62
+ # Custom output location
63
+ mcp-vector-search visualize export -o graph.json
64
+
65
+ # Export only code chunks (exclude documentation)
66
+ mcp-vector-search visualize export --code-only
67
+ """
68
+ asyncio.run(_export_chunks(output, file_path, code_only))
69
+
70
+
71
+ async def _export_chunks(
72
+ output: Path, file_filter: str | None, code_only: bool = False
73
+ ) -> None:
74
+ """Export chunk relationship data.
75
+
76
+ Args:
77
+ output: Path to output JSON file
78
+ file_filter: Optional file pattern to filter chunks
79
+ code_only: If True, exclude documentation chunks (text, comment, docstring)
80
+ """
81
+ try:
82
+ # Load project
83
+ project_manager = ProjectManager(Path.cwd())
84
+
85
+ if not project_manager.is_initialized():
86
+ console.print(
87
+ "[red]Project not initialized. Run 'mcp-vector-search init' first.[/red]"
88
+ )
89
+ raise typer.Exit(1)
90
+
91
+ config = project_manager.load_config()
92
+
93
+ # Get database
94
+ embedding_function, _ = create_embedding_function(config.embedding_model)
95
+ database = ChromaVectorDatabase(
96
+ persist_directory=config.index_path,
97
+ embedding_function=embedding_function,
98
+ )
99
+ await database.initialize()
100
+
101
+ # Get all chunks with metadata
102
+ console.print("[cyan]Fetching chunks from database...[/cyan]")
103
+ chunks = await database.get_all_chunks()
104
+
105
+ if len(chunks) == 0:
106
+ console.print(
107
+ "[yellow]No chunks found in index. Run 'mcp-vector-search index' first.[/yellow]"
108
+ )
109
+ raise typer.Exit(1)
110
+
111
+ console.print(f"[green]✓[/green] Retrieved {len(chunks)} chunks")
112
+
113
+ # Apply file filter if specified
114
+ if file_filter:
115
+ chunks = [c for c in chunks if fnmatch(str(c.file_path), file_filter)]
116
+ console.print(
117
+ f"[cyan]Filtered to {len(chunks)} chunks matching '{file_filter}'[/cyan]"
118
+ )
119
+
120
+ # Apply code-only filter if requested
121
+ if code_only:
122
+ original_count = len(chunks)
123
+ chunks = [
124
+ c
125
+ for c in chunks
126
+ if c.chunk_type not in ["text", "comment", "docstring"]
127
+ ]
128
+ filtered_count = len(chunks)
129
+ console.print(
130
+ f"[dim]Filtered out {original_count - filtered_count} documentation chunks "
131
+ f"({original_count} → {filtered_count} chunks)[/dim]"
132
+ )
133
+
134
+ # Build graph data using refactored module
135
+ graph_data = await build_graph_data(
136
+ chunks=chunks,
137
+ database=database,
138
+ project_manager=project_manager,
139
+ code_only=code_only,
140
+ )
141
+
142
+ # Export to JSON using refactored module
143
+ export_to_json(graph_data, output)
144
+
145
+ await database.close()
146
+
147
+ console.print()
148
+ # Count cycles from graph_data links
149
+ cycles = [link for link in graph_data["links"] if link.get("is_cycle", False)]
150
+ cycle_warning = f"[yellow]Cycles: {len(cycles)} ⚠️[/yellow]\n" if cycles else ""
151
+
152
+ # Count subprojects
153
+ subprojects_count = len(graph_data["metadata"].get("subprojects", []))
154
+
155
+ console.print(
156
+ Panel.fit(
157
+ f"[green]✓[/green] Exported graph data to [cyan]{output}[/cyan]\n\n"
158
+ f"Nodes: {len(graph_data['nodes'])}\n"
159
+ f"Links: {len(graph_data['links'])}\n"
160
+ f"{cycle_warning}"
161
+ f"{'Subprojects: ' + str(subprojects_count) if subprojects_count else ''}\n\n"
162
+ f"[dim]Next: Run 'mcp-vector-search visualize serve' to view[/dim]",
163
+ title="Export Complete",
164
+ border_style="green",
165
+ )
166
+ )
167
+
168
+ except Exception as e:
169
+ logger.error(f"Export failed: {e}")
170
+ console.print(f"[red]✗ Export failed: {e}[/red]")
171
+ raise typer.Exit(1)
172
+
173
+
174
+ @app.command()
175
+ def serve(
176
+ port: int = typer.Option(
177
+ 8080, "--port", "-p", help="Port for visualization server"
178
+ ),
179
+ graph_file: Path = typer.Option(
180
+ Path("chunk-graph.json"),
181
+ "--graph",
182
+ "-g",
183
+ help="Graph JSON file to visualize",
184
+ ),
185
+ code_only: bool = typer.Option(
186
+ False,
187
+ "--code-only",
188
+ help="Exclude documentation chunks (text, comment, docstring)",
189
+ ),
190
+ ) -> None:
191
+ """Start local HTTP server for D3.js visualization.
192
+
193
+ Examples:
194
+ # Start server on default port 8080
195
+ mcp-vector-search visualize serve
196
+
197
+ # Custom port
198
+ mcp-vector-search visualize serve --port 3000
199
+
200
+ # Custom graph file
201
+ mcp-vector-search visualize serve --graph my-graph.json
202
+
203
+ # Serve with code-only filter
204
+ mcp-vector-search visualize serve --code-only
205
+ """
206
+ # Use specified port or find free one
207
+ if port == 8080: # Default port, try to find free one
208
+ try:
209
+ port = find_free_port(8080, 8099)
210
+ except OSError as e:
211
+ console.print(f"[red]✗ {e}[/red]")
212
+ raise typer.Exit(1)
213
+
214
+ # Get visualization directory - use project-local storage
215
+ project_manager = ProjectManager(Path.cwd())
216
+ if not project_manager.is_initialized():
217
+ console.print(
218
+ "[red]Project not initialized. Run 'mcp-vector-search init' first.[/red]"
219
+ )
220
+ raise typer.Exit(1)
221
+
222
+ viz_dir = project_manager.project_root / ".mcp-vector-search" / "visualization"
223
+
224
+ if not viz_dir.exists():
225
+ console.print(
226
+ f"[yellow]Visualization directory not found. Creating at {viz_dir}...[/yellow]"
227
+ )
228
+ viz_dir.mkdir(parents=True, exist_ok=True)
229
+
230
+ # Always ensure index.html exists (regenerate if missing)
231
+ html_file = viz_dir / "index.html"
232
+ if not html_file.exists():
233
+ console.print("[yellow]Creating visualization HTML file...[/yellow]")
234
+ export_to_html(html_file)
235
+
236
+ # Check if we need to regenerate the graph file
237
+ needs_regeneration = not graph_file.exists() or code_only
238
+
239
+ if graph_file.exists() and not needs_regeneration:
240
+ # Use existing unfiltered file
241
+ dest = viz_dir / "chunk-graph.json"
242
+ shutil.copy(graph_file, dest)
243
+ console.print(f"[green]✓[/green] Copied graph data to {dest}")
244
+ else:
245
+ # Generate new file (with filter if requested)
246
+ if graph_file.exists() and code_only:
247
+ console.print(
248
+ "[yellow]Regenerating filtered graph data (--code-only)...[/yellow]"
249
+ )
250
+ elif not graph_file.exists():
251
+ console.print(
252
+ f"[yellow]Graph file {graph_file} not found. Generating it now...[/yellow]"
253
+ )
254
+
255
+ asyncio.run(_export_chunks(graph_file, None, code_only))
256
+ console.print()
257
+
258
+ # Copy the newly generated graph to visualization directory
259
+ if graph_file.exists():
260
+ dest = viz_dir / "chunk-graph.json"
261
+ shutil.copy(graph_file, dest)
262
+ console.print(f"[green]✓[/green] Copied graph data to {dest}")
263
+
264
+ # Start server using refactored module
265
+ start_visualization_server(port, viz_dir, auto_open=True)