@adityaaria/spark 6.0.19 → 6.0.21
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/bin/spark-install.sh +88 -2
- package/package.json +1 -1
- package/skills/project-scanner/SKILL.md +2 -2
- package/skills/using-spark/SKILL.md +8 -1
- package/src/cli/index.js +6 -0
- package/src/cli/output.js +1 -1
- package/src/dashboard/public/index.html +664 -222
- package/src/dashboard/server.js +132 -0
|
@@ -3,204 +3,221 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>SPARK | Command Center</title>
|
|
6
|
+
<title>SPARK | Command Center v2</title>
|
|
7
7
|
<!-- Google Fonts -->
|
|
8
8
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
|
|
9
9
|
<!-- Marked.js for Markdown parsing -->
|
|
10
10
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
11
|
-
<!--
|
|
12
|
-
<script src="https://
|
|
11
|
+
<!-- Vis-Network for Interactive Graphs -->
|
|
12
|
+
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vis-network/9.1.9/standalone/umd/vis-network.min.js"></script>
|
|
13
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
|
|
13
14
|
<style>
|
|
14
15
|
:root {
|
|
15
|
-
--bg-color: #
|
|
16
|
-
--
|
|
17
|
-
--glass-border:
|
|
18
|
-
--accent
|
|
19
|
-
--accent: #
|
|
20
|
-
--danger: #
|
|
21
|
-
--
|
|
22
|
-
--text-
|
|
16
|
+
--bg-color: #0d0d0d;
|
|
17
|
+
--panel-bg: #151515;
|
|
18
|
+
--glass-border: #2a2a2a;
|
|
19
|
+
--accent: #dcedc1; /* Sage green from reference */
|
|
20
|
+
--accent-dark: #b8c99d;
|
|
21
|
+
--danger: #ff5e57;
|
|
22
|
+
--warning: #ffb86c;
|
|
23
|
+
--text-main: #f4f4f4;
|
|
24
|
+
--text-muted: #888888;
|
|
25
|
+
--font-main: 'Inter', sans-serif;
|
|
26
|
+
--font-mono: 'Courier New', Courier, monospace;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
* {
|
|
26
|
-
box-sizing: border-box;
|
|
27
|
-
margin: 0;
|
|
28
|
-
padding: 0;
|
|
29
|
-
}
|
|
29
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
30
30
|
|
|
31
31
|
body {
|
|
32
|
-
font-family:
|
|
32
|
+
font-family: var(--font-main);
|
|
33
33
|
background-color: var(--bg-color);
|
|
34
34
|
color: var(--text-main);
|
|
35
35
|
min-height: 100vh;
|
|
36
36
|
display: flex;
|
|
37
|
-
|
|
38
|
-
radial-gradient(circle at 15% 50%, var(--accent-glow), transparent 25%),
|
|
39
|
-
radial-gradient(circle at 85% 30%, rgba(255, 71, 87, 0.1), transparent 25%);
|
|
37
|
+
-webkit-font-smoothing: antialiased;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
/* Sidebar */
|
|
43
41
|
.sidebar {
|
|
44
42
|
width: 280px;
|
|
45
|
-
background: var(--
|
|
43
|
+
background: var(--bg-color);
|
|
46
44
|
border-right: 1px solid var(--glass-border);
|
|
47
|
-
|
|
48
|
-
padding: 24px;
|
|
45
|
+
padding: 32px 24px;
|
|
49
46
|
display: flex;
|
|
50
47
|
flex-direction: column;
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
.logo {
|
|
54
|
-
font-size:
|
|
55
|
-
font-weight:
|
|
56
|
-
letter-spacing:
|
|
57
|
-
margin-bottom:
|
|
51
|
+
font-size: 20px;
|
|
52
|
+
font-weight: 400;
|
|
53
|
+
letter-spacing: 1px;
|
|
54
|
+
margin-bottom: 48px;
|
|
58
55
|
display: flex;
|
|
59
56
|
align-items: center;
|
|
60
|
-
gap:
|
|
57
|
+
gap: 12px;
|
|
58
|
+
color: var(--text-main);
|
|
61
59
|
}
|
|
62
60
|
|
|
63
|
-
.logo
|
|
64
|
-
|
|
61
|
+
.logo svg { stroke: var(--text-main); }
|
|
62
|
+
.logo span { color: var(--text-muted); font-weight: 300; }
|
|
63
|
+
|
|
64
|
+
.nav-section { margin-bottom: 32px; }
|
|
65
|
+
.nav-title {
|
|
66
|
+
font-size: 11px;
|
|
67
|
+
text-transform: uppercase;
|
|
68
|
+
color: var(--text-muted);
|
|
69
|
+
letter-spacing: 1.5px;
|
|
70
|
+
margin-bottom: 16px;
|
|
71
|
+
padding-left: 12px;
|
|
65
72
|
}
|
|
66
73
|
|
|
67
74
|
.nav-item {
|
|
68
|
-
padding: 12px
|
|
69
|
-
margin-bottom:
|
|
70
|
-
border-radius:
|
|
75
|
+
padding: 10px 12px;
|
|
76
|
+
margin-bottom: 4px;
|
|
77
|
+
border-radius: 6px;
|
|
71
78
|
cursor: pointer;
|
|
72
79
|
transition: all 0.2s ease;
|
|
73
80
|
color: var(--text-muted);
|
|
74
|
-
font-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
color: var(--text-main);
|
|
81
|
+
font-size: 14px;
|
|
82
|
+
font-weight: 400;
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: 12px;
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
.nav-item:hover { color: var(--text-main); }
|
|
82
89
|
.nav-item.active {
|
|
83
|
-
background: rgba(
|
|
84
|
-
color: var(--
|
|
85
|
-
border: 1px solid rgba(0, 255, 170, 0.2);
|
|
90
|
+
background: rgba(255, 255, 255, 0.05);
|
|
91
|
+
color: var(--text-main);
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
/* Main Content */
|
|
89
95
|
.content {
|
|
90
96
|
flex: 1;
|
|
91
|
-
padding:
|
|
97
|
+
padding: 48px;
|
|
92
98
|
overflow-y: auto;
|
|
93
99
|
height: 100vh;
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
94
102
|
}
|
|
95
103
|
|
|
96
104
|
.header {
|
|
97
105
|
display: flex;
|
|
98
106
|
justify-content: space-between;
|
|
99
|
-
align-items:
|
|
100
|
-
margin-bottom:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
.header h1 {
|
|
104
|
-
font-size: 32px;
|
|
105
|
-
font-weight: 700;
|
|
107
|
+
align-items: flex-end;
|
|
108
|
+
margin-bottom: 48px;
|
|
109
|
+
padding-bottom: 24px;
|
|
110
|
+
border-bottom: 1px solid var(--glass-border);
|
|
106
111
|
}
|
|
107
112
|
|
|
113
|
+
.header h1 { font-size: 28px; font-weight: 400; letter-spacing: -0.5px; }
|
|
114
|
+
|
|
108
115
|
.status-badge {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
border-radius: 20px;
|
|
113
|
-
font-size: 14px;
|
|
114
|
-
font-weight: 600;
|
|
115
|
-
border: 1px solid rgba(0, 255, 170, 0.2);
|
|
116
|
+
color: var(--text-muted);
|
|
117
|
+
font-family: var(--font-mono);
|
|
118
|
+
font-size: 12px;
|
|
116
119
|
display: flex;
|
|
117
120
|
align-items: center;
|
|
118
|
-
gap:
|
|
121
|
+
gap: 8px;
|
|
119
122
|
}
|
|
120
|
-
|
|
121
123
|
.status-badge::before {
|
|
122
|
-
content: '';
|
|
123
|
-
|
|
124
|
-
width: 8px;
|
|
125
|
-
height: 8px;
|
|
126
|
-
border-radius: 50%;
|
|
127
|
-
background: var(--accent);
|
|
128
|
-
box-shadow: 0 0 8px var(--accent);
|
|
124
|
+
content: ''; display: block; width: 6px; height: 6px; border-radius: 50%;
|
|
125
|
+
background: var(--text-muted);
|
|
129
126
|
}
|
|
127
|
+
.status-badge.active::before { background: var(--accent); }
|
|
130
128
|
|
|
131
|
-
/* Cards / Panels */
|
|
132
129
|
.panel {
|
|
133
|
-
background: var(--
|
|
130
|
+
background: var(--panel-bg);
|
|
134
131
|
border: 1px solid var(--glass-border);
|
|
135
|
-
border-radius:
|
|
136
|
-
padding:
|
|
137
|
-
|
|
138
|
-
margin-bottom: 30px;
|
|
139
|
-
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
|
132
|
+
border-radius: 12px;
|
|
133
|
+
padding: 32px;
|
|
134
|
+
margin-bottom: 24px;
|
|
140
135
|
animation: fadeIn 0.4s ease forwards;
|
|
136
|
+
flex: 1;
|
|
141
137
|
}
|
|
138
|
+
|
|
139
|
+
.panel h2 { font-weight: 400; margin-bottom: 8px; font-size: 20px; }
|
|
140
|
+
.panel p { color: var(--text-muted); font-size: 14px; margin-bottom: 24px; }
|
|
142
141
|
|
|
143
142
|
@keyframes fadeIn {
|
|
144
|
-
from { opacity: 0; transform: translateY(
|
|
143
|
+
from { opacity: 0; transform: translateY(5px); }
|
|
145
144
|
to { opacity: 1; transform: translateY(0); }
|
|
146
145
|
}
|
|
147
146
|
|
|
148
|
-
/*
|
|
149
|
-
.
|
|
150
|
-
|
|
147
|
+
/* Widgets / Cards (Reference Style) */
|
|
148
|
+
.widget-row { display: flex; gap: 24px; margin-bottom: 24px; }
|
|
149
|
+
.widget {
|
|
150
|
+
flex: 1;
|
|
151
|
+
background: var(--panel-bg);
|
|
152
|
+
border: 1px solid var(--glass-border);
|
|
153
|
+
border-radius: 12px;
|
|
154
|
+
padding: 24px;
|
|
155
|
+
display: flex;
|
|
156
|
+
flex-direction: column;
|
|
157
|
+
justify-content: flex-end;
|
|
151
158
|
}
|
|
152
|
-
.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
font-weight:
|
|
159
|
+
.widget-value {
|
|
160
|
+
font-family: var(--font-mono);
|
|
161
|
+
font-size: 42px;
|
|
162
|
+
font-weight: 300;
|
|
156
163
|
color: var(--text-main);
|
|
164
|
+
margin-bottom: 4px;
|
|
165
|
+
letter-spacing: -1px;
|
|
157
166
|
}
|
|
158
|
-
.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
.markdown-body ul, .markdown-body ol { margin-bottom: 16px; padding-left: 24px; color: var(--text-muted); }
|
|
162
|
-
.markdown-body li { margin-bottom: 8px; }
|
|
163
|
-
.markdown-body code {
|
|
164
|
-
background: rgba(0,0,0,0.3);
|
|
165
|
-
padding: 3px 6px;
|
|
166
|
-
border-radius: 4px;
|
|
167
|
-
font-family: monospace;
|
|
168
|
-
font-size: 14px;
|
|
169
|
-
color: #ff9e64;
|
|
170
|
-
}
|
|
171
|
-
.markdown-body pre {
|
|
172
|
-
background: #111520;
|
|
173
|
-
padding: 16px;
|
|
174
|
-
border-radius: 8px;
|
|
175
|
-
overflow-x: auto;
|
|
176
|
-
border: 1px solid var(--glass-border);
|
|
177
|
-
margin-bottom: 16px;
|
|
167
|
+
.widget-label {
|
|
168
|
+
font-size: 12px;
|
|
169
|
+
color: var(--text-muted);
|
|
178
170
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
171
|
+
|
|
172
|
+
.widget.accent-card {
|
|
173
|
+
background: var(--accent);
|
|
174
|
+
border: none;
|
|
175
|
+
color: #111;
|
|
183
176
|
}
|
|
177
|
+
.widget.accent-card .widget-value,
|
|
178
|
+
.widget.accent-card .widget-label { color: #111; font-weight: 500; }
|
|
184
179
|
|
|
185
|
-
/*
|
|
186
|
-
.
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
180
|
+
/* Markdown Styling */
|
|
181
|
+
.markdown-body { line-height: 1.6; font-size: 15px; }
|
|
182
|
+
.markdown-body h1, .markdown-body h2, .markdown-body h3 { margin-top: 32px; margin-bottom: 16px; font-weight: 400; }
|
|
183
|
+
.markdown-body h1 { font-size: 24px; border-bottom: 1px solid var(--glass-border); padding-bottom: 12px; }
|
|
184
|
+
.markdown-body h2 { font-size: 20px; }
|
|
185
|
+
.markdown-body p { margin-bottom: 16px; color: var(--text-muted); }
|
|
186
|
+
.markdown-body ul { margin-bottom: 16px; padding-left: 24px; color: var(--text-muted); }
|
|
187
|
+
.markdown-body code { font-family: var(--font-mono); background: rgba(255,255,255,0.05); padding: 2px 6px; border-radius: 4px; font-size: 13px; }
|
|
188
|
+
.markdown-body pre { background: #000; border: 1px solid var(--glass-border); padding: 20px; border-radius: 8px; overflow-x: auto; margin-bottom: 20px; }
|
|
189
|
+
|
|
190
|
+
/* Form Styles */
|
|
191
|
+
.form-group { margin-bottom: 24px; }
|
|
192
|
+
.form-group label { display: block; margin-bottom: 8px; color: var(--text-muted); font-size: 13px; }
|
|
193
|
+
.form-group input, .form-group textarea {
|
|
194
|
+
width: 100%; padding: 14px; border-radius: 8px;
|
|
195
|
+
background: #0d0d0d; border: 1px solid var(--glass-border);
|
|
196
|
+
color: var(--text-main); font-family: var(--font-mono); font-size: 14px;
|
|
197
|
+
transition: border 0.2s;
|
|
190
198
|
}
|
|
191
|
-
.
|
|
192
|
-
.
|
|
199
|
+
.form-group input:focus, .form-group textarea:focus { outline: none; border-color: var(--text-muted); }
|
|
200
|
+
.form-group textarea { min-height: 180px; resize: vertical; }
|
|
201
|
+
|
|
193
202
|
.btn {
|
|
194
|
-
background: var(--text-main);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
padding: 10px 24px;
|
|
198
|
-
border-radius: 6px;
|
|
199
|
-
font-weight: 600;
|
|
200
|
-
cursor: pointer;
|
|
201
|
-
transition: transform 0.1s ease;
|
|
203
|
+
background: transparent; color: var(--text-main); font-weight: 400;
|
|
204
|
+
padding: 10px 20px; border-radius: 20px; border: 1px solid var(--glass-border);
|
|
205
|
+
cursor: pointer; transition: 0.2s; font-size: 13px;
|
|
202
206
|
}
|
|
203
|
-
.btn:hover {
|
|
207
|
+
.btn:hover { background: rgba(255,255,255,0.05); border-color: var(--text-muted); }
|
|
208
|
+
.btn-accent {
|
|
209
|
+
background: var(--accent); color: #111; border: none; font-weight: 500;
|
|
210
|
+
}
|
|
211
|
+
.btn-accent:hover { background: var(--accent-dark); }
|
|
212
|
+
|
|
213
|
+
/* Graph & Tree */
|
|
214
|
+
#network-graph { width: 100%; height: 500px; border: 1px solid var(--glass-border); border-radius: 12px; background: #0d0d0d; }
|
|
215
|
+
.tree-node { padding: 6px 0; margin-left: 24px; cursor: pointer; color: var(--text-muted); font-family: var(--font-mono); font-size: 13px; }
|
|
216
|
+
.tree-node:hover { color: var(--text-main); }
|
|
217
|
+
.tree-node.danger { color: var(--danger); }
|
|
218
|
+
.tree-node.danger::after { content: ' • FLAG'; font-size: 10px; letter-spacing: 1px; }
|
|
219
|
+
|
|
220
|
+
.hidden { display: none !important; }
|
|
204
221
|
|
|
205
222
|
</style>
|
|
206
223
|
</head>
|
|
@@ -208,156 +225,581 @@
|
|
|
208
225
|
|
|
209
226
|
<div class="sidebar">
|
|
210
227
|
<div class="logo">
|
|
211
|
-
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2"
|
|
212
|
-
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
|
|
213
|
-
</svg>
|
|
228
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
|
|
214
229
|
SPARK<span>UI</span>
|
|
215
230
|
</div>
|
|
216
|
-
|
|
217
|
-
|
|
231
|
+
|
|
232
|
+
<div class="nav-section">
|
|
233
|
+
<div class="nav-title">Views</div>
|
|
234
|
+
<div class="nav-item active" onclick="switchView('overview')">📄 Overview</div>
|
|
235
|
+
<div class="nav-item" onclick="switchView('heatmap')">🔥 Heatmap</div>
|
|
236
|
+
<div class="nav-item" onclick="switchView('graph')">🕸 Architecture Graph</div>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
<div class="nav-section">
|
|
240
|
+
<div class="nav-title">Tools</div>
|
|
241
|
+
<div class="nav-item" onclick="switchView('studio')">✍️ Skill Studio</div>
|
|
242
|
+
<div class="nav-item" onclick="switchView('exporter')">🚀 API Exporter</div>
|
|
243
|
+
<div class="nav-item" onclick="switchView('guide')">📚 Guide & Commands</div>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
<div class="nav-section" id="docs-nav-section" style="display: none;">
|
|
247
|
+
<div class="nav-title">Scanned Docs</div>
|
|
248
|
+
<div id="nav-container"></div>
|
|
218
249
|
</div>
|
|
219
250
|
</div>
|
|
220
251
|
|
|
221
252
|
<div class="content">
|
|
222
253
|
<div class="header">
|
|
223
|
-
<
|
|
254
|
+
<div>
|
|
255
|
+
<h1 id="page-title">Dashboard Overview</h1>
|
|
256
|
+
<div style="font-family: var(--font-mono); color: var(--text-muted); font-size: 12px; margin-top: 8px;" id="current-time">11:37 AM Time</div>
|
|
257
|
+
</div>
|
|
224
258
|
<div class="status-badge" id="agent-status">Checking Agents...</div>
|
|
225
259
|
</div>
|
|
226
260
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
261
|
+
<!-- View: Overview (Markdown reader) -->
|
|
262
|
+
<div id="view-overview" class="panel markdown-body">
|
|
263
|
+
<h3>Welcome to SPARK UI</h3>
|
|
264
|
+
<p>Select a scanned document from the sidebar to view it.</p>
|
|
265
|
+
</div>
|
|
266
|
+
|
|
267
|
+
<!-- View: Heatmap -->
|
|
268
|
+
<div id="view-heatmap" class="panel hidden">
|
|
269
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 40px;">
|
|
270
|
+
<div>
|
|
271
|
+
<h2>Health Tracking</h2>
|
|
272
|
+
<p>AI-driven legacy trap and anti-pattern detection.</p>
|
|
273
|
+
</div>
|
|
274
|
+
<div style="text-align: right;">
|
|
275
|
+
<div style="font-family: var(--font-mono); font-size: 42px; font-weight: 300; color: var(--text-main);" id="health-score">--</div>
|
|
276
|
+
<div style="font-size: 12px; color: var(--text-muted);">Overall Score</div>
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
|
|
280
|
+
<div class="widget-row">
|
|
281
|
+
<div class="widget">
|
|
282
|
+
<div class="widget-value" id="stat-total">0</div>
|
|
283
|
+
<div class="widget-label">Total Scanned</div>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="widget accent-card">
|
|
286
|
+
<div class="widget-value" id="stat-clean">0</div>
|
|
287
|
+
<div class="widget-label">Clean Code</div>
|
|
288
|
+
</div>
|
|
289
|
+
<div class="widget">
|
|
290
|
+
<div class="widget-value" style="color: var(--danger);" id="stat-danger">0</div>
|
|
291
|
+
<div class="widget-label">Anti-Patterns</div>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
<div class="widget-row">
|
|
296
|
+
<div class="widget" style="flex: 1;">
|
|
297
|
+
<h3 style="margin-bottom: 24px; font-size: 14px; font-weight: 400;">Distribution</h3>
|
|
298
|
+
<canvas id="healthChart" style="max-height: 200px;"></canvas>
|
|
299
|
+
</div>
|
|
300
|
+
<div class="widget" style="flex: 2; overflow-y: auto; max-height: 290px; justify-content: flex-start;">
|
|
301
|
+
<h3 style="margin-bottom: 24px; font-size: 14px; font-weight: 400; display: flex; justify-content: space-between;">
|
|
302
|
+
<span>Detailed Report</span>
|
|
303
|
+
<span style="border: 1px solid var(--glass-border); padding: 4px 12px; border-radius: 20px; font-size: 11px;">Flagged Files</span>
|
|
304
|
+
</h3>
|
|
305
|
+
<div id="danger-list">
|
|
306
|
+
<p style="color: var(--text-muted); font-size: 13px;">Scanning...</p>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<div style="margin-top: 20px;">
|
|
312
|
+
<h3 style="margin-bottom: 16px; font-size: 14px; font-weight: 400;">Directory Tree</h3>
|
|
313
|
+
<div id="file-tree" style="background: var(--bg-color); padding: 24px; border-radius: 12px; border: 1px solid var(--glass-border); max-height: 250px; overflow-y: auto;"></div>
|
|
231
314
|
</div>
|
|
232
315
|
</div>
|
|
316
|
+
|
|
317
|
+
<!-- View: Graph -->
|
|
318
|
+
<div id="view-graph" class="panel hidden">
|
|
319
|
+
<h2>Interactive Architecture Graph</h2>
|
|
320
|
+
<p>Visualizing relationships between system components.</p>
|
|
321
|
+
<div id="network-graph"></div>
|
|
322
|
+
</div>
|
|
323
|
+
|
|
324
|
+
<!-- View: Skill Studio -->
|
|
325
|
+
<div id="view-studio" class="panel hidden" style="display: flex; gap: 40px;">
|
|
326
|
+
<div style="flex: 1;">
|
|
327
|
+
<h2>Create Custom Skill</h2>
|
|
328
|
+
<p>Define a new agent skill. This will be safely saved to <code>.agent/skills/</code> and protected from SPARK updates.</p>
|
|
329
|
+
<div style="margin-top: 20px;">
|
|
330
|
+
<div class="form-group">
|
|
331
|
+
<label>Skill Name (e.g., custom-deploy)</label>
|
|
332
|
+
<input type="text" id="skill-name" placeholder="lowercase-with-dashes">
|
|
333
|
+
</div>
|
|
334
|
+
<div class="form-group">
|
|
335
|
+
<label>Description</label>
|
|
336
|
+
<input type="text" id="skill-desc" placeholder="What does this skill do?">
|
|
337
|
+
</div>
|
|
338
|
+
<div class="form-group">
|
|
339
|
+
<label>Execution Steps (Markdown)</label>
|
|
340
|
+
<textarea id="skill-steps" placeholder="Enter markdown checklist..."></textarea>
|
|
341
|
+
</div>
|
|
342
|
+
<button class="btn btn-accent" onclick="saveSkill()">Save Skill</button>
|
|
343
|
+
<p id="skill-msg" style="margin-top: 16px; color: var(--text-muted); font-size: 13px;"></p>
|
|
344
|
+
</div>
|
|
345
|
+
</div>
|
|
346
|
+
<div style="width: 320px; padding-left: 32px; display: flex; flex-direction: column;">
|
|
347
|
+
<h3 style="font-size: 14px; font-weight: 400; margin-bottom: 24px;">Installed Skills</h3>
|
|
348
|
+
<div id="skill-list" style="flex: 1; overflow-y: auto;">
|
|
349
|
+
<p style="color: var(--text-muted); font-size: 13px;">Loading...</p>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
<!-- View: Guide -->
|
|
355
|
+
<div id="view-guide" class="panel hidden markdown-body">
|
|
356
|
+
<h2>Loading Documentation...</h2>
|
|
357
|
+
</div>
|
|
358
|
+
|
|
359
|
+
<!-- View: API Exporter -->
|
|
360
|
+
<div id="view-exporter" class="panel hidden">
|
|
361
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 32px;">
|
|
362
|
+
<div>
|
|
363
|
+
<h2>API Exporter</h2>
|
|
364
|
+
<p>Select endpoints to generate a Postman Collection.</p>
|
|
365
|
+
</div>
|
|
366
|
+
<div style="display: flex; gap: 12px;">
|
|
367
|
+
<button class="btn" onclick="exportEnv()">.env</button>
|
|
368
|
+
<button class="btn btn-accent" onclick="exportPostman()">Export Collection</button>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
<div style="display: flex; justify-content: space-between; padding: 16px 20px; background: var(--bg-color); border: 1px solid var(--glass-border); border-radius: 12px;">
|
|
372
|
+
<label style="cursor: pointer; display: flex; align-items: center; gap: 12px; font-size: 14px;">
|
|
373
|
+
<input type="checkbox" id="selectAllEndpoints" onchange="toggleAllEndpoints(this)">
|
|
374
|
+
Select All
|
|
375
|
+
</label>
|
|
376
|
+
<span id="endpoint-count" style="font-family: var(--font-mono); font-size: 13px; color: var(--text-muted);">0 items</span>
|
|
377
|
+
</div>
|
|
378
|
+
<div id="endpoint-list" style="margin-top: 16px; overflow-y: auto;">
|
|
379
|
+
<p style="color: var(--text-muted); padding: 20px; text-align: center; font-size: 14px;">No endpoints found.</p>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
233
383
|
</div>
|
|
234
384
|
|
|
235
385
|
<script>
|
|
236
|
-
// Initialize Mermaid
|
|
237
|
-
mermaid.initialize({
|
|
238
|
-
startOnLoad: false,
|
|
239
|
-
theme: 'dark',
|
|
240
|
-
fontFamily: 'Inter'
|
|
241
|
-
});
|
|
242
|
-
|
|
243
386
|
let docsData = [];
|
|
387
|
+
let treeData = [];
|
|
388
|
+
let networkInstance = null;
|
|
389
|
+
let chartInstance = null;
|
|
390
|
+
|
|
391
|
+
function switchView(viewName) {
|
|
392
|
+
document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));
|
|
393
|
+
event.currentTarget.classList.add('active');
|
|
394
|
+
|
|
395
|
+
['overview', 'heatmap', 'graph', 'studio', 'guide', 'exporter'].forEach(v => {
|
|
396
|
+
document.getElementById(`view-${v}`).classList.add('hidden');
|
|
397
|
+
});
|
|
398
|
+
document.getElementById(`view-${viewName}`).classList.remove('hidden');
|
|
399
|
+
|
|
400
|
+
const titles = {
|
|
401
|
+
overview: 'Dashboard Overview',
|
|
402
|
+
heatmap: 'Codebase Heatmap',
|
|
403
|
+
graph: 'Architecture Graph',
|
|
404
|
+
studio: 'Skill Studio',
|
|
405
|
+
guide: 'Documentation & Guide',
|
|
406
|
+
exporter: 'API Exporter'
|
|
407
|
+
};
|
|
408
|
+
document.getElementById('page-title').innerText = titles[viewName];
|
|
409
|
+
|
|
410
|
+
if (viewName === 'graph') renderGraph();
|
|
411
|
+
if (viewName === 'heatmap') fetchTree();
|
|
412
|
+
if (viewName === 'guide') fetchReadme();
|
|
413
|
+
if (viewName === 'studio') fetchSkills();
|
|
414
|
+
if (viewName === 'exporter') extractEndpoints();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
setInterval(() => {
|
|
418
|
+
const now = new Date();
|
|
419
|
+
document.getElementById('current-time').innerText = now.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) + ' Time';
|
|
420
|
+
}, 1000);
|
|
244
421
|
|
|
245
422
|
async function fetchDocs() {
|
|
246
423
|
try {
|
|
247
424
|
const res = await fetch('/api/docs');
|
|
248
425
|
const data = await res.json();
|
|
426
|
+
if (!data.error) {
|
|
427
|
+
docsData = data.docs;
|
|
428
|
+
const nav = document.getElementById('nav-container');
|
|
429
|
+
document.getElementById('docs-nav-section').style.display = 'block';
|
|
430
|
+
nav.innerHTML = '';
|
|
431
|
+
docsData.forEach(doc => {
|
|
432
|
+
const el = document.createElement('div');
|
|
433
|
+
el.className = 'nav-item';
|
|
434
|
+
el.innerText = '📄 ' + doc.filename.replace('.md', '').replace(/_/g, ' ');
|
|
435
|
+
el.onclick = (e) => {
|
|
436
|
+
switchView('overview');
|
|
437
|
+
document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));
|
|
438
|
+
e.currentTarget.classList.add('active');
|
|
439
|
+
document.getElementById('page-title').innerText = doc.filename;
|
|
440
|
+
document.getElementById('view-overview').innerHTML = marked.parse(doc.content);
|
|
441
|
+
};
|
|
442
|
+
nav.appendChild(el);
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
} catch (err) {}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function fetchStatus() {
|
|
449
|
+
try {
|
|
450
|
+
const res = await fetch('/api/status');
|
|
451
|
+
const data = await res.json();
|
|
452
|
+
const badge = document.getElementById('agent-status');
|
|
453
|
+
if (data.installed && data.agents && data.agents.length > 0) {
|
|
454
|
+
badge.innerHTML = `Agents Active: ${data.agents.join(', ')}`;
|
|
455
|
+
badge.classList.add('active');
|
|
456
|
+
} else {
|
|
457
|
+
badge.innerHTML = `No Agents Active`;
|
|
458
|
+
badge.classList.remove('active');
|
|
459
|
+
}
|
|
460
|
+
} catch (e) {}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
async function fetchReadme() {
|
|
464
|
+
try {
|
|
465
|
+
const res = await fetch('/api/readme');
|
|
466
|
+
const data = await res.json();
|
|
467
|
+
document.getElementById('view-guide').innerHTML = marked.parse(data.content);
|
|
468
|
+
} catch (e) {}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async function fetchTree() {
|
|
472
|
+
if(treeData.length > 0) return; // already fetched
|
|
473
|
+
try {
|
|
474
|
+
const res = await fetch('/api/tree');
|
|
475
|
+
const data = await res.json();
|
|
476
|
+
treeData = data.tree;
|
|
249
477
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
478
|
+
// Naive simulation of finding legacy traps
|
|
479
|
+
const dangerousFiles = ['controller', 'utils', 'helper', 'api'];
|
|
480
|
+
let totalFiles = 0;
|
|
481
|
+
let dangerFilesCount = 0;
|
|
482
|
+
let dangerListHTML = '';
|
|
483
|
+
|
|
484
|
+
function buildTreeHtml(nodes) {
|
|
485
|
+
let html = '';
|
|
486
|
+
for(const node of nodes) {
|
|
487
|
+
const isDanger = node.type === 'file' && dangerousFiles.some(d => node.name.toLowerCase().includes(d));
|
|
488
|
+
|
|
489
|
+
if (node.type === 'file') {
|
|
490
|
+
totalFiles++;
|
|
491
|
+
if (isDanger) {
|
|
492
|
+
dangerFilesCount++;
|
|
493
|
+
dangerListHTML += `<div style="padding: 12px 16px; background: var(--bg-color); border: 1px solid var(--glass-border); border-radius: 8px; margin-bottom: 12px; display: flex; flex-direction: column; gap: 8px;">
|
|
494
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
495
|
+
<span style="font-family: var(--font-mono); font-size: 13px; color: var(--text-main);">${node.path}</span>
|
|
496
|
+
<span style="font-size: 10px; color: var(--danger); border: 1px solid var(--glass-border); padding: 4px 8px; border-radius: 20px;">Flagged</span>
|
|
497
|
+
</div>
|
|
498
|
+
<div style="font-size: 12px; color: var(--text-muted); line-height: 1.4;">${node.dangerReason || 'Legacy anti-pattern detected.'}</div>
|
|
499
|
+
</div>`;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const cls = `tree-node ${node.type} ${isDanger ? 'danger' : ''}`;
|
|
504
|
+
html += `<div class="${cls}">${node.name}</div>`;
|
|
505
|
+
if(node.children) {
|
|
506
|
+
html += `<div style="margin-left: 20px;">${buildTreeHtml(node.children)}</div>`;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return html;
|
|
253
510
|
}
|
|
254
511
|
|
|
255
|
-
|
|
256
|
-
|
|
512
|
+
document.getElementById('file-tree').innerHTML = buildTreeHtml(treeData);
|
|
513
|
+
|
|
514
|
+
// Update Widgets
|
|
515
|
+
const cleanFilesCount = totalFiles - dangerFilesCount;
|
|
516
|
+
const healthScore = totalFiles === 0 ? 100 : Math.round((cleanFilesCount / totalFiles) * 100);
|
|
517
|
+
|
|
518
|
+
document.getElementById('stat-total').innerText = totalFiles;
|
|
519
|
+
document.getElementById('stat-clean').innerText = cleanFilesCount;
|
|
520
|
+
document.getElementById('stat-danger').innerText = dangerFilesCount;
|
|
521
|
+
document.getElementById('health-score').innerText = healthScore;
|
|
257
522
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// Try to find PROJECT_SCAN.md first
|
|
261
|
-
const mainDoc = docsData.find(d => d.filename === 'PROJECT_SCAN.md') || docsData[0];
|
|
262
|
-
renderDoc(mainDoc.filename);
|
|
523
|
+
if (dangerListHTML === '') {
|
|
524
|
+
document.getElementById('danger-list').innerHTML = '<p style="color: var(--text-muted); font-size: 13px;">No dangerous files found! Your codebase is healthy.</p>';
|
|
263
525
|
} else {
|
|
264
|
-
|
|
526
|
+
document.getElementById('danger-list').innerHTML = dangerListHTML;
|
|
265
527
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
528
|
+
|
|
529
|
+
// Render Chart
|
|
530
|
+
if (chartInstance) chartInstance.destroy();
|
|
531
|
+
const ctx = document.getElementById('healthChart').getContext('2d');
|
|
532
|
+
chartInstance = new Chart(ctx, {
|
|
533
|
+
type: 'doughnut',
|
|
534
|
+
data: {
|
|
535
|
+
labels: ['Clean Code', 'Anti-Patterns'],
|
|
536
|
+
datasets: [{
|
|
537
|
+
data: [cleanFilesCount, dangerFilesCount],
|
|
538
|
+
backgroundColor: ['#dcedc1', '#2a2a2a'],
|
|
539
|
+
borderWidth: 0,
|
|
540
|
+
hoverOffset: 4
|
|
541
|
+
}]
|
|
542
|
+
},
|
|
543
|
+
options: {
|
|
544
|
+
responsive: true,
|
|
545
|
+
maintainAspectRatio: false,
|
|
546
|
+
cutout: '75%',
|
|
547
|
+
plugins: {
|
|
548
|
+
legend: { display: false }
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
} catch (e) {}
|
|
269
554
|
}
|
|
270
555
|
|
|
271
|
-
async function
|
|
556
|
+
async function fetchSkills() {
|
|
272
557
|
try {
|
|
273
|
-
const res = await fetch('/api/
|
|
558
|
+
const res = await fetch('/api/skills');
|
|
274
559
|
const data = await res.json();
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
560
|
+
const list = document.getElementById('skill-list');
|
|
561
|
+
list.innerHTML = '';
|
|
562
|
+
|
|
563
|
+
if (data.skills && data.skills.length > 0) {
|
|
564
|
+
data.skills.forEach(skill => {
|
|
565
|
+
const isCore = skill.type === 'core';
|
|
566
|
+
const badge = isCore ? 'Core' : 'Custom';
|
|
567
|
+
list.innerHTML += `<div style="padding: 16px 0; border-bottom: 1px solid var(--glass-border); display: flex; justify-content: space-between; align-items: center;">
|
|
568
|
+
<span style="font-size: 14px; color: var(--text-main);">${skill.name}</span>
|
|
569
|
+
<span style="font-size: 10px; color: var(--text-muted); border: 1px solid var(--glass-border); padding: 4px 8px; border-radius: 20px;">${badge}</span>
|
|
570
|
+
</div>`;
|
|
571
|
+
});
|
|
278
572
|
} else {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
573
|
+
list.innerHTML = '<p style="color: var(--text-muted);">No skills found.</p>';
|
|
574
|
+
}
|
|
575
|
+
} catch (e) {}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
async function saveSkill() {
|
|
579
|
+
const name = document.getElementById('skill-name').value;
|
|
580
|
+
const desc = document.getElementById('skill-desc').value;
|
|
581
|
+
const steps = document.getElementById('skill-steps').value;
|
|
582
|
+
|
|
583
|
+
if(!name || !steps) {
|
|
584
|
+
alert("Name and Steps are required!");
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
const markdown = `---
|
|
589
|
+
name: ${name}
|
|
590
|
+
description: ${desc}
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
# ${name}
|
|
594
|
+
|
|
595
|
+
## Execution Checklist
|
|
596
|
+
${steps}
|
|
597
|
+
`;
|
|
598
|
+
|
|
599
|
+
try {
|
|
600
|
+
const res = await fetch('/api/skills', {
|
|
601
|
+
method: 'POST',
|
|
602
|
+
headers: {'Content-Type': 'application/json'},
|
|
603
|
+
body: JSON.stringify({ name, content: markdown })
|
|
604
|
+
});
|
|
605
|
+
const data = await res.json();
|
|
606
|
+
if(data.success) {
|
|
607
|
+
document.getElementById('skill-msg').innerText = "✅ Skill saved safely to: " + data.path;
|
|
608
|
+
setTimeout(() => document.getElementById('skill-msg').innerText = "", 5000);
|
|
282
609
|
}
|
|
283
610
|
} catch (e) {
|
|
284
|
-
|
|
611
|
+
alert("Failed to save");
|
|
285
612
|
}
|
|
286
613
|
}
|
|
287
614
|
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
|
|
615
|
+
function renderGraph() {
|
|
616
|
+
if (networkInstance) return; // Already rendered
|
|
617
|
+
|
|
618
|
+
const container = document.getElementById('network-graph');
|
|
619
|
+
if (typeof vis === 'undefined') {
|
|
620
|
+
container.innerHTML = '<div style="padding: 40px; text-align: center; color: var(--danger);">Failed to load graph library. Please check your internet connection or use a local build.</div>';
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Create some dummy nodes for architecture visualization (in a real scenario we'd parse .docs/)
|
|
625
|
+
const nodes = new vis.DataSet([
|
|
626
|
+
{ id: 1, label: 'Client App', color: '#00ffaa', shape: 'box' },
|
|
627
|
+
{ id: 2, label: 'API Gateway', color: '#00ffaa', shape: 'box' },
|
|
628
|
+
{ id: 3, label: 'Auth Service', color: '#ff4757', shape: 'box' },
|
|
629
|
+
{ id: 4, label: 'Database', color: '#f1f5f9', shape: 'database' },
|
|
630
|
+
]);
|
|
631
|
+
const edges = new vis.DataSet([
|
|
632
|
+
{ from: 1, to: 2, arrows: 'to' },
|
|
633
|
+
{ from: 2, to: 3, arrows: 'to', label: 'verify token' },
|
|
634
|
+
{ from: 3, to: 4, arrows: 'to' },
|
|
635
|
+
]);
|
|
291
636
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
637
|
+
const data = { nodes, edges };
|
|
638
|
+
const options = {
|
|
639
|
+
nodes: {
|
|
640
|
+
font: { color: '#f4f4f4', face: 'Inter', size: 14 },
|
|
641
|
+
margin: 12,
|
|
642
|
+
borderWidth: 1,
|
|
643
|
+
color: { background: '#151515', border: '#2a2a2a' },
|
|
644
|
+
shapeProperties: { borderRadius: 6 }
|
|
645
|
+
},
|
|
646
|
+
edges: {
|
|
647
|
+
color: { color: '#2a2a2a' },
|
|
648
|
+
font: { color: '#888888', align: 'horizontal', background: '#0d0d0d', strokeWidth: 0, face: 'Courier New', size: 11 },
|
|
649
|
+
smooth: { type: 'cubicBezier' }
|
|
650
|
+
},
|
|
651
|
+
layout: {
|
|
652
|
+
hierarchical: {
|
|
653
|
+
direction: 'UD',
|
|
654
|
+
sortMethod: 'directed',
|
|
655
|
+
nodeSpacing: 150,
|
|
656
|
+
levelSeparation: 150
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
physics: {
|
|
660
|
+
hierarchicalRepulsion: { nodeDistance: 150 }
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
networkInstance = new vis.Network(container, data, options);
|
|
299
664
|
}
|
|
300
665
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
666
|
+
// --- API Exporter Logic ---
|
|
667
|
+
let detectedEndpoints = [];
|
|
668
|
+
|
|
669
|
+
function extractEndpoints() {
|
|
670
|
+
detectedEndpoints = [];
|
|
671
|
+
// Parse through all docs to find patterns like: - **GET** `/api/users`
|
|
672
|
+
docsData.forEach(doc => {
|
|
673
|
+
const regex = /(GET|POST|PUT|PATCH|DELETE)[^a-zA-Z0-9/]+([/\w:-]+)/gi;
|
|
674
|
+
let match;
|
|
675
|
+
while ((match = regex.exec(doc.content)) !== null) {
|
|
676
|
+
const method = match[1].toUpperCase();
|
|
677
|
+
const path = match[2];
|
|
678
|
+
if (!detectedEndpoints.find(e => e.method === method && e.path === path) && path.includes('/')) {
|
|
679
|
+
detectedEndpoints.push({ method, path });
|
|
680
|
+
}
|
|
308
681
|
}
|
|
309
682
|
});
|
|
310
683
|
|
|
311
|
-
const
|
|
312
|
-
if (
|
|
684
|
+
const list = document.getElementById('endpoint-list');
|
|
685
|
+
if (detectedEndpoints.length > 0) {
|
|
686
|
+
document.getElementById('endpoint-count').innerText = `${detectedEndpoints.length} Endpoints`;
|
|
687
|
+
list.innerHTML = '';
|
|
688
|
+
detectedEndpoints.forEach((ep, index) => {
|
|
689
|
+
list.innerHTML += `<div style="padding: 16px; border-bottom: 1px solid var(--glass-border); display: flex; align-items: center; gap: 16px;">
|
|
690
|
+
<input type="checkbox" class="ep-checkbox" value="${index}" checked>
|
|
691
|
+
<span style="font-family: var(--font-mono); font-size: 13px; color: var(--text-muted); width: 60px;">${ep.method}</span>
|
|
692
|
+
<span style="font-family: var(--font-mono); font-size: 14px; color: var(--text-main);">${ep.path}</span>
|
|
693
|
+
</div>`;
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
}
|
|
313
697
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
const container = document.getElementById('content-container');
|
|
318
|
-
const parsedHTML = marked.parse(doc.content);
|
|
319
|
-
|
|
320
|
-
container.innerHTML = `
|
|
321
|
-
<div class="panel markdown-body">
|
|
322
|
-
${parsedHTML}
|
|
323
|
-
</div>
|
|
324
|
-
`;
|
|
698
|
+
function toggleAllEndpoints(cb) {
|
|
699
|
+
document.querySelectorAll('.ep-checkbox').forEach(el => el.checked = cb.checked);
|
|
700
|
+
}
|
|
325
701
|
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
702
|
+
function getHeuristicMock(path) {
|
|
703
|
+
const p = path.toLowerCase();
|
|
704
|
+
if (p.includes('user')) return { id: "USR-123", name: "John Doe", email: "john@example.com", role: "admin", createdAt: "2026-07-01T10:00:00Z" };
|
|
705
|
+
if (p.includes('product') || p.includes('item')) return { id: "PRD-456", name: "Premium Widget", price: 99.99, stock: 50 };
|
|
706
|
+
if (p.includes('order') || p.includes('transaction')) return { id: "ORD-789", total: 99.99, status: "completed" };
|
|
707
|
+
if (p.includes('auth') || p.includes('login')) return { token: "eyJhbGci...", user: { id: 1, role: "user" } };
|
|
708
|
+
return { id: "123", status: "success", message: "Operation completed successfully." };
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function exportPostman() {
|
|
712
|
+
const selectedIndices = Array.from(document.querySelectorAll('.ep-checkbox:checked')).map(cb => parseInt(cb.value));
|
|
713
|
+
if (selectedIndices.length === 0) return alert("Select at least one endpoint!");
|
|
714
|
+
|
|
715
|
+
const collection = {
|
|
716
|
+
info: {
|
|
717
|
+
name: "SPARK Generated Collection",
|
|
718
|
+
description: "Auto-generated API Collection with positive & negative cases using SPARK AI heuristics.",
|
|
719
|
+
schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
|
720
|
+
},
|
|
721
|
+
item: []
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
selectedIndices.forEach(idx => {
|
|
725
|
+
const ep = detectedEndpoints[idx];
|
|
726
|
+
const mock = getHeuristicMock(ep.path);
|
|
336
727
|
|
|
337
|
-
//
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
728
|
+
// Construct URL
|
|
729
|
+
const urlParts = ep.path.split('/').filter(p => p);
|
|
730
|
+
const pathArr = urlParts.map(p => p.startsWith(':') ? `{{${p.replace(':','')}}}` : p);
|
|
731
|
+
|
|
732
|
+
const item = {
|
|
733
|
+
name: `${ep.method} ${ep.path}`,
|
|
734
|
+
item: [
|
|
735
|
+
{
|
|
736
|
+
name: "Positive Case (200 OK)",
|
|
737
|
+
request: {
|
|
738
|
+
method: ep.method,
|
|
739
|
+
header: [{ key: "Authorization", value: "Bearer {{token}}" }, { key: "Content-Type", value: "application/json" }],
|
|
740
|
+
body: ep.method !== 'GET' ? { mode: "raw", raw: JSON.stringify(mock, null, 2) } : undefined,
|
|
741
|
+
url: { raw: `{{baseUrl}}/${pathArr.join('/')}`, host: ["{{baseUrl}}"], path: pathArr }
|
|
742
|
+
},
|
|
743
|
+
response: [{
|
|
744
|
+
name: "Success",
|
|
745
|
+
status: "OK",
|
|
746
|
+
code: 200,
|
|
747
|
+
header: [{ key: "Content-Type", value: "application/json" }],
|
|
748
|
+
body: JSON.stringify({ success: true, data: mock }, null, 2)
|
|
749
|
+
}]
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
name: "Negative Case (400 Bad Request)",
|
|
753
|
+
request: {
|
|
754
|
+
method: ep.method,
|
|
755
|
+
header: [{ key: "Authorization", value: "Bearer {{token}}" }, { key: "Content-Type", value: "application/json" }],
|
|
756
|
+
body: ep.method !== 'GET' ? { mode: "raw", raw: "{}" } : undefined,
|
|
757
|
+
url: { raw: `{{baseUrl}}/${pathArr.join('/')}`, host: ["{{baseUrl}}"], path: pathArr }
|
|
758
|
+
},
|
|
759
|
+
response: [{
|
|
760
|
+
name: "Bad Request",
|
|
761
|
+
status: "Bad Request",
|
|
762
|
+
code: 400,
|
|
763
|
+
header: [{ key: "Content-Type", value: "application/json" }],
|
|
764
|
+
body: JSON.stringify({ success: false, error: { code: "VALIDATION_ERROR", message: "Invalid parameters provided" } }, null, 2)
|
|
765
|
+
}]
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
name: "Negative Case (401 Unauthorized)",
|
|
769
|
+
request: {
|
|
770
|
+
method: ep.method,
|
|
771
|
+
header: [], // No auth
|
|
772
|
+
url: { raw: `{{baseUrl}}/${pathArr.join('/')}`, host: ["{{baseUrl}}"], path: pathArr }
|
|
773
|
+
},
|
|
774
|
+
response: [{
|
|
775
|
+
name: "Unauthorized",
|
|
776
|
+
status: "Unauthorized",
|
|
777
|
+
code: 401,
|
|
778
|
+
header: [{ key: "Content-Type", value: "application/json" }],
|
|
779
|
+
body: JSON.stringify({ success: false, error: { code: "UNAUTHORIZED", message: "Missing or invalid token" } }, null, 2)
|
|
780
|
+
}]
|
|
781
|
+
}
|
|
782
|
+
]
|
|
783
|
+
};
|
|
784
|
+
collection.item.push(item);
|
|
344
785
|
});
|
|
786
|
+
|
|
787
|
+
downloadFile(JSON.stringify(collection, null, 2), 'SPARK_API_Collection.json', 'application/json');
|
|
345
788
|
}
|
|
346
789
|
|
|
347
|
-
function
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
`;
|
|
790
|
+
function exportEnv() {
|
|
791
|
+
const envContent = `BASE_URL=http://localhost:3000\nTOKEN=your_jwt_token_here\nNODE_ENV=development\n`;
|
|
792
|
+
downloadFile(envContent, '.env.example', 'text/plain');
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function downloadFile(content, filename, contentType) {
|
|
796
|
+
const blob = new Blob([content], { type: contentType });
|
|
797
|
+
const url = URL.createObjectURL(blob);
|
|
798
|
+
const a = document.createElement('a');
|
|
799
|
+
a.href = url;
|
|
800
|
+
a.download = filename;
|
|
801
|
+
a.click();
|
|
802
|
+
URL.revokeObjectURL(url);
|
|
361
803
|
}
|
|
362
804
|
|
|
363
805
|
// Init
|