mcp-vector-search 0.4.11__py3-none-any.whl → 0.4.13__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.

@@ -11,7 +11,13 @@ from .. import __build__, __version__
11
11
  from .commands.auto_index import auto_index_app
12
12
  from .commands.config import config_app
13
13
  from .commands.index import index_app
14
- from .commands.init import init_app
14
+ from .commands.init import (
15
+ init_app,
16
+ main as init_main,
17
+ check_initialization as init_check,
18
+ init_mcp_integration,
19
+ list_embedding_models,
20
+ )
15
21
  from .commands.install import install_app
16
22
  from .commands.mcp import mcp_app
17
23
  from .commands.search import (
@@ -22,7 +28,9 @@ from .commands.search import (
22
28
  )
23
29
  from .commands.status import status_app
24
30
  from .commands.watch import app as watch_app
31
+ from .commands.reset import reset_app, health_main
25
32
  from .didyoumean import create_enhanced_typer, add_common_suggestions
33
+ from .suggestions import get_contextual_suggestions, ContextualSuggestionProvider
26
34
  from .output import print_error, setup_logging
27
35
 
28
36
  # Install rich traceback handler
@@ -39,18 +47,26 @@ app = create_enhanced_typer(
39
47
  rich_markup_mode="rich",
40
48
  )
41
49
 
42
- # Add install command directly (not as subcommand app)
50
+ # Import command functions for direct registration and aliases
43
51
  from .commands.install import main as install_main, demo as install_demo
44
52
  from .commands.status import main as status_main
53
+ from .commands.index import main as index_main
54
+ # Note: config doesn't have a main function, it uses subcommands via config_app
45
55
  app.command("install", help="🚀 Install mcp-vector-search in projects")(install_main)
46
56
  app.command("demo", help="🎬 Run installation demo with sample project")(install_demo)
47
57
  app.command("status", help="📊 Show project status and statistics")(status_main)
48
- app.add_typer(init_app, name="init", help="🔧 Initialize project for semantic search")
58
+ # Register init as a direct command
59
+ app.command("init", help="🔧 Initialize project for semantic search")(init_main)
60
+ # Add init subcommands as separate commands
61
+ app.command("init-check", help="Check if project is initialized")(init_check)
62
+ app.command("init-mcp", help="Install/fix Claude Code MCP integration")(init_mcp_integration)
63
+ app.command("init-models", help="List available embedding models")(list_embedding_models)
49
64
  app.add_typer(index_app, name="index", help="Index codebase for semantic search")
50
65
  app.add_typer(config_app, name="config", help="Manage project configuration")
51
66
  app.add_typer(watch_app, name="watch", help="Watch for file changes and update index")
52
67
  app.add_typer(auto_index_app, name="auto-index", help="Manage automatic indexing")
53
68
  app.add_typer(mcp_app, name="mcp", help="Manage Claude Code MCP integration")
69
+ app.add_typer(reset_app, name="reset", help="Reset and recovery operations")
54
70
 
55
71
  # Add search command - simplified syntax as default
56
72
  app.command("search", help="Search code semantically")(search_main)
@@ -58,13 +74,34 @@ app.command("search", help="Search code semantically")(search_main)
58
74
  # Keep old nested structure for backward compatibility
59
75
  app.add_typer(search_app, name="search-legacy", help="Legacy search commands", hidden=True)
60
76
  app.add_typer(status_app, name="status-legacy", help="Legacy status commands", hidden=True)
77
+
78
+ # Add command aliases for better user experience
61
79
  app.command("find", help="Search code semantically (alias for search)")(search_main)
80
+ app.command("f", help="Search code semantically (short alias)", hidden=True)(search_main) # Hidden short alias
81
+ app.command("s", help="Search code semantically (short alias)", hidden=True)(search_main) # Hidden short alias
82
+ app.command("query", help="Search code semantically (alias for search)", hidden=True)(search_main) # Hidden alias
83
+
84
+ # Index aliases
85
+ app.command("i", help="Index codebase (short alias)", hidden=True)(index_main) # Hidden short alias
86
+ app.command("build", help="Index codebase (alias for index)", hidden=True)(index_main) # Hidden alias
87
+ app.command("scan", help="Index codebase (alias for index)", hidden=True)(index_main) # Hidden alias
88
+
89
+ # Status aliases
90
+ app.command("st", help="Show status (short alias)", hidden=True)(status_main) # Hidden short alias
91
+ app.command("info", help="Show project information (alias for status)", hidden=True)(status_main) # Hidden alias
92
+
93
+ # Config aliases - Since config uses subcommands, these will be handled by the enhanced typer error resolution
94
+ # app.command("c", help="Manage configuration (short alias)", hidden=True) # Will be handled by typo resolution
95
+ # app.command("cfg", help="Manage configuration (alias for config)", hidden=True) # Will be handled by typo resolution
96
+
97
+ # Specialized search commands
62
98
  app.command("search-similar", help="Find code similar to a specific file or function")(
63
99
  search_similar_cmd
64
100
  )
65
101
  app.command("search-context", help="Search for code based on contextual description")(
66
102
  search_context_cmd
67
103
  )
104
+ app.command("health", help="Check index health and optionally repair")(health_main)
68
105
 
69
106
 
70
107
  # Add interactive search command
@@ -197,6 +234,8 @@ def main(
197
234
  if project_root:
198
235
  logger.info(f"Using project root: {project_root}")
199
236
 
237
+ # Note: Contextual help moved to a separate command to avoid interfering with didyoumean
238
+
200
239
 
201
240
  @app.command()
202
241
  def version() -> None:
@@ -209,7 +248,7 @@ def version() -> None:
209
248
 
210
249
 
211
250
  def handle_command_error(ctx, param, value):
212
- """Handle command errors with suggestions."""
251
+ """Handle command errors with enhanced suggestions."""
213
252
  if ctx.resilient_parsing:
214
253
  return
215
254
 
@@ -224,10 +263,34 @@ def handle_command_error(ctx, param, value):
224
263
  match = re.search(r"No such command '([^']+)'", str(e))
225
264
  if match:
226
265
  command_name = match.group(1)
266
+
267
+ # Use both the original suggestions and contextual suggestions
227
268
  add_common_suggestions(ctx, command_name)
269
+
270
+ # Add contextual suggestions based on project state
271
+ try:
272
+ project_root = ctx.obj.get("project_root") if ctx.obj else None
273
+ get_contextual_suggestions(project_root, command_name)
274
+ except Exception:
275
+ # If contextual suggestions fail, don't break the error flow
276
+ pass
228
277
  raise
229
278
 
230
279
 
280
+
281
+
282
+ @app.command()
283
+ def help_contextual() -> None:
284
+ """Show contextual help and suggestions based on project state."""
285
+ try:
286
+ project_root = Path.cwd()
287
+ console.print(f"[bold blue]mcp-vector-search[/bold blue] version [green]{__version__}[/green]")
288
+ console.print("[dim]CLI-first semantic code search with MCP integration[/dim]")
289
+ get_contextual_suggestions(project_root)
290
+ except Exception as e:
291
+ console.print("\n[dim]Use [bold]mcp-vector-search --help[/bold] for more information.[/dim]")
292
+
293
+
231
294
  @app.command()
232
295
  def doctor() -> None:
233
296
  """Check system dependencies and configuration."""
@@ -250,5 +313,54 @@ def doctor() -> None:
250
313
 
251
314
 
252
315
 
316
+ def cli_with_suggestions():
317
+ """CLI wrapper that catches errors and provides suggestions."""
318
+ import sys
319
+ import click
320
+
321
+ try:
322
+ # Call the app with standalone_mode=False to get exceptions instead of sys.exit
323
+ app(standalone_mode=False)
324
+ except click.UsageError as e:
325
+ # Check if it's a "No such command" error
326
+ if "No such command" in str(e):
327
+ # Extract the command name from the error
328
+ import re
329
+ match = re.search(r"No such command '([^']+)'", str(e))
330
+ if match:
331
+ command_name = match.group(1)
332
+
333
+ # Show enhanced suggestions
334
+ from rich.console import Console
335
+ console = Console(stderr=True)
336
+ console.print(f"\\n[red]Error:[/red] {e}")
337
+
338
+ # Show enhanced suggestions
339
+ add_common_suggestions(None, command_name)
340
+
341
+ # Show contextual suggestions too
342
+ try:
343
+ project_root = Path.cwd()
344
+ get_contextual_suggestions(project_root, command_name)
345
+ except Exception:
346
+ pass
347
+
348
+ sys.exit(2) # Exit with error code
349
+
350
+ # For other usage errors, show the default message and exit
351
+ click.echo(f"Error: {e}", err=True)
352
+ sys.exit(2)
353
+ except click.Abort:
354
+ # User interrupted (Ctrl+C)
355
+ sys.exit(1)
356
+ except SystemExit as e:
357
+ # Re-raise system exits
358
+ raise
359
+ except Exception as e:
360
+ # For other exceptions, show error and exit
361
+ click.echo(f"Unexpected error: {e}", err=True)
362
+ sys.exit(1)
363
+
364
+
253
365
  if __name__ == "__main__":
254
- app()
366
+ cli_with_suggestions()
@@ -0,0 +1,325 @@
1
+ """Contextual suggestion system for better user experience."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Dict, List, Optional, Set, Tuple
6
+ from rich.console import Console
7
+
8
+ from ..core.project import ProjectManager
9
+ from ..core.exceptions import ProjectNotFoundError
10
+
11
+
12
+ class ContextualSuggestionProvider:
13
+ """Provides context-aware suggestions based on project state and user workflow."""
14
+
15
+ def __init__(self, project_root: Optional[Path] = None):
16
+ """Initialize the suggestion provider.
17
+
18
+ Args:
19
+ project_root: Root directory of the project (defaults to current directory)
20
+ """
21
+ self.project_root = project_root or Path.cwd()
22
+ self.console = Console(stderr=True)
23
+
24
+ def get_project_state(self) -> Dict[str, bool]:
25
+ """Analyze the current project state.
26
+
27
+ Returns:
28
+ Dictionary with boolean flags indicating project state
29
+ """
30
+ state = {
31
+ 'is_initialized': False,
32
+ 'has_index': False,
33
+ 'has_config': False,
34
+ 'has_recent_changes': False,
35
+ 'is_git_repo': False,
36
+ 'has_mcp_config': False
37
+ }
38
+
39
+ try:
40
+ # Check if project is initialized
41
+ config_dir = self.project_root / '.mcp-vector-search'
42
+ state['is_initialized'] = config_dir.exists()
43
+
44
+ if state['is_initialized']:
45
+ # Check for config
46
+ config_file = config_dir / 'config.json'
47
+ state['has_config'] = config_file.exists()
48
+
49
+ # Check for index
50
+ index_dir = config_dir / 'chroma_db'
51
+ state['has_index'] = index_dir.exists() and any(index_dir.iterdir())
52
+
53
+ # Check if it's a git repo
54
+ git_dir = self.project_root / '.git'
55
+ state['is_git_repo'] = git_dir.exists()
56
+
57
+ # Check for MCP configuration (Claude Desktop config)
58
+ home = Path.home()
59
+ claude_config = home / 'Library' / 'Application Support' / 'Claude' / 'claude_desktop_config.json'
60
+ if claude_config.exists():
61
+ try:
62
+ with open(claude_config) as f:
63
+ config_data = json.load(f)
64
+ mcp_servers = config_data.get('mcpServers', {})
65
+ state['has_mcp_config'] = 'mcp-vector-search' in mcp_servers
66
+ except (json.JSONDecodeError, IOError):
67
+ pass
68
+
69
+ # TODO: Check for recent file changes (would need file system monitoring)
70
+ # For now, we'll assume false
71
+ state['has_recent_changes'] = False
72
+
73
+ except Exception:
74
+ # If we can't determine state, provide conservative defaults
75
+ pass
76
+
77
+ return state
78
+
79
+ def get_workflow_suggestions(self, failed_command: str) -> List[Dict[str, str]]:
80
+ """Get workflow-based suggestions for a failed command.
81
+
82
+ Args:
83
+ failed_command: The command that failed
84
+
85
+ Returns:
86
+ List of suggestion dictionaries with 'command', 'reason', and 'priority'
87
+ """
88
+ suggestions = []
89
+ state = self.get_project_state()
90
+
91
+ # High priority suggestions based on project state
92
+ if not state['is_initialized']:
93
+ suggestions.append({
94
+ 'command': 'init',
95
+ 'reason': 'Project is not initialized for vector search',
96
+ 'priority': 'high',
97
+ 'description': 'Set up the project configuration and create necessary directories'
98
+ })
99
+ elif not state['has_index']:
100
+ suggestions.append({
101
+ 'command': 'index',
102
+ 'reason': 'No search index found - create one to enable searching',
103
+ 'priority': 'high',
104
+ 'description': 'Build the vector index for your codebase'
105
+ })
106
+
107
+ # Context-specific suggestions based on the failed command
108
+ if failed_command.lower() in ['search', 'find', 'query', 's', 'f']:
109
+ if not state['has_index']:
110
+ suggestions.append({
111
+ 'command': 'index',
112
+ 'reason': 'Cannot search without an index',
113
+ 'priority': 'high',
114
+ 'description': 'Build the search index first'
115
+ })
116
+ else:
117
+ suggestions.extend([
118
+ {
119
+ 'command': 'search',
120
+ 'reason': 'Correct command for semantic code search',
121
+ 'priority': 'high',
122
+ 'description': 'Search your codebase semantically'
123
+ },
124
+ {
125
+ 'command': 'interactive',
126
+ 'reason': 'Try interactive search for better experience',
127
+ 'priority': 'medium',
128
+ 'description': 'Start an interactive search session'
129
+ }
130
+ ])
131
+
132
+ elif failed_command.lower() in ['index', 'build', 'scan', 'i', 'b']:
133
+ suggestions.append({
134
+ 'command': 'index',
135
+ 'reason': 'Correct command for building search index',
136
+ 'priority': 'high',
137
+ 'description': 'Index your codebase for semantic search'
138
+ })
139
+
140
+ elif failed_command.lower() in ['status', 'info', 'stat', 'st']:
141
+ suggestions.append({
142
+ 'command': 'status',
143
+ 'reason': 'Show project status and statistics',
144
+ 'priority': 'high',
145
+ 'description': 'Display current project information'
146
+ })
147
+
148
+ elif failed_command.lower() in ['config', 'configure', 'settings', 'c']:
149
+ suggestions.append({
150
+ 'command': 'config',
151
+ 'reason': 'Manage project configuration',
152
+ 'priority': 'high',
153
+ 'description': 'View or modify project settings'
154
+ })
155
+
156
+ # MCP-related suggestions
157
+ if not state['has_mcp_config'] and failed_command.lower() in ['mcp', 'claude', 'server']:
158
+ suggestions.append({
159
+ 'command': 'init-mcp',
160
+ 'reason': 'Set up Claude Code MCP integration',
161
+ 'priority': 'medium',
162
+ 'description': 'Configure MCP server for Claude Code integration'
163
+ })
164
+
165
+ # Remove duplicates while preserving order
166
+ seen = set()
167
+ unique_suggestions = []
168
+ for suggestion in suggestions:
169
+ key = suggestion['command']
170
+ if key not in seen:
171
+ seen.add(key)
172
+ unique_suggestions.append(suggestion)
173
+
174
+ return unique_suggestions
175
+
176
+ def get_next_steps(self) -> List[Dict[str, str]]:
177
+ """Get suggested next steps based on current project state.
178
+
179
+ Returns:
180
+ List of suggested next step dictionaries
181
+ """
182
+ state = self.get_project_state()
183
+ next_steps = []
184
+
185
+ if not state['is_initialized']:
186
+ next_steps.append({
187
+ 'command': 'init',
188
+ 'description': 'Initialize the project for semantic search',
189
+ 'priority': 'high'
190
+ })
191
+ elif not state['has_index']:
192
+ next_steps.append({
193
+ 'command': 'index',
194
+ 'description': 'Build the search index for your codebase',
195
+ 'priority': 'high'
196
+ })
197
+ else:
198
+ # Project is ready for use
199
+ next_steps.extend([
200
+ {
201
+ 'command': 'search "your query here"',
202
+ 'description': 'Search your codebase semantically',
203
+ 'priority': 'high'
204
+ },
205
+ {
206
+ 'command': 'status',
207
+ 'description': 'Check project statistics and index health',
208
+ 'priority': 'medium'
209
+ }
210
+ ])
211
+
212
+ if state['has_recent_changes']:
213
+ next_steps.insert(0, {
214
+ 'command': 'index --force',
215
+ 'description': 'Update the index with recent changes',
216
+ 'priority': 'high'
217
+ })
218
+
219
+ if not state['has_mcp_config'] and state['is_initialized']:
220
+ next_steps.append({
221
+ 'command': 'init-mcp',
222
+ 'description': 'Set up Claude Code integration',
223
+ 'priority': 'low'
224
+ })
225
+
226
+ return next_steps
227
+
228
+ def show_contextual_help(self, failed_command: Optional[str] = None) -> None:
229
+ """Show contextual help and suggestions.
230
+
231
+ Args:
232
+ failed_command: The command that failed (if any)
233
+ """
234
+ if failed_command:
235
+ self.console.print(f"\n[yellow]Command '{failed_command}' not recognized.[/yellow]")
236
+
237
+ suggestions = self.get_workflow_suggestions(failed_command)
238
+ if suggestions:
239
+ self.console.print("\n[bold]Based on your project state, you might want to try:[/bold]")
240
+
241
+ for i, suggestion in enumerate(suggestions[:3], 1): # Show top 3
242
+ priority_color = {
243
+ 'high': 'red',
244
+ 'medium': 'yellow',
245
+ 'low': 'dim'
246
+ }.get(suggestion['priority'], 'white')
247
+
248
+ self.console.print(
249
+ f" [{priority_color}]{i}.[/{priority_color}] "
250
+ f"[bold cyan]mcp-vector-search {suggestion['command']}[/bold cyan]"
251
+ )
252
+ self.console.print(f" {suggestion['description']}")
253
+ if suggestion.get('reason'):
254
+ self.console.print(f" [dim]({suggestion['reason']})[/dim]")
255
+ else:
256
+ # Show general next steps
257
+ next_steps = self.get_next_steps()
258
+ if next_steps:
259
+ self.console.print("\n[bold]Suggested next steps:[/bold]")
260
+
261
+ for i, step in enumerate(next_steps[:3], 1):
262
+ priority_color = {
263
+ 'high': 'green',
264
+ 'medium': 'yellow',
265
+ 'low': 'dim'
266
+ }.get(step['priority'], 'white')
267
+
268
+ self.console.print(
269
+ f" [{priority_color}]{i}.[/{priority_color}] "
270
+ f"[bold cyan]mcp-vector-search {step['command']}[/bold cyan]"
271
+ )
272
+ self.console.print(f" {step['description']}")
273
+
274
+ def get_command_completion_suggestions(self, partial_command: str) -> List[str]:
275
+ """Get command completion suggestions for a partial command.
276
+
277
+ Args:
278
+ partial_command: Partial command string
279
+
280
+ Returns:
281
+ List of possible command completions
282
+ """
283
+ all_commands = [
284
+ 'search', 'index', 'status', 'config', 'init', 'mcp',
285
+ 'doctor', 'version', 'watch', 'auto-index', 'history',
286
+ 'interactive', 'demo', 'install', 'reset', 'health'
287
+ ]
288
+
289
+ # Add common aliases and shortcuts
290
+ all_commands.extend(['s', 'i', 'st', 'c', 'f', 'find'])
291
+
292
+ partial_lower = partial_command.lower()
293
+ matches = [
294
+ cmd for cmd in all_commands
295
+ if cmd.startswith(partial_lower)
296
+ ]
297
+
298
+ return sorted(matches)
299
+
300
+
301
+ def get_contextual_suggestions(project_root: Optional[Path] = None,
302
+ failed_command: Optional[str] = None) -> None:
303
+ """Get and display contextual suggestions.
304
+
305
+ Args:
306
+ project_root: Root directory of the project
307
+ failed_command: The command that failed
308
+ """
309
+ provider = ContextualSuggestionProvider(project_root)
310
+ provider.show_contextual_help(failed_command)
311
+
312
+
313
+ def suggest_workflow_commands(project_root: Optional[Path] = None) -> List[str]:
314
+ """Get workflow command suggestions for the current project state.
315
+
316
+ Args:
317
+ project_root: Root directory of the project
318
+
319
+ Returns:
320
+ List of suggested commands in priority order
321
+ """
322
+ provider = ContextualSuggestionProvider(project_root)
323
+ next_steps = provider.get_next_steps()
324
+
325
+ return [step['command'] for step in next_steps]