@girardmedia/bootspring 1.1.0
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 +255 -0
- package/agents/README.md +93 -0
- package/agents/api-expert/context.md +416 -0
- package/agents/architecture-expert/context.md +454 -0
- package/agents/backend-expert/context.md +483 -0
- package/agents/code-review-expert/context.md +365 -0
- package/agents/database-expert/context.md +250 -0
- package/agents/devops-expert/context.md +446 -0
- package/agents/frontend-expert/context.md +364 -0
- package/agents/index.js +140 -0
- package/agents/performance-expert/context.md +377 -0
- package/agents/security-expert/context.md +343 -0
- package/agents/testing-expert/context.md +414 -0
- package/agents/ui-ux-expert/context.md +448 -0
- package/agents/vercel-expert/context.md +426 -0
- package/bin/bootspring.js +310 -0
- package/cli/agent.js +337 -0
- package/cli/context.js +194 -0
- package/cli/dashboard.js +150 -0
- package/cli/generate.js +294 -0
- package/cli/init.js +410 -0
- package/cli/loop.js +421 -0
- package/cli/mcp.js +241 -0
- package/cli/memory.js +303 -0
- package/cli/orchestrator.js +400 -0
- package/cli/plugin.js +451 -0
- package/cli/quality.js +332 -0
- package/cli/skill.js +369 -0
- package/cli/task.js +628 -0
- package/cli/telemetry.js +114 -0
- package/cli/todo.js +614 -0
- package/cli/update.js +312 -0
- package/core/config.js +245 -0
- package/core/context.js +329 -0
- package/core/entitlements.js +209 -0
- package/core/index.js +43 -0
- package/core/policies.js +68 -0
- package/core/telemetry.js +247 -0
- package/core/utils.js +380 -0
- package/dashboard/server.js +818 -0
- package/docs/integrations/claude-code.md +42 -0
- package/docs/integrations/codex.md +42 -0
- package/docs/mcp-api-platform.md +102 -0
- package/generators/generate.js +598 -0
- package/generators/index.js +18 -0
- package/hooks/context-detector.js +177 -0
- package/hooks/index.js +35 -0
- package/hooks/prompt-enhancer.js +289 -0
- package/intelligence/git-memory.js +551 -0
- package/intelligence/index.js +59 -0
- package/intelligence/orchestrator.js +964 -0
- package/intelligence/prd.js +447 -0
- package/intelligence/recommendation-weights.json +18 -0
- package/intelligence/recommendations.js +234 -0
- package/mcp/capabilities.js +71 -0
- package/mcp/contracts/mcp-contract.v1.json +497 -0
- package/mcp/registry.js +213 -0
- package/mcp/response-formatter.js +462 -0
- package/mcp/server.js +99 -0
- package/mcp/tools/agent-tool.js +137 -0
- package/mcp/tools/capabilities-tool.js +54 -0
- package/mcp/tools/context-tool.js +49 -0
- package/mcp/tools/dashboard-tool.js +58 -0
- package/mcp/tools/generate-tool.js +46 -0
- package/mcp/tools/loop-tool.js +134 -0
- package/mcp/tools/memory-tool.js +180 -0
- package/mcp/tools/orchestrator-tool.js +232 -0
- package/mcp/tools/plugin-tool.js +76 -0
- package/mcp/tools/quality-tool.js +47 -0
- package/mcp/tools/skill-tool.js +233 -0
- package/mcp/tools/telemetry-tool.js +95 -0
- package/mcp/tools/todo-tool.js +133 -0
- package/package.json +98 -0
- package/plugins/index.js +141 -0
- package/quality/index.js +380 -0
- package/quality/lint-budgets.json +19 -0
- package/skills/index.js +787 -0
- package/skills/patterns/README.md +163 -0
- package/skills/patterns/api/route-handler.md +217 -0
- package/skills/patterns/api/server-action.md +249 -0
- package/skills/patterns/auth/clerk.md +132 -0
- package/skills/patterns/database/prisma.md +180 -0
- package/skills/patterns/payments/stripe.md +272 -0
- package/skills/patterns/security/validation.md +268 -0
- package/skills/patterns/testing/vitest.md +307 -0
- package/templates/bootspring.config.js +83 -0
- package/templates/mcp.json +9 -0
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bootspring Dashboard Server
|
|
5
|
+
* Real-time project monitoring and management
|
|
6
|
+
*
|
|
7
|
+
* @package bootspring
|
|
8
|
+
* @module dashboard
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const http = require('http');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const { WebSocketServer } = require('ws');
|
|
15
|
+
|
|
16
|
+
// Configuration
|
|
17
|
+
const DEFAULT_PORT = 3456;
|
|
18
|
+
const port = parseInt(process.env.BOOTSPRING_PORT || DEFAULT_PORT);
|
|
19
|
+
|
|
20
|
+
// Find project root
|
|
21
|
+
function findProjectRoot() {
|
|
22
|
+
let dir = process.cwd();
|
|
23
|
+
while (dir !== path.parse(dir).root) {
|
|
24
|
+
if (fs.existsSync(path.join(dir, 'bootspring.config.js')) ||
|
|
25
|
+
fs.existsSync(path.join(dir, 'package.json'))) {
|
|
26
|
+
return dir;
|
|
27
|
+
}
|
|
28
|
+
dir = path.dirname(dir);
|
|
29
|
+
}
|
|
30
|
+
return process.cwd();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const projectRoot = findProjectRoot();
|
|
34
|
+
|
|
35
|
+
// Load project config
|
|
36
|
+
function loadConfig() {
|
|
37
|
+
try {
|
|
38
|
+
const configPath = path.join(projectRoot, 'bootspring.config.js');
|
|
39
|
+
if (fs.existsSync(configPath)) {
|
|
40
|
+
delete require.cache[require.resolve(configPath)];
|
|
41
|
+
return require(configPath);
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
// Ignore
|
|
45
|
+
}
|
|
46
|
+
return { project: { name: 'Bootspring Project' } };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Load todos
|
|
50
|
+
function loadTodos() {
|
|
51
|
+
try {
|
|
52
|
+
const todoPath = path.join(projectRoot, 'todo.md');
|
|
53
|
+
if (!fs.existsSync(todoPath)) return [];
|
|
54
|
+
|
|
55
|
+
const content = fs.readFileSync(todoPath, 'utf-8');
|
|
56
|
+
const lines = content.split('\n');
|
|
57
|
+
const todos = [];
|
|
58
|
+
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
const match = line.match(/^- \[([ x])\] (.+)$/);
|
|
61
|
+
if (match) {
|
|
62
|
+
todos.push({
|
|
63
|
+
done: match[1] === 'x',
|
|
64
|
+
text: match[2].trim()
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return todos;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Get git info
|
|
76
|
+
function getGitInfo() {
|
|
77
|
+
try {
|
|
78
|
+
const { execSync } = require('child_process');
|
|
79
|
+
const branch = execSync('git branch --show-current', { cwd: projectRoot, encoding: 'utf-8' }).trim();
|
|
80
|
+
const status = execSync('git status --porcelain', { cwd: projectRoot, encoding: 'utf-8' });
|
|
81
|
+
const lastCommit = execSync('git log -1 --format="%h %s" 2>/dev/null || echo "No commits"', { cwd: projectRoot, encoding: 'utf-8' }).trim();
|
|
82
|
+
|
|
83
|
+
const changes = status.split('\n').filter(Boolean);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
branch,
|
|
87
|
+
lastCommit,
|
|
88
|
+
changedFiles: changes.length,
|
|
89
|
+
status: changes.length === 0 ? 'clean' : 'modified'
|
|
90
|
+
};
|
|
91
|
+
} catch (e) {
|
|
92
|
+
return { branch: 'unknown', lastCommit: '', changedFiles: 0, status: 'unknown' };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Get project stats
|
|
97
|
+
function getStats() {
|
|
98
|
+
const config = loadConfig();
|
|
99
|
+
const todos = loadTodos();
|
|
100
|
+
const git = getGitInfo();
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
project: config.project || { name: 'Bootspring Project' },
|
|
104
|
+
todos: {
|
|
105
|
+
total: todos.length,
|
|
106
|
+
done: todos.filter(t => t.done).length,
|
|
107
|
+
items: todos
|
|
108
|
+
},
|
|
109
|
+
git,
|
|
110
|
+
timestamp: new Date().toISOString()
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Dashboard HTML
|
|
115
|
+
function getDashboardHTML() {
|
|
116
|
+
const config = loadConfig();
|
|
117
|
+
const projectName = config.project?.name || 'Bootspring Project';
|
|
118
|
+
|
|
119
|
+
return `<!DOCTYPE html>
|
|
120
|
+
<html lang="en">
|
|
121
|
+
<head>
|
|
122
|
+
<meta charset="UTF-8">
|
|
123
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
124
|
+
<title>${projectName} - Bootspring Dashboard</title>
|
|
125
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
126
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
127
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
128
|
+
<style>
|
|
129
|
+
:root {
|
|
130
|
+
--bg-primary: #0f172a;
|
|
131
|
+
--bg-secondary: #1e293b;
|
|
132
|
+
--bg-tertiary: #334155;
|
|
133
|
+
--text-primary: #f8fafc;
|
|
134
|
+
--text-secondary: #94a3b8;
|
|
135
|
+
--text-muted: #64748b;
|
|
136
|
+
--accent-cyan: #06b6d4;
|
|
137
|
+
--accent-green: #22c55e;
|
|
138
|
+
--accent-yellow: #eab308;
|
|
139
|
+
--accent-red: #ef4444;
|
|
140
|
+
--accent-purple: #a855f7;
|
|
141
|
+
--border: #334155;
|
|
142
|
+
--radius: 12px;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
* {
|
|
146
|
+
margin: 0;
|
|
147
|
+
padding: 0;
|
|
148
|
+
box-sizing: border-box;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
body {
|
|
152
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
153
|
+
background: var(--bg-primary);
|
|
154
|
+
color: var(--text-primary);
|
|
155
|
+
min-height: 100vh;
|
|
156
|
+
line-height: 1.6;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.container {
|
|
160
|
+
max-width: 1400px;
|
|
161
|
+
margin: 0 auto;
|
|
162
|
+
padding: 2rem;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
header {
|
|
166
|
+
display: flex;
|
|
167
|
+
align-items: center;
|
|
168
|
+
justify-content: space-between;
|
|
169
|
+
margin-bottom: 2rem;
|
|
170
|
+
padding-bottom: 1.5rem;
|
|
171
|
+
border-bottom: 1px solid var(--border);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.logo {
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
gap: 1rem;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.logo-icon {
|
|
181
|
+
width: 48px;
|
|
182
|
+
height: 48px;
|
|
183
|
+
background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple));
|
|
184
|
+
border-radius: 12px;
|
|
185
|
+
display: flex;
|
|
186
|
+
align-items: center;
|
|
187
|
+
justify-content: center;
|
|
188
|
+
font-size: 1.5rem;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.logo h1 {
|
|
192
|
+
font-size: 1.5rem;
|
|
193
|
+
font-weight: 700;
|
|
194
|
+
background: linear-gradient(135deg, var(--accent-cyan), var(--accent-purple));
|
|
195
|
+
-webkit-background-clip: text;
|
|
196
|
+
-webkit-text-fill-color: transparent;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.logo span {
|
|
200
|
+
font-size: 0.875rem;
|
|
201
|
+
color: var(--text-secondary);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.status-badge {
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
gap: 0.5rem;
|
|
208
|
+
padding: 0.5rem 1rem;
|
|
209
|
+
background: var(--bg-secondary);
|
|
210
|
+
border-radius: 999px;
|
|
211
|
+
font-size: 0.875rem;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.status-dot {
|
|
215
|
+
width: 8px;
|
|
216
|
+
height: 8px;
|
|
217
|
+
border-radius: 50%;
|
|
218
|
+
background: var(--accent-green);
|
|
219
|
+
animation: pulse 2s infinite;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@keyframes pulse {
|
|
223
|
+
0%, 100% { opacity: 1; }
|
|
224
|
+
50% { opacity: 0.5; }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.grid {
|
|
228
|
+
display: grid;
|
|
229
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
230
|
+
gap: 1.5rem;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.card {
|
|
234
|
+
background: var(--bg-secondary);
|
|
235
|
+
border-radius: var(--radius);
|
|
236
|
+
border: 1px solid var(--border);
|
|
237
|
+
overflow: hidden;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.card-header {
|
|
241
|
+
padding: 1rem 1.25rem;
|
|
242
|
+
border-bottom: 1px solid var(--border);
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
justify-content: space-between;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.card-title {
|
|
249
|
+
font-size: 0.875rem;
|
|
250
|
+
font-weight: 600;
|
|
251
|
+
text-transform: uppercase;
|
|
252
|
+
letter-spacing: 0.05em;
|
|
253
|
+
color: var(--text-secondary);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.card-body {
|
|
257
|
+
padding: 1.25rem;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.stat-value {
|
|
261
|
+
font-size: 2.5rem;
|
|
262
|
+
font-weight: 700;
|
|
263
|
+
font-family: 'JetBrains Mono', monospace;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.stat-label {
|
|
267
|
+
color: var(--text-muted);
|
|
268
|
+
font-size: 0.875rem;
|
|
269
|
+
margin-top: 0.25rem;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.todo-list {
|
|
273
|
+
list-style: none;
|
|
274
|
+
max-height: 400px;
|
|
275
|
+
overflow-y: auto;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.todo-item {
|
|
279
|
+
display: flex;
|
|
280
|
+
align-items: center;
|
|
281
|
+
gap: 0.75rem;
|
|
282
|
+
padding: 0.75rem 0;
|
|
283
|
+
border-bottom: 1px solid var(--border);
|
|
284
|
+
transition: background 0.2s;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.todo-item:last-child {
|
|
288
|
+
border-bottom: none;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.todo-item:hover {
|
|
292
|
+
background: var(--bg-tertiary);
|
|
293
|
+
margin: 0 -1.25rem;
|
|
294
|
+
padding-left: 1.25rem;
|
|
295
|
+
padding-right: 1.25rem;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.todo-checkbox {
|
|
299
|
+
width: 18px;
|
|
300
|
+
height: 18px;
|
|
301
|
+
border: 2px solid var(--border);
|
|
302
|
+
border-radius: 4px;
|
|
303
|
+
display: flex;
|
|
304
|
+
align-items: center;
|
|
305
|
+
justify-content: center;
|
|
306
|
+
flex-shrink: 0;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.todo-item.done .todo-checkbox {
|
|
310
|
+
background: var(--accent-green);
|
|
311
|
+
border-color: var(--accent-green);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.todo-item.done .todo-checkbox::after {
|
|
315
|
+
content: '✓';
|
|
316
|
+
color: white;
|
|
317
|
+
font-size: 12px;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.todo-item.done .todo-text {
|
|
321
|
+
text-decoration: line-through;
|
|
322
|
+
color: var(--text-muted);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.todo-text {
|
|
326
|
+
flex: 1;
|
|
327
|
+
font-size: 0.9375rem;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.progress-bar {
|
|
331
|
+
height: 8px;
|
|
332
|
+
background: var(--bg-tertiary);
|
|
333
|
+
border-radius: 4px;
|
|
334
|
+
overflow: hidden;
|
|
335
|
+
margin-top: 1rem;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.progress-fill {
|
|
339
|
+
height: 100%;
|
|
340
|
+
background: linear-gradient(90deg, var(--accent-cyan), var(--accent-green));
|
|
341
|
+
border-radius: 4px;
|
|
342
|
+
transition: width 0.5s ease;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.git-info {
|
|
346
|
+
display: flex;
|
|
347
|
+
flex-direction: column;
|
|
348
|
+
gap: 0.75rem;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.git-row {
|
|
352
|
+
display: flex;
|
|
353
|
+
align-items: center;
|
|
354
|
+
gap: 0.75rem;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.git-label {
|
|
358
|
+
color: var(--text-muted);
|
|
359
|
+
font-size: 0.8125rem;
|
|
360
|
+
min-width: 80px;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.git-value {
|
|
364
|
+
font-family: 'JetBrains Mono', monospace;
|
|
365
|
+
font-size: 0.875rem;
|
|
366
|
+
color: var(--text-primary);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.git-badge {
|
|
370
|
+
padding: 0.25rem 0.75rem;
|
|
371
|
+
border-radius: 999px;
|
|
372
|
+
font-size: 0.75rem;
|
|
373
|
+
font-weight: 500;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.git-badge.clean {
|
|
377
|
+
background: rgba(34, 197, 94, 0.2);
|
|
378
|
+
color: var(--accent-green);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.git-badge.modified {
|
|
382
|
+
background: rgba(234, 179, 8, 0.2);
|
|
383
|
+
color: var(--accent-yellow);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.quick-actions {
|
|
387
|
+
display: grid;
|
|
388
|
+
grid-template-columns: repeat(2, 1fr);
|
|
389
|
+
gap: 0.75rem;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.action-btn {
|
|
393
|
+
display: flex;
|
|
394
|
+
align-items: center;
|
|
395
|
+
gap: 0.5rem;
|
|
396
|
+
padding: 0.75rem 1rem;
|
|
397
|
+
background: var(--bg-tertiary);
|
|
398
|
+
border: 1px solid var(--border);
|
|
399
|
+
border-radius: 8px;
|
|
400
|
+
color: var(--text-primary);
|
|
401
|
+
font-size: 0.875rem;
|
|
402
|
+
cursor: pointer;
|
|
403
|
+
transition: all 0.2s;
|
|
404
|
+
text-decoration: none;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.action-btn:hover {
|
|
408
|
+
background: var(--accent-cyan);
|
|
409
|
+
border-color: var(--accent-cyan);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.action-icon {
|
|
413
|
+
font-size: 1.125rem;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.agents-grid {
|
|
417
|
+
display: grid;
|
|
418
|
+
grid-template-columns: repeat(3, 1fr);
|
|
419
|
+
gap: 0.5rem;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.agent-chip {
|
|
423
|
+
padding: 0.5rem 0.75rem;
|
|
424
|
+
background: var(--bg-tertiary);
|
|
425
|
+
border-radius: 6px;
|
|
426
|
+
font-size: 0.75rem;
|
|
427
|
+
text-align: center;
|
|
428
|
+
color: var(--text-secondary);
|
|
429
|
+
transition: all 0.2s;
|
|
430
|
+
cursor: pointer;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.agent-chip:hover {
|
|
434
|
+
background: var(--accent-purple);
|
|
435
|
+
color: white;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.empty-state {
|
|
439
|
+
text-align: center;
|
|
440
|
+
padding: 2rem;
|
|
441
|
+
color: var(--text-muted);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.timestamp {
|
|
445
|
+
font-size: 0.75rem;
|
|
446
|
+
color: var(--text-muted);
|
|
447
|
+
font-family: 'JetBrains Mono', monospace;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
footer {
|
|
451
|
+
margin-top: 3rem;
|
|
452
|
+
padding-top: 1.5rem;
|
|
453
|
+
border-top: 1px solid var(--border);
|
|
454
|
+
text-align: center;
|
|
455
|
+
color: var(--text-muted);
|
|
456
|
+
font-size: 0.875rem;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
footer a {
|
|
460
|
+
color: var(--accent-cyan);
|
|
461
|
+
text-decoration: none;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
footer a:hover {
|
|
465
|
+
text-decoration: underline;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
@media (max-width: 768px) {
|
|
469
|
+
.container {
|
|
470
|
+
padding: 1rem;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
header {
|
|
474
|
+
flex-direction: column;
|
|
475
|
+
gap: 1rem;
|
|
476
|
+
align-items: flex-start;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.grid {
|
|
480
|
+
grid-template-columns: 1fr;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.agents-grid {
|
|
484
|
+
grid-template-columns: repeat(2, 1fr);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
</style>
|
|
488
|
+
</head>
|
|
489
|
+
<body>
|
|
490
|
+
<div class="container">
|
|
491
|
+
<header>
|
|
492
|
+
<div class="logo">
|
|
493
|
+
<div class="logo-icon">⚡</div>
|
|
494
|
+
<div>
|
|
495
|
+
<h1>Bootspring</h1>
|
|
496
|
+
<span id="project-name">${projectName}</span>
|
|
497
|
+
</div>
|
|
498
|
+
</div>
|
|
499
|
+
<div class="status-badge">
|
|
500
|
+
<div class="status-dot"></div>
|
|
501
|
+
<span>Live</span>
|
|
502
|
+
<span class="timestamp" id="timestamp"></span>
|
|
503
|
+
</div>
|
|
504
|
+
</header>
|
|
505
|
+
|
|
506
|
+
<div class="grid">
|
|
507
|
+
<!-- Progress Card -->
|
|
508
|
+
<div class="card">
|
|
509
|
+
<div class="card-header">
|
|
510
|
+
<span class="card-title">📊 Progress</span>
|
|
511
|
+
</div>
|
|
512
|
+
<div class="card-body">
|
|
513
|
+
<div class="stat-value" id="progress-value">0%</div>
|
|
514
|
+
<div class="stat-label" id="progress-label">0 of 0 tasks complete</div>
|
|
515
|
+
<div class="progress-bar">
|
|
516
|
+
<div class="progress-fill" id="progress-bar" style="width: 0%"></div>
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
</div>
|
|
520
|
+
|
|
521
|
+
<!-- Git Card -->
|
|
522
|
+
<div class="card">
|
|
523
|
+
<div class="card-header">
|
|
524
|
+
<span class="card-title">🔀 Git Status</span>
|
|
525
|
+
</div>
|
|
526
|
+
<div class="card-body">
|
|
527
|
+
<div class="git-info">
|
|
528
|
+
<div class="git-row">
|
|
529
|
+
<span class="git-label">Branch</span>
|
|
530
|
+
<span class="git-value" id="git-branch">main</span>
|
|
531
|
+
</div>
|
|
532
|
+
<div class="git-row">
|
|
533
|
+
<span class="git-label">Status</span>
|
|
534
|
+
<span class="git-badge clean" id="git-status">clean</span>
|
|
535
|
+
</div>
|
|
536
|
+
<div class="git-row">
|
|
537
|
+
<span class="git-label">Last commit</span>
|
|
538
|
+
<span class="git-value" id="git-commit">...</span>
|
|
539
|
+
</div>
|
|
540
|
+
</div>
|
|
541
|
+
</div>
|
|
542
|
+
</div>
|
|
543
|
+
|
|
544
|
+
<!-- Quick Actions -->
|
|
545
|
+
<div class="card">
|
|
546
|
+
<div class="card-header">
|
|
547
|
+
<span class="card-title">⚡ Quick Actions</span>
|
|
548
|
+
</div>
|
|
549
|
+
<div class="card-body">
|
|
550
|
+
<div class="quick-actions">
|
|
551
|
+
<button class="action-btn" onclick="runCommand('todo list')">
|
|
552
|
+
<span class="action-icon">📝</span>
|
|
553
|
+
<span>View Todos</span>
|
|
554
|
+
</button>
|
|
555
|
+
<button class="action-btn" onclick="runCommand('quality pre-commit')">
|
|
556
|
+
<span class="action-icon">✅</span>
|
|
557
|
+
<span>Quality Check</span>
|
|
558
|
+
</button>
|
|
559
|
+
<button class="action-btn" onclick="runCommand('generate')">
|
|
560
|
+
<span class="action-icon">🔄</span>
|
|
561
|
+
<span>Regenerate</span>
|
|
562
|
+
</button>
|
|
563
|
+
<button class="action-btn" onclick="runCommand('context validate')">
|
|
564
|
+
<span class="action-icon">🔍</span>
|
|
565
|
+
<span>Validate</span>
|
|
566
|
+
</button>
|
|
567
|
+
</div>
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
|
|
571
|
+
<!-- Agents -->
|
|
572
|
+
<div class="card">
|
|
573
|
+
<div class="card-header">
|
|
574
|
+
<span class="card-title">🤖 Agents</span>
|
|
575
|
+
</div>
|
|
576
|
+
<div class="card-body">
|
|
577
|
+
<div class="agents-grid">
|
|
578
|
+
<div class="agent-chip" onclick="invokeAgent('database')">Database</div>
|
|
579
|
+
<div class="agent-chip" onclick="invokeAgent('security')">Security</div>
|
|
580
|
+
<div class="agent-chip" onclick="invokeAgent('frontend')">Frontend</div>
|
|
581
|
+
<div class="agent-chip" onclick="invokeAgent('backend')">Backend</div>
|
|
582
|
+
<div class="agent-chip" onclick="invokeAgent('api')">API</div>
|
|
583
|
+
<div class="agent-chip" onclick="invokeAgent('testing')">Testing</div>
|
|
584
|
+
<div class="agent-chip" onclick="invokeAgent('performance')">Performance</div>
|
|
585
|
+
<div class="agent-chip" onclick="invokeAgent('devops')">DevOps</div>
|
|
586
|
+
<div class="agent-chip" onclick="invokeAgent('architecture')">Architecture</div>
|
|
587
|
+
</div>
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
|
|
591
|
+
<!-- Todo List (spans 2 columns) -->
|
|
592
|
+
<div class="card" style="grid-column: span 2;">
|
|
593
|
+
<div class="card-header">
|
|
594
|
+
<span class="card-title">📋 Todo List</span>
|
|
595
|
+
<span class="timestamp" id="todo-count">0 items</span>
|
|
596
|
+
</div>
|
|
597
|
+
<div class="card-body">
|
|
598
|
+
<ul class="todo-list" id="todo-list">
|
|
599
|
+
<li class="empty-state">No todos yet. Add one with: bootspring todo add "Your task"</li>
|
|
600
|
+
</ul>
|
|
601
|
+
</div>
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
|
|
605
|
+
<footer>
|
|
606
|
+
<p>
|
|
607
|
+
<strong>Bootspring</strong> - Development scaffolding with intelligence
|
|
608
|
+
<br>
|
|
609
|
+
<a href="https://bootspring.com" target="_blank">Documentation</a> ·
|
|
610
|
+
<a href="https://github.com/bootspring/bootspring" target="_blank">GitHub</a>
|
|
611
|
+
</p>
|
|
612
|
+
</footer>
|
|
613
|
+
</div>
|
|
614
|
+
|
|
615
|
+
<script>
|
|
616
|
+
let ws;
|
|
617
|
+
let reconnectAttempts = 0;
|
|
618
|
+
|
|
619
|
+
function connect() {
|
|
620
|
+
ws = new WebSocket('ws://' + window.location.host);
|
|
621
|
+
|
|
622
|
+
ws.onopen = () => {
|
|
623
|
+
console.log('Connected to dashboard');
|
|
624
|
+
reconnectAttempts = 0;
|
|
625
|
+
ws.send(JSON.stringify({ type: 'subscribe' }));
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
ws.onmessage = (event) => {
|
|
629
|
+
const data = JSON.parse(event.data);
|
|
630
|
+
updateDashboard(data);
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
ws.onclose = () => {
|
|
634
|
+
console.log('Disconnected, reconnecting...');
|
|
635
|
+
reconnectAttempts++;
|
|
636
|
+
setTimeout(connect, Math.min(1000 * reconnectAttempts, 5000));
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
ws.onerror = (error) => {
|
|
640
|
+
console.error('WebSocket error:', error);
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function updateDashboard(data) {
|
|
645
|
+
// Update timestamp
|
|
646
|
+
document.getElementById('timestamp').textContent =
|
|
647
|
+
new Date(data.timestamp).toLocaleTimeString();
|
|
648
|
+
|
|
649
|
+
// Update progress
|
|
650
|
+
const total = data.todos.total || 0;
|
|
651
|
+
const done = data.todos.done || 0;
|
|
652
|
+
const percent = total > 0 ? Math.round((done / total) * 100) : 0;
|
|
653
|
+
|
|
654
|
+
document.getElementById('progress-value').textContent = percent + '%';
|
|
655
|
+
document.getElementById('progress-label').textContent =
|
|
656
|
+
done + ' of ' + total + ' tasks complete';
|
|
657
|
+
document.getElementById('progress-bar').style.width = percent + '%';
|
|
658
|
+
|
|
659
|
+
// Update git info
|
|
660
|
+
document.getElementById('git-branch').textContent = data.git.branch;
|
|
661
|
+
document.getElementById('git-commit').textContent =
|
|
662
|
+
data.git.lastCommit.substring(0, 50) + (data.git.lastCommit.length > 50 ? '...' : '');
|
|
663
|
+
|
|
664
|
+
const statusEl = document.getElementById('git-status');
|
|
665
|
+
statusEl.textContent = data.git.status;
|
|
666
|
+
statusEl.className = 'git-badge ' + data.git.status;
|
|
667
|
+
|
|
668
|
+
// Update todos
|
|
669
|
+
const todoList = document.getElementById('todo-list');
|
|
670
|
+
document.getElementById('todo-count').textContent = total + ' items';
|
|
671
|
+
|
|
672
|
+
if (data.todos.items.length === 0) {
|
|
673
|
+
todoList.innerHTML = '<li class="empty-state">No todos yet. Add one with: bootspring todo add "Your task"</li>';
|
|
674
|
+
} else {
|
|
675
|
+
todoList.innerHTML = data.todos.items.map((todo, i) =>
|
|
676
|
+
'<li class="todo-item ' + (todo.done ? 'done' : '') + '">' +
|
|
677
|
+
'<div class="todo-checkbox"></div>' +
|
|
678
|
+
'<span class="todo-text">' + escapeHtml(todo.text) + '</span>' +
|
|
679
|
+
'</li>'
|
|
680
|
+
).join('');
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function escapeHtml(text) {
|
|
685
|
+
const div = document.createElement('div');
|
|
686
|
+
div.textContent = text;
|
|
687
|
+
return div.innerHTML;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function runCommand(cmd) {
|
|
691
|
+
alert('Run in terminal:\\nbootspring ' + cmd);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function invokeAgent(name) {
|
|
695
|
+
alert('Run in Claude Code:\\n@bootspring agent invoke ' + name + '-expert');
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Initial connection
|
|
699
|
+
connect();
|
|
700
|
+
|
|
701
|
+
// Poll for updates every 5 seconds as backup
|
|
702
|
+
setInterval(() => {
|
|
703
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
704
|
+
ws.send(JSON.stringify({ type: 'refresh' }));
|
|
705
|
+
}
|
|
706
|
+
}, 5000);
|
|
707
|
+
</script>
|
|
708
|
+
</body>
|
|
709
|
+
</html>`;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Create HTTP server
|
|
713
|
+
const server = http.createServer((req, res) => {
|
|
714
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
715
|
+
|
|
716
|
+
// API endpoints
|
|
717
|
+
if (url.pathname === '/api/stats') {
|
|
718
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
719
|
+
res.end(JSON.stringify(getStats()));
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (url.pathname === '/api/todos') {
|
|
724
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
725
|
+
res.end(JSON.stringify(loadTodos()));
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (url.pathname === '/api/config') {
|
|
730
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
731
|
+
res.end(JSON.stringify(loadConfig()));
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// Dashboard HTML
|
|
736
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
737
|
+
res.end(getDashboardHTML());
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
// Create WebSocket server
|
|
741
|
+
const wss = new WebSocketServer({ server });
|
|
742
|
+
|
|
743
|
+
wss.on('connection', (ws) => {
|
|
744
|
+
console.log('Dashboard client connected');
|
|
745
|
+
|
|
746
|
+
// Send initial data
|
|
747
|
+
ws.send(JSON.stringify(getStats()));
|
|
748
|
+
|
|
749
|
+
// Handle messages
|
|
750
|
+
ws.on('message', (message) => {
|
|
751
|
+
try {
|
|
752
|
+
const data = JSON.parse(message);
|
|
753
|
+
|
|
754
|
+
if (data.type === 'refresh' || data.type === 'subscribe') {
|
|
755
|
+
ws.send(JSON.stringify(getStats()));
|
|
756
|
+
}
|
|
757
|
+
} catch (e) {
|
|
758
|
+
// Ignore parse errors
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
ws.on('close', () => {
|
|
763
|
+
console.log('Dashboard client disconnected');
|
|
764
|
+
});
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
// Broadcast updates to all clients
|
|
768
|
+
function broadcast(data) {
|
|
769
|
+
wss.clients.forEach(client => {
|
|
770
|
+
if (client.readyState === 1) { // WebSocket.OPEN
|
|
771
|
+
client.send(JSON.stringify(data));
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Watch for file changes
|
|
777
|
+
const watchFiles = ['todo.md', 'bootspring.config.js'];
|
|
778
|
+
let debounceTimer;
|
|
779
|
+
|
|
780
|
+
for (const file of watchFiles) {
|
|
781
|
+
const filePath = path.join(projectRoot, file);
|
|
782
|
+
if (fs.existsSync(filePath)) {
|
|
783
|
+
fs.watch(filePath, () => {
|
|
784
|
+
clearTimeout(debounceTimer);
|
|
785
|
+
debounceTimer = setTimeout(() => {
|
|
786
|
+
broadcast(getStats());
|
|
787
|
+
}, 100);
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Start server
|
|
793
|
+
server.listen(port, () => {
|
|
794
|
+
console.log(`
|
|
795
|
+
\x1b[36m╔══════════════════════════════════════════╗
|
|
796
|
+
║ ║
|
|
797
|
+
║ ⚡ Bootspring Dashboard ║
|
|
798
|
+
║ ║
|
|
799
|
+
╚══════════════════════════════════════════╝\x1b[0m
|
|
800
|
+
|
|
801
|
+
\x1b[32m✓\x1b[0m Dashboard running at \x1b[36mhttp://localhost:${port}\x1b[0m
|
|
802
|
+
\x1b[32m✓\x1b[0m WebSocket server ready
|
|
803
|
+
\x1b[32m✓\x1b[0m Watching for changes
|
|
804
|
+
|
|
805
|
+
\x1b[2mProject: ${projectRoot}\x1b[0m
|
|
806
|
+
\x1b[2mPress Ctrl+C to stop\x1b[0m
|
|
807
|
+
`);
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
// Handle shutdown
|
|
811
|
+
process.on('SIGINT', () => {
|
|
812
|
+
console.log('\n\x1b[33mShutting down dashboard...\x1b[0m');
|
|
813
|
+
wss.close();
|
|
814
|
+
server.close();
|
|
815
|
+
process.exit(0);
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
module.exports = { server, wss, getStats, broadcast };
|