codegraphcontext 0.1.13__tar.gz → 0.1.14__tar.gz
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.
- {codegraphcontext-0.1.13/src/codegraphcontext.egg-info → codegraphcontext-0.1.14}/PKG-INFO +23 -9
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/README.md +20 -8
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/pyproject.toml +4 -2
- codegraphcontext-0.1.14/src/codegraphcontext/cli/main.py +288 -0
- codegraphcontext-0.1.14/src/codegraphcontext/cli/setup_macos.py +95 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/cli/setup_wizard.py +7 -2
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/core/watcher.py +55 -22
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/server.py +122 -25
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/tools/code_finder.py +3 -3
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/tools/graph_builder.py +54 -13
- codegraphcontext-0.1.14/src/codegraphcontext/tools/languages/cpp.py +127 -0
- codegraphcontext-0.1.14/src/codegraphcontext/tools/languages/javascript.py +550 -0
- codegraphcontext-0.1.14/src/codegraphcontext/tools/languages/rust.py +120 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14/src/codegraphcontext.egg-info}/PKG-INFO +23 -9
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext.egg-info/SOURCES.txt +5 -5
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext.egg-info/requires.txt +2 -0
- codegraphcontext-0.1.14/tests/test_end_to_end.py +175 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/tests/test_graph_indexing.py +10 -8
- codegraphcontext-0.1.14/tests/test_graph_indexing_js.py +115 -0
- codegraphcontext-0.1.13/src/codegraphcontext/cli/main.py +0 -183
- codegraphcontext-0.1.13/src/codegraphcontext/tools/languages/javascript.py +0 -302
- codegraphcontext-0.1.13/tests/test_cgc_integration.py +0 -30
- codegraphcontext-0.1.13/tests/test_graph_connections.py +0 -26
- codegraphcontext-0.1.13/tests/test_imports.py +0 -185
- codegraphcontext-0.1.13/tests/test_jsonrpc.py +0 -113
- codegraphcontext-0.1.13/tests/test_tools.py +0 -268
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/LICENSE +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/MANIFEST.in +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/setup.cfg +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/__init__.py +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/__main__.py +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/cli/__init__.py +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/core/__init__.py +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/core/database.py +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/core/jobs.py +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/prompts.py +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/tools/__init__.py +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/tools/import_extractor.py +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/tools/languages/python.py +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/tools/system.py +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/utils/debug_log.py +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext.egg-info/dependency_links.txt +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext.egg-info/entry_points.txt +0 -0
- {codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codegraphcontext
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: An MCP server that indexes local code into a graph database to provide context to AI assistants.
|
|
5
5
|
Author-email: Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -46,6 +46,8 @@ Requires-Dist: inquirerpy>=0.3.4
|
|
|
46
46
|
Requires-Dist: python-dotenv>=1.0.0
|
|
47
47
|
Requires-Dist: tree-sitter==0.20.4
|
|
48
48
|
Requires-Dist: tree-sitter-languages==1.10.2
|
|
49
|
+
Requires-Dist: pyyaml
|
|
50
|
+
Requires-Dist: pytest
|
|
49
51
|
Provides-Extra: dev
|
|
50
52
|
Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
51
53
|
Requires-Dist: black>=23.11.0; extra == "dev"
|
|
@@ -53,14 +55,26 @@ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
|
53
55
|
Dynamic: license-file
|
|
54
56
|
|
|
55
57
|
# CodeGraphContext
|
|
58
|
+
|
|
59
|
+
<!-- ====== Project stats ====== -->
|
|
60
|
+
<!-- ====== Project stats ====== -->
|
|
61
|
+
|
|
62
|
+
[](https://github.com/Shashankss1205/CodeGraphContext/stargazers)
|
|
63
|
+
[](https://github.com/Shashankss1205/CodeGraphContext/network/members)
|
|
64
|
+
[](https://github.com/Shashankss1205/CodeGraphContext/issues)
|
|
65
|
+
[](https://github.com/Shashankss1205/CodeGraphContext/pulls)
|
|
66
|
+
[](https://github.com/Shashankss1205/CodeGraphContext/pulls?q=is%3Apr+is%3Aclosed)
|
|
67
|
+
[](https://github.com/Shashankss1205/CodeGraphContext/graphs/contributors)
|
|
68
|
+
[](https://github.com/Shashankss1205/CodeGraphContext)
|
|
56
69
|
[](https://github.com/Shashankss1205/CodeGraphContext/actions/workflows/test.yml)
|
|
57
|
-
[](https://pypi.org/project/codegraphcontext/)
|
|
58
|
-
[](https://pypi.org/project/codegraphcontext/)
|
|
59
|
-
[](https://pypi.org/project/codegraphcontext/)
|
|
71
|
+
[](https://pypi.org/project/codegraphcontext/)
|
|
72
|
+
[](LICENSE)
|
|
73
|
+
[](http://codegraphcontext.vercel.app/)
|
|
74
|
+
[](https://youtu.be/KYYSdxhg1xU)
|
|
75
|
+
[](https://discord.gg/dR4QY32uYQ)
|
|
76
|
+
|
|
77
|
+
|
|
64
78
|
|
|
65
79
|
An MCP server that indexes local code into a graph database to provide context to AI assistants.
|
|
66
80
|
|
|
@@ -71,7 +85,7 @@ An MCP server that indexes local code into a graph database to provide context t
|
|
|
71
85
|

|
|
72
86
|
|
|
73
87
|
## Project Details
|
|
74
|
-
- **Version:** 0.1.
|
|
88
|
+
- **Version:** 0.1.14
|
|
75
89
|
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
76
90
|
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
77
91
|
- **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
# CodeGraphContext
|
|
2
|
+
|
|
3
|
+
<!-- ====== Project stats ====== -->
|
|
4
|
+
<!-- ====== Project stats ====== -->
|
|
5
|
+
|
|
6
|
+
[](https://github.com/Shashankss1205/CodeGraphContext/stargazers)
|
|
7
|
+
[](https://github.com/Shashankss1205/CodeGraphContext/network/members)
|
|
8
|
+
[](https://github.com/Shashankss1205/CodeGraphContext/issues)
|
|
9
|
+
[](https://github.com/Shashankss1205/CodeGraphContext/pulls)
|
|
10
|
+
[](https://github.com/Shashankss1205/CodeGraphContext/pulls?q=is%3Apr+is%3Aclosed)
|
|
11
|
+
[](https://github.com/Shashankss1205/CodeGraphContext/graphs/contributors)
|
|
12
|
+
[](https://github.com/Shashankss1205/CodeGraphContext)
|
|
2
13
|
[](https://github.com/Shashankss1205/CodeGraphContext/actions/workflows/test.yml)
|
|
3
|
-
[](https://pypi.org/project/codegraphcontext/)
|
|
4
|
-
[](https://pypi.org/project/codegraphcontext/)
|
|
5
|
-
[](https://pypi.org/project/codegraphcontext/)
|
|
15
|
+
[](https://pypi.org/project/codegraphcontext/)
|
|
16
|
+
[](LICENSE)
|
|
17
|
+
[](http://codegraphcontext.vercel.app/)
|
|
18
|
+
[](https://youtu.be/KYYSdxhg1xU)
|
|
19
|
+
[](https://discord.gg/dR4QY32uYQ)
|
|
20
|
+
|
|
21
|
+
|
|
10
22
|
|
|
11
23
|
An MCP server that indexes local code into a graph database to provide context to AI assistants.
|
|
12
24
|
|
|
@@ -17,7 +29,7 @@ An MCP server that indexes local code into a graph database to provide context t
|
|
|
17
29
|

|
|
18
30
|
|
|
19
31
|
## Project Details
|
|
20
|
-
- **Version:** 0.1.
|
|
32
|
+
- **Version:** 0.1.14
|
|
21
33
|
- **Authors:** Shashank Shekhar Singh <shashankshekharsingh1205@gmail.com>
|
|
22
34
|
- **License:** MIT License (See [LICENSE](LICENSE) for details)
|
|
23
35
|
- **Website:** [CodeGraphContext](http://codegraphcontext.vercel.app/)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "codegraphcontext"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.14"
|
|
4
4
|
description = "An MCP server that indexes local code into a graph database to provide context to AI assistants."
|
|
5
5
|
authors = [{ name = "Shashank Shekhar Singh", email = "shashankshekharsingh1205@gmail.com" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -24,7 +24,9 @@ dependencies = [
|
|
|
24
24
|
"inquirerpy>=0.3.4",
|
|
25
25
|
"python-dotenv>=1.0.0",
|
|
26
26
|
"tree-sitter==0.20.4",
|
|
27
|
-
"tree-sitter-languages==1.10.2"
|
|
27
|
+
"tree-sitter-languages==1.10.2",
|
|
28
|
+
"pyyaml",
|
|
29
|
+
"pytest"
|
|
28
30
|
]
|
|
29
31
|
|
|
30
32
|
[project.urls]
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# src/codegraphcontext/cli/main.py
|
|
2
|
+
"""
|
|
3
|
+
This module defines the command-line interface (CLI) for the CodeGraphContext application.
|
|
4
|
+
It uses the Typer library to create a user-friendly and well-documented CLI.
|
|
5
|
+
|
|
6
|
+
Commands:
|
|
7
|
+
- setup: Runs an interactive wizard to configure the Neo4j database connection.
|
|
8
|
+
- start: Launches the main MCP server.
|
|
9
|
+
- tool: A placeholder for directly calling server tools (for debugging).
|
|
10
|
+
- help: Displays help information.
|
|
11
|
+
- version: Show the installed version.
|
|
12
|
+
"""
|
|
13
|
+
import typer
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.table import Table
|
|
16
|
+
from typing import Optional
|
|
17
|
+
import asyncio
|
|
18
|
+
import logging
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import time
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from dotenv import load_dotenv, find_dotenv
|
|
24
|
+
from importlib.metadata import version as pkg_version, PackageNotFoundError
|
|
25
|
+
from codegraphcontext.server import MCPServer
|
|
26
|
+
from .setup_wizard import run_setup_wizard
|
|
27
|
+
|
|
28
|
+
# Set the log level for the noisy neo4j logger to WARNING to keep the output clean.
|
|
29
|
+
logging.getLogger("neo4j").setLevel(logging.WARNING)
|
|
30
|
+
|
|
31
|
+
# Initialize the Typer app and Rich console for formatted output.
|
|
32
|
+
app = typer.Typer(
|
|
33
|
+
name="cgc",
|
|
34
|
+
help="CodeGraphContext: An MCP server for AI-powered code analysis.",
|
|
35
|
+
add_completion=False,
|
|
36
|
+
)
|
|
37
|
+
console = Console(stderr=True)
|
|
38
|
+
|
|
39
|
+
# Configure basic logging for the application.
|
|
40
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_version() -> str:
|
|
44
|
+
"""
|
|
45
|
+
Try to read version from the installed package metadata.
|
|
46
|
+
Fallback to a dev version if not installed.
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
return pkg_version("codegraphcontext") # must match [project].name in pyproject.toml
|
|
50
|
+
except PackageNotFoundError:
|
|
51
|
+
return "0.0.0 (dev)"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.command()
|
|
55
|
+
def setup():
|
|
56
|
+
"""
|
|
57
|
+
Runs the interactive setup wizard to configure the server and database connection.
|
|
58
|
+
This helps users set up a local Docker-based Neo4j instance or connect to a remote one.
|
|
59
|
+
"""
|
|
60
|
+
run_setup_wizard()
|
|
61
|
+
|
|
62
|
+
def _load_credentials():
|
|
63
|
+
"""
|
|
64
|
+
Loads Neo4j credentials from various sources into environment variables.
|
|
65
|
+
Priority order:
|
|
66
|
+
1. Local `mcp.json`
|
|
67
|
+
2. Global `~/.codegraphcontext/.env`
|
|
68
|
+
3. Any `.env` file found in the directory tree.
|
|
69
|
+
"""
|
|
70
|
+
# 1. Prefer loading from mcp.json
|
|
71
|
+
mcp_file_path = Path.cwd() / "mcp.json"
|
|
72
|
+
if mcp_file_path.exists():
|
|
73
|
+
try:
|
|
74
|
+
with open(mcp_file_path, "r") as f:
|
|
75
|
+
mcp_config = json.load(f)
|
|
76
|
+
server_env = mcp_config.get("mcpServers", {}).get("CodeGraphContext", {}).get("env", {})
|
|
77
|
+
for key, value in server_env.items():
|
|
78
|
+
os.environ[key] = value
|
|
79
|
+
console.print("[green]Loaded Neo4j credentials from local mcp.json.[/green]")
|
|
80
|
+
return
|
|
81
|
+
except Exception as e:
|
|
82
|
+
console.print(f"[bold red]Error loading mcp.json:[/bold red] {e}")
|
|
83
|
+
|
|
84
|
+
# 2. Try global .env file
|
|
85
|
+
global_env_path = Path.home() / ".codegraphcontext" / ".env"
|
|
86
|
+
if global_env_path.exists():
|
|
87
|
+
try:
|
|
88
|
+
load_dotenv(dotenv_path=global_env_path)
|
|
89
|
+
console.print(f"[green]Loaded Neo4j credentials from global .env file: {global_env_path}[/green]")
|
|
90
|
+
return
|
|
91
|
+
except Exception as e:
|
|
92
|
+
console.print(f"[bold red]Error loading global .env file from {global_env_path}:[/bold red] {e}")
|
|
93
|
+
|
|
94
|
+
# 3. Fallback to any discovered .env
|
|
95
|
+
try:
|
|
96
|
+
dotenv_path = find_dotenv(usecwd=True, raise_error_if_not_found=False)
|
|
97
|
+
if dotenv_path:
|
|
98
|
+
load_dotenv(dotenv_path)
|
|
99
|
+
console.print(f"[green]Loaded Neo4j credentials from discovered .env file: {dotenv_path}[/green]")
|
|
100
|
+
else:
|
|
101
|
+
console.print("[yellow]No local mcp.json or .env file found. Credentials may not be set.[/yellow]")
|
|
102
|
+
except Exception as e:
|
|
103
|
+
console.print(f"[bold red]Error loading .env file:[/bold red] {e}")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@app.command()
|
|
107
|
+
def start():
|
|
108
|
+
"""
|
|
109
|
+
Starts the CodeGraphContext MCP server, which listens for JSON-RPC requests from stdin.
|
|
110
|
+
"""
|
|
111
|
+
console.print("[bold green]Starting CodeGraphContext Server...[/bold green]")
|
|
112
|
+
_load_credentials()
|
|
113
|
+
|
|
114
|
+
server = None
|
|
115
|
+
loop = asyncio.new_event_loop()
|
|
116
|
+
asyncio.set_event_loop(loop)
|
|
117
|
+
try:
|
|
118
|
+
# Initialize and run the main server.
|
|
119
|
+
server = MCPServer(loop=loop)
|
|
120
|
+
loop.run_until_complete(server.run())
|
|
121
|
+
except ValueError as e:
|
|
122
|
+
# This typically happens if credentials are still not found after all checks.
|
|
123
|
+
console.print(f"[bold red]Configuration Error:[/bold red] {e}")
|
|
124
|
+
console.print("Please run `cgc setup` to configure the server.")
|
|
125
|
+
except KeyboardInterrupt:
|
|
126
|
+
# Handle graceful shutdown on Ctrl+C.
|
|
127
|
+
console.print("\n[bold yellow]Server stopped by user.[/bold yellow]")
|
|
128
|
+
finally:
|
|
129
|
+
# Ensure server and event loop are properly closed.
|
|
130
|
+
if server:
|
|
131
|
+
server.shutdown()
|
|
132
|
+
loop.close()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _run_tool(tool_name: str, tool_args: dict):
|
|
137
|
+
"""Helper function to run a tool and handle the server lifecycle."""
|
|
138
|
+
_load_credentials()
|
|
139
|
+
try:
|
|
140
|
+
loop = asyncio.new_event_loop()
|
|
141
|
+
asyncio.set_event_loop(loop)
|
|
142
|
+
server = MCPServer(loop=loop)
|
|
143
|
+
|
|
144
|
+
result = loop.run_until_complete(server.handle_tool_call(tool_name, tool_args))
|
|
145
|
+
|
|
146
|
+
if isinstance(result, dict) and "job_id" in result:
|
|
147
|
+
job_id = result["job_id"]
|
|
148
|
+
console.print(f"[green]Successfully started job '{job_id}' for tool '{tool_name}'.[/green]")
|
|
149
|
+
console.print(f"Estimated files: {result.get('estimated_files')}, Estimated duration: {result.get('estimated_duration_human')}")
|
|
150
|
+
console.print(f"\n[bold yellow]Polling for completion...[/bold yellow]")
|
|
151
|
+
|
|
152
|
+
while True:
|
|
153
|
+
time.sleep(2)
|
|
154
|
+
status_result = loop.run_until_complete(server.handle_tool_call("check_job_status", {"job_id": job_id}))
|
|
155
|
+
job_status = status_result.get("job", {}).get("status")
|
|
156
|
+
processed_files = status_result.get("job", {}).get("processed_files", 0)
|
|
157
|
+
total_files = status_result.get("job", {}).get("total_files", 0)
|
|
158
|
+
console.print(f"Job status: {job_status} ({processed_files}/{total_files} files)")
|
|
159
|
+
if job_status in ["completed", "failed", "cancelled"]:
|
|
160
|
+
console.print(json.dumps(status_result, indent=2))
|
|
161
|
+
break
|
|
162
|
+
else:
|
|
163
|
+
console.print(json.dumps(result, indent=2))
|
|
164
|
+
|
|
165
|
+
except ValueError as e:
|
|
166
|
+
console.print(f"[bold red]Configuration Error:[/bold red] {e}")
|
|
167
|
+
except Exception as e:
|
|
168
|
+
console.print(f"[bold red]An unexpected error occurred:[/bold red] {e}")
|
|
169
|
+
finally:
|
|
170
|
+
if 'loop' in locals() and loop.is_running():
|
|
171
|
+
loop.close()
|
|
172
|
+
|
|
173
|
+
@app.command()
|
|
174
|
+
def index(path: Optional[str] = typer.Argument(None, help="Path to the directory or file to index. Defaults to the current directory.")):
|
|
175
|
+
"""
|
|
176
|
+
Indexes a directory or file by adding it to the code graph.
|
|
177
|
+
If no path is provided, it indexes the current directory.
|
|
178
|
+
"""
|
|
179
|
+
if path is None:
|
|
180
|
+
path = "."
|
|
181
|
+
_run_tool("add_code_to_graph", {"path": path})
|
|
182
|
+
|
|
183
|
+
@app.command()
|
|
184
|
+
def delete(path: str = typer.Argument(..., help="Path of the repository to delete from the code graph.")):
|
|
185
|
+
"""
|
|
186
|
+
Deletes a repository from the code graph.
|
|
187
|
+
"""
|
|
188
|
+
_run_tool("delete_repository", {"repo_path": path})
|
|
189
|
+
|
|
190
|
+
@app.command()
|
|
191
|
+
def visualize(query: Optional[str] = typer.Argument(None, help="The Cypher query to visualize.")):
|
|
192
|
+
"""
|
|
193
|
+
Generates a URL to visualize a Cypher query in the Neo4j Browser.
|
|
194
|
+
If no query is provided, a default query will be used.
|
|
195
|
+
"""
|
|
196
|
+
if query is None:
|
|
197
|
+
query = "MATCH p=()-->() RETURN p"
|
|
198
|
+
_run_tool("visualize_graph_query", {"cypher_query": query})
|
|
199
|
+
|
|
200
|
+
@app.command(name="list_repos")
|
|
201
|
+
def list_repos():
|
|
202
|
+
"""
|
|
203
|
+
Lists all indexed repositories.
|
|
204
|
+
"""
|
|
205
|
+
_run_tool("list_indexed_repositories", {})
|
|
206
|
+
|
|
207
|
+
@app.command(name="add_package")
|
|
208
|
+
def add_package(package_name: str = typer.Argument(..., help="Name of the Python package to add.")):
|
|
209
|
+
"""
|
|
210
|
+
Adds a Python package to the code graph.
|
|
211
|
+
"""
|
|
212
|
+
_run_tool("add_package_to_graph", {"package_name": package_name})
|
|
213
|
+
|
|
214
|
+
@app.command()
|
|
215
|
+
def cypher(query: str = typer.Argument(..., help="The read-only Cypher query to execute.")):
|
|
216
|
+
"""
|
|
217
|
+
Executes a read-only Cypher query.
|
|
218
|
+
"""
|
|
219
|
+
_run_tool("execute_cypher_query", {"cypher_query": query})
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@app.command(name="list_mcp_tools")
|
|
223
|
+
def list_mcp_tools():
|
|
224
|
+
"""
|
|
225
|
+
Lists all available tools and their descriptions.
|
|
226
|
+
"""
|
|
227
|
+
_load_credentials()
|
|
228
|
+
console.print("[bold green]Available Tools:[/bold green]")
|
|
229
|
+
try:
|
|
230
|
+
# Instantiate the server to access the tool definitions.
|
|
231
|
+
server = MCPServer()
|
|
232
|
+
tools = server.tools.values()
|
|
233
|
+
|
|
234
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
235
|
+
table.add_column("Tool Name", style="dim", width=30)
|
|
236
|
+
table.add_column("Description")
|
|
237
|
+
|
|
238
|
+
for tool in sorted(tools, key=lambda t: t['name']):
|
|
239
|
+
table.add_row(tool['name'], tool['description'])
|
|
240
|
+
|
|
241
|
+
console.print(table)
|
|
242
|
+
|
|
243
|
+
except ValueError as e:
|
|
244
|
+
console.print(f"[bold red]Error loading tools:[/bold red] {e}")
|
|
245
|
+
console.print("Please ensure your Neo4j credentials are set up correctly (`cgc setup`), as they are needed to initialize the server.")
|
|
246
|
+
except Exception as e:
|
|
247
|
+
console.print(f"[bold red]An unexpected error occurred:[/bold red] {e}")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@app.command()
|
|
251
|
+
def help(ctx: typer.Context):
|
|
252
|
+
"""Show the main help message and exit."""
|
|
253
|
+
root_ctx = ctx.parent or ctx
|
|
254
|
+
typer.echo(root_ctx.get_help())
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@app.command("version")
|
|
258
|
+
def version_cmd():
|
|
259
|
+
"""Show the application version."""
|
|
260
|
+
console.print(f"CodeGraphContext [bold cyan]{get_version()}[/bold cyan]")
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@app.callback(invoke_without_command=True)
|
|
264
|
+
def main(
|
|
265
|
+
ctx: typer.Context,
|
|
266
|
+
version_: bool = typer.Option(
|
|
267
|
+
None,
|
|
268
|
+
"--version",
|
|
269
|
+
"-v",
|
|
270
|
+
help="Show the application version and exit.",
|
|
271
|
+
is_eager=True,
|
|
272
|
+
),
|
|
273
|
+
):
|
|
274
|
+
"""
|
|
275
|
+
Main entry point for the cgc CLI application.
|
|
276
|
+
If no subcommand is provided, it displays a welcome message with instructions.
|
|
277
|
+
"""
|
|
278
|
+
if version_:
|
|
279
|
+
console.print(f"CodeGraphContext [bold cyan]{get_version()}[/bold cyan]")
|
|
280
|
+
raise typer.Exit()
|
|
281
|
+
|
|
282
|
+
if ctx.invoked_subcommand is None:
|
|
283
|
+
console.print("[bold green]👋 Welcome to CodeGraphContext (cgc)![/bold green]\n")
|
|
284
|
+
console.print("👉 Run [cyan]cgc setup[/cyan] to configure the server and database.")
|
|
285
|
+
console.print("👉 Run [cyan]cgc start[/cyan] to launch the server.")
|
|
286
|
+
console.print("👉 Run [cyan]cgc help[/cyan] to see all available commands.\n")
|
|
287
|
+
console.print("👉 Run [cyan]cgc --version[/cyan] to check the version.\n")
|
|
288
|
+
console.print("👉 Running [green]codegraphcontext [white]works the same as using [green]cgc")
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# src/codegraphcontext/cli/setup_macos.py
|
|
2
|
+
import platform
|
|
3
|
+
import time
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
def _has_brew(run_command, console) -> bool:
|
|
7
|
+
return run_command(["which", "brew"], console, check=False) is not None
|
|
8
|
+
|
|
9
|
+
def _brew_install_neo4j(run_command, console) -> str:
|
|
10
|
+
if run_command(["brew", "install", "neo4j@5"], console, check=False):
|
|
11
|
+
return "neo4j@5"
|
|
12
|
+
if run_command(["brew", "install", "neo4j"], console, check=False):
|
|
13
|
+
return "neo4j"
|
|
14
|
+
return ""
|
|
15
|
+
|
|
16
|
+
def _brew_start(service: str, run_command, console) -> bool:
|
|
17
|
+
return run_command(["brew", "services", "start", service], console, check=False) is not None
|
|
18
|
+
|
|
19
|
+
def _set_initial_password(new_pw: str, run_command, console) -> bool:
|
|
20
|
+
cmd = [
|
|
21
|
+
"cypher-shell",
|
|
22
|
+
"-u", "neo4j",
|
|
23
|
+
"-p", "neo4j",
|
|
24
|
+
f"ALTER CURRENT USER SET PASSWORD FROM 'neo4j' TO '{new_pw}'",
|
|
25
|
+
]
|
|
26
|
+
return run_command(cmd, console, check=False) is not None
|
|
27
|
+
|
|
28
|
+
def setup_macos_binary(console, prompt, run_command, _generate_mcp_json):
|
|
29
|
+
"""Automates Neo4j install & config on macOS via Homebrew."""
|
|
30
|
+
os_name = platform.system()
|
|
31
|
+
console.print(f"Detected Operating System: [bold yellow]{os_name}[/bold yellow]")
|
|
32
|
+
|
|
33
|
+
if os_name != "Darwin":
|
|
34
|
+
console.print("[yellow]This installer is for macOS only.[/yellow]")
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
console.print("[bold]Starting automated Neo4j installation for macOS.[/bold]")
|
|
38
|
+
|
|
39
|
+
if not prompt([{
|
|
40
|
+
"type": "confirm",
|
|
41
|
+
"message": "Proceed with Homebrew-based install of Neo4j?",
|
|
42
|
+
"name": "proceed",
|
|
43
|
+
"default": True
|
|
44
|
+
}]).get("proceed"):
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
console.print("\n[bold]Step: Checking for Homebrew...[/bold]")
|
|
48
|
+
if not _has_brew(run_command, console):
|
|
49
|
+
console.print(
|
|
50
|
+
"[bold red]Homebrew not found.[/bold red] "
|
|
51
|
+
"Install from [bold blue]https://brew.sh[/bold blue] and re-run this setup."
|
|
52
|
+
)
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
console.print("\n[bold]Step: Installing Neo4j via Homebrew...[/bold]")
|
|
56
|
+
service = _brew_install_neo4j(run_command, console)
|
|
57
|
+
if not service:
|
|
58
|
+
console.print("[bold red]Failed to install Neo4j via Homebrew.[/bold red]")
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
console.print(f"\n[bold]Step: Starting Neo4j service ({service})...[/bold]")
|
|
62
|
+
if not _brew_start(service, run_command, console):
|
|
63
|
+
console.print("[bold red]Failed to start Neo4j with brew services.[/bold red]")
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
while True:
|
|
67
|
+
answers = prompt([
|
|
68
|
+
{"type": "password", "message": "Enter a new password for Neo4j:", "name": "pw"},
|
|
69
|
+
{"type": "password", "message": "Confirm the new password:", "name": "pw2"},
|
|
70
|
+
]) or {}
|
|
71
|
+
if not answers:
|
|
72
|
+
return
|
|
73
|
+
pw, pw2 = answers.get("pw"), answers.get("pw2")
|
|
74
|
+
if pw and pw == pw2:
|
|
75
|
+
new_password = pw
|
|
76
|
+
break
|
|
77
|
+
console.print("[red]Passwords do not match or are empty. Please try again.[/red]")
|
|
78
|
+
|
|
79
|
+
console.print("\n[yellow]Waiting 10 seconds for Neo4j to finish starting...[/yellow]")
|
|
80
|
+
time.sleep(10)
|
|
81
|
+
|
|
82
|
+
console.print("\n[bold]Step: Setting initial password with cypher-shell...[/bold]")
|
|
83
|
+
if not _set_initial_password(new_password, run_command, console):
|
|
84
|
+
console.print(
|
|
85
|
+
"[bold red]Failed to set the initial password.[/bold red]\n"
|
|
86
|
+
"Try manually:\n"
|
|
87
|
+
" cypher-shell -u neo4j -p neo4j \"ALTER CURRENT USER SET PASSWORD FROM 'neo4j' TO '<your_pw>'\""
|
|
88
|
+
)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
creds = {"uri": "neo4j://localhost:7687", "username": "neo4j", "password": new_password}
|
|
92
|
+
_generate_mcp_json(creds)
|
|
93
|
+
|
|
94
|
+
env_path = Path.home() / ".codegraphcontext" / ".env"
|
|
95
|
+
console.print(f"\n[bold green]All done![/bold green] Neo4j running. Credentials saved to: [bold]{env_path}[/bold]")
|
{codegraphcontext-0.1.13 → codegraphcontext-0.1.14}/src/codegraphcontext/cli/setup_wizard.py
RENAMED
|
@@ -374,11 +374,16 @@ def setup_local_db():
|
|
|
374
374
|
]
|
|
375
375
|
result = prompt(questions)
|
|
376
376
|
local_method = result.get("local_method")
|
|
377
|
-
|
|
377
|
+
|
|
378
378
|
if local_method and "Docker" in local_method:
|
|
379
379
|
setup_docker()
|
|
380
380
|
elif local_method:
|
|
381
|
-
|
|
381
|
+
if platform.system() == "Darwin":
|
|
382
|
+
# lazy import to avoid circular import
|
|
383
|
+
from .setup_macos import setup_macos_binary
|
|
384
|
+
setup_macos_binary(console, prompt, run_command, _generate_mcp_json)
|
|
385
|
+
else:
|
|
386
|
+
setup_local_binary()
|
|
382
387
|
|
|
383
388
|
def setup_docker():
|
|
384
389
|
"""Creates Docker files and runs docker-compose for Neo4j."""
|
|
@@ -25,7 +25,7 @@ class RepositoryEventHandler(FileSystemEventHandler):
|
|
|
25
25
|
to build a baseline and then uses this cached state to perform efficient
|
|
26
26
|
updates when files are changed, created, or deleted.
|
|
27
27
|
"""
|
|
28
|
-
def __init__(self, graph_builder: "GraphBuilder", repo_path: Path, debounce_interval=2.0):
|
|
28
|
+
def __init__(self, graph_builder: "GraphBuilder", repo_path: Path, debounce_interval=2.0, perform_initial_scan: bool = True):
|
|
29
29
|
"""
|
|
30
30
|
Initializes the event handler.
|
|
31
31
|
|
|
@@ -33,6 +33,7 @@ class RepositoryEventHandler(FileSystemEventHandler):
|
|
|
33
33
|
graph_builder: An instance of the GraphBuilder to perform graph operations.
|
|
34
34
|
repo_path: The absolute path to the repository directory to watch.
|
|
35
35
|
debounce_interval: The time in seconds to wait for more changes before processing an event.
|
|
36
|
+
perform_initial_scan: Whether to perform an initial scan of the repository.
|
|
36
37
|
"""
|
|
37
38
|
super().__init__()
|
|
38
39
|
self.graph_builder = graph_builder
|
|
@@ -45,7 +46,8 @@ class RepositoryEventHandler(FileSystemEventHandler):
|
|
|
45
46
|
self.imports_map = {}
|
|
46
47
|
|
|
47
48
|
# Perform the initial scan and linking when the watcher is created.
|
|
48
|
-
|
|
49
|
+
if perform_initial_scan:
|
|
50
|
+
self._initial_scan()
|
|
49
51
|
|
|
50
52
|
def _initial_scan(self):
|
|
51
53
|
"""Scans the entire repository, parses all files, and builds the initial graph."""
|
|
@@ -82,30 +84,39 @@ class RepositoryEventHandler(FileSystemEventHandler):
|
|
|
82
84
|
def _handle_modification(self, event_path_str: str):
|
|
83
85
|
"""
|
|
84
86
|
Orchestrates the complete update cycle for a modified or created file.
|
|
85
|
-
This involves
|
|
87
|
+
This involves re-scanning the entire repo to update cross-file relationships.
|
|
86
88
|
"""
|
|
87
|
-
logger.info(f"File change detected, starting full
|
|
89
|
+
logger.info(f"File change detected, starting full repository refresh for: {event_path_str}")
|
|
88
90
|
modified_path = Path(event_path_str)
|
|
89
91
|
|
|
90
|
-
# 1.
|
|
91
|
-
|
|
92
|
+
# 1. Get all supported files in the repository.
|
|
93
|
+
supported_extensions = self.graph_builder.parsers.keys()
|
|
94
|
+
all_files = [f for f in self.repo_path.rglob("*") if f.is_file() and f.suffix in supported_extensions]
|
|
95
|
+
|
|
96
|
+
# 2. Re-scan all files to get a fresh, global map of all symbols.
|
|
97
|
+
self.imports_map = self.graph_builder._pre_scan_for_imports(all_files)
|
|
98
|
+
logger.info("Refreshed global imports map.")
|
|
99
|
+
|
|
100
|
+
# 3. Update the specific file that changed in the graph.
|
|
101
|
+
# This deletes old nodes and adds new ones for the single file.
|
|
102
|
+
self.graph_builder.update_file_in_graph(
|
|
92
103
|
modified_path, self.repo_path, self.imports_map
|
|
93
104
|
)
|
|
94
105
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
# 4. Re-parse all files to have a complete, in-memory representation for the linking pass.
|
|
107
|
+
# This is necessary because a change in one file can affect relationships in others.
|
|
108
|
+
self.all_file_data = []
|
|
109
|
+
for f in all_files:
|
|
110
|
+
parsed_data = self.graph_builder.parse_file(self.repo_path, f)
|
|
111
|
+
if "error" not in parsed_data:
|
|
112
|
+
self.all_file_data.append(parsed_data)
|
|
113
|
+
logger.info("Refreshed in-memory cache of all file data.")
|
|
103
114
|
|
|
104
|
-
#
|
|
105
|
-
|
|
106
|
-
logger.info("Re-linking the call graph with updated information...")
|
|
115
|
+
# 5. CRITICAL: Re-link the entire graph using the fully updated cache and imports map.
|
|
116
|
+
logger.info("Re-linking the entire graph for calls and inheritance...")
|
|
107
117
|
self.graph_builder._create_all_function_calls(self.all_file_data, self.imports_map)
|
|
108
|
-
|
|
118
|
+
self.graph_builder._create_all_inheritance_links(self.all_file_data, self.imports_map)
|
|
119
|
+
logger.info(f"Graph refresh for change in {event_path_str} complete! ✅")
|
|
109
120
|
|
|
110
121
|
# The following methods are called by the watchdog observer when a file event occurs.
|
|
111
122
|
def on_created(self, event):
|
|
@@ -136,8 +147,9 @@ class CodeWatcher:
|
|
|
136
147
|
self.graph_builder = graph_builder
|
|
137
148
|
self.observer = Observer()
|
|
138
149
|
self.watched_paths = set() # Keep track of paths already being watched.
|
|
150
|
+
self.watches = {} # Store watch objects to allow unscheduling
|
|
139
151
|
|
|
140
|
-
def watch_directory(self, path: str):
|
|
152
|
+
def watch_directory(self, path: str, perform_initial_scan: bool = True):
|
|
141
153
|
"""Schedules a directory to be watched for changes."""
|
|
142
154
|
path_obj = Path(path).resolve()
|
|
143
155
|
path_str = str(path_obj)
|
|
@@ -147,13 +159,34 @@ class CodeWatcher:
|
|
|
147
159
|
return {"message": f"Path already being watched: {path_str}"}
|
|
148
160
|
|
|
149
161
|
# Create a new, dedicated event handler for this specific repository path.
|
|
150
|
-
event_handler = RepositoryEventHandler(self.graph_builder, path_obj)
|
|
162
|
+
event_handler = RepositoryEventHandler(self.graph_builder, path_obj, perform_initial_scan=perform_initial_scan)
|
|
151
163
|
|
|
152
|
-
self.observer.schedule(event_handler, path_str, recursive=True)
|
|
164
|
+
watch = self.observer.schedule(event_handler, path_str, recursive=True)
|
|
165
|
+
self.watches[path_str] = watch
|
|
153
166
|
self.watched_paths.add(path_str)
|
|
154
167
|
logger.info(f"Started watching for code changes in: {path_str}")
|
|
155
168
|
|
|
156
|
-
return {"message": f"Started watching {path_str}.
|
|
169
|
+
return {"message": f"Started watching {path_str}."}
|
|
170
|
+
def unwatch_directory(self, path: str):
|
|
171
|
+
"""Stops watching a directory for changes."""
|
|
172
|
+
path_obj = Path(path).resolve()
|
|
173
|
+
path_str = str(path_obj)
|
|
174
|
+
|
|
175
|
+
if path_str not in self.watched_paths:
|
|
176
|
+
logger.warning(f"Attempted to unwatch a path that is not being watched: {path_str}")
|
|
177
|
+
return {"error": f"Path not currently being watched: {path_str}"}
|
|
178
|
+
|
|
179
|
+
watch = self.watches.pop(path_str, None)
|
|
180
|
+
if watch:
|
|
181
|
+
self.observer.unschedule(watch)
|
|
182
|
+
|
|
183
|
+
self.watched_paths.discard(path_str)
|
|
184
|
+
logger.info(f"Stopped watching for code changes in: {path_str}")
|
|
185
|
+
return {"message": f"Stopped watching {path_str}."}
|
|
186
|
+
|
|
187
|
+
def list_watched_paths(self) -> list:
|
|
188
|
+
"""Returns a list of all currently watched directory paths."""
|
|
189
|
+
return list(self.watched_paths)
|
|
157
190
|
|
|
158
191
|
def start(self):
|
|
159
192
|
"""Starts the observer thread."""
|