@adityaaria/spark 6.0.18 → 6.0.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adityaaria/spark",
3
- "version": "6.0.18",
3
+ "version": "6.0.19",
4
4
  "description": "SPARK skills and runtime bootstrap for coding agents",
5
5
  "type": "module",
6
6
  "publishConfig": {
package/src/cli/index.js CHANGED
@@ -14,6 +14,12 @@ export async function run(argv = [], env = process.env) {
14
14
  return;
15
15
  }
16
16
 
17
+ if (command === 'dashboard' || command === 'ui') {
18
+ const { startDashboard } = await import('../dashboard/server.js');
19
+ startDashboard();
20
+ return;
21
+ }
22
+
17
23
  throw new Error(`Unknown command: ${command}`);
18
24
  }
19
25
 
@@ -0,0 +1,369 @@
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>SPARK | Command Center</title>
7
+ <!-- Google Fonts -->
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
9
+ <!-- Marked.js for Markdown parsing -->
10
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
11
+ <!-- Mermaid.js for Diagrams -->
12
+ <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
13
+ <style>
14
+ :root {
15
+ --bg-color: #0b0f19;
16
+ --glass-bg: rgba(255, 255, 255, 0.03);
17
+ --glass-border: rgba(255, 255, 255, 0.08);
18
+ --accent-glow: rgba(0, 255, 170, 0.15);
19
+ --accent: #00ffaa;
20
+ --danger: #ff4757;
21
+ --text-main: #f1f5f9;
22
+ --text-muted: #94a3b8;
23
+ }
24
+
25
+ * {
26
+ box-sizing: border-box;
27
+ margin: 0;
28
+ padding: 0;
29
+ }
30
+
31
+ body {
32
+ font-family: 'Inter', sans-serif;
33
+ background-color: var(--bg-color);
34
+ color: var(--text-main);
35
+ min-height: 100vh;
36
+ display: flex;
37
+ background-image:
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%);
40
+ }
41
+
42
+ /* Sidebar */
43
+ .sidebar {
44
+ width: 280px;
45
+ background: var(--glass-bg);
46
+ border-right: 1px solid var(--glass-border);
47
+ backdrop-filter: blur(12px);
48
+ padding: 24px;
49
+ display: flex;
50
+ flex-direction: column;
51
+ }
52
+
53
+ .logo {
54
+ font-size: 24px;
55
+ font-weight: 700;
56
+ letter-spacing: 2px;
57
+ margin-bottom: 40px;
58
+ display: flex;
59
+ align-items: center;
60
+ gap: 10px;
61
+ }
62
+
63
+ .logo span {
64
+ color: var(--accent);
65
+ }
66
+
67
+ .nav-item {
68
+ padding: 12px 16px;
69
+ margin-bottom: 8px;
70
+ border-radius: 8px;
71
+ cursor: pointer;
72
+ transition: all 0.2s ease;
73
+ color: var(--text-muted);
74
+ font-weight: 600;
75
+ }
76
+
77
+ .nav-item:hover {
78
+ background: rgba(255,255,255,0.05);
79
+ color: var(--text-main);
80
+ }
81
+
82
+ .nav-item.active {
83
+ background: rgba(0, 255, 170, 0.1);
84
+ color: var(--accent);
85
+ border: 1px solid rgba(0, 255, 170, 0.2);
86
+ }
87
+
88
+ /* Main Content */
89
+ .content {
90
+ flex: 1;
91
+ padding: 40px;
92
+ overflow-y: auto;
93
+ height: 100vh;
94
+ }
95
+
96
+ .header {
97
+ display: flex;
98
+ justify-content: space-between;
99
+ align-items: center;
100
+ margin-bottom: 40px;
101
+ }
102
+
103
+ .header h1 {
104
+ font-size: 32px;
105
+ font-weight: 700;
106
+ }
107
+
108
+ .status-badge {
109
+ background: rgba(0, 255, 170, 0.1);
110
+ color: var(--accent);
111
+ padding: 6px 12px;
112
+ border-radius: 20px;
113
+ font-size: 14px;
114
+ font-weight: 600;
115
+ border: 1px solid rgba(0, 255, 170, 0.2);
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 6px;
119
+ }
120
+
121
+ .status-badge::before {
122
+ content: '';
123
+ display: block;
124
+ width: 8px;
125
+ height: 8px;
126
+ border-radius: 50%;
127
+ background: var(--accent);
128
+ box-shadow: 0 0 8px var(--accent);
129
+ }
130
+
131
+ /* Cards / Panels */
132
+ .panel {
133
+ background: var(--glass-bg);
134
+ border: 1px solid var(--glass-border);
135
+ border-radius: 16px;
136
+ padding: 30px;
137
+ backdrop-filter: blur(10px);
138
+ margin-bottom: 30px;
139
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
140
+ animation: fadeIn 0.4s ease forwards;
141
+ }
142
+
143
+ @keyframes fadeIn {
144
+ from { opacity: 0; transform: translateY(10px); }
145
+ to { opacity: 1; transform: translateY(0); }
146
+ }
147
+
148
+ /* Markdown Styling */
149
+ .markdown-body {
150
+ line-height: 1.6;
151
+ }
152
+ .markdown-body h1, .markdown-body h2, .markdown-body h3 {
153
+ margin-top: 24px;
154
+ margin-bottom: 16px;
155
+ font-weight: 600;
156
+ color: var(--text-main);
157
+ }
158
+ .markdown-body h1 { font-size: 28px; border-bottom: 1px solid var(--glass-border); padding-bottom: 8px; }
159
+ .markdown-body h2 { font-size: 22px; }
160
+ .markdown-body p { margin-bottom: 16px; color: var(--text-muted); }
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;
178
+ }
179
+ .markdown-body pre code {
180
+ background: none;
181
+ padding: 0;
182
+ color: #f1f5f9;
183
+ }
184
+
185
+ /* Empty State */
186
+ .empty-state {
187
+ text-align: center;
188
+ padding: 60px 20px;
189
+ color: var(--text-muted);
190
+ }
191
+ .empty-state h3 { margin-bottom: 10px; color: var(--text-main); }
192
+ .empty-state p { margin-bottom: 20px; }
193
+ .btn {
194
+ background: var(--text-main);
195
+ color: var(--bg-color);
196
+ border: none;
197
+ padding: 10px 24px;
198
+ border-radius: 6px;
199
+ font-weight: 600;
200
+ cursor: pointer;
201
+ transition: transform 0.1s ease;
202
+ }
203
+ .btn:hover { transform: scale(1.05); }
204
+
205
+ </style>
206
+ </head>
207
+ <body>
208
+
209
+ <div class="sidebar">
210
+ <div class="logo">
211
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
212
+ <path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
213
+ </svg>
214
+ SPARK<span>UI</span>
215
+ </div>
216
+ <div id="nav-container">
217
+ <!-- Nav items will be injected here -->
218
+ </div>
219
+ </div>
220
+
221
+ <div class="content">
222
+ <div class="header">
223
+ <h1 id="page-title">Dashboard Overview</h1>
224
+ <div class="status-badge" id="agent-status">Checking Agents...</div>
225
+ </div>
226
+
227
+ <div id="content-container">
228
+ <div class="panel empty-state">
229
+ <h3>Loading Knowledge Base...</h3>
230
+ <p>Fetching architectural DNA from .docs/</p>
231
+ </div>
232
+ </div>
233
+ </div>
234
+
235
+ <script>
236
+ // Initialize Mermaid
237
+ mermaid.initialize({
238
+ startOnLoad: false,
239
+ theme: 'dark',
240
+ fontFamily: 'Inter'
241
+ });
242
+
243
+ let docsData = [];
244
+
245
+ async function fetchDocs() {
246
+ try {
247
+ const res = await fetch('/api/docs');
248
+ const data = await res.json();
249
+
250
+ if (data.error) {
251
+ renderEmptyState(data.error);
252
+ return;
253
+ }
254
+
255
+ docsData = data.docs;
256
+ renderNav();
257
+
258
+ // Show first doc by default or overview
259
+ if(docsData.length > 0) {
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);
263
+ } else {
264
+ renderEmptyState("The .docs/ directory is empty. Generate artifacts using the project-scanner skill.");
265
+ }
266
+ } catch (err) {
267
+ renderEmptyState("Failed to connect to the SPARK local server.");
268
+ }
269
+ }
270
+
271
+ async function fetchStatus() {
272
+ try {
273
+ const res = await fetch('/api/status');
274
+ const data = await res.json();
275
+ const badge = document.getElementById('agent-status');
276
+ if (data.installed && data.agents) {
277
+ badge.innerHTML = `Agents Active: ${data.agents.join(', ')}`;
278
+ } else {
279
+ badge.innerHTML = `No Agents Detected`;
280
+ badge.style.color = 'var(--text-muted)';
281
+ badge.style.borderColor = 'var(--glass-border)';
282
+ }
283
+ } catch (e) {
284
+ console.error(e);
285
+ }
286
+ }
287
+
288
+ function renderNav() {
289
+ const nav = document.getElementById('nav-container');
290
+ nav.innerHTML = '';
291
+
292
+ docsData.forEach(doc => {
293
+ const el = document.createElement('div');
294
+ el.className = 'nav-item';
295
+ el.innerText = doc.filename.replace('.md', '').replace(/_/g, ' ');
296
+ el.onclick = () => renderDoc(doc.filename);
297
+ nav.appendChild(el);
298
+ });
299
+ }
300
+
301
+ function renderDoc(filename) {
302
+ // Update active state
303
+ document.querySelectorAll('.nav-item').forEach(el => {
304
+ if (el.innerText === filename.replace('.md', '').replace(/_/g, ' ')) {
305
+ el.classList.add('active');
306
+ } else {
307
+ el.classList.remove('active');
308
+ }
309
+ });
310
+
311
+ const doc = docsData.find(d => d.filename === filename);
312
+ if (!doc) return;
313
+
314
+ document.getElementById('page-title').innerText = filename;
315
+
316
+ // Render Markdown
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
+ `;
325
+
326
+ // Render Mermaid diagrams if any
327
+ const codeBlocks = container.querySelectorAll('code.language-mermaid');
328
+ codeBlocks.forEach((block, index) => {
329
+ const graphDefinition = block.innerText;
330
+ const parent = block.parentElement; // the <pre> tag
331
+
332
+ // Create a div for mermaid to render into
333
+ const mermaidDiv = document.createElement('div');
334
+ mermaidDiv.className = 'mermaid-chart';
335
+ mermaidDiv.id = `mermaid-${index}`;
336
+
337
+ // Render it
338
+ mermaid.render(`mermaid-graph-${index}`, graphDefinition).then((result) => {
339
+ mermaidDiv.innerHTML = result.svg;
340
+ parent.replaceWith(mermaidDiv);
341
+ }).catch(e => {
342
+ console.error("Mermaid error:", e);
343
+ });
344
+ });
345
+ }
346
+
347
+ function renderEmptyState(message) {
348
+ document.getElementById('content-container').innerHTML = `
349
+ <div class="panel empty-state">
350
+ <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--text-muted)" stroke-width="1.5" style="margin-bottom: 20px;">
351
+ <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path>
352
+ <polyline points="14 2 14 8 20 8"></polyline>
353
+ <line x1="16" y1="13" x2="8" y2="13"></line>
354
+ <line x1="16" y1="17" x2="8" y2="17"></line>
355
+ <polyline points="10 9 9 9 8 9"></polyline>
356
+ </svg>
357
+ <h3>No Data Available</h3>
358
+ <p>${message}</p>
359
+ </div>
360
+ `;
361
+ }
362
+
363
+ // Init
364
+ fetchStatus();
365
+ fetchDocs();
366
+
367
+ </script>
368
+ </body>
369
+ </html>
@@ -0,0 +1,86 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import http from 'http';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+
9
+ const PORT = 4321;
10
+ const PUBLIC_DIR = path.join(__dirname, 'public');
11
+ const DOCS_DIR = path.join(process.cwd(), '.docs');
12
+ const LOCK_FILE = path.join(process.cwd(), '.spark-lock.json');
13
+
14
+ const server = http.createServer((req, res) => {
15
+ res.setHeader('Access-Control-Allow-Origin', '*');
16
+
17
+ // API Route: Get all markdown docs
18
+ if (req.url === '/api/docs' && req.method === 'GET') {
19
+ res.setHeader('Content-Type', 'application/json');
20
+ if (!fs.existsSync(DOCS_DIR)) {
21
+ return res.end(JSON.stringify({ error: 'No .docs/ directory found in this project. Please run project-scanner skill first.' }));
22
+ }
23
+
24
+ try {
25
+ const files = fs.readdirSync(DOCS_DIR).filter(f => f.endsWith('.md'));
26
+ const docs = files.map(filename => {
27
+ const content = fs.readFileSync(path.join(DOCS_DIR, filename), 'utf-8');
28
+ return { filename, content };
29
+ });
30
+ return res.end(JSON.stringify({ docs }));
31
+ } catch (e) {
32
+ return res.end(JSON.stringify({ error: e.message }));
33
+ }
34
+ }
35
+
36
+ // API Route: Get Spark Lock status
37
+ if (req.url === '/api/status' && req.method === 'GET') {
38
+ res.setHeader('Content-Type', 'application/json');
39
+ let status = { installed: false, agents: [] };
40
+ if (fs.existsSync(LOCK_FILE)) {
41
+ try {
42
+ status = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
43
+ status.installed = true;
44
+ } catch (e) {}
45
+ }
46
+ return res.end(JSON.stringify(status));
47
+ }
48
+
49
+ // Static File Server
50
+ let filePath = req.url === '/' ? '/index.html' : req.url;
51
+ // Prevent path traversal
52
+ filePath = path.normalize(filePath).replace(/^(\.\.[\/\\])+/, '');
53
+ const ext = path.extname(filePath);
54
+
55
+ const contentTypes = {
56
+ '.html': 'text/html',
57
+ '.css': 'text/css',
58
+ '.js': 'text/javascript',
59
+ '.png': 'image/png',
60
+ '.svg': 'image/svg+xml'
61
+ };
62
+
63
+ const contentType = contentTypes[ext] || 'text/plain';
64
+ const fullPath = path.join(PUBLIC_DIR, filePath);
65
+
66
+ if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
67
+ res.writeHead(200, { 'Content-Type': contentType });
68
+ fs.createReadStream(fullPath).pipe(res);
69
+ } else {
70
+ res.writeHead(404);
71
+ res.end('404 Not Found');
72
+ }
73
+ });
74
+
75
+ export function startDashboard() {
76
+ server.listen(PORT, async () => {
77
+ console.log(`\nšŸš€ SPARK Dashboard running at http://localhost:${PORT}`);
78
+ console.log(`Open this URL in your browser to view the AI Knowledge Base.`);
79
+ console.log(`Press Ctrl+C to stop.\n`);
80
+
81
+ // Attempt to open browser automatically
82
+ const { exec } = await import('child_process');
83
+ const startCmd = process.platform == 'darwin' ? 'open' : process.platform == 'win32' ? 'start' : 'xdg-open';
84
+ exec(`${startCmd} http://localhost:${PORT}`);
85
+ });
86
+ }