codegraphcontext 0.4.8__py3-none-any.whl → 0.4.10__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.
- codegraphcontext/api/__init__.py +0 -0
- codegraphcontext/api/app.py +65 -0
- codegraphcontext/api/mcp_sse.py +64 -0
- codegraphcontext/api/router.py +102 -0
- codegraphcontext/api/schemas.py +27 -0
- codegraphcontext/cli/__init__.py +1 -1
- codegraphcontext/cli/cli_helpers.py +40 -2
- codegraphcontext/cli/config_manager.py +28 -5
- codegraphcontext/cli/main.py +72 -11
- codegraphcontext/cli/setup_wizard.py +5 -5
- codegraphcontext/cli/visualizer.py +1 -0
- codegraphcontext/core/__init__.py +10 -3
- codegraphcontext/core/bundle_registry.py +1 -0
- codegraphcontext/core/cgcignore.py +1 -0
- codegraphcontext/core/database_falkordb.py +14 -3
- codegraphcontext/core/database_kuzu.py +280 -137
- codegraphcontext/core/database_ladybug.py +1138 -0
- codegraphcontext/core/falkor_worker.py +1 -0
- codegraphcontext/core/watcher.py +6 -4
- codegraphcontext/prompts.py +2 -2
- codegraphcontext/server.py +45 -0
- codegraphcontext/tool_definitions.py +1 -0
- codegraphcontext/tools/__init__.py +1 -0
- codegraphcontext/tools/advanced_language_query_tool.py +1 -0
- codegraphcontext/tools/code_finder.py +67 -49
- codegraphcontext/tools/datasources/__init__.py +1 -0
- codegraphcontext/tools/datasources/cassandra_ingester.py +1 -0
- codegraphcontext/tools/datasources/mysql_ingester.py +1 -0
- codegraphcontext/tools/datasources/redis_ingester.py +1 -0
- codegraphcontext/tools/graph_builder.py +20 -37
- codegraphcontext/tools/handlers/analysis_handlers.py +4 -2
- codegraphcontext/tools/handlers/indexing_handlers.py +1 -0
- codegraphcontext/tools/handlers/management_handlers.py +1 -0
- codegraphcontext/tools/handlers/query_handlers.py +1 -0
- codegraphcontext/tools/handlers/watcher_handlers.py +1 -0
- codegraphcontext/tools/indexing/__init__.py +1 -0
- codegraphcontext/tools/indexing/constants.py +1 -0
- codegraphcontext/tools/indexing/discovery.py +73 -18
- codegraphcontext/tools/indexing/embeddings.py +1 -0
- codegraphcontext/tools/indexing/persistence/__init__.py +1 -0
- codegraphcontext/tools/indexing/persistence/writer.py +307 -132
- codegraphcontext/tools/indexing/pipeline.py +46 -21
- codegraphcontext/tools/indexing/pre_scan.py +1 -0
- codegraphcontext/tools/indexing/resolution/__init__.py +1 -0
- codegraphcontext/tools/indexing/resolution/calls.py +154 -48
- codegraphcontext/tools/indexing/resolution/inheritance.py +11 -3
- codegraphcontext/tools/indexing/resolution/post_resolution.py +1 -0
- codegraphcontext/tools/indexing/sanitize.py +1 -0
- codegraphcontext/tools/indexing/schema.py +72 -64
- codegraphcontext/tools/indexing/schema_contract.py +1 -0
- codegraphcontext/tools/indexing/scip_pipeline.py +12 -8
- codegraphcontext/tools/indexing/vector_resolver.py +1 -0
- codegraphcontext/tools/languages/c.py +87 -4
- codegraphcontext/tools/languages/cpp.py +110 -53
- codegraphcontext/tools/languages/csharp.py +24 -4
- codegraphcontext/tools/languages/css.py +3 -3
- codegraphcontext/tools/languages/dart.py +139 -80
- codegraphcontext/tools/languages/elixir.py +55 -9
- codegraphcontext/tools/languages/go.py +64 -2
- codegraphcontext/tools/languages/gradle.py +1 -0
- codegraphcontext/tools/languages/haskell.py +427 -426
- codegraphcontext/tools/languages/html.py +9 -8
- codegraphcontext/tools/languages/java.py +36 -11
- codegraphcontext/tools/languages/javascript.py +2 -0
- codegraphcontext/tools/languages/kotlin.py +42 -6
- codegraphcontext/tools/languages/lua.py +1 -0
- codegraphcontext/tools/languages/maven.py +1 -0
- codegraphcontext/tools/languages/mybatis.py +1 -0
- codegraphcontext/tools/languages/perl.py +27 -4
- codegraphcontext/tools/languages/php.py +7 -0
- codegraphcontext/tools/languages/python.py +8 -1
- codegraphcontext/tools/languages/ruby.py +21 -1
- codegraphcontext/tools/languages/rust.py +59 -39
- codegraphcontext/tools/languages/scala.py +20 -7
- codegraphcontext/tools/languages/swift.py +58 -53
- codegraphcontext/tools/languages/typescript.py +4 -3
- codegraphcontext/tools/languages/typescriptjsx.py +1 -0
- codegraphcontext/tools/query_tool_languages/c_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/cpp_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/csharp_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/dart_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/go_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/haskell_toolkit.py +5 -4
- codegraphcontext/tools/query_tool_languages/java_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/javascript_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/perl_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/python_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/ruby_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/rust_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/scala_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/swift_toolkit.py +1 -0
- codegraphcontext/tools/query_tool_languages/typescript_toolkit.py +1 -0
- codegraphcontext/tools/report_generator.py +1 -0
- codegraphcontext/tools/scip_indexer.py +10 -0
- codegraphcontext/tools/scip_pb2.py +1 -0
- codegraphcontext/tools/tree_sitter_parser.py +1 -0
- codegraphcontext/tools/type_utils.py +1 -0
- codegraphcontext/utils/debug_log.py +1 -0
- codegraphcontext/utils/git_utils.py +1 -0
- codegraphcontext/utils/path_ignore.py +1 -0
- codegraphcontext/utils/repo_path.py +1 -0
- codegraphcontext/utils/tool_limits.py +1 -0
- codegraphcontext/utils/tree_sitter_manager.py +1 -0
- codegraphcontext/utils/visualize_graph.py +1 -0
- codegraphcontext/viz/server.py +1 -0
- {codegraphcontext-0.4.8.dist-info → codegraphcontext-0.4.10.dist-info}/METADATA +11 -10
- codegraphcontext-0.4.10.dist-info/RECORD +156 -0
- codegraphcontext-0.4.8.dist-info/RECORD +0 -150
- {codegraphcontext-0.4.8.dist-info → codegraphcontext-0.4.10.dist-info}/WHEEL +0 -0
- {codegraphcontext-0.4.8.dist-info → codegraphcontext-0.4.10.dist-info}/entry_points.txt +0 -0
- {codegraphcontext-0.4.8.dist-info → codegraphcontext-0.4.10.dist-info}/licenses/LICENSE +0 -0
- {codegraphcontext-0.4.8.dist-info → codegraphcontext-0.4.10.dist-info}/top_level.txt +0 -0
|
File without changes
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# src/codegraphcontext/api/app.py
|
|
2
|
+
import os
|
|
3
|
+
from fastapi import FastAPI
|
|
4
|
+
from fastapi.responses import HTMLResponse
|
|
5
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
6
|
+
from .router import router
|
|
7
|
+
from .mcp_sse import handle_sse, handle_messages
|
|
8
|
+
|
|
9
|
+
def create_app() -> FastAPI:
|
|
10
|
+
app = FastAPI(
|
|
11
|
+
title="CodeGraphContext Gateway",
|
|
12
|
+
description="HTTP API gateway for CodeGraphContext MCP server. Enables integration with ChatGPT Actions, Claude, and web frontends.",
|
|
13
|
+
version="0.1.0"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Enable CORS for the website/frontend
|
|
17
|
+
app.add_middleware(
|
|
18
|
+
CORSMiddleware,
|
|
19
|
+
allow_origins=["*"], # In production, restrict this
|
|
20
|
+
allow_credentials=True,
|
|
21
|
+
allow_methods=["*"],
|
|
22
|
+
allow_headers=["*"],
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
app.include_router(router, prefix="/api/v1")
|
|
26
|
+
|
|
27
|
+
# MCP-over-SSE Endpoints
|
|
28
|
+
app.add_api_route("/api/v1/mcp/sse", handle_sse, methods=["GET"])
|
|
29
|
+
app.add_api_route("/api/v1/mcp/messages", handle_messages, methods=["POST"])
|
|
30
|
+
|
|
31
|
+
@app.get("/", response_class=HTMLResponse)
|
|
32
|
+
async def root():
|
|
33
|
+
return """
|
|
34
|
+
<!DOCTYPE html>
|
|
35
|
+
<html>
|
|
36
|
+
<head>
|
|
37
|
+
<title>CGC Gateway</title>
|
|
38
|
+
<style>
|
|
39
|
+
body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background: #0f172a; color: white; margin: 0; }
|
|
40
|
+
.card { background: #1e293b; padding: 2rem; border-radius: 1rem; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); text-align: center; max-width: 400px; border: 1px solid #334155; }
|
|
41
|
+
h1 { color: #38bdf8; margin-top: 0; }
|
|
42
|
+
p { color: #94a3b8; line-height: 1.6; }
|
|
43
|
+
.btn { display: inline-block; background: #38bdf8; color: #0f172a; padding: 0.75rem 1.5rem; border-radius: 0.5rem; text-decoration: none; font-weight: bold; margin-top: 1rem; transition: background 0.2s; }
|
|
44
|
+
.btn:hover { background: #7dd3fc; }
|
|
45
|
+
.links { margin-top: 1.5rem; font-size: 0.9rem; }
|
|
46
|
+
.links a { color: #38bdf8; text-decoration: none; margin: 0 0.5rem; }
|
|
47
|
+
</style>
|
|
48
|
+
</head>
|
|
49
|
+
<body>
|
|
50
|
+
<div class="card">
|
|
51
|
+
<h1>CGC Gateway</h1>
|
|
52
|
+
<p>CodeGraphContext HTTP API is running. This gateway allows ChatGPT and Claude to interact with your code graph.</p>
|
|
53
|
+
<a href="/docs" class="btn">View API Docs</a>
|
|
54
|
+
<div class="links">
|
|
55
|
+
<a href="/openapi.json">OpenAPI Spec</a>
|
|
56
|
+
<a href="https://github.com/Shashankss1205/CodeGraphContext" target="_blank">GitHub</a>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</body>
|
|
60
|
+
</html>
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
return app
|
|
64
|
+
|
|
65
|
+
app = create_app()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# src/codegraphcontext/api/mcp_sse.py
|
|
2
|
+
import json
|
|
3
|
+
import asyncio
|
|
4
|
+
from fastapi import Request
|
|
5
|
+
from mcp.server import Server
|
|
6
|
+
from mcp.server.models import InitializationOptions
|
|
7
|
+
from mcp.types import Tool, TextContent, ServerCapabilities, ToolsCapability
|
|
8
|
+
from mcp.server.sse import SseServerTransport
|
|
9
|
+
|
|
10
|
+
from codegraphcontext.api.router import get_server
|
|
11
|
+
from codegraphcontext.tool_definitions import TOOLS
|
|
12
|
+
|
|
13
|
+
# Create the MCP Server instance using the SDK
|
|
14
|
+
mcp_server = Server("CodeGraphContext")
|
|
15
|
+
|
|
16
|
+
@mcp_server.list_tools()
|
|
17
|
+
async def handle_list_tools() -> list[Tool]:
|
|
18
|
+
"""List available tools."""
|
|
19
|
+
tools = []
|
|
20
|
+
for name, defn in TOOLS.items():
|
|
21
|
+
tools.append(Tool(
|
|
22
|
+
name=name,
|
|
23
|
+
description=defn["description"],
|
|
24
|
+
inputSchema=defn["inputSchema"]
|
|
25
|
+
))
|
|
26
|
+
return tools
|
|
27
|
+
|
|
28
|
+
@mcp_server.call_tool()
|
|
29
|
+
async def handle_call_tool(name: str, arguments: dict | None) -> list[TextContent]:
|
|
30
|
+
"""Handle tool execution."""
|
|
31
|
+
server = get_server()
|
|
32
|
+
args = arguments or {}
|
|
33
|
+
|
|
34
|
+
# Execute via the existing handler logic
|
|
35
|
+
result = await server.handle_tool_call(name, args)
|
|
36
|
+
|
|
37
|
+
if "error" in result:
|
|
38
|
+
return [TextContent(type="text", text=f"Error: {result['error']}")]
|
|
39
|
+
|
|
40
|
+
# Format result as JSON string for the AI
|
|
41
|
+
return [TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
42
|
+
|
|
43
|
+
# Create the SSE transport.
|
|
44
|
+
# The messages_url is where the client will POST JSON-RPC messages.
|
|
45
|
+
sse = SseServerTransport("/api/v1/mcp/messages")
|
|
46
|
+
|
|
47
|
+
async def handle_sse(request: Request):
|
|
48
|
+
"""Entry point for the SSE connection."""
|
|
49
|
+
async with sse.connect_sse(request.scope, request.receive, request._send) as (read_stream, write_stream):
|
|
50
|
+
await mcp_server.run(
|
|
51
|
+
read_stream,
|
|
52
|
+
write_stream,
|
|
53
|
+
InitializationOptions(
|
|
54
|
+
server_name="CodeGraphContext",
|
|
55
|
+
server_version="0.1.0",
|
|
56
|
+
capabilities=ServerCapabilities(
|
|
57
|
+
tools=ToolsCapability(listChanged=False)
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
async def handle_messages(request: Request):
|
|
63
|
+
"""Endpoint for receiving messages from the client."""
|
|
64
|
+
await sse.handle_post_message(request.scope, request.receive, request._send)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# src/codegraphcontext/api/router.py
|
|
2
|
+
import asyncio
|
|
3
|
+
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
|
|
4
|
+
from typing import Dict, Any, List
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .schemas import (
|
|
8
|
+
IndexRequest,
|
|
9
|
+
QueryRequest,
|
|
10
|
+
SearchRequest,
|
|
11
|
+
ToolCallRequest,
|
|
12
|
+
ApiResponse
|
|
13
|
+
)
|
|
14
|
+
from codegraphcontext.server import MCPServer
|
|
15
|
+
|
|
16
|
+
router = APIRouter()
|
|
17
|
+
|
|
18
|
+
# Global server instance (initialized on startup)
|
|
19
|
+
_server_instance: MCPServer = None
|
|
20
|
+
|
|
21
|
+
def get_server() -> MCPServer:
|
|
22
|
+
global _server_instance
|
|
23
|
+
if _server_instance is None:
|
|
24
|
+
# Note: In a real production app, we'd handle initialization better
|
|
25
|
+
_server_instance = MCPServer(cwd=Path.cwd())
|
|
26
|
+
return _server_instance
|
|
27
|
+
|
|
28
|
+
@router.get("/status", response_model=ApiResponse)
|
|
29
|
+
async def get_status(server: MCPServer = Depends(get_server)):
|
|
30
|
+
status = server.db_manager.is_connected()
|
|
31
|
+
return ApiResponse(
|
|
32
|
+
status="ok",
|
|
33
|
+
message="Connected" if status else "Disconnected",
|
|
34
|
+
data={"database": server.resolved_context.database}
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
@router.get("/tools", response_model=ApiResponse)
|
|
38
|
+
async def list_tools(server: MCPServer = Depends(get_server)):
|
|
39
|
+
return ApiResponse(
|
|
40
|
+
status="ok",
|
|
41
|
+
data={"tools": list(server.tools.values())}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@router.post("/tools/call", response_model=ApiResponse)
|
|
45
|
+
async def call_tool(
|
|
46
|
+
request: ToolCallRequest,
|
|
47
|
+
server: MCPServer = Depends(get_server)
|
|
48
|
+
):
|
|
49
|
+
try:
|
|
50
|
+
result = await server.handle_tool_call(request.name, request.arguments)
|
|
51
|
+
if "error" in result:
|
|
52
|
+
return ApiResponse(status="error", error=result["error"])
|
|
53
|
+
return ApiResponse(status="ok", data=result)
|
|
54
|
+
except Exception as e:
|
|
55
|
+
return ApiResponse(status="error", error=str(e))
|
|
56
|
+
|
|
57
|
+
@router.post("/index", response_model=ApiResponse)
|
|
58
|
+
async def index_repository(
|
|
59
|
+
request: IndexRequest,
|
|
60
|
+
background_tasks: BackgroundTasks,
|
|
61
|
+
server: MCPServer = Depends(get_server)
|
|
62
|
+
):
|
|
63
|
+
# Map to add_code_to_graph tool
|
|
64
|
+
args = {
|
|
65
|
+
"path": request.path,
|
|
66
|
+
"repo_name": request.repo_name,
|
|
67
|
+
"branch": request.branch,
|
|
68
|
+
"force": request.force
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# We call handle_tool_call which is async
|
|
72
|
+
# But add_code_to_graph starts a background job anyway
|
|
73
|
+
result = await server.handle_tool_call("add_code_to_graph", args)
|
|
74
|
+
|
|
75
|
+
if "error" in result:
|
|
76
|
+
raise HTTPException(status_code=400, detail=result["error"])
|
|
77
|
+
|
|
78
|
+
return ApiResponse(
|
|
79
|
+
status="ok",
|
|
80
|
+
message="Indexing job started",
|
|
81
|
+
data=result
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
@router.post("/query", response_model=ApiResponse)
|
|
85
|
+
async def execute_query(
|
|
86
|
+
request: QueryRequest,
|
|
87
|
+
server: MCPServer = Depends(get_server)
|
|
88
|
+
):
|
|
89
|
+
result = await server.handle_tool_call("execute_cypher_query", {
|
|
90
|
+
"query": request.query,
|
|
91
|
+
"params": request.params
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
if "error" in result:
|
|
95
|
+
return ApiResponse(status="error", error=result["error"])
|
|
96
|
+
|
|
97
|
+
return ApiResponse(status="ok", data=result)
|
|
98
|
+
|
|
99
|
+
@router.get("/repositories", response_model=ApiResponse)
|
|
100
|
+
async def list_repositories(server: MCPServer = Depends(get_server)):
|
|
101
|
+
result = await server.handle_tool_call("list_indexed_repositories", {})
|
|
102
|
+
return ApiResponse(status="ok", data=result)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# src/codegraphcontext/api/schemas.py
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
from typing import Dict, Any, List, Optional
|
|
4
|
+
|
|
5
|
+
class IndexRequest(BaseModel):
|
|
6
|
+
path: str = Field(..., description="Local path to the repository or file to index")
|
|
7
|
+
repo_name: Optional[str] = Field(None, description="Optional name for the repository")
|
|
8
|
+
branch: str = "main"
|
|
9
|
+
force: bool = False
|
|
10
|
+
|
|
11
|
+
class QueryRequest(BaseModel):
|
|
12
|
+
query: str = Field(..., description="Cypher query to execute")
|
|
13
|
+
params: Dict[str, Any] = Field(default_factory=dict, description="Parameters for the query")
|
|
14
|
+
|
|
15
|
+
class SearchRequest(BaseModel):
|
|
16
|
+
query: str = Field(..., description="Search query")
|
|
17
|
+
top_k: int = 10
|
|
18
|
+
|
|
19
|
+
class ToolCallRequest(BaseModel):
|
|
20
|
+
name: str = Field(..., description="Name of the MCP tool to call")
|
|
21
|
+
arguments: Dict[str, Any] = Field(default_factory=dict, description="Arguments for the tool")
|
|
22
|
+
|
|
23
|
+
class ApiResponse(BaseModel):
|
|
24
|
+
status: str
|
|
25
|
+
message: Optional[str] = None
|
|
26
|
+
data: Optional[Any] = None
|
|
27
|
+
error: Optional[str] = None
|
codegraphcontext/cli/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
# src/codegraphcontext/cli/__init__.py
|
|
1
|
+
# src/codegraphcontext/cli/__init__.py
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# src/codegraphcontext/cli/cli_helpers.py
|
|
1
2
|
import asyncio
|
|
2
3
|
import json
|
|
3
4
|
import uuid
|
|
@@ -87,8 +88,9 @@ def _initialize_services(cli_context_flag: Optional[str] = None) -> tuple[Any, A
|
|
|
87
88
|
):
|
|
88
89
|
os.environ["DEFAULT_DATABASE"] = ctx.database
|
|
89
90
|
|
|
90
|
-
# Pass the exact DB path resolved from the context
|
|
91
|
-
|
|
91
|
+
# Pass the exact DB path resolved from the context, or the runtime override
|
|
92
|
+
runtime_path = os.getenv("CGC_RUNTIME_DB_PATH")
|
|
93
|
+
db_manager = get_database_manager(db_path=runtime_path or ctx.db_path)
|
|
92
94
|
except ValueError as e:
|
|
93
95
|
console.print(f"[bold red]Database Configuration Error:[/bold red] {e}")
|
|
94
96
|
return None, None, None, ctx
|
|
@@ -866,3 +868,39 @@ def list_watching_helper():
|
|
|
866
868
|
console.print(f"\n[cyan]To see watched directories in MCP mode:[/cyan]")
|
|
867
869
|
console.print(f" 1. Start the MCP server: cgc mcp start")
|
|
868
870
|
console.print(f" 2. Use the 'list_watched_paths' MCP tool from your IDE")
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
def setup_scip_helper() -> None:
|
|
874
|
+
"""Diagnostic and setup helper for SCIP indexers."""
|
|
875
|
+
from ..tools.scip_indexer import EXTENSION_TO_SCIP
|
|
876
|
+
import shutil
|
|
877
|
+
|
|
878
|
+
console.print("[bold cyan]🔍 Checking SCIP Indexer Availability...[/bold cyan]\n")
|
|
879
|
+
|
|
880
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
881
|
+
table.add_column("Language", style="cyan")
|
|
882
|
+
table.add_column("Binary", style="yellow")
|
|
883
|
+
table.add_column("Status", style="green")
|
|
884
|
+
table.add_column("Install Hint", style="dim")
|
|
885
|
+
|
|
886
|
+
langs = {}
|
|
887
|
+
for ext, (lang, binary, hint, docker) in EXTENSION_TO_SCIP.items():
|
|
888
|
+
if lang not in langs:
|
|
889
|
+
langs[lang] = (binary, hint, docker)
|
|
890
|
+
|
|
891
|
+
for lang, (binary, hint, docker) in sorted(langs.items()):
|
|
892
|
+
is_installed = shutil.which(binary) is not None
|
|
893
|
+
status = "[green]✓ Installed[/green]" if is_installed else "[red]✗ Not Found[/red]"
|
|
894
|
+
table.add_row(lang, binary, status, hint)
|
|
895
|
+
|
|
896
|
+
console.print(table)
|
|
897
|
+
|
|
898
|
+
# Check Docker
|
|
899
|
+
has_docker = shutil.which("docker") is not None
|
|
900
|
+
if has_docker:
|
|
901
|
+
console.print("\n[green]✓ Docker is available (Auto-fallback enabled)[/green]")
|
|
902
|
+
else:
|
|
903
|
+
console.print("\n[yellow]⚠ Docker not found. Local binaries are required for SCIP.[/yellow]")
|
|
904
|
+
|
|
905
|
+
console.print("\n[dim]To enable SCIP indexing, run:[/dim]")
|
|
906
|
+
console.print("[bold white]cgc config set SCIP_INDEXER true[/bold white]")
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# src/codegraphcontext/cli/config_manager.py
|
|
1
2
|
"""
|
|
2
3
|
Configuration management for CodeGraphContext.
|
|
3
4
|
Handles reading, writing, and validating configuration settings.
|
|
@@ -170,6 +171,21 @@ coverage/
|
|
|
170
171
|
"""
|
|
171
172
|
|
|
172
173
|
|
|
174
|
+
def normalize_config_path(value: str, *, absolute: bool = False, base_dir: Optional[Path] = None) -> str:
|
|
175
|
+
"""Normalize config path values.
|
|
176
|
+
|
|
177
|
+
- Expands ``~`` and environment variables.
|
|
178
|
+
- Optionally resolves to an absolute path.
|
|
179
|
+
"""
|
|
180
|
+
expanded = os.path.expandvars(os.path.expanduser(str(value)))
|
|
181
|
+
path_obj = Path(expanded)
|
|
182
|
+
if absolute and not path_obj.is_absolute():
|
|
183
|
+
path_obj = (base_dir or Path.cwd()) / path_obj
|
|
184
|
+
if absolute:
|
|
185
|
+
return str(path_obj.resolve())
|
|
186
|
+
return str(path_obj)
|
|
187
|
+
|
|
188
|
+
|
|
173
189
|
def ensure_config_dir(path: Path = CONFIG_DIR):
|
|
174
190
|
"""
|
|
175
191
|
Ensure that the configuration directory exists.
|
|
@@ -423,7 +439,7 @@ def validate_config_value(key: str, value: str) -> tuple[bool, Optional[str]]:
|
|
|
423
439
|
|
|
424
440
|
if key in ("LOG_FILE_PATH", "DEBUG_LOG_PATH"):
|
|
425
441
|
# Validate path is writable
|
|
426
|
-
log_path = Path(value)
|
|
442
|
+
log_path = Path(normalize_config_path(value, absolute=True))
|
|
427
443
|
try:
|
|
428
444
|
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
429
445
|
except Exception as e:
|
|
@@ -431,7 +447,7 @@ def validate_config_value(key: str, value: str) -> tuple[bool, Optional[str]]:
|
|
|
431
447
|
|
|
432
448
|
if key in ("FALKORDB_PATH", "FALKORDB_SOCKET_PATH"):
|
|
433
449
|
# Validate path is writable
|
|
434
|
-
db_path = Path(value)
|
|
450
|
+
db_path = Path(normalize_config_path(value, absolute=True))
|
|
435
451
|
try:
|
|
436
452
|
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
437
453
|
except Exception as e:
|
|
@@ -634,10 +650,17 @@ def _default_global_db_path(database: str) -> str:
|
|
|
634
650
|
"""Return the canonical DB path for the global context.
|
|
635
651
|
|
|
636
652
|
New layout: ``~/.codegraphcontext/global/db/<backend>/``
|
|
637
|
-
For backward-compat,
|
|
653
|
+
For backward-compat, we check:
|
|
654
|
+
1. FALKORDB_PATH in config (if database is falkordb)
|
|
655
|
+
2. Legacy flat path
|
|
656
|
+
3. New layout default
|
|
638
657
|
"""
|
|
639
|
-
if database == "falkordb"
|
|
640
|
-
|
|
658
|
+
if database == "falkordb":
|
|
659
|
+
custom_path = load_config().get("FALKORDB_PATH")
|
|
660
|
+
if custom_path:
|
|
661
|
+
return str(Path(custom_path).resolve())
|
|
662
|
+
if _LEGACY_FALKORDB_PATH.exists():
|
|
663
|
+
return str(_LEGACY_FALKORDB_PATH)
|
|
641
664
|
return str(CONFIG_DIR / "global" / "db" / database)
|
|
642
665
|
|
|
643
666
|
|
codegraphcontext/cli/main.py
CHANGED
|
@@ -41,6 +41,7 @@ from .cli_helpers import (
|
|
|
41
41
|
watch_helper,
|
|
42
42
|
unwatch_helper,
|
|
43
43
|
list_watching_helper,
|
|
44
|
+
setup_scip_helper,
|
|
44
45
|
)
|
|
45
46
|
|
|
46
47
|
# Set the log level for the noisy neo4j, asyncio, and urllib3 loggers to keep the output clean.
|
|
@@ -77,7 +78,7 @@ from .visualizer import (
|
|
|
77
78
|
# Initialize the Typer app and Rich console for formatted output.
|
|
78
79
|
app = typer.Typer(
|
|
79
80
|
name="cgc",
|
|
80
|
-
help="CodeGraphContext: An MCP server for AI-powered code analysis
|
|
81
|
+
help="CodeGraphContext: An MCP server for AI-powered code analysis.",
|
|
81
82
|
add_completion=True,
|
|
82
83
|
)
|
|
83
84
|
console = Console(stderr=True)
|
|
@@ -304,6 +305,7 @@ def _load_credentials():
|
|
|
304
305
|
from codegraphcontext.cli.config_manager import (
|
|
305
306
|
ensure_config_dir,
|
|
306
307
|
codegraphcontext_dotenv_at_cwd,
|
|
308
|
+
normalize_config_path,
|
|
307
309
|
)
|
|
308
310
|
|
|
309
311
|
# Ensure config directory exists (lazy initialization)
|
|
@@ -337,6 +339,14 @@ def _load_credentials():
|
|
|
337
339
|
with open(mcp_file_path, "r") as f:
|
|
338
340
|
mcp_config = json.load(f)
|
|
339
341
|
server_env = mcp_config.get("mcpServers", {}).get("CodeGraphContext", {}).get("env", {})
|
|
342
|
+
if isinstance(server_env, dict):
|
|
343
|
+
normalized_env = {}
|
|
344
|
+
for env_key, env_value in server_env.items():
|
|
345
|
+
if env_value is not None and "PATH" in env_key:
|
|
346
|
+
normalized_env[env_key] = normalize_config_path(str(env_value), absolute=True)
|
|
347
|
+
else:
|
|
348
|
+
normalized_env[env_key] = env_value
|
|
349
|
+
server_env = normalized_env
|
|
340
350
|
_append_source("mcp.json", server_env)
|
|
341
351
|
except Exception as e:
|
|
342
352
|
console.print(f"[yellow]Warning: Could not load mcp.json: {e}[/yellow]")
|
|
@@ -383,6 +393,8 @@ def _load_credentials():
|
|
|
383
393
|
if value is not None: # Only set non-None values
|
|
384
394
|
if key in runtime_env:
|
|
385
395
|
continue
|
|
396
|
+
if "PATH" in key:
|
|
397
|
+
value = normalize_config_path(str(value), absolute=True)
|
|
386
398
|
os.environ[key] = str(value)
|
|
387
399
|
|
|
388
400
|
# Report what was loaded
|
|
@@ -543,9 +555,9 @@ def config_db(backend: str = typer.Argument(..., help="Database backend: 'neo4j'
|
|
|
543
555
|
cgc config db kuzudb
|
|
544
556
|
"""
|
|
545
557
|
backend = backend.lower()
|
|
546
|
-
if backend not in ['falkordb', 'falkordb-remote', 'neo4j', 'kuzudb']:
|
|
558
|
+
if backend not in ['falkordb', 'falkordb-remote', 'neo4j', 'kuzudb', 'ladybugdb']:
|
|
547
559
|
console.print(f"[bold red]Invalid backend: {backend}[/bold red]")
|
|
548
|
-
console.print("Must be 'falkordb', 'falkordb-remote', 'neo4j', or '
|
|
560
|
+
console.print("Must be 'falkordb', 'falkordb-remote', 'neo4j', 'kuzudb', or 'ladybugdb'")
|
|
549
561
|
raise typer.Exit(code=1)
|
|
550
562
|
|
|
551
563
|
updated = config_manager.set_config_value("DEFAULT_DATABASE", backend)
|
|
@@ -760,6 +772,29 @@ registry_app = typer.Typer(
|
|
|
760
772
|
app.add_typer(registry_app, name="registry")
|
|
761
773
|
|
|
762
774
|
|
|
775
|
+
# Create API command group
|
|
776
|
+
api_app = typer.Typer(help="CGC Gateway (HTTP API) commands")
|
|
777
|
+
app.add_typer(api_app, name="api")
|
|
778
|
+
|
|
779
|
+
@api_app.command("start")
|
|
780
|
+
def api_start(
|
|
781
|
+
host: str = typer.Option("0.0.0.0", help="Host to bind the server to"),
|
|
782
|
+
port: int = typer.Option(8000, help="Port to bind the server to"),
|
|
783
|
+
reload: bool = typer.Option(False, help="Enable auto-reload (development only)"),
|
|
784
|
+
):
|
|
785
|
+
"""
|
|
786
|
+
Start the CGC Gateway HTTP API server.
|
|
787
|
+
|
|
788
|
+
This server provides a REST API that can be used by ChatGPT Actions,
|
|
789
|
+
Claude, or web frontends to interact with the CodeGraphContext graph.
|
|
790
|
+
"""
|
|
791
|
+
import uvicorn
|
|
792
|
+
console.print(f"[bold green]Starting CGC Gateway on {host}:{port}...[/bold green]")
|
|
793
|
+
_load_credentials()
|
|
794
|
+
uvicorn.run("codegraphcontext.api.app:app", host=host, port=port, reload=reload)
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
|
|
763
798
|
@registry_app.callback()
|
|
764
799
|
def registry_callback(ctx: typer.Context):
|
|
765
800
|
"""Browse and download bundles from the registry."""
|
|
@@ -956,6 +991,15 @@ def doctor():
|
|
|
956
991
|
console.print(f" [red]✗[/red] KuzuDB is not installed")
|
|
957
992
|
console.print(f" Run: pip install kuzu")
|
|
958
993
|
all_checks_passed = False
|
|
994
|
+
elif default_db == "ladybugdb":
|
|
995
|
+
from importlib.util import find_spec
|
|
996
|
+
|
|
997
|
+
if find_spec("ladybug") is not None:
|
|
998
|
+
console.print(f" [green]✓[/green] LadybugDB core (ladybug) is installed")
|
|
999
|
+
else:
|
|
1000
|
+
console.print(f" [red]✗[/red] LadybugDB core (ladybug) is not installed")
|
|
1001
|
+
console.print(f" Run: pip install ladybug")
|
|
1002
|
+
all_checks_passed = False
|
|
959
1003
|
else:
|
|
960
1004
|
# FalkorDB
|
|
961
1005
|
try:
|
|
@@ -1046,13 +1090,6 @@ def doctor():
|
|
|
1046
1090
|
|
|
1047
1091
|
|
|
1048
1092
|
|
|
1049
|
-
@app.command()
|
|
1050
|
-
def start():
|
|
1051
|
-
"""
|
|
1052
|
-
[DEPRECATED] Use 'cgc mcp start' instead. This command will be removed in a future version.
|
|
1053
|
-
"""
|
|
1054
|
-
console.print("[yellow]⚠️ 'cgc start' is deprecated. Use 'cgc mcp start' instead.[/yellow]")
|
|
1055
|
-
mcp_start()
|
|
1056
1093
|
|
|
1057
1094
|
|
|
1058
1095
|
@app.command()
|
|
@@ -1106,6 +1143,16 @@ def stats(
|
|
|
1106
1143
|
path = str(Path(path).resolve())
|
|
1107
1144
|
stats_helper(path, context)
|
|
1108
1145
|
|
|
1146
|
+
@app.command("setup-scip")
|
|
1147
|
+
def setup_scip():
|
|
1148
|
+
"""
|
|
1149
|
+
Check availability of SCIP indexers and provide installation hints.
|
|
1150
|
+
|
|
1151
|
+
This command audits your system for SCIP binaries (like scip-python, scip-go)
|
|
1152
|
+
and checks if Docker is available for fallback indexing.
|
|
1153
|
+
"""
|
|
1154
|
+
setup_scip_helper()
|
|
1155
|
+
|
|
1109
1156
|
@app.command()
|
|
1110
1157
|
def delete(
|
|
1111
1158
|
path: Optional[str] = typer.Argument(None, help="Path of the repository to delete from the code graph."),
|
|
@@ -2552,12 +2599,22 @@ def main(
|
|
|
2552
2599
|
"-h",
|
|
2553
2600
|
help="[Root-level only] Show help and exit",
|
|
2554
2601
|
is_eager=True,
|
|
2555
|
-
),
|
|
2602
|
+
),
|
|
2603
|
+
db_path: Optional[str] = typer.Option(
|
|
2604
|
+
None,
|
|
2605
|
+
"--path",
|
|
2606
|
+
"--db-path",
|
|
2607
|
+
help="[Global] Temporarily override database path (for local DBs like KuzuDB)"
|
|
2608
|
+
),
|
|
2556
2609
|
):
|
|
2557
2610
|
"""
|
|
2558
2611
|
Main entry point for the cgc CLI application.
|
|
2559
2612
|
If no subcommand is provided, it displays a welcome message with instructions.
|
|
2560
2613
|
"""
|
|
2614
|
+
if db_path:
|
|
2615
|
+
os.environ["CGC_RUNTIME_DB_PATH"] = db_path
|
|
2616
|
+
if database:
|
|
2617
|
+
os.environ["CGC_RUNTIME_DB_TYPE"] = database
|
|
2561
2618
|
# Initialize context object for sharing state with subcommands
|
|
2562
2619
|
ctx.ensure_object(dict)
|
|
2563
2620
|
|
|
@@ -2571,6 +2628,10 @@ def main(
|
|
|
2571
2628
|
if version_:
|
|
2572
2629
|
console.print(f"CodeGraphContext [bold cyan]{get_version()}[/bold cyan]")
|
|
2573
2630
|
raise typer.Exit()
|
|
2631
|
+
|
|
2632
|
+
if help_:
|
|
2633
|
+
typer.echo(ctx.get_help())
|
|
2634
|
+
raise typer.Exit()
|
|
2574
2635
|
|
|
2575
2636
|
if ctx.invoked_subcommand is None:
|
|
2576
2637
|
console.print("[bold green]👋 Welcome to CodeGraphContext (cgc)![/bold green]\n")
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# src/codegraphcontext/cli/setup_wizard.py
|
|
1
2
|
from InquirerPy import prompt
|
|
2
3
|
from rich.console import Console
|
|
3
4
|
import subprocess
|
|
@@ -10,6 +11,7 @@ import sys
|
|
|
10
11
|
import shutil
|
|
11
12
|
import yaml
|
|
12
13
|
from codegraphcontext.core.database import DatabaseManager
|
|
14
|
+
from codegraphcontext.cli.config_manager import normalize_config_path
|
|
13
15
|
|
|
14
16
|
console = Console()
|
|
15
17
|
|
|
@@ -513,17 +515,15 @@ def configure_mcp_client():
|
|
|
513
515
|
except Exception:
|
|
514
516
|
pass
|
|
515
517
|
|
|
516
|
-
# Add all configuration values,
|
|
518
|
+
# Add all configuration values, normalizing path-related settings
|
|
517
519
|
for key, value in config.items():
|
|
518
520
|
# Skip database credentials (already added above)
|
|
519
521
|
if key in ["NEO4J_URI", "NEO4J_USERNAME", "NEO4J_PASSWORD"]:
|
|
520
522
|
continue
|
|
521
523
|
|
|
522
|
-
#
|
|
524
|
+
# Expand ~/$VARS and convert relative paths to absolute for MCP env
|
|
523
525
|
if "PATH" in key and value:
|
|
524
|
-
|
|
525
|
-
if not path_obj.is_absolute():
|
|
526
|
-
value = str(path_obj.resolve())
|
|
526
|
+
value = normalize_config_path(value, absolute=True)
|
|
527
527
|
|
|
528
528
|
env_vars[key] = value
|
|
529
529
|
|
|
@@ -60,7 +60,7 @@ def _is_nornic_configured() -> bool:
|
|
|
60
60
|
os.getenv('NORNIC_PASSWORD')
|
|
61
61
|
])
|
|
62
62
|
|
|
63
|
-
def get_database_manager(db_path: Optional[str] = None) -> Union['DatabaseManager', 'FalkorDBManager', 'FalkorDBRemoteManager', 'KuzuDBManager', 'NornicDBManager']:
|
|
63
|
+
def get_database_manager(db_path: Optional[str] = None) -> Union['DatabaseManager', 'FalkorDBManager', 'FalkorDBRemoteManager', 'KuzuDBManager', 'NornicDBManager', 'LadybugDBManager']:
|
|
64
64
|
"""
|
|
65
65
|
Factory function to get the appropriate database manager based on configuration.
|
|
66
66
|
|
|
@@ -126,8 +126,14 @@ def get_database_manager(db_path: Optional[str] = None) -> Union['DatabaseManage
|
|
|
126
126
|
from .database_nornic import NornicDBManager
|
|
127
127
|
info_logger("Using Nornic DB (explicit)")
|
|
128
128
|
return NornicDBManager()
|
|
129
|
+
elif db_type == 'ladybugdb':
|
|
130
|
+
if not _is_kuzudb_available():
|
|
131
|
+
raise ValueError("Database set to 'ladybugdb' but LadybugDB core (kuzu) is not installed.\nRun 'pip install kuzu'")
|
|
132
|
+
from .database_ladybug import LadybugDBManager
|
|
133
|
+
info_logger(f"Using LadybugDB (explicit) at {db_path or 'default path'}")
|
|
134
|
+
return LadybugDBManager(db_path=db_path)
|
|
129
135
|
else:
|
|
130
|
-
raise ValueError(f"Unknown database type: '{db_type}'. Use 'kuzudb', 'falkordb', 'falkordb-remote', 'neo4j', or 'nornic'.")
|
|
136
|
+
raise ValueError(f"Unknown database type: '{db_type}'. Use 'kuzudb', 'ladybugdb', 'falkordb', 'falkordb-remote', 'neo4j', or 'nornic'.")
|
|
131
137
|
|
|
132
138
|
# Implicit: remote FalkorDB when FALKORDB_HOST is set (explicit infra signal)
|
|
133
139
|
if _is_falkordb_remote_configured():
|
|
@@ -182,6 +188,7 @@ from .database import DatabaseManager
|
|
|
182
188
|
from .database_falkordb import FalkorDBManager
|
|
183
189
|
from .database_falkordb_remote import FalkorDBRemoteManager
|
|
184
190
|
from .database_kuzu import KuzuDBManager
|
|
191
|
+
from .database_ladybug import LadybugDBManager
|
|
185
192
|
from .database_nornic import NornicDBManager
|
|
186
193
|
|
|
187
|
-
__all__ = ['DatabaseManager', 'FalkorDBManager', 'FalkorDBRemoteManager', 'KuzuDBManager', 'NornicDBManager', 'get_database_manager']
|
|
194
|
+
__all__ = ['DatabaseManager', 'FalkorDBManager', 'FalkorDBRemoteManager', 'KuzuDBManager', 'LadybugDBManager', 'NornicDBManager', 'get_database_manager']
|