ctxgraph-code 0.1.2__tar.gz → 0.1.3__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.
Files changed (31) hide show
  1. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/PKG-INFO +10 -6
  2. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/README.md +9 -5
  3. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/pyproject.toml +1 -1
  4. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/cli.py +34 -10
  5. ctxgraph_code-0.1.3/src/ctxgraph_code/view/__init__.py +0 -0
  6. ctxgraph_code-0.1.3/src/ctxgraph_code/view/visualizer.py +288 -0
  7. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code.egg-info/PKG-INFO +10 -6
  8. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code.egg-info/SOURCES.txt +3 -1
  9. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/setup.cfg +0 -0
  10. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/__init__.py +0 -0
  11. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/__main__.py +0 -0
  12. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/analyzers/__init__.py +0 -0
  13. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/analyzers/python/__init__.py +0 -0
  14. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/analyzers/python/importer.py +0 -0
  15. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/analyzers/python/semantic.py +0 -0
  16. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/analyzers/python/symbols.py +0 -0
  17. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/config/__init__.py +0 -0
  18. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/config/init.py +0 -0
  19. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/config/settings.py +0 -0
  20. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/exclude/__init__.py +0 -0
  21. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/exclude/patterns.py +0 -0
  22. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/graph/__init__.py +0 -0
  23. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/graph/builder.py +0 -0
  24. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/graph/models.py +0 -0
  25. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/graph/query.py +0 -0
  26. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/graph/storage.py +0 -0
  27. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code/render.py +0 -0
  28. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code.egg-info/dependency_links.txt +0 -0
  29. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code.egg-info/entry_points.txt +0 -0
  30. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code.egg-info/requires.txt +0 -0
  31. {ctxgraph_code-0.1.2 → ctxgraph_code-0.1.3}/src/ctxgraph_code.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ctxgraph-code
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Code knowledge graph for Claude Code. Build a relationship graph of your Python codebase and query it during coding sessions.
5
5
  Author: ctxgraph-code contributors
6
6
  License: MIT
@@ -165,13 +165,17 @@ Generates a focused context summary: relevant files, their symbols, and dependen
165
165
  ### `view`
166
166
 
167
167
  ```bash
168
- ctxgraph-code view
169
- ctxgraph-code view --output tree.txt
168
+ ctxgraph-code view # generates interactive D3.js HTML and opens browser
169
+ ctxgraph-code view --no-open # generate HTML without opening browser
170
+ ctxgraph-code view --tree # show text tree instead (useful in terminal)
171
+ ctxgraph-code view --output graph.html # save to custom path
170
172
  ```
171
173
 
172
- Prints a hierarchical directory tree from the graph showing every file and its symbols (classes marked `[C]`, functions/methods marked `[M]`). Also lists import and call edges at the bottom.
174
+ Opens an **interactive D3.js force-directed graph** in the browser. Drag nodes, zoom/pan, search by name, filter by type (File/Class/Function). Hover to highlight connected nodes and see summaries.
173
175
 
174
- Useful for a quick visual scan of the project structure without reading every file.
176
+ The HTML is self-contained (loads D3.js from CDN) and saved to `.ctxgraph/graph.html`.
177
+
178
+ Use `--tree` for a terminal-friendly text view of the directory hierarchy with symbols and edges.
175
179
 
176
180
  ### `info`
177
181
 
@@ -286,7 +290,7 @@ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.
286
290
  | CLI commands | 9 (build, capsule, query, view, serve, info, init, ask, chat, history, skill) | 9 (init, build, query, deps, usedby, overview, symbols, context, setup, view, info) |
287
291
  | LLM integration | Built-in (Ollama, Claude, OpenAI, Azure) | None (delegates to Claude Code) |
288
292
  | Chat sessions | Yes | No |
289
- | Visualizer | D3.js HTML + SVG | Text tree (`view` command) |
293
+ | Visualizer | D3.js HTML + SVG | D3.js HTML (`view` opens in browser, `--tree` for text) |
290
294
  | Skills system | Yes (customizable skill TOML files) | No |
291
295
  | MCP server | Yes | No |
292
296
  | Token savings | Yes (capsule DSL compression) | No |
@@ -142,13 +142,17 @@ Generates a focused context summary: relevant files, their symbols, and dependen
142
142
  ### `view`
143
143
 
144
144
  ```bash
145
- ctxgraph-code view
146
- ctxgraph-code view --output tree.txt
145
+ ctxgraph-code view # generates interactive D3.js HTML and opens browser
146
+ ctxgraph-code view --no-open # generate HTML without opening browser
147
+ ctxgraph-code view --tree # show text tree instead (useful in terminal)
148
+ ctxgraph-code view --output graph.html # save to custom path
147
149
  ```
148
150
 
149
- Prints a hierarchical directory tree from the graph showing every file and its symbols (classes marked `[C]`, functions/methods marked `[M]`). Also lists import and call edges at the bottom.
151
+ Opens an **interactive D3.js force-directed graph** in the browser. Drag nodes, zoom/pan, search by name, filter by type (File/Class/Function). Hover to highlight connected nodes and see summaries.
150
152
 
151
- Useful for a quick visual scan of the project structure without reading every file.
153
+ The HTML is self-contained (loads D3.js from CDN) and saved to `.ctxgraph/graph.html`.
154
+
155
+ Use `--tree` for a terminal-friendly text view of the directory hierarchy with symbols and edges.
152
156
 
153
157
  ### `info`
154
158
 
@@ -263,7 +267,7 @@ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.
263
267
  | CLI commands | 9 (build, capsule, query, view, serve, info, init, ask, chat, history, skill) | 9 (init, build, query, deps, usedby, overview, symbols, context, setup, view, info) |
264
268
  | LLM integration | Built-in (Ollama, Claude, OpenAI, Azure) | None (delegates to Claude Code) |
265
269
  | Chat sessions | Yes | No |
266
- | Visualizer | D3.js HTML + SVG | Text tree (`view` command) |
270
+ | Visualizer | D3.js HTML + SVG | D3.js HTML (`view` opens in browser, `--tree` for text) |
267
271
  | Skills system | Yes (customizable skill TOML files) | No |
268
272
  | MCP server | Yes | No |
269
273
  | Token savings | Yes (capsule DSL compression) | No |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ctxgraph-code"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Code knowledge graph for Claude Code. Build a relationship graph of your Python codebase and query it during coding sessions."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -17,7 +17,6 @@ from ctxgraph_code.render import (
17
17
  render_deps,
18
18
  render_overview,
19
19
  render_symbols,
20
- render_treeview,
21
20
  render_usedby,
22
21
  )
23
22
 
@@ -357,10 +356,16 @@ def view(
357
356
  None, "--repo", "-r", help="Repository path"
358
357
  ),
359
358
  output: Optional[str] = typer.Option(
360
- None, "--output", "-o", help="Save tree to file"
359
+ None, "--output", "-o", help="Save graph HTML to file"
360
+ ),
361
+ no_open: bool = typer.Option(
362
+ False, "--no-open", help="Generate HTML but don't open browser"
363
+ ),
364
+ tree: bool = typer.Option(
365
+ False, "--tree", help="Show text tree instead of interactive graph"
361
366
  ),
362
367
  ):
363
- """Visualize the graph as a directory tree with symbols and edges."""
368
+ """Open an interactive D3.js graph in the browser."""
364
369
  path = Path(repo_path).resolve() if repo_path else Path.cwd()
365
370
 
366
371
  storage = get_storage(path)
@@ -368,14 +373,33 @@ def view(
368
373
  console.print("[red]No graph found. Run [bold]ctxgraph-code build[/bold] first.[/red]")
369
374
  raise typer.Exit(1)
370
375
 
371
- tree = render_treeview(storage)
376
+ if tree:
377
+ from ctxgraph_code.render import render_treeview
378
+ text = render_treeview(storage)
379
+ if output:
380
+ out_path = Path(output)
381
+ out_path.write_text(text, encoding="utf-8")
382
+ console.print(f"Saved tree to [bold]{out_path}[/bold]")
383
+ else:
384
+ console.print(text)
385
+ return
386
+
387
+ from ctxgraph_code.view.visualizer import render_view
388
+ html = render_view(storage)
389
+
390
+ graph_dir = path / ".ctxgraph"
391
+ graph_dir.mkdir(parents=True, exist_ok=True)
392
+ out_path = Path(output) if output else (graph_dir / "graph.html")
393
+ out_path.write_text(html, encoding="utf-8")
394
+
395
+ console.print(f"Graph saved to [bold]{out_path}[/bold]")
372
396
 
373
- if output:
374
- out_path = Path(output)
375
- out_path.write_text(tree, encoding="utf-8")
376
- console.print(f"Saved tree to [bold]{out_path}[/bold]")
397
+ if not no_open:
398
+ import webbrowser
399
+ webbrowser.open(str(out_path.resolve()))
400
+ console.print("[green]Opened in browser.[/green]")
377
401
  else:
378
- console.print(tree)
402
+ console.print(f"Open {out_path} in a browser to view.")
379
403
 
380
404
 
381
405
  @app.command()
@@ -423,7 +447,7 @@ def version():
423
447
  try:
424
448
  ver = _v("ctxgraph-code")
425
449
  except Exception:
426
- ver = "0.1.2"
450
+ ver = "0.1.3"
427
451
  console.print(f"ctxgraph-code version [bold]{ver}[/bold]")
428
452
 
429
453
 
File without changes
@@ -0,0 +1,288 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ from ctxgraph_code.graph.storage import Storage
6
+
7
+
8
+ def render_view(storage: Storage) -> str:
9
+ nodes = storage.get_all_nodes()
10
+ edges = storage.get_all_edges()
11
+
12
+ file_nodes = [n for n in nodes if n.type == "file"]
13
+ symbol_nodes = [n for n in nodes if n.type != "file"]
14
+
15
+ graph_data: dict = {
16
+ "nodes": [],
17
+ "links": [],
18
+ }
19
+
20
+ file_node_ids = {n.id for n in file_nodes}
21
+
22
+ for node in file_nodes:
23
+ graph_data["nodes"].append(
24
+ {
25
+ "id": node.id,
26
+ "label": _short_path(node.path or node.name),
27
+ "type": "file",
28
+ "summary": (node.summary or "")[:100],
29
+ "importance": node.importance,
30
+ }
31
+ )
32
+
33
+ for node in symbol_nodes:
34
+ graph_data["nodes"].append(
35
+ {
36
+ "id": node.id,
37
+ "label": node.name,
38
+ "type": node.type,
39
+ "summary": (node.summary or "")[:100],
40
+ "importance": node.importance,
41
+ }
42
+ )
43
+
44
+ seen_links = set()
45
+ for edge in edges:
46
+ if edge.source_id in file_node_ids or edge.target_id in file_node_ids:
47
+ key = (edge.source_id, edge.target_id)
48
+ if key not in seen_links:
49
+ seen_links.add(key)
50
+ graph_data["links"].append(
51
+ {
52
+ "source": edge.source_id,
53
+ "target": edge.target_id,
54
+ "relation": edge.relation,
55
+ }
56
+ )
57
+
58
+ json_data = json.dumps(graph_data, indent=2)
59
+ template = _get_html_template()
60
+ return template.replace("/* GRAPH_DATA */", json_data)
61
+
62
+
63
+ def _short_path(path: str) -> str:
64
+ parts = path.split("/")
65
+ if len(parts) > 3:
66
+ return "/".join(parts[:2] + ["..."] + parts[-1:])
67
+ return path
68
+
69
+
70
+ def _get_html_template() -> str:
71
+ return """<!DOCTYPE html>
72
+ <html lang="en">
73
+ <head>
74
+ <meta charset="UTF-8">
75
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
76
+ <title>ctxgraph-code - Knowledge Graph</title>
77
+ <script src="https://d3js.org/d3.v7.min.js"></script>
78
+ <style>
79
+ * { margin: 0; padding: 0; box-sizing: border-box; }
80
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0d1117; color: #c9d1d9; overflow: hidden; }
81
+ #container { width: 100vw; height: 100vh; position: relative; }
82
+ svg { width: 100%; height: 100%; }
83
+ #toolbar { position: absolute; top: 16px; left: 16px; z-index: 10; display: flex; gap: 8px; align-items: center; background: #161b22; padding: 12px 16px; border-radius: 8px; border: 1px solid #30363d; }
84
+ #toolbar input { background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; padding: 6px 12px; border-radius: 4px; width: 220px; font-size: 13px; }
85
+ #toolbar select { background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; padding: 6px 8px; border-radius: 4px; font-size: 13px; }
86
+ #toolbar label { font-size: 13px; color: #8b949e; }
87
+ #legend { position: absolute; bottom: 16px; left: 16px; z-index: 10; background: #161b22; padding: 12px; border-radius: 8px; border: 1px solid #30363d; font-size: 12px; display: flex; gap: 16px; }
88
+ .legend-item { display: flex; align-items: center; gap: 6px; }
89
+ .legend-dot { width: 10px; height: 10px; border-radius: 50%; }
90
+ #tooltip { position: absolute; z-index: 20; background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 12px; font-size: 13px; max-width: 400px; pointer-events: none; display: none; box-shadow: 0 4px 12px rgba(0,0,0,0.4); }
91
+ #tooltip .tt-name { font-weight: 600; color: #58a6ff; margin-bottom: 4px; }
92
+ #tooltip .tt-type { color: #8b949e; font-size: 11px; margin-bottom: 4px; }
93
+ #tooltip .tt-summary { color: #c9d1d9; font-size: 12px; }
94
+ #stats { position: absolute; bottom: 16px; right: 16px; z-index: 10; background: #161b22; padding: 8px 12px; border-radius: 8px; border: 1px solid #30363d; font-size: 11px; color: #8b949e; }
95
+ .link { stroke-opacity: 0.4; }
96
+ .link.imports { stroke: #58a6ff; }
97
+ .link.calls { stroke: #3fb950; }
98
+ .link.defines { stroke: #d29922; }
99
+ .link.extends { stroke: #bc8cff; }
100
+ .node { cursor: pointer; transition: opacity 0.2s; }
101
+ .node:hover { opacity: 0.8; }
102
+ .node-label { font-size: 11px; fill: #8b949e; pointer-events: none; text-shadow: 0 1px 2px #0d1117, 0 -1px 2px #0d1117, 1px 0 2px #0d1117, -1px 0 2px #0d1117; }
103
+ .node.highlighted .node-label { fill: #c9d1d9; font-weight: 600; }
104
+ .link.highlighted { stroke-opacity: 0.8; }
105
+ </style>
106
+ </head>
107
+ <body>
108
+ <div id="container">
109
+ <div id="toolbar">
110
+ <label>Search:</label>
111
+ <input type="text" id="search" placeholder="Search nodes..." oninput="filterGraph(this.value)">
112
+ <label>Filter:</label>
113
+ <select id="filterType" onchange="filterByType(this.value)">
114
+ <option value="all">All</option>
115
+ <option value="file">Files</option>
116
+ <option value="class">Classes</option>
117
+ <option value="function">Functions</option>
118
+ </select>
119
+ </div>
120
+ <div id="legend">
121
+ <div class="legend-item"><div class="legend-dot" style="background:#58a6ff"></div> File</div>
122
+ <div class="legend-item"><div class="legend-dot" style="background:#d29922"></div> Class</div>
123
+ <div class="legend-item"><div class="legend-dot" style="background:#3fb950"></div> Function</div>
124
+ <div class="legend-item"><svg width="20" height="2"><line x1="0" y1="1" x2="20" y2="1" stroke="#58a6ff" stroke-width="1.5" stroke-dasharray="4,2"/></svg> Import</div>
125
+ <div class="legend-item"><svg width="20" height="2"><line x1="0" y1="1" x2="20" y2="1" stroke="#3fb950" stroke-width="1.5" stroke-dasharray="2,2"/></svg> Call</div>
126
+ </div>
127
+ <div id="stats"></div>
128
+ <div id="tooltip">
129
+ <div class="tt-name"></div>
130
+ <div class="tt-type"></div>
131
+ <div class="tt-summary"></div>
132
+ </div>
133
+ <svg width="100%" height="100%"></svg>
134
+ </div>
135
+ <script>
136
+ document.addEventListener("DOMContentLoaded", () => {
137
+ const graphData = /* GRAPH_DATA */;
138
+ const width = window.innerWidth;
139
+ const height = window.innerHeight;
140
+
141
+ const svg = d3.select("svg");
142
+ svg.attr("width", width).attr("height", height);
143
+ svg.attr("viewBox", `0 0 ${width} ${height}`);
144
+
145
+ const g = svg.append("g");
146
+
147
+ const zoom = d3.zoom()
148
+ .scaleExtent([0.1, 4])
149
+ .on("zoom", (event) => g.attr("transform", event.transform));
150
+
151
+ svg.call(zoom);
152
+
153
+ const colorMap = { file: "#58a6ff", class: "#d29922", function: "#3fb950", module: "#bc8cff" };
154
+
155
+ const simulation = d3.forceSimulation(graphData.nodes)
156
+ .force("link", d3.forceLink(graphData.links).id(d => d.id).distance(d => {
157
+ return d.relation === "imports" ? 100 : 80;
158
+ }))
159
+ .force("charge", d3.forceManyBody().strength(-200))
160
+ .force("center", d3.forceCenter(width / 2, height / 2))
161
+ .force("collision", d3.forceCollide(20));
162
+
163
+ const link = g.append("g")
164
+ .selectAll("line")
165
+ .data(graphData.links)
166
+ .join("line")
167
+ .attr("class", d => `link ${d.relation}`)
168
+ .attr("stroke-width", 1.5);
169
+
170
+ const node = g.append("g")
171
+ .selectAll("g")
172
+ .data(graphData.nodes)
173
+ .join("g")
174
+ .attr("class", "node")
175
+ .call(d3.drag()
176
+ .on("start", (event, d) => {
177
+ if (!event.active) simulation.alphaTarget(0.3).restart();
178
+ d.fx = d.x;
179
+ d.fy = d.y;
180
+ })
181
+ .on("drag", (event, d) => {
182
+ d.fx = event.x;
183
+ d.fy = event.y;
184
+ })
185
+ .on("end", (event, d) => {
186
+ if (!event.active) simulation.alphaTarget(0);
187
+ d.fx = null;
188
+ d.fy = null;
189
+ })
190
+ );
191
+
192
+ node.append("circle")
193
+ .attr("r", d => 5 + (d.importance || 0.5) * 8)
194
+ .attr("fill", d => colorMap[d.type] || "#8b949e")
195
+ .attr("stroke", "#161b22")
196
+ .attr("stroke-width", 1.5);
197
+
198
+ node.append("text")
199
+ .attr("class", "node-label")
200
+ .text(d => d.label)
201
+ .attr("dx", d => 10 + (d.importance || 0.5) * 5)
202
+ .attr("dy", 4);
203
+
204
+ node.on("mouseover", function(event, d) {
205
+ const tt = d3.select("#tooltip");
206
+ tt.style("display", "block");
207
+ tt.select(".tt-name").text(d.label);
208
+ tt.select(".tt-type").text(`Type: ${d.type}`);
209
+ tt.select(".tt-summary").text(d.summary || "");
210
+
211
+ const connected = new Set();
212
+ graphData.links.forEach(l => {
213
+ const sid = typeof l.source === 'object' ? l.source.id : l.source;
214
+ const tid = typeof l.target === 'object' ? l.target.id : l.target;
215
+ if (sid === d.id) connected.add(tid);
216
+ if (tid === d.id) connected.add(sid);
217
+ });
218
+
219
+ d3.selectAll(".node").each(function(n) {
220
+ if (n.id === d.id || connected.has(n.id)) {
221
+ d3.select(this).classed("highlighted", true);
222
+ d3.select(this).select("circle").attr("opacity", 1);
223
+ d3.select(this).select("text").attr("opacity", 1);
224
+ } else {
225
+ d3.select(this).select("circle").attr("opacity", 0.15);
226
+ d3.select(this).select("text").attr("opacity", 0.15);
227
+ }
228
+ });
229
+
230
+ d3.selectAll(".link").each(function(l) {
231
+ const sid = typeof l.source === 'object' ? l.source.id : l.source;
232
+ const tid = typeof l.target === 'object' ? l.target.id : l.target;
233
+ if (sid === d.id || tid === d.id) {
234
+ d3.select(this).classed("highlighted", true);
235
+ }
236
+ });
237
+ })
238
+ .on("mousemove", function(event) {
239
+ d3.select("#tooltip")
240
+ .style("left", (event.pageX + 12) + "px")
241
+ .style("top", (event.pageY - 10) + "px");
242
+ })
243
+ .on("mouseout", function() {
244
+ d3.select("#tooltip").style("display", "none");
245
+ d3.selectAll(".node").each(function(n) {
246
+ d3.select(this).classed("highlighted", false);
247
+ d3.select(this).select("circle").attr("opacity", 1);
248
+ d3.select(this).select("text").attr("opacity", 1);
249
+ });
250
+ d3.selectAll(".link").classed("highlighted", false);
251
+ });
252
+
253
+ simulation.on("tick", () => {
254
+ link
255
+ .attr("x1", d => d.source.x)
256
+ .attr("y1", d => d.source.y)
257
+ .attr("x2", d => d.target.x)
258
+ .attr("y2", d => d.target.y);
259
+ node.attr("transform", d => `translate(${d.x},${d.y})`);
260
+ });
261
+
262
+ d3.select("#stats").text(`Nodes: ${graphData.nodes.length} | Edges: ${graphData.links.length}`);
263
+
264
+ window.filterGraph = function(query) {
265
+ const q = query.toLowerCase();
266
+ d3.selectAll(".node").each(function(d) {
267
+ const match = !q || d.label.toLowerCase().includes(q) || (d.summary && d.summary.toLowerCase().includes(q));
268
+ d3.select(this).style("display", match ? null : "none");
269
+ });
270
+ };
271
+
272
+ window.filterByType = function(type) {
273
+ d3.selectAll(".node").each(function(d) {
274
+ const match = type === "all" || d.type === type;
275
+ d3.select(this).style("display", match ? null : "none");
276
+ });
277
+ };
278
+
279
+ window.addEventListener("resize", () => {
280
+ const w = window.innerWidth;
281
+ const h = window.innerHeight;
282
+ svg.attr("width", w).attr("height", h);
283
+ svg.attr("viewBox", `0 0 ${w} ${h}`);
284
+ });
285
+ });
286
+ </script>
287
+ </body>
288
+ </html>"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ctxgraph-code
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Code knowledge graph for Claude Code. Build a relationship graph of your Python codebase and query it during coding sessions.
5
5
  Author: ctxgraph-code contributors
6
6
  License: MIT
@@ -165,13 +165,17 @@ Generates a focused context summary: relevant files, their symbols, and dependen
165
165
  ### `view`
166
166
 
167
167
  ```bash
168
- ctxgraph-code view
169
- ctxgraph-code view --output tree.txt
168
+ ctxgraph-code view # generates interactive D3.js HTML and opens browser
169
+ ctxgraph-code view --no-open # generate HTML without opening browser
170
+ ctxgraph-code view --tree # show text tree instead (useful in terminal)
171
+ ctxgraph-code view --output graph.html # save to custom path
170
172
  ```
171
173
 
172
- Prints a hierarchical directory tree from the graph showing every file and its symbols (classes marked `[C]`, functions/methods marked `[M]`). Also lists import and call edges at the bottom.
174
+ Opens an **interactive D3.js force-directed graph** in the browser. Drag nodes, zoom/pan, search by name, filter by type (File/Class/Function). Hover to highlight connected nodes and see summaries.
173
175
 
174
- Useful for a quick visual scan of the project structure without reading every file.
176
+ The HTML is self-contained (loads D3.js from CDN) and saved to `.ctxgraph/graph.html`.
177
+
178
+ Use `--tree` for a terminal-friendly text view of the directory hierarchy with symbols and edges.
175
179
 
176
180
  ### `info`
177
181
 
@@ -286,7 +290,7 @@ Built-in default exclusion patterns (always applied): `__pycache__`, `*.pyc`, `.
286
290
  | CLI commands | 9 (build, capsule, query, view, serve, info, init, ask, chat, history, skill) | 9 (init, build, query, deps, usedby, overview, symbols, context, setup, view, info) |
287
291
  | LLM integration | Built-in (Ollama, Claude, OpenAI, Azure) | None (delegates to Claude Code) |
288
292
  | Chat sessions | Yes | No |
289
- | Visualizer | D3.js HTML + SVG | Text tree (`view` command) |
293
+ | Visualizer | D3.js HTML + SVG | D3.js HTML (`view` opens in browser, `--tree` for text) |
290
294
  | Skills system | Yes (customizable skill TOML files) | No |
291
295
  | MCP server | Yes | No |
292
296
  | Token savings | Yes (capsule DSL compression) | No |
@@ -24,4 +24,6 @@ src/ctxgraph_code/graph/__init__.py
24
24
  src/ctxgraph_code/graph/builder.py
25
25
  src/ctxgraph_code/graph/models.py
26
26
  src/ctxgraph_code/graph/query.py
27
- src/ctxgraph_code/graph/storage.py
27
+ src/ctxgraph_code/graph/storage.py
28
+ src/ctxgraph_code/view/__init__.py
29
+ src/ctxgraph_code/view/visualizer.py
File without changes