@diegonogueiradev_/mcp-graph 1.0.0 → 2.0.1
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.
- package/LICENSE +21 -0
- package/README.md +240 -0
- package/dist/api/middleware/error-handler.d.ts +3 -0
- package/dist/api/middleware/error-handler.d.ts.map +1 -0
- package/dist/api/middleware/error-handler.js +35 -0
- package/dist/api/middleware/error-handler.js.map +1 -0
- package/dist/api/middleware/validate.d.ts +5 -0
- package/dist/api/middleware/validate.d.ts.map +1 -0
- package/dist/api/middleware/validate.js +23 -0
- package/dist/api/middleware/validate.js.map +1 -0
- package/dist/api/router.d.ts +11 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +41 -0
- package/dist/api/router.js.map +1 -0
- package/dist/api/routes/capture.d.ts +3 -0
- package/dist/api/routes/capture.d.ts.map +1 -0
- package/dist/api/routes/capture.js +31 -0
- package/dist/api/routes/capture.js.map +1 -0
- package/dist/api/routes/context.d.ts +4 -0
- package/dist/api/routes/context.d.ts.map +1 -0
- package/dist/api/routes/context.js +25 -0
- package/dist/api/routes/context.js.map +1 -0
- package/dist/api/routes/docs-cache.d.ts +4 -0
- package/dist/api/routes/docs-cache.d.ts.map +1 -0
- package/dist/api/routes/docs-cache.js +79 -0
- package/dist/api/routes/docs-cache.js.map +1 -0
- package/dist/api/routes/edges.d.ts +4 -0
- package/dist/api/routes/edges.d.ts.map +1 -0
- package/dist/api/routes/edges.js +50 -0
- package/dist/api/routes/edges.js.map +1 -0
- package/dist/api/routes/events.d.ts +4 -0
- package/dist/api/routes/events.d.ts.map +1 -0
- package/dist/api/routes/events.js +37 -0
- package/dist/api/routes/events.js.map +1 -0
- package/dist/api/routes/graph.d.ts +4 -0
- package/dist/api/routes/graph.d.ts.map +1 -0
- package/dist/api/routes/graph.js +39 -0
- package/dist/api/routes/graph.js.map +1 -0
- package/dist/api/routes/import.d.ts +4 -0
- package/dist/api/routes/import.d.ts.map +1 -0
- package/dist/api/routes/import.js +92 -0
- package/dist/api/routes/import.js.map +1 -0
- package/dist/api/routes/insights.d.ts +4 -0
- package/dist/api/routes/insights.d.ts.map +1 -0
- package/dist/api/routes/insights.js +40 -0
- package/dist/api/routes/insights.js.map +1 -0
- package/dist/api/routes/integrations.d.ts +4 -0
- package/dist/api/routes/integrations.d.ts.map +1 -0
- package/dist/api/routes/integrations.js +56 -0
- package/dist/api/routes/integrations.js.map +1 -0
- package/dist/api/routes/nodes.d.ts +4 -0
- package/dist/api/routes/nodes.d.ts.map +1 -0
- package/dist/api/routes/nodes.js +123 -0
- package/dist/api/routes/nodes.js.map +1 -0
- package/dist/api/routes/project.d.ts +4 -0
- package/dist/api/routes/project.d.ts.map +1 -0
- package/dist/api/routes/project.js +33 -0
- package/dist/api/routes/project.js.map +1 -0
- package/dist/api/routes/search.d.ts +4 -0
- package/dist/api/routes/search.d.ts.map +1 -0
- package/dist/api/routes/search.js +25 -0
- package/dist/api/routes/search.js.map +1 -0
- package/dist/api/routes/skills.d.ts +3 -0
- package/dist/api/routes/skills.d.ts.map +1 -0
- package/dist/api/routes/skills.js +16 -0
- package/dist/api/routes/skills.js.map +1 -0
- package/dist/api/routes/stats.d.ts +4 -0
- package/dist/api/routes/stats.d.ts.map +1 -0
- package/dist/api/routes/stats.js +14 -0
- package/dist/api/routes/stats.js.map +1 -0
- package/dist/cli/commands/import-cmd.d.ts +3 -0
- package/dist/cli/commands/import-cmd.d.ts.map +1 -0
- package/dist/cli/commands/import-cmd.js +38 -0
- package/dist/cli/commands/import-cmd.js.map +1 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +55 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +3 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +18 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +3 -0
- package/dist/cli/commands/stats.d.ts.map +1 -0
- package/dist/cli/commands/stats.js +39 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +17 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/capture/content-extractor.d.ts +21 -0
- package/dist/core/capture/content-extractor.d.ts.map +1 -0
- package/dist/core/capture/content-extractor.js +74 -0
- package/dist/core/capture/content-extractor.js.map +1 -0
- package/dist/core/capture/web-capture.d.ts +20 -0
- package/dist/core/capture/web-capture.d.ts.map +1 -0
- package/dist/core/capture/web-capture.js +51 -0
- package/dist/core/capture/web-capture.js.map +1 -0
- package/dist/core/config/config-loader.d.ts +3 -0
- package/dist/core/config/config-loader.d.ts.map +1 -0
- package/dist/core/config/config-loader.js +43 -0
- package/dist/core/config/config-loader.js.map +1 -0
- package/dist/core/config/config-schema.d.ts +11 -0
- package/dist/core/config/config-schema.d.ts.map +1 -0
- package/dist/core/config/config-schema.js +12 -0
- package/dist/core/config/config-schema.js.map +1 -0
- package/dist/core/docs/docs-cache-store.d.ts +24 -0
- package/dist/core/docs/docs-cache-store.d.ts.map +1 -0
- package/dist/core/docs/docs-cache-store.js +61 -0
- package/dist/core/docs/docs-cache-store.js.map +1 -0
- package/dist/core/docs/docs-syncer.d.ts +13 -0
- package/dist/core/docs/docs-syncer.d.ts.map +1 -0
- package/dist/core/docs/docs-syncer.js +38 -0
- package/dist/core/docs/docs-syncer.js.map +1 -0
- package/dist/core/events/event-bus.d.ts +26 -0
- package/dist/core/events/event-bus.d.ts.map +1 -0
- package/dist/core/events/event-bus.js +47 -0
- package/dist/core/events/event-bus.js.map +1 -0
- package/dist/core/events/event-types.d.ts +57 -0
- package/dist/core/events/event-types.d.ts.map +1 -0
- package/dist/core/events/event-types.js +2 -0
- package/dist/core/events/event-types.js.map +1 -0
- package/dist/core/graph/mermaid-export.d.ts +9 -0
- package/dist/core/graph/mermaid-export.d.ts.map +1 -0
- package/dist/core/graph/mermaid-export.js +80 -0
- package/dist/core/graph/mermaid-export.js.map +1 -0
- package/dist/core/importer/prd-to-graph.d.ts.map +1 -1
- package/dist/core/importer/prd-to-graph.js +7 -0
- package/dist/core/importer/prd-to-graph.js.map +1 -1
- package/dist/core/insights/bottleneck-detector.d.ts +31 -0
- package/dist/core/insights/bottleneck-detector.d.ts.map +1 -0
- package/dist/core/insights/bottleneck-detector.js +69 -0
- package/dist/core/insights/bottleneck-detector.js.map +1 -0
- package/dist/core/insights/metrics-calculator.d.ts +31 -0
- package/dist/core/insights/metrics-calculator.d.ts.map +1 -0
- package/dist/core/insights/metrics-calculator.js +78 -0
- package/dist/core/insights/metrics-calculator.js.map +1 -0
- package/dist/core/insights/skill-recommender.d.ts +21 -0
- package/dist/core/insights/skill-recommender.d.ts.map +1 -0
- package/dist/core/insights/skill-recommender.js +129 -0
- package/dist/core/insights/skill-recommender.js.map +1 -0
- package/dist/core/integrations/serena-reader.d.ts +18 -0
- package/dist/core/integrations/serena-reader.d.ts.map +1 -0
- package/dist/core/integrations/serena-reader.js +50 -0
- package/dist/core/integrations/serena-reader.js.map +1 -0
- package/dist/core/integrations/tool-status.d.ts +18 -0
- package/dist/core/integrations/tool-status.d.ts.map +1 -0
- package/dist/core/integrations/tool-status.js +92 -0
- package/dist/core/integrations/tool-status.js.map +1 -0
- package/dist/core/parser/file-reader.d.ts +13 -0
- package/dist/core/parser/file-reader.d.ts.map +1 -0
- package/dist/core/parser/file-reader.js +52 -0
- package/dist/core/parser/file-reader.js.map +1 -0
- package/dist/core/parser/read-html.d.ts +7 -0
- package/dist/core/parser/read-html.d.ts.map +1 -0
- package/dist/core/parser/read-html.js +51 -0
- package/dist/core/parser/read-html.js.map +1 -0
- package/dist/core/parser/read-pdf.d.ts +10 -0
- package/dist/core/parser/read-pdf.d.ts.map +1 -0
- package/dist/core/parser/read-pdf.js +16 -0
- package/dist/core/parser/read-pdf.js.map +1 -0
- package/dist/core/planner/next-task.d.ts.map +1 -1
- package/dist/core/planner/next-task.js +4 -1
- package/dist/core/planner/next-task.js.map +1 -1
- package/dist/core/search/fts-search.d.ts.map +1 -1
- package/dist/core/search/fts-search.js +6 -1
- package/dist/core/search/fts-search.js.map +1 -1
- package/dist/core/store/migrations.d.ts.map +1 -1
- package/dist/core/store/migrations.js +38 -0
- package/dist/core/store/migrations.js.map +1 -1
- package/dist/core/store/sqlite-store.d.ts +7 -0
- package/dist/core/store/sqlite-store.d.ts.map +1 -1
- package/dist/core/store/sqlite-store.js +28 -3
- package/dist/core/store/sqlite-store.js.map +1 -1
- package/dist/core/utils/logger.d.ts +1 -0
- package/dist/core/utils/logger.d.ts.map +1 -1
- package/dist/core/utils/logger.js +5 -0
- package/dist/core/utils/logger.js.map +1 -1
- package/dist/mcp/init-project.d.ts.map +1 -1
- package/dist/mcp/init-project.js +12 -16
- package/dist/mcp/init-project.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +17 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.js +0 -0
- package/dist/mcp/tools/export-mermaid.d.ts +4 -0
- package/dist/mcp/tools/export-mermaid.d.ts.map +1 -0
- package/dist/mcp/tools/export-mermaid.js +27 -0
- package/dist/mcp/tools/export-mermaid.js.map +1 -0
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +2 -0
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/web/dashboard/dist/assets/code-graph-tab-jvBo8Q9t.js +1 -0
- package/dist/web/dashboard/dist/assets/constants-CLJl-f3f.js +1 -0
- package/dist/web/dashboard/dist/assets/graph-tab-BoKfDlvO.js +1 -0
- package/dist/web/dashboard/dist/assets/graph-utils-BZV40eAE.css +1 -0
- package/dist/web/dashboard/dist/assets/graph-utils-uGOH4eMw.js +23 -0
- package/dist/web/dashboard/dist/assets/index-DM_LGeRr.css +1 -0
- package/dist/web/dashboard/dist/assets/index-DrHbgcp5.js +53 -0
- package/dist/web/dashboard/dist/assets/insights-tab-D7sHV2xV.js +1 -0
- package/dist/web/dashboard/dist/assets/prd-backlog-tab-C_Uq1Qte.js +1 -0
- package/dist/web/dashboard/dist/index.html +13 -0
- package/dist/web/public/css/styles.css +646 -0
- package/dist/web/public/index.html +209 -0
- package/dist/web/public/js/api-client.js +85 -0
- package/dist/web/public/js/app.js +112 -0
- package/dist/web/public/js/capture-form.js +196 -0
- package/dist/web/public/js/filters.js +94 -0
- package/dist/web/public/js/force-graph-renderer.js +498 -0
- package/dist/web/public/js/graph-renderer.js +62 -0
- package/dist/web/public/js/import-form.js +105 -0
- package/dist/web/public/js/node-detail.js +106 -0
- package/dist/web/public/js/tabs/code-graph-tab.js +66 -0
- package/dist/web/public/js/tabs/graph-tab.js +238 -0
- package/dist/web/public/js/tabs/insights-tab.js +236 -0
- package/dist/web/public/js/tabs/knowledge-tab.js +201 -0
- package/dist/web/public/js/tabs/prd-backlog-tab.js +167 -0
- package/dist/web/public/vendor/force-graph.min.js +5 -0
- package/dist/web/public/vendor/mermaid.min.js +2843 -0
- package/package.json +22 -3
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>mcp-graph Workstation</title>
|
|
7
|
+
<link rel="stylesheet" href="/css/styles.css">
|
|
8
|
+
<script src="/vendor/mermaid.min.js"></script>
|
|
9
|
+
<script src="/vendor/force-graph.min.js"></script>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<!-- ── Header ──────────────────────────────────── -->
|
|
13
|
+
<header class="header">
|
|
14
|
+
<div class="header-left">
|
|
15
|
+
<h1 class="logo">mcp-graph <span class="logo-sub">Workstation</span></h1>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="header-right">
|
|
18
|
+
<div class="stats-bar" id="stats-bar"></div>
|
|
19
|
+
<button class="btn-icon" id="theme-toggle" title="Toggle theme">
|
|
20
|
+
<span id="theme-icon">☽</span>
|
|
21
|
+
</button>
|
|
22
|
+
</div>
|
|
23
|
+
</header>
|
|
24
|
+
|
|
25
|
+
<!-- ── Tabs ────────────────────────────────────── -->
|
|
26
|
+
<nav class="tabs" id="tabs">
|
|
27
|
+
<button class="tab active" data-tab="graph">Graph</button>
|
|
28
|
+
<button class="tab" data-tab="prd-backlog">PRD & Backlog</button>
|
|
29
|
+
<button class="tab" data-tab="code-graph">Code Graph</button>
|
|
30
|
+
<button class="tab" data-tab="knowledge">Knowledge</button>
|
|
31
|
+
<button class="tab" data-tab="insights">Insights</button>
|
|
32
|
+
</nav>
|
|
33
|
+
|
|
34
|
+
<!-- ── Tab Content ─────────────────────────────── -->
|
|
35
|
+
<main class="main">
|
|
36
|
+
<!-- Tab 1: Graph -->
|
|
37
|
+
<section class="tab-content active" id="tab-graph">
|
|
38
|
+
<div class="graph-layout">
|
|
39
|
+
<aside class="filters-panel" id="filters-panel">
|
|
40
|
+
<h3>Filters</h3>
|
|
41
|
+
<div class="filter-group">
|
|
42
|
+
<label>Status</label>
|
|
43
|
+
<div id="filter-status" class="filter-checks"></div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="filter-group">
|
|
46
|
+
<label>Type</label>
|
|
47
|
+
<div id="filter-type" class="filter-checks"></div>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="filter-group mermaid-filter" id="mermaid-filters-direction">
|
|
50
|
+
<label>Direction</label>
|
|
51
|
+
<select id="filter-direction">
|
|
52
|
+
<option value="TD">Top-Down</option>
|
|
53
|
+
<option value="LR">Left-Right</option>
|
|
54
|
+
</select>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="filter-group mermaid-filter" id="mermaid-filters-format">
|
|
57
|
+
<label>Format</label>
|
|
58
|
+
<select id="filter-format">
|
|
59
|
+
<option value="flowchart">Flowchart</option>
|
|
60
|
+
<option value="mindmap">Mindmap</option>
|
|
61
|
+
</select>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="filter-group force-filter" id="force-filters-edge-types">
|
|
64
|
+
<label>Edge Types</label>
|
|
65
|
+
<div id="filter-edge-types" class="filter-checks"></div>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="filter-group force-filter" id="force-filters-depth">
|
|
68
|
+
<label>Focus Depth <span id="depth-value" class="text-muted">All</span></label>
|
|
69
|
+
<input type="range" id="filter-focus-depth" min="0" max="5" value="0" step="1" class="depth-slider">
|
|
70
|
+
<div class="depth-labels"><span>All</span><span>1</span><span>2</span><span>3</span><span>4</span><span>5</span></div>
|
|
71
|
+
</div>
|
|
72
|
+
<button class="btn btn-sm" id="btn-apply-filters">Apply</button>
|
|
73
|
+
<button class="btn btn-sm btn-ghost" id="btn-clear-filters">Clear</button>
|
|
74
|
+
<hr>
|
|
75
|
+
<button class="btn btn-primary btn-sm" id="btn-import-prd">Import PRD</button>
|
|
76
|
+
<button class="btn btn-sm" id="btn-capture-web">Capture Web</button>
|
|
77
|
+
</aside>
|
|
78
|
+
|
|
79
|
+
<div class="graph-main">
|
|
80
|
+
<div class="view-toggle" id="view-toggle">
|
|
81
|
+
<button class="view-btn active" data-view="force">Force Graph</button>
|
|
82
|
+
<button class="view-btn" data-view="mermaid">Mermaid</button>
|
|
83
|
+
<div class="zoom-controls" id="zoom-controls">
|
|
84
|
+
<button class="btn-icon" id="zoom-in" title="Zoom in">+</button>
|
|
85
|
+
<button class="btn-icon" id="zoom-out" title="Zoom out">−</button>
|
|
86
|
+
<button class="btn-icon" id="zoom-fit" title="Fit to view">⊡</button>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="force-graph-container" id="force-graph-container"></div>
|
|
90
|
+
<div class="mermaid-container hidden" id="mermaid-container">
|
|
91
|
+
<div class="empty-state" id="graph-empty">
|
|
92
|
+
<p>No graph data. Import a PRD to get started.</p>
|
|
93
|
+
</div>
|
|
94
|
+
<div id="mermaid-output"></div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<aside class="detail-panel hidden" id="detail-panel">
|
|
99
|
+
<div class="detail-header">
|
|
100
|
+
<h3 id="detail-title">Node Detail</h3>
|
|
101
|
+
<button class="btn-icon" id="detail-close">×</button>
|
|
102
|
+
</div>
|
|
103
|
+
<div id="detail-body"></div>
|
|
104
|
+
</aside>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<!-- Node table -->
|
|
108
|
+
<div class="node-table-section">
|
|
109
|
+
<div class="table-toolbar">
|
|
110
|
+
<input type="text" id="table-search" placeholder="Search nodes..." class="input-search">
|
|
111
|
+
</div>
|
|
112
|
+
<div class="table-wrapper">
|
|
113
|
+
<table class="node-table" id="node-table">
|
|
114
|
+
<thead>
|
|
115
|
+
<tr>
|
|
116
|
+
<th data-sort="title">Title</th>
|
|
117
|
+
<th data-sort="type">Type</th>
|
|
118
|
+
<th data-sort="status">Status</th>
|
|
119
|
+
<th data-sort="priority">Pri</th>
|
|
120
|
+
<th data-sort="xpSize">Size</th>
|
|
121
|
+
<th data-sort="sprint">Sprint</th>
|
|
122
|
+
</tr>
|
|
123
|
+
</thead>
|
|
124
|
+
<tbody id="node-table-body"></tbody>
|
|
125
|
+
</table>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
</section>
|
|
129
|
+
|
|
130
|
+
<!-- Tab 2: PRD & Backlog -->
|
|
131
|
+
<section class="tab-content" id="tab-prd-backlog">
|
|
132
|
+
<div class="prd-layout">
|
|
133
|
+
<div class="prd-source">
|
|
134
|
+
<h3>PRD Source</h3>
|
|
135
|
+
<div id="prd-source-content" class="prd-text">
|
|
136
|
+
<div class="empty-state">
|
|
137
|
+
<p>No PRD imported yet. Use the Import PRD button on the Graph tab.</p>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
<div class="backlog-panel">
|
|
142
|
+
<div class="backlog-header">
|
|
143
|
+
<h3>Backlog</h3>
|
|
144
|
+
<div class="next-task-badge" id="next-task-badge"></div>
|
|
145
|
+
</div>
|
|
146
|
+
<div class="progress-bars" id="progress-bars"></div>
|
|
147
|
+
<div class="backlog-list" id="backlog-list"></div>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
</section>
|
|
151
|
+
|
|
152
|
+
<!-- Tab 3: Code Graph -->
|
|
153
|
+
<section class="tab-content" id="tab-code-graph">
|
|
154
|
+
<div class="empty-state"><p>Loading...</p></div>
|
|
155
|
+
</section>
|
|
156
|
+
|
|
157
|
+
<!-- Tab 4: Knowledge -->
|
|
158
|
+
<section class="tab-content" id="tab-knowledge">
|
|
159
|
+
<div class="empty-state"><p>Loading...</p></div>
|
|
160
|
+
</section>
|
|
161
|
+
|
|
162
|
+
<!-- Tab 5: Insights -->
|
|
163
|
+
<section class="tab-content" id="tab-insights">
|
|
164
|
+
<div class="empty-state"><p>Loading...</p></div>
|
|
165
|
+
</section>
|
|
166
|
+
</main>
|
|
167
|
+
|
|
168
|
+
<!-- ── Import Modal ────────────────────────────── -->
|
|
169
|
+
<dialog class="modal" id="import-modal">
|
|
170
|
+
<div class="modal-content">
|
|
171
|
+
<div class="modal-header">
|
|
172
|
+
<h3>Import PRD</h3>
|
|
173
|
+
<button class="btn-icon" id="modal-close">×</button>
|
|
174
|
+
</div>
|
|
175
|
+
<div class="modal-body">
|
|
176
|
+
<div class="drop-zone" id="drop-zone">
|
|
177
|
+
<p>Drag & drop a file here<br>or click to select</p>
|
|
178
|
+
<input type="file" id="file-input" accept=".md,.txt,.pdf,.html" hidden>
|
|
179
|
+
</div>
|
|
180
|
+
<div class="import-options">
|
|
181
|
+
<label>
|
|
182
|
+
<input type="checkbox" id="import-force"> Force re-import (clear existing nodes from this file)
|
|
183
|
+
</label>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="import-status" id="import-status"></div>
|
|
186
|
+
</div>
|
|
187
|
+
<div class="modal-footer">
|
|
188
|
+
<button class="btn btn-ghost" id="import-cancel">Cancel</button>
|
|
189
|
+
<button class="btn btn-primary" id="import-submit" disabled>Import</button>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</dialog>
|
|
193
|
+
|
|
194
|
+
<!-- ── Scripts ─────────────────────────────────── -->
|
|
195
|
+
<script src="/js/api-client.js"></script>
|
|
196
|
+
<script src="/js/graph-renderer.js"></script>
|
|
197
|
+
<script src="/js/force-graph-renderer.js"></script>
|
|
198
|
+
<script src="/js/filters.js"></script>
|
|
199
|
+
<script src="/js/node-detail.js"></script>
|
|
200
|
+
<script src="/js/import-form.js"></script>
|
|
201
|
+
<script src="/js/tabs/graph-tab.js"></script>
|
|
202
|
+
<script src="/js/tabs/prd-backlog-tab.js"></script>
|
|
203
|
+
<script src="/js/tabs/code-graph-tab.js"></script>
|
|
204
|
+
<script src="/js/tabs/knowledge-tab.js"></script>
|
|
205
|
+
<script src="/js/tabs/insights-tab.js"></script>
|
|
206
|
+
<script src="/js/capture-form.js"></script>
|
|
207
|
+
<script src="/js/app.js"></script>
|
|
208
|
+
</body>
|
|
209
|
+
</html>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client — thin fetch() wrapper for /api/v1/
|
|
3
|
+
*/
|
|
4
|
+
const ApiClient = (() => {
|
|
5
|
+
const BASE = '/api/v1';
|
|
6
|
+
|
|
7
|
+
async function request(path, options = {}) {
|
|
8
|
+
const url = `${BASE}${path}`;
|
|
9
|
+
const res = await fetch(url, {
|
|
10
|
+
headers: { 'Content-Type': 'application/json', ...options.headers },
|
|
11
|
+
...options,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (res.status === 204) return null;
|
|
15
|
+
|
|
16
|
+
const body = await res.json();
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
const err = new Error(body.error || `HTTP ${res.status}`);
|
|
19
|
+
err.status = res.status;
|
|
20
|
+
err.details = body.details;
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
return body;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
// Generic request (for integrations/insights)
|
|
28
|
+
request,
|
|
29
|
+
|
|
30
|
+
// Project
|
|
31
|
+
getProject() { return request('/project'); },
|
|
32
|
+
initProject(name) { return request('/project/init', { method: 'POST', body: JSON.stringify({ name }) }); },
|
|
33
|
+
|
|
34
|
+
// Nodes
|
|
35
|
+
getNodes(params = {}) {
|
|
36
|
+
const qs = new URLSearchParams(params).toString();
|
|
37
|
+
return request(`/nodes${qs ? '?' + qs : ''}`);
|
|
38
|
+
},
|
|
39
|
+
getNode(id) { return request(`/nodes/${id}`); },
|
|
40
|
+
createNode(data) { return request('/nodes', { method: 'POST', body: JSON.stringify(data) }); },
|
|
41
|
+
updateNode(id, data) { return request(`/nodes/${id}`, { method: 'PATCH', body: JSON.stringify(data) }); },
|
|
42
|
+
deleteNode(id) { return request(`/nodes/${id}`, { method: 'DELETE' }); },
|
|
43
|
+
|
|
44
|
+
// Edges
|
|
45
|
+
getEdges() { return request('/edges'); },
|
|
46
|
+
createEdge(data) { return request('/edges', { method: 'POST', body: JSON.stringify(data) }); },
|
|
47
|
+
deleteEdge(id) { return request(`/edges/${id}`, { method: 'DELETE' }); },
|
|
48
|
+
|
|
49
|
+
// Stats
|
|
50
|
+
getStats() { return request('/stats'); },
|
|
51
|
+
|
|
52
|
+
// Search
|
|
53
|
+
search(q, limit) {
|
|
54
|
+
const qs = new URLSearchParams({ q, ...(limit ? { limit: String(limit) } : {}) }).toString();
|
|
55
|
+
return request(`/search?${qs}`);
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Graph
|
|
59
|
+
getGraph() { return request('/graph'); },
|
|
60
|
+
getMermaid(params = {}) {
|
|
61
|
+
const qs = new URLSearchParams(params).toString();
|
|
62
|
+
const url = `${BASE}/graph/mermaid${qs ? '?' + qs : ''}`;
|
|
63
|
+
return fetch(url).then(r => r.text());
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// Import (multipart)
|
|
67
|
+
async importFile(file, force = false) {
|
|
68
|
+
const formData = new FormData();
|
|
69
|
+
formData.append('file', file);
|
|
70
|
+
if (force) formData.append('force', 'true');
|
|
71
|
+
|
|
72
|
+
const res = await fetch(`${BASE}/import`, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
body: formData,
|
|
75
|
+
});
|
|
76
|
+
const body = await res.json();
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
const err = new Error(body.error || `HTTP ${res.status}`);
|
|
79
|
+
err.status = res.status;
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
return body;
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
})();
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App bootstrap — tab routing, theme, stats bar
|
|
3
|
+
*/
|
|
4
|
+
(async function App() {
|
|
5
|
+
// ── Theme ──────────────────────────────────────
|
|
6
|
+
const THEME_KEY = 'mcp-graph-theme';
|
|
7
|
+
let darkMode = localStorage.getItem(THEME_KEY) === 'dark';
|
|
8
|
+
applyTheme();
|
|
9
|
+
|
|
10
|
+
document.getElementById('theme-toggle')?.addEventListener('click', () => {
|
|
11
|
+
darkMode = !darkMode;
|
|
12
|
+
localStorage.setItem(THEME_KEY, darkMode ? 'dark' : 'light');
|
|
13
|
+
applyTheme();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
function applyTheme() {
|
|
17
|
+
document.body.classList.toggle('dark', darkMode);
|
|
18
|
+
const icon = document.getElementById('theme-icon');
|
|
19
|
+
if (icon) icon.textContent = darkMode ? '\u2600' : '\u263D';
|
|
20
|
+
GraphRenderer.setTheme(darkMode ? 'dark' : 'default');
|
|
21
|
+
ForceGraphRenderer.setTheme(darkMode ? 'dark' : 'default');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ── Initialize modules ─────────────────────────
|
|
25
|
+
Filters.init();
|
|
26
|
+
NodeDetail.init();
|
|
27
|
+
ImportForm.init();
|
|
28
|
+
CaptureForm.init();
|
|
29
|
+
GraphTab.init();
|
|
30
|
+
PrdBacklogTab.init();
|
|
31
|
+
CodeGraphTab.init();
|
|
32
|
+
KnowledgeTab.init();
|
|
33
|
+
InsightsTab.init();
|
|
34
|
+
|
|
35
|
+
// ── Tab routing ────────────────────────────────
|
|
36
|
+
const tabs = document.querySelectorAll('.tab:not([disabled])');
|
|
37
|
+
const contents = document.querySelectorAll('.tab-content');
|
|
38
|
+
|
|
39
|
+
tabs.forEach(tab => {
|
|
40
|
+
tab.addEventListener('click', () => {
|
|
41
|
+
const target = tab.dataset.tab;
|
|
42
|
+
tabs.forEach(t => t.classList.remove('active'));
|
|
43
|
+
tab.classList.add('active');
|
|
44
|
+
contents.forEach(c => c.classList.toggle('active', c.id === `tab-${target}`));
|
|
45
|
+
loadTab(target);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
async function loadTab(name) {
|
|
50
|
+
switch (name) {
|
|
51
|
+
case 'graph': await GraphTab.load(); break;
|
|
52
|
+
case 'prd-backlog': await PrdBacklogTab.load(); break;
|
|
53
|
+
case 'code-graph': await CodeGraphTab.load(); break;
|
|
54
|
+
case 'knowledge': await KnowledgeTab.load(); break;
|
|
55
|
+
case 'insights': await InsightsTab.load(); break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Stats bar ──────────────────────────────────
|
|
60
|
+
async function updateStats() {
|
|
61
|
+
try {
|
|
62
|
+
const stats = await ApiClient.getStats();
|
|
63
|
+
const bar = document.getElementById('stats-bar');
|
|
64
|
+
if (bar) {
|
|
65
|
+
const done = stats.byStatus?.done || 0;
|
|
66
|
+
bar.textContent = `${done}/${stats.totalNodes} done`;
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Project may not be initialized
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Events ─────────────────────────────────────
|
|
74
|
+
document.addEventListener('import-completed', () => {
|
|
75
|
+
loadTab(getActiveTab());
|
|
76
|
+
updateStats();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
window.addEventListener('graph-updated', () => {
|
|
80
|
+
loadTab(getActiveTab());
|
|
81
|
+
updateStats();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
function getActiveTab() {
|
|
85
|
+
const active = document.querySelector('.tab.active');
|
|
86
|
+
return active?.dataset.tab || 'graph';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── SSE Real-Time Updates ─────────────────────
|
|
90
|
+
function connectSSE() {
|
|
91
|
+
const es = new EventSource('/api/v1/events');
|
|
92
|
+
|
|
93
|
+
es.addEventListener('node:created', () => { loadTab(getActiveTab()); updateStats(); });
|
|
94
|
+
es.addEventListener('node:updated', () => { loadTab(getActiveTab()); updateStats(); });
|
|
95
|
+
es.addEventListener('node:deleted', () => { loadTab(getActiveTab()); updateStats(); });
|
|
96
|
+
es.addEventListener('edge:created', () => { loadTab(getActiveTab()); });
|
|
97
|
+
es.addEventListener('edge:deleted', () => { loadTab(getActiveTab()); });
|
|
98
|
+
es.addEventListener('import:completed', () => { loadTab(getActiveTab()); updateStats(); });
|
|
99
|
+
|
|
100
|
+
es.onerror = () => {
|
|
101
|
+
es.close();
|
|
102
|
+
// Reconnect after 5s
|
|
103
|
+
setTimeout(connectSSE, 5000);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
connectSSE();
|
|
108
|
+
|
|
109
|
+
// ── Initial load ───────────────────────────────
|
|
110
|
+
await updateStats();
|
|
111
|
+
await GraphTab.load();
|
|
112
|
+
})();
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/// <reference lib="dom" />
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Web capture form — URL input + capture button.
|
|
6
|
+
* Integrates with the /api/v1/capture endpoint.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line no-unused-vars
|
|
10
|
+
const CaptureForm = (() => {
|
|
11
|
+
let captureModal = null;
|
|
12
|
+
let lastCaptureResult = null;
|
|
13
|
+
|
|
14
|
+
function init() {
|
|
15
|
+
const btn = document.getElementById("btn-capture-web");
|
|
16
|
+
if (btn) btn.addEventListener("click", openModal);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function createModal() {
|
|
20
|
+
if (captureModal) return captureModal;
|
|
21
|
+
|
|
22
|
+
const overlay = document.createElement("div");
|
|
23
|
+
overlay.className = "modal-overlay";
|
|
24
|
+
overlay.id = "capture-modal";
|
|
25
|
+
overlay.innerHTML = `
|
|
26
|
+
<div class="modal-content">
|
|
27
|
+
<div class="modal-header">
|
|
28
|
+
<h2>Capture Web Page</h2>
|
|
29
|
+
<button class="modal-close" id="capture-close">×</button>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="modal-body">
|
|
32
|
+
<div class="form-group">
|
|
33
|
+
<label for="capture-url">URL</label>
|
|
34
|
+
<input type="url" id="capture-url" placeholder="https://example.com/prd" required />
|
|
35
|
+
</div>
|
|
36
|
+
<div class="form-group">
|
|
37
|
+
<label for="capture-selector">CSS Selector (optional)</label>
|
|
38
|
+
<input type="text" id="capture-selector" placeholder="main, article, .content" />
|
|
39
|
+
</div>
|
|
40
|
+
<div class="form-group">
|
|
41
|
+
<label for="capture-wait">Wait for Selector (optional)</label>
|
|
42
|
+
<input type="text" id="capture-wait" placeholder=".loaded, #content" />
|
|
43
|
+
</div>
|
|
44
|
+
<div id="capture-status" class="capture-status" style="display:none;"></div>
|
|
45
|
+
<div id="capture-preview" class="capture-preview" style="display:none;">
|
|
46
|
+
<h3>Extracted Content</h3>
|
|
47
|
+
<div class="capture-meta"></div>
|
|
48
|
+
<pre class="capture-text"></pre>
|
|
49
|
+
<div class="capture-actions">
|
|
50
|
+
<button id="capture-import-btn" class="btn btn-primary">Import as PRD</button>
|
|
51
|
+
<button id="capture-copy-btn" class="btn">Copy Text</button>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
<div class="modal-footer">
|
|
56
|
+
<button id="capture-submit" class="btn btn-primary">Capture</button>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
document.body.appendChild(overlay);
|
|
62
|
+
captureModal = overlay;
|
|
63
|
+
|
|
64
|
+
overlay.querySelector("#capture-close").addEventListener("click", closeModal);
|
|
65
|
+
overlay.addEventListener("click", (e) => {
|
|
66
|
+
if (e.target === overlay) closeModal();
|
|
67
|
+
});
|
|
68
|
+
overlay.querySelector("#capture-submit").addEventListener("click", handleCapture);
|
|
69
|
+
overlay.querySelector("#capture-url").addEventListener("keydown", (e) => {
|
|
70
|
+
if (e.key === "Enter") handleCapture();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return overlay;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function openModal() {
|
|
77
|
+
const modal = createModal();
|
|
78
|
+
modal.style.display = "flex";
|
|
79
|
+
const urlInput = document.getElementById("capture-url");
|
|
80
|
+
if (urlInput) urlInput.focus();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function closeModal() {
|
|
84
|
+
if (captureModal) {
|
|
85
|
+
captureModal.style.display = "none";
|
|
86
|
+
const status = captureModal.querySelector("#capture-status");
|
|
87
|
+
const preview = captureModal.querySelector("#capture-preview");
|
|
88
|
+
if (status) status.style.display = "none";
|
|
89
|
+
if (preview) preview.style.display = "none";
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function showStatus(message, type) {
|
|
94
|
+
const status = document.getElementById("capture-status");
|
|
95
|
+
if (!status) return;
|
|
96
|
+
status.style.display = "block";
|
|
97
|
+
status.className = "capture-status capture-status-" + type;
|
|
98
|
+
status.textContent = message;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function handleCapture() {
|
|
102
|
+
const urlInput = document.getElementById("capture-url");
|
|
103
|
+
const selectorInput = document.getElementById("capture-selector");
|
|
104
|
+
const waitInput = document.getElementById("capture-wait");
|
|
105
|
+
const preview = document.getElementById("capture-preview");
|
|
106
|
+
const submitBtn = document.getElementById("capture-submit");
|
|
107
|
+
|
|
108
|
+
const url = urlInput?.value?.trim();
|
|
109
|
+
if (!url) {
|
|
110
|
+
showStatus("Please enter a URL", "error");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
showStatus("Capturing page... This may take a few seconds.", "info");
|
|
115
|
+
if (preview) preview.style.display = "none";
|
|
116
|
+
if (submitBtn) submitBtn.disabled = true;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const body = { url };
|
|
120
|
+
const selector = selectorInput?.value?.trim();
|
|
121
|
+
const waitForSelector = waitInput?.value?.trim();
|
|
122
|
+
if (selector) body.selector = selector;
|
|
123
|
+
if (waitForSelector) body.waitForSelector = waitForSelector;
|
|
124
|
+
|
|
125
|
+
const result = await ApiClient.request("/capture", {
|
|
126
|
+
method: "POST",
|
|
127
|
+
headers: { "Content-Type": "application/json" },
|
|
128
|
+
body: JSON.stringify(body),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
lastCaptureResult = result;
|
|
132
|
+
showStatus("Captured successfully: " + result.wordCount + " words", "success");
|
|
133
|
+
|
|
134
|
+
if (preview) {
|
|
135
|
+
preview.style.display = "block";
|
|
136
|
+
const meta = preview.querySelector(".capture-meta");
|
|
137
|
+
const text = preview.querySelector(".capture-text");
|
|
138
|
+
if (meta) {
|
|
139
|
+
meta.innerHTML =
|
|
140
|
+
"<p><strong>Title:</strong> " + (result.title || "\u2014") + "</p>" +
|
|
141
|
+
"<p><strong>Description:</strong> " + (result.description || "\u2014") + "</p>" +
|
|
142
|
+
"<p><strong>Words:</strong> " + result.wordCount + "</p>" +
|
|
143
|
+
"<p><strong>Captured:</strong> " + new Date(result.capturedAt).toLocaleString() + "</p>";
|
|
144
|
+
}
|
|
145
|
+
if (text) {
|
|
146
|
+
text.textContent = result.text.substring(0, 5000) +
|
|
147
|
+
(result.text.length > 5000 ? "\n\n... (truncated)" : "");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const importBtn = preview.querySelector("#capture-import-btn");
|
|
151
|
+
if (importBtn) {
|
|
152
|
+
importBtn.onclick = function () { importCapturedContent(result); };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const copyBtn = preview.querySelector("#capture-copy-btn");
|
|
156
|
+
if (copyBtn) {
|
|
157
|
+
copyBtn.onclick = function () {
|
|
158
|
+
navigator.clipboard.writeText(result.text).then(function () {
|
|
159
|
+
copyBtn.textContent = "Copied!";
|
|
160
|
+
setTimeout(function () { copyBtn.textContent = "Copy Text"; }, 2000);
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch (err) {
|
|
166
|
+
showStatus("Capture failed: " + err.message, "error");
|
|
167
|
+
} finally {
|
|
168
|
+
if (submitBtn) submitBtn.disabled = false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function importCapturedContent(result) {
|
|
173
|
+
showStatus("Importing captured content as PRD...", "info");
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const blob = new Blob([result.text], { type: "text/markdown" });
|
|
177
|
+
const formData = new FormData();
|
|
178
|
+
const fileName = (result.title || "captured-page").replace(/[^a-zA-Z0-9-_]/g, "-") + ".md";
|
|
179
|
+
formData.append("file", blob, fileName);
|
|
180
|
+
|
|
181
|
+
const res = await fetch("/api/v1/import", { method: "POST", body: formData });
|
|
182
|
+
const importResult = await res.json();
|
|
183
|
+
|
|
184
|
+
showStatus(
|
|
185
|
+
"Imported: " + importResult.nodesCreated + " nodes, " + importResult.edgesCreated + " edges created",
|
|
186
|
+
"success",
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
document.dispatchEvent(new CustomEvent("import-completed"));
|
|
190
|
+
} catch (err) {
|
|
191
|
+
showStatus("Import failed: " + err.message, "error");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { init, openModal };
|
|
196
|
+
})();
|