interlinked-mapper 0.1.0__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.
- interlinked/__init__.py +3 -0
- interlinked/analyzer/__init__.py +7 -0
- interlinked/analyzer/dead_code.py +137 -0
- interlinked/analyzer/graph.py +822 -0
- interlinked/analyzer/parser.py +1141 -0
- interlinked/analyzer/similarity.py +486 -0
- interlinked/cli.py +136 -0
- interlinked/commander/__init__.py +6 -0
- interlinked/commander/llm.py +304 -0
- interlinked/commander/query.py +966 -0
- interlinked/commander/repl.py +50 -0
- interlinked/mcp_server.py +324 -0
- interlinked/models.py +107 -0
- interlinked/visualizer/__init__.py +1 -0
- interlinked/visualizer/layouts.py +181 -0
- interlinked/visualizer/server.py +428 -0
- interlinked_mapper-0.1.0.dist-info/METADATA +26 -0
- interlinked_mapper-0.1.0.dist-info/RECORD +21 -0
- interlinked_mapper-0.1.0.dist-info/WHEEL +5 -0
- interlinked_mapper-0.1.0.dist-info/entry_points.txt +2 -0
- interlinked_mapper-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
"""FastAPI server — serves the frontend and provides REST + SSE APIs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from fastapi import FastAPI, Request
|
|
11
|
+
from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse
|
|
12
|
+
from fastapi.staticfiles import StaticFiles
|
|
13
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
14
|
+
|
|
15
|
+
from interlinked.analyzer.graph import CodeGraph
|
|
16
|
+
from interlinked.commander.query import QueryEngine
|
|
17
|
+
from interlinked.commander.llm import LLMAdapter, get_system_prompt
|
|
18
|
+
from interlinked.visualizer.layouts import compute_layout
|
|
19
|
+
from interlinked.models import ViewState
|
|
20
|
+
|
|
21
|
+
FRONTEND_DIR = Path(__file__).parent / "frontend" / "dist"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _rebuild_graph(project_path: str, graph: CodeGraph) -> dict:
|
|
25
|
+
"""Re-parse a project and rebuild the graph in-place. Returns stats."""
|
|
26
|
+
from interlinked.analyzer.parser import parse_project
|
|
27
|
+
from interlinked.analyzer.dead_code import detect_dead_code
|
|
28
|
+
from interlinked.analyzer.similarity import analyze_similarity
|
|
29
|
+
|
|
30
|
+
path = Path(project_path).resolve()
|
|
31
|
+
if not path.exists():
|
|
32
|
+
raise ValueError(f"Path does not exist: {path}")
|
|
33
|
+
|
|
34
|
+
nodes, edges = parse_project(str(path))
|
|
35
|
+
graph.build_from(nodes, edges)
|
|
36
|
+
dead = detect_dead_code(graph)
|
|
37
|
+
try:
|
|
38
|
+
analyze_similarity(graph)
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
return {"path": str(path), "nodes": len(nodes), "edges": len(edges), "dead": len(dead)}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def create_app(graph: CodeGraph, initial_path: str | None = None) -> FastAPI:
|
|
45
|
+
"""Create and configure the FastAPI application."""
|
|
46
|
+
app = FastAPI(title="Interlinked", version="0.1.0")
|
|
47
|
+
engine = QueryEngine(graph)
|
|
48
|
+
llm = LLMAdapter(engine)
|
|
49
|
+
app_state = {"project_path": initial_path or ""}
|
|
50
|
+
sse_queues: list[asyncio.Queue] = []
|
|
51
|
+
|
|
52
|
+
app.add_middleware(
|
|
53
|
+
CORSMiddleware,
|
|
54
|
+
allow_origins=["*"],
|
|
55
|
+
allow_methods=["*"],
|
|
56
|
+
allow_headers=["*"],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# ── Broadcast view changes to all SSE clients ────────────────
|
|
60
|
+
|
|
61
|
+
def _snapshot_with_layout() -> dict:
|
|
62
|
+
snap = engine.snapshot()
|
|
63
|
+
snap["layout"] = compute_layout(
|
|
64
|
+
[n for n in graph.all_nodes()],
|
|
65
|
+
graph.all_edges(),
|
|
66
|
+
)
|
|
67
|
+
return snap
|
|
68
|
+
|
|
69
|
+
def on_view_change(snapshot: dict) -> None:
|
|
70
|
+
snapshot["layout"] = compute_layout(
|
|
71
|
+
[n for n in graph.all_nodes()],
|
|
72
|
+
graph.all_edges(),
|
|
73
|
+
)
|
|
74
|
+
msg = json.dumps({"type": "snapshot", "data": snapshot})
|
|
75
|
+
dead: list[asyncio.Queue] = []
|
|
76
|
+
for q in sse_queues:
|
|
77
|
+
try:
|
|
78
|
+
q.put_nowait(msg)
|
|
79
|
+
except Exception:
|
|
80
|
+
dead.append(q)
|
|
81
|
+
for q in dead:
|
|
82
|
+
sse_queues.remove(q)
|
|
83
|
+
|
|
84
|
+
engine.on_change(on_view_change)
|
|
85
|
+
|
|
86
|
+
# ── REST endpoints ───────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
@app.get("/api/project")
|
|
89
|
+
async def get_project() -> JSONResponse:
|
|
90
|
+
return JSONResponse({"path": app_state["project_path"]})
|
|
91
|
+
|
|
92
|
+
@app.post("/api/switch_project")
|
|
93
|
+
async def switch_project(body: dict) -> JSONResponse:
|
|
94
|
+
"""Switch to a different project. Re-parses and rebuilds the graph."""
|
|
95
|
+
project_path = body.get("path", "")
|
|
96
|
+
if not project_path:
|
|
97
|
+
return JSONResponse({"error": "No path provided"}, status_code=400)
|
|
98
|
+
try:
|
|
99
|
+
result = _rebuild_graph(project_path, graph)
|
|
100
|
+
app_state["project_path"] = result["path"]
|
|
101
|
+
engine.reset_filter()
|
|
102
|
+
from interlinked.models import ViewContext
|
|
103
|
+
engine.state.context = ViewContext(
|
|
104
|
+
what=f"Switched to project: {Path(result['path']).name}",
|
|
105
|
+
why=f"{result['nodes']} symbols, {result['edges']} edges, {result['dead']} dead",
|
|
106
|
+
where=result["path"],
|
|
107
|
+
source="system",
|
|
108
|
+
)
|
|
109
|
+
engine._notify()
|
|
110
|
+
return JSONResponse({"result": f"Switched to {result['path']}", **result})
|
|
111
|
+
except Exception as e:
|
|
112
|
+
return JSONResponse({"error": str(e)}, status_code=400)
|
|
113
|
+
|
|
114
|
+
@app.get("/api/snapshot")
|
|
115
|
+
async def get_snapshot() -> JSONResponse:
|
|
116
|
+
snap = engine.snapshot()
|
|
117
|
+
layout = compute_layout(
|
|
118
|
+
[n for n in graph.all_nodes()],
|
|
119
|
+
graph.all_edges(),
|
|
120
|
+
)
|
|
121
|
+
snap["layout"] = layout
|
|
122
|
+
return JSONResponse(content=snap)
|
|
123
|
+
|
|
124
|
+
@app.get("/api/stats")
|
|
125
|
+
async def get_stats() -> JSONResponse:
|
|
126
|
+
return JSONResponse(content=engine.stats())
|
|
127
|
+
|
|
128
|
+
@app.get("/api/health")
|
|
129
|
+
async def get_health() -> JSONResponse:
|
|
130
|
+
return JSONResponse(content=json.loads(engine.health()))
|
|
131
|
+
|
|
132
|
+
@app.post("/api/command")
|
|
133
|
+
async def run_command(body: dict) -> JSONResponse:
|
|
134
|
+
"""Execute a command string against the QueryEngine.
|
|
135
|
+
|
|
136
|
+
Body: {"command": "view.zoom('module')"}
|
|
137
|
+
"""
|
|
138
|
+
cmd = body.get("command", "")
|
|
139
|
+
if not cmd:
|
|
140
|
+
return JSONResponse({"error": "No command provided"}, status_code=400)
|
|
141
|
+
|
|
142
|
+
# Security: only allow access to the engine object
|
|
143
|
+
local_ns: dict[str, Any] = {"view": engine, "graph": graph}
|
|
144
|
+
try:
|
|
145
|
+
# Try as expression first
|
|
146
|
+
try:
|
|
147
|
+
result = eval(cmd, {"__builtins__": {}}, local_ns)
|
|
148
|
+
except SyntaxError:
|
|
149
|
+
exec(cmd, {"__builtins__": {}}, local_ns)
|
|
150
|
+
result = "OK"
|
|
151
|
+
|
|
152
|
+
# Serialize result
|
|
153
|
+
if hasattr(result, "model_dump"):
|
|
154
|
+
result = result.model_dump()
|
|
155
|
+
elif isinstance(result, list) and result and hasattr(result[0], "model_dump"):
|
|
156
|
+
result = [r.model_dump() for r in result]
|
|
157
|
+
|
|
158
|
+
return JSONResponse({"result": result})
|
|
159
|
+
except Exception as e:
|
|
160
|
+
return JSONResponse({"error": str(e)}, status_code=400)
|
|
161
|
+
|
|
162
|
+
@app.post("/api/nl")
|
|
163
|
+
async def natural_language(body: dict) -> JSONResponse:
|
|
164
|
+
"""Natural language command."""
|
|
165
|
+
text = body.get("text", "")
|
|
166
|
+
if not text:
|
|
167
|
+
return JSONResponse({"error": "No text provided"}, status_code=400)
|
|
168
|
+
result = engine.nl(text)
|
|
169
|
+
snap = engine.snapshot()
|
|
170
|
+
layout = compute_layout(
|
|
171
|
+
[n for n in graph.all_nodes()],
|
|
172
|
+
graph.all_edges(),
|
|
173
|
+
)
|
|
174
|
+
snap["layout"] = layout
|
|
175
|
+
return JSONResponse({"result": result, "snapshot": snap})
|
|
176
|
+
|
|
177
|
+
@app.post("/api/zoom")
|
|
178
|
+
async def set_zoom(body: dict) -> JSONResponse:
|
|
179
|
+
level = body.get("level", "module")
|
|
180
|
+
result = engine.zoom(level)
|
|
181
|
+
return JSONResponse({"result": result})
|
|
182
|
+
|
|
183
|
+
@app.post("/api/edge_types")
|
|
184
|
+
async def set_edge_types(body: dict) -> JSONResponse:
|
|
185
|
+
edge_types = body.get("edge_types", [])
|
|
186
|
+
result = engine.set_edge_types(edge_types)
|
|
187
|
+
return JSONResponse({"result": result})
|
|
188
|
+
|
|
189
|
+
@app.post("/api/focus")
|
|
190
|
+
async def set_focus(body: dict) -> JSONResponse:
|
|
191
|
+
node_id = body.get("node_id", "")
|
|
192
|
+
depth = body.get("depth", 2)
|
|
193
|
+
result = engine.focus(node_id, depth)
|
|
194
|
+
return JSONResponse({"result": result})
|
|
195
|
+
|
|
196
|
+
@app.post("/api/query")
|
|
197
|
+
async def run_query(body: dict) -> JSONResponse:
|
|
198
|
+
expr = body.get("expression", "")
|
|
199
|
+
results = engine.query(expr)
|
|
200
|
+
snap = engine.snapshot()
|
|
201
|
+
layout = compute_layout(
|
|
202
|
+
[n for n in graph.all_nodes()],
|
|
203
|
+
graph.all_edges(),
|
|
204
|
+
)
|
|
205
|
+
snap["layout"] = layout
|
|
206
|
+
return JSONResponse({"results": results, "snapshot": snap})
|
|
207
|
+
|
|
208
|
+
@app.post("/api/propose")
|
|
209
|
+
async def propose_function(body: dict) -> JSONResponse:
|
|
210
|
+
result = engine.propose_function(
|
|
211
|
+
name=body.get("name", ""),
|
|
212
|
+
module=body.get("module", ""),
|
|
213
|
+
calls=body.get("calls"),
|
|
214
|
+
called_by=body.get("called_by"),
|
|
215
|
+
signature=body.get("signature"),
|
|
216
|
+
color=body.get("color"),
|
|
217
|
+
)
|
|
218
|
+
return JSONResponse({"result": result})
|
|
219
|
+
|
|
220
|
+
@app.post("/api/clear_proposed")
|
|
221
|
+
async def clear_proposed() -> JSONResponse:
|
|
222
|
+
result = engine.clear_proposed()
|
|
223
|
+
return JSONResponse({"result": result})
|
|
224
|
+
|
|
225
|
+
@app.post("/api/isolate")
|
|
226
|
+
async def isolate_target(body: dict) -> JSONResponse:
|
|
227
|
+
target = body.get("target", "")
|
|
228
|
+
level = body.get("level", "function")
|
|
229
|
+
depth = body.get("depth", 3)
|
|
230
|
+
edge_types = body.get("edge_types")
|
|
231
|
+
result = engine.isolate(target, level=level, depth=depth, edge_types=edge_types)
|
|
232
|
+
snap = engine.snapshot()
|
|
233
|
+
layout = compute_layout(
|
|
234
|
+
[n for n in graph.all_nodes()],
|
|
235
|
+
graph.all_edges(),
|
|
236
|
+
)
|
|
237
|
+
snap["layout"] = layout
|
|
238
|
+
return JSONResponse({"result": result, "snapshot": snap})
|
|
239
|
+
|
|
240
|
+
@app.post("/api/find_duplicates")
|
|
241
|
+
async def find_duplicates(body: dict) -> JSONResponse:
|
|
242
|
+
threshold = body.get("threshold", 0.6)
|
|
243
|
+
scope = body.get("scope")
|
|
244
|
+
result = engine.find_duplicates(threshold=threshold, scope=scope)
|
|
245
|
+
snap = engine.snapshot()
|
|
246
|
+
layout = compute_layout(
|
|
247
|
+
[n for n in graph.all_nodes()],
|
|
248
|
+
graph.all_edges(),
|
|
249
|
+
)
|
|
250
|
+
snap["layout"] = layout
|
|
251
|
+
return JSONResponse({"result": result, "snapshot": snap})
|
|
252
|
+
|
|
253
|
+
@app.post("/api/similar_to")
|
|
254
|
+
async def similar_to(body: dict) -> JSONResponse:
|
|
255
|
+
target = body.get("target", "")
|
|
256
|
+
threshold = body.get("threshold", 0.5)
|
|
257
|
+
result = engine.similar_to(target, threshold=threshold)
|
|
258
|
+
snap = engine.snapshot()
|
|
259
|
+
layout = compute_layout(
|
|
260
|
+
[n for n in graph.all_nodes()],
|
|
261
|
+
graph.all_edges(),
|
|
262
|
+
)
|
|
263
|
+
snap["layout"] = layout
|
|
264
|
+
return JSONResponse({"result": result, "snapshot": snap})
|
|
265
|
+
|
|
266
|
+
@app.post("/api/get_context")
|
|
267
|
+
async def get_context(body: dict) -> JSONResponse:
|
|
268
|
+
target = body.get("target", "")
|
|
269
|
+
result = engine.get_context(target)
|
|
270
|
+
return JSONResponse({"result": result})
|
|
271
|
+
|
|
272
|
+
@app.post("/api/reset")
|
|
273
|
+
async def reset_filters() -> JSONResponse:
|
|
274
|
+
result = engine.reset_filter()
|
|
275
|
+
return JSONResponse({"result": result})
|
|
276
|
+
|
|
277
|
+
@app.post("/api/trace_variable")
|
|
278
|
+
async def trace_variable(body: dict) -> JSONResponse:
|
|
279
|
+
var_name = body.get("variable", "")
|
|
280
|
+
origin = body.get("origin")
|
|
281
|
+
result = engine.trace_variable(var_name, origin)
|
|
282
|
+
snap = engine.snapshot()
|
|
283
|
+
layout = compute_layout(
|
|
284
|
+
[n for n in graph.all_nodes()],
|
|
285
|
+
graph.all_edges(),
|
|
286
|
+
)
|
|
287
|
+
snap["layout"] = layout
|
|
288
|
+
return JSONResponse({"result": result, "snapshot": snap})
|
|
289
|
+
|
|
290
|
+
# ── LLM chat + settings ──────────────────────────────────────
|
|
291
|
+
|
|
292
|
+
@app.post("/api/chat")
|
|
293
|
+
async def chat(body: dict) -> JSONResponse:
|
|
294
|
+
"""Send a natural language message through the LLM adapter.
|
|
295
|
+
|
|
296
|
+
Body: {"message": "show me the analyzer module and everything that connects to it"}
|
|
297
|
+
Returns: {"explanation": str, "commands_run": [...], "results": [...], "snapshot": {...}}
|
|
298
|
+
"""
|
|
299
|
+
message = body.get("message", "")
|
|
300
|
+
if not message:
|
|
301
|
+
return JSONResponse({"error": "No message provided"}, status_code=400)
|
|
302
|
+
|
|
303
|
+
result = await llm.chat(message)
|
|
304
|
+
|
|
305
|
+
# Set natural language context so the UI knows what it's looking at
|
|
306
|
+
from interlinked.models import ViewContext
|
|
307
|
+
explanation = result.get("explanation", "")
|
|
308
|
+
commands_run = result.get("commands_run", [])
|
|
309
|
+
# Derive "where" from highlighted nodes
|
|
310
|
+
highlighted = engine.state.highlighted_node_ids
|
|
311
|
+
if highlighted:
|
|
312
|
+
# Get the common scope prefix
|
|
313
|
+
parts = [h.rsplit(".", 1)[0] for h in highlighted[:10] if "." in h]
|
|
314
|
+
if parts:
|
|
315
|
+
common = parts[0]
|
|
316
|
+
for p in parts[1:]:
|
|
317
|
+
while not p.startswith(common) and "." in common:
|
|
318
|
+
common = common.rsplit(".", 1)[0]
|
|
319
|
+
where = common if common else ", ".join(h.split(".")[-1] for h in highlighted[:5])
|
|
320
|
+
else:
|
|
321
|
+
where = ", ".join(h.split(".")[-1] for h in highlighted[:5])
|
|
322
|
+
else:
|
|
323
|
+
where = ""
|
|
324
|
+
|
|
325
|
+
engine.state.context = ViewContext(
|
|
326
|
+
what=explanation,
|
|
327
|
+
why=message,
|
|
328
|
+
where=where,
|
|
329
|
+
source="llm",
|
|
330
|
+
)
|
|
331
|
+
engine._notify()
|
|
332
|
+
|
|
333
|
+
# Always return fresh snapshot after commands have executed
|
|
334
|
+
snap = engine.snapshot()
|
|
335
|
+
layout = compute_layout(
|
|
336
|
+
[n for n in graph.all_nodes()],
|
|
337
|
+
graph.all_edges(),
|
|
338
|
+
)
|
|
339
|
+
snap["layout"] = layout
|
|
340
|
+
result["snapshot"] = snap
|
|
341
|
+
|
|
342
|
+
return JSONResponse(result)
|
|
343
|
+
|
|
344
|
+
@app.get("/api/system-prompt")
|
|
345
|
+
async def system_prompt() -> JSONResponse:
|
|
346
|
+
"""Return the system prompt that teaches an LLM how to drive the view.
|
|
347
|
+
|
|
348
|
+
Any external LLM agent can GET this to learn the full API.
|
|
349
|
+
"""
|
|
350
|
+
return JSONResponse({"prompt": get_system_prompt(engine)})
|
|
351
|
+
|
|
352
|
+
@app.get("/api/settings")
|
|
353
|
+
async def get_settings() -> JSONResponse:
|
|
354
|
+
return JSONResponse({
|
|
355
|
+
"has_api_key": llm.is_configured,
|
|
356
|
+
"model": llm.model,
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
@app.post("/api/settings")
|
|
360
|
+
async def update_settings(body: dict) -> JSONResponse:
|
|
361
|
+
if "api_key" in body:
|
|
362
|
+
llm.set_api_key(body["api_key"])
|
|
363
|
+
if "model" in body:
|
|
364
|
+
llm.set_model(body["model"])
|
|
365
|
+
return JSONResponse({
|
|
366
|
+
"has_api_key": llm.is_configured,
|
|
367
|
+
"model": llm.model,
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
@app.post("/api/chat/clear")
|
|
371
|
+
async def clear_chat() -> JSONResponse:
|
|
372
|
+
llm.clear_history()
|
|
373
|
+
return JSONResponse({"result": "Chat history cleared."})
|
|
374
|
+
|
|
375
|
+
# ── SSE for live updates ─────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
@app.get("/api/events")
|
|
378
|
+
async def sse_events(request: Request) -> StreamingResponse:
|
|
379
|
+
"""Server-Sent Events stream. Pushes snapshot updates to all connected clients."""
|
|
380
|
+
q: asyncio.Queue = asyncio.Queue(maxsize=50)
|
|
381
|
+
sse_queues.append(q)
|
|
382
|
+
|
|
383
|
+
async def event_generator():
|
|
384
|
+
# Send initial snapshot
|
|
385
|
+
snap = _snapshot_with_layout()
|
|
386
|
+
yield f"data: {json.dumps({'type': 'snapshot', 'data': snap})}\n\n"
|
|
387
|
+
try:
|
|
388
|
+
while True:
|
|
389
|
+
if await request.is_disconnected():
|
|
390
|
+
break
|
|
391
|
+
try:
|
|
392
|
+
msg = await asyncio.wait_for(q.get(), timeout=30)
|
|
393
|
+
yield f"data: {msg}\n\n"
|
|
394
|
+
except asyncio.TimeoutError:
|
|
395
|
+
# Keepalive
|
|
396
|
+
yield ": keepalive\n\n"
|
|
397
|
+
finally:
|
|
398
|
+
if q in sse_queues:
|
|
399
|
+
sse_queues.remove(q)
|
|
400
|
+
|
|
401
|
+
return StreamingResponse(
|
|
402
|
+
event_generator(),
|
|
403
|
+
media_type="text/event-stream",
|
|
404
|
+
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
# ── Serve frontend (SPA fallback) ────────────────────────────
|
|
408
|
+
|
|
409
|
+
@app.get("/")
|
|
410
|
+
async def serve_index() -> HTMLResponse:
|
|
411
|
+
index_path = FRONTEND_DIR / "index.html"
|
|
412
|
+
if index_path.exists():
|
|
413
|
+
return HTMLResponse(content=index_path.read_text())
|
|
414
|
+
# Fallback: serve the embedded single-file frontend
|
|
415
|
+
return HTMLResponse(content=_get_embedded_frontend())
|
|
416
|
+
|
|
417
|
+
if FRONTEND_DIR.exists():
|
|
418
|
+
app.mount("/assets", StaticFiles(directory=FRONTEND_DIR / "assets"), name="assets")
|
|
419
|
+
|
|
420
|
+
return app
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _get_embedded_frontend() -> str:
|
|
424
|
+
"""Return the single-file embedded frontend HTML."""
|
|
425
|
+
frontend_file = Path(__file__).parent / "frontend" / "index.html"
|
|
426
|
+
if frontend_file.exists():
|
|
427
|
+
return frontend_file.read_text()
|
|
428
|
+
return "<html><body><h1>Interlinked</h1><p>Frontend not found.</p></body></html>"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: interlinked-mapper
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python program topology explorer — visualize the shape of your codebase
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/austerecryptid/interlinked
|
|
7
|
+
Project-URL: Repository, https://github.com/austerecryptid/interlinked
|
|
8
|
+
Keywords: ast,code-analysis,topology,graph,visualization,mcp,networkx
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
12
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Framework :: FastAPI
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: networkx>=3.1
|
|
22
|
+
Requires-Dist: fastapi>=0.110
|
|
23
|
+
Requires-Dist: uvicorn[standard]>=0.29
|
|
24
|
+
Requires-Dist: pydantic>=2.0
|
|
25
|
+
Requires-Dist: httpx>=0.27
|
|
26
|
+
Requires-Dist: mcp>=1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
interlinked/__init__.py,sha256=n-e2URoZB6ggGZGUfp2iUF1b99Ik8b77-7kQLxdncAs,81
|
|
2
|
+
interlinked/cli.py,sha256=Txb31yJwBmM0qTG_2v1P4SoHzqZZzX1KlvT44EAkcRM,5354
|
|
3
|
+
interlinked/mcp_server.py,sha256=cPYQkom6GBMxO320dmf7DzFIMVKOI56JBlZyFRfKarY,14028
|
|
4
|
+
interlinked/models.py,sha256=8rnrUsWq4n3Gu3_c5PtLmLSGup_RVlEZzLTxwq1xXuA,3293
|
|
5
|
+
interlinked/analyzer/__init__.py,sha256=IUVK2g10kPb710x4GZ64BM1l46cXFUoPN28KQOKVgog,302
|
|
6
|
+
interlinked/analyzer/dead_code.py,sha256=Om4U8e7hYPLRtewRTEbY2u_3CDQ3jrzB88-QQyyiHno,5767
|
|
7
|
+
interlinked/analyzer/graph.py,sha256=zyEjTm9RdL95jNmQ5fNTV2Ni9YmfpLtGySvpN4He8lw,33789
|
|
8
|
+
interlinked/analyzer/parser.py,sha256=RQscrCC-gswyreHl-_XmZi4pWFMfzM88rXiHg_EMfl8,48798
|
|
9
|
+
interlinked/analyzer/similarity.py,sha256=F2eo2PorcKFdR3eqZU-NlUjUO38Gox_Z-5uJaJ9sbck,17599
|
|
10
|
+
interlinked/commander/__init__.py,sha256=u3mUgPptlhh3ieYuBw1c4Mcg03mqti6sCydNi1fMkeg,211
|
|
11
|
+
interlinked/commander/llm.py,sha256=OXwl_0ZDhUNditnIP0piKS4CPlv4rdaNw2m8ixzeu88,13864
|
|
12
|
+
interlinked/commander/query.py,sha256=xAgvek1NRRg7eGjyk1dUeVaUzGqES0EzF5jIzQA7bq4,43553
|
|
13
|
+
interlinked/commander/repl.py,sha256=_5Y-x8EVvbSuV2B8gKVJ2hxz30VjZSO0ZSUDkrDgHMo,2001
|
|
14
|
+
interlinked/visualizer/__init__.py,sha256=JbjxQIkzieTfneMTrD55D85dNQVbG_ApOON0WauWPxU,54
|
|
15
|
+
interlinked/visualizer/layouts.py,sha256=PQI-jX0xUk434opZ3YDGvpSlxuM1Z3VTpvpWK0LsUv0,5122
|
|
16
|
+
interlinked/visualizer/server.py,sha256=PKXWd-_YP37kNLpbrmnvryao1VvN7URxH8XaXHXbmU8,16005
|
|
17
|
+
interlinked_mapper-0.1.0.dist-info/METADATA,sha256=zyLHl3crVsKNWXrznX_6oQkKvq3iajREtXSnNYUfmu0,1106
|
|
18
|
+
interlinked_mapper-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
19
|
+
interlinked_mapper-0.1.0.dist-info/entry_points.txt,sha256=a_FXoNklFSXCPppdEZkxsyyV9FXYO63rPgerO06glTA,53
|
|
20
|
+
interlinked_mapper-0.1.0.dist-info/top_level.txt,sha256=yRUrFRu0dPsOlTUmNsnA7AziN-ITuSn4222SH7h7_uA,12
|
|
21
|
+
interlinked_mapper-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
interlinked
|