codegraphcontext 0.4.9__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 +37 -0
- codegraphcontext/cli/config_manager.py +18 -2
- codegraphcontext/cli/main.py +61 -10
- 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 +12 -1
- codegraphcontext/core/database_kuzu.py +238 -134
- 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/tool_definitions.py +1 -0
- codegraphcontext/tools/__init__.py +1 -0
- codegraphcontext/tools/advanced_language_query_tool.py +1 -0
- 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 +16 -37
- codegraphcontext/tools/handlers/analysis_handlers.py +1 -0
- 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 +102 -27
- codegraphcontext/tools/indexing/pipeline.py +45 -20
- codegraphcontext/tools/indexing/pre_scan.py +1 -0
- codegraphcontext/tools/indexing/resolution/__init__.py +1 -0
- codegraphcontext/tools/indexing/resolution/calls.py +71 -8
- codegraphcontext/tools/indexing/resolution/inheritance.py +1 -0
- codegraphcontext/tools/indexing/resolution/post_resolution.py +1 -0
- codegraphcontext/tools/indexing/sanitize.py +1 -0
- codegraphcontext/tools/indexing/schema.py +19 -18
- 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 +1 -0
- codegraphcontext/tools/languages/cpp.py +1 -0
- codegraphcontext/tools/languages/csharp.py +1 -0
- codegraphcontext/tools/languages/css.py +1 -0
- codegraphcontext/tools/languages/dart.py +1 -0
- codegraphcontext/tools/languages/elixir.py +1 -0
- codegraphcontext/tools/languages/go.py +1 -0
- codegraphcontext/tools/languages/gradle.py +1 -0
- codegraphcontext/tools/languages/haskell.py +427 -426
- codegraphcontext/tools/languages/html.py +1 -0
- codegraphcontext/tools/languages/java.py +1 -0
- codegraphcontext/tools/languages/javascript.py +1 -0
- codegraphcontext/tools/languages/kotlin.py +1 -0
- 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 +1 -0
- codegraphcontext/tools/languages/php.py +1 -0
- codegraphcontext/tools/languages/python.py +1 -0
- codegraphcontext/tools/languages/ruby.py +1 -0
- codegraphcontext/tools/languages/rust.py +1 -0
- codegraphcontext/tools/languages/scala.py +1 -0
- codegraphcontext/tools/languages/swift.py +1 -0
- 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.9.dist-info → codegraphcontext-0.4.10.dist-info}/METADATA +11 -10
- codegraphcontext-0.4.10.dist-info/RECORD +156 -0
- codegraphcontext-0.4.9.dist-info/RECORD +0 -150
- {codegraphcontext-0.4.9.dist-info → codegraphcontext-0.4.10.dist-info}/WHEEL +0 -0
- {codegraphcontext-0.4.9.dist-info → codegraphcontext-0.4.10.dist-info}/entry_points.txt +0 -0
- {codegraphcontext-0.4.9.dist-info → codegraphcontext-0.4.10.dist-info}/licenses/LICENSE +0 -0
- {codegraphcontext-0.4.9.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
|
|
@@ -867,3 +868,39 @@ def list_watching_helper():
|
|
|
867
868
|
console.print(f"\n[cyan]To see watched directories in MCP mode:[/cyan]")
|
|
868
869
|
console.print(f" 1. Start the MCP server: cgc mcp start")
|
|
869
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:
|
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."),
|
|
@@ -2581,6 +2628,10 @@ def main(
|
|
|
2581
2628
|
if version_:
|
|
2582
2629
|
console.print(f"CodeGraphContext [bold cyan]{get_version()}[/bold cyan]")
|
|
2583
2630
|
raise typer.Exit()
|
|
2631
|
+
|
|
2632
|
+
if help_:
|
|
2633
|
+
typer.echo(ctx.get_help())
|
|
2634
|
+
raise typer.Exit()
|
|
2584
2635
|
|
|
2585
2636
|
if ctx.invoked_subcommand is None:
|
|
2586
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']
|
|
@@ -89,6 +89,7 @@ class FalkorDBManager:
|
|
|
89
89
|
'FALKORDB_PATH',
|
|
90
90
|
config_db_path or str(Path.home() / '.codegraphcontext' / 'global' / 'falkordb.db')
|
|
91
91
|
)
|
|
92
|
+
self.db_path = os.path.abspath(self.db_path)
|
|
92
93
|
|
|
93
94
|
# Socket path with fallback chain
|
|
94
95
|
if socket_path:
|
|
@@ -103,6 +104,7 @@ class FalkorDBManager:
|
|
|
103
104
|
'FALKORDB_SOCKET_PATH',
|
|
104
105
|
config_socket_path or str(Path.home() / '.codegraphcontext' / 'global' / 'falkordb.sock')
|
|
105
106
|
)
|
|
107
|
+
self.socket_path = os.path.abspath(self.socket_path)
|
|
106
108
|
|
|
107
109
|
self.graph_name = os.getenv('FALKORDB_GRAPH_NAME', 'codegraph')
|
|
108
110
|
self._initialized = True
|
|
@@ -463,11 +465,20 @@ class FalkorDBSessionWrapper:
|
|
|
463
465
|
|
|
464
466
|
class FalkorDBRecord(dict):
|
|
465
467
|
"""
|
|
466
|
-
Dict wrapper that provides a .data() method
|
|
468
|
+
Dict wrapper that provides a .data() method and integer/key index access
|
|
469
|
+
for compatibility with Neo4j and Kuzu records.
|
|
467
470
|
"""
|
|
468
471
|
def data(self):
|
|
469
472
|
return self
|
|
470
473
|
|
|
474
|
+
def __getitem__(self, key):
|
|
475
|
+
if isinstance(key, int):
|
|
476
|
+
keys = list(self.keys())
|
|
477
|
+
if 0 <= key < len(keys):
|
|
478
|
+
return super().__getitem__(keys[key])
|
|
479
|
+
raise IndexError(f"Index {key} out of range")
|
|
480
|
+
return super().__getitem__(key)
|
|
481
|
+
|
|
471
482
|
class FalkorDBResultWrapper:
|
|
472
483
|
"""
|
|
473
484
|
Wrapper class to provide Neo4j result-like interface for FalkorDB results.
|