@girardmedia/bootspring 1.2.0 → 2.0.3
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/README.md +107 -14
- package/bin/bootspring.js +166 -27
- package/cli/agent.js +189 -17
- package/cli/analyze.js +499 -0
- package/cli/audit.js +557 -0
- package/cli/auth.js +495 -38
- package/cli/billing.js +302 -0
- package/cli/build.js +695 -0
- package/cli/business.js +109 -26
- package/cli/checkpoint-utils.js +168 -0
- package/cli/checkpoint.js +639 -0
- package/cli/cloud-sync.js +447 -0
- package/cli/content.js +198 -0
- package/cli/context.js +1 -1
- package/cli/deploy.js +543 -0
- package/cli/fundraise.js +112 -50
- package/cli/github-cmd.js +435 -0
- package/cli/health.js +477 -0
- package/cli/init.js +84 -13
- package/cli/legal.js +107 -95
- package/cli/log.js +2 -2
- package/cli/loop.js +976 -73
- package/cli/manager.js +711 -0
- package/cli/metrics.js +480 -0
- package/cli/monitor.js +812 -0
- package/cli/onboard.js +521 -0
- package/cli/orchestrator.js +12 -24
- package/cli/prd.js +594 -0
- package/cli/preseed-start.js +1483 -0
- package/cli/preseed.js +2302 -0
- package/cli/project.js +436 -0
- package/cli/quality.js +233 -0
- package/cli/security.js +913 -0
- package/cli/seed.js +1441 -5
- package/cli/skill.js +273 -211
- package/cli/suggest.js +989 -0
- package/cli/switch.js +453 -0
- package/cli/visualize.js +527 -0
- package/cli/watch.js +769 -0
- package/cli/workspace.js +607 -0
- package/core/analyze-workflow.js +1134 -0
- package/core/api-client.js +535 -22
- package/core/audit-workflow.js +1350 -0
- package/core/build-orchestrator.js +480 -0
- package/core/build-state.js +577 -0
- package/core/checkpoint-engine.js +408 -0
- package/core/config.js +1109 -26
- package/core/context-loader.js +21 -1
- package/core/deploy-workflow.js +836 -0
- package/core/entitlements.js +93 -22
- package/core/github-sync.js +610 -0
- package/core/index.js +8 -1
- package/core/ingest.js +1111 -0
- package/core/metrics-engine.js +768 -0
- package/core/onboard-workflow.js +1007 -0
- package/core/preseed-workflow.js +934 -0
- package/core/preseed.js +1617 -0
- package/core/project-context.js +325 -0
- package/core/project-state.js +694 -0
- package/core/r2-sync.js +583 -0
- package/core/scaffold.js +525 -7
- package/core/session.js +258 -0
- package/core/task-extractor.js +758 -0
- package/core/telemetry.js +28 -6
- package/core/tier-enforcement.js +737 -0
- package/core/utils.js +38 -14
- package/generators/questionnaire.js +15 -12
- package/generators/sections/ai.js +7 -7
- package/generators/sections/content.js +300 -0
- package/generators/sections/index.js +3 -0
- package/generators/sections/plugins.js +7 -6
- package/generators/templates/build-planning.template.js +596 -0
- package/generators/templates/content.template.js +819 -0
- package/generators/templates/index.js +2 -1
- package/hooks/git-autopilot.js +1250 -0
- package/hooks/index.js +9 -0
- package/intelligence/agent-collab.js +2057 -0
- package/intelligence/auto-suggest.js +634 -0
- package/intelligence/content-gen.js +1589 -0
- package/intelligence/cross-project.js +1647 -0
- package/intelligence/index.js +184 -0
- package/intelligence/learning/insights.json +517 -7
- package/intelligence/learning/pattern-learner.js +1008 -14
- package/intelligence/memory/decision-tracker.js +1431 -31
- package/intelligence/memory/decisions.jsonl +0 -0
- package/intelligence/orchestrator.js +2896 -1
- package/intelligence/prd.js +92 -1
- package/intelligence/recommendation-weights.json +14 -2
- package/intelligence/recommendations.js +463 -9
- package/intelligence/workflow-composer.js +1451 -0
- package/marketplace/index.d.ts +324 -0
- package/marketplace/index.js +1921 -0
- package/mcp/contracts/mcp-contract.v1.json +342 -4
- package/mcp/registry.js +680 -3
- package/mcp/response-formatter.js +23 -0
- package/mcp/tools/assist-tool.js +78 -4
- package/mcp/tools/autopilot-tool.js +408 -0
- package/mcp/tools/content-tool.js +571 -0
- package/mcp/tools/dashboard-tool.js +251 -5
- package/mcp/tools/mvp-tool.js +344 -0
- package/mcp/tools/plugin-tool.js +23 -1
- package/mcp/tools/prd-tool.js +579 -0
- package/mcp/tools/seed-tool.js +447 -0
- package/mcp/tools/skill-tool.js +43 -14
- package/mcp/tools/suggest-tool.js +147 -0
- package/package.json +15 -6
- package/agents/README.md +0 -93
- package/agents/ai-integration-expert/context.md +0 -386
- package/agents/api-expert/context.md +0 -416
- package/agents/architecture-expert/context.md +0 -454
- package/agents/auth-expert/context.md +0 -399
- package/agents/backend-expert/context.md +0 -483
- package/agents/business-strategy-expert/context.md +0 -180
- package/agents/code-review-expert/context.md +0 -365
- package/agents/competitive-analysis-expert/context.md +0 -239
- package/agents/data-modeling-expert/context.md +0 -352
- package/agents/database-expert/context.md +0 -250
- package/agents/devops-expert/context.md +0 -446
- package/agents/email-expert/context.md +0 -379
- package/agents/financial-expert/context.md +0 -213
- package/agents/frontend-expert/context.md +0 -364
- package/agents/fundraising-expert/context.md +0 -257
- package/agents/growth-expert/context.md +0 -249
- package/agents/index.js +0 -140
- package/agents/investor-relations-expert/context.md +0 -266
- package/agents/legal-expert/context.md +0 -284
- package/agents/marketing-expert/context.md +0 -236
- package/agents/monitoring-expert/context.md +0 -362
- package/agents/operations-expert/context.md +0 -279
- package/agents/partnerships-expert/context.md +0 -286
- package/agents/payment-expert/context.md +0 -340
- package/agents/performance-expert/context.md +0 -377
- package/agents/private-equity-expert/context.md +0 -246
- package/agents/railway-expert/context.md +0 -284
- package/agents/research-expert/context.md +0 -245
- package/agents/sales-expert/context.md +0 -241
- package/agents/security-expert/context.md +0 -343
- package/agents/testing-expert/context.md +0 -414
- package/agents/ui-ux-expert/context.md +0 -448
- package/agents/vercel-expert/context.md +0 -426
- package/skills/index.js +0 -787
- package/skills/patterns/README.md +0 -163
- package/skills/patterns/ai/agents.md +0 -281
- package/skills/patterns/ai/claude.md +0 -138
- package/skills/patterns/ai/embeddings.md +0 -150
- package/skills/patterns/ai/rag.md +0 -266
- package/skills/patterns/ai/streaming.md +0 -170
- package/skills/patterns/ai/structured-output.md +0 -162
- package/skills/patterns/ai/tools.md +0 -154
- package/skills/patterns/analytics/tracking.md +0 -220
- package/skills/patterns/api/errors.md +0 -296
- package/skills/patterns/api/graphql.md +0 -440
- package/skills/patterns/api/middleware.md +0 -279
- package/skills/patterns/api/openapi.md +0 -285
- package/skills/patterns/api/rate-limiting.md +0 -231
- package/skills/patterns/api/route-handler.md +0 -217
- package/skills/patterns/api/server-action.md +0 -249
- package/skills/patterns/api/versioning.md +0 -443
- package/skills/patterns/api/webhooks.md +0 -247
- package/skills/patterns/auth/clerk.md +0 -132
- package/skills/patterns/auth/mfa.md +0 -313
- package/skills/patterns/auth/nextauth.md +0 -140
- package/skills/patterns/auth/oauth.md +0 -237
- package/skills/patterns/auth/rbac.md +0 -152
- package/skills/patterns/auth/session-management.md +0 -367
- package/skills/patterns/auth/session.md +0 -120
- package/skills/patterns/database/audit.md +0 -177
- package/skills/patterns/database/migrations.md +0 -177
- package/skills/patterns/database/pagination.md +0 -230
- package/skills/patterns/database/pooling.md +0 -357
- package/skills/patterns/database/prisma.md +0 -180
- package/skills/patterns/database/relations.md +0 -187
- package/skills/patterns/database/seeding.md +0 -246
- package/skills/patterns/database/soft-delete.md +0 -153
- package/skills/patterns/database/transactions.md +0 -162
- package/skills/patterns/deployment/ci-cd.md +0 -231
- package/skills/patterns/deployment/docker.md +0 -188
- package/skills/patterns/deployment/monitoring.md +0 -387
- package/skills/patterns/deployment/vercel.md +0 -160
- package/skills/patterns/email/resend.md +0 -143
- package/skills/patterns/email/templates.md +0 -245
- package/skills/patterns/email/transactional.md +0 -503
- package/skills/patterns/email/verification.md +0 -176
- package/skills/patterns/files/download.md +0 -243
- package/skills/patterns/files/upload.md +0 -239
- package/skills/patterns/i18n/nextintl.md +0 -188
- package/skills/patterns/logging/structured.md +0 -292
- package/skills/patterns/notifications/email-queue.md +0 -248
- package/skills/patterns/notifications/push.md +0 -279
- package/skills/patterns/payments/checkout.md +0 -303
- package/skills/patterns/payments/invoices.md +0 -287
- package/skills/patterns/payments/portal.md +0 -245
- package/skills/patterns/payments/stripe.md +0 -272
- package/skills/patterns/payments/subscriptions.md +0 -300
- package/skills/patterns/payments/usage.md +0 -279
- package/skills/patterns/performance/caching.md +0 -276
- package/skills/patterns/performance/code-splitting.md +0 -233
- package/skills/patterns/performance/edge.md +0 -254
- package/skills/patterns/performance/isr.md +0 -266
- package/skills/patterns/performance/lazy-loading.md +0 -281
- package/skills/patterns/realtime/sse.md +0 -327
- package/skills/patterns/realtime/websockets.md +0 -336
- package/skills/patterns/search/filtering.md +0 -329
- package/skills/patterns/search/fulltext.md +0 -260
- package/skills/patterns/security/audit-logging.md +0 -444
- package/skills/patterns/security/csrf.md +0 -234
- package/skills/patterns/security/headers.md +0 -252
- package/skills/patterns/security/sanitization.md +0 -258
- package/skills/patterns/security/secrets.md +0 -261
- package/skills/patterns/security/validation.md +0 -268
- package/skills/patterns/security/xss.md +0 -229
- package/skills/patterns/seo/metadata.md +0 -252
- package/skills/patterns/state/context.md +0 -349
- package/skills/patterns/state/react-query.md +0 -313
- package/skills/patterns/state/url-state.md +0 -482
- package/skills/patterns/state/zustand.md +0 -262
- package/skills/patterns/testing/api.md +0 -259
- package/skills/patterns/testing/component.md +0 -233
- package/skills/patterns/testing/coverage.md +0 -207
- package/skills/patterns/testing/fixtures.md +0 -225
- package/skills/patterns/testing/integration.md +0 -436
- package/skills/patterns/testing/mocking.md +0 -177
- package/skills/patterns/testing/playwright.md +0 -162
- package/skills/patterns/testing/snapshot.md +0 -175
- package/skills/patterns/testing/vitest.md +0 -307
- package/skills/patterns/ui/accordions.md +0 -395
- package/skills/patterns/ui/cards.md +0 -299
- package/skills/patterns/ui/dropdowns.md +0 -476
- package/skills/patterns/ui/empty-states.md +0 -320
- package/skills/patterns/ui/forms.md +0 -405
- package/skills/patterns/ui/inputs.md +0 -319
- package/skills/patterns/ui/layouts.md +0 -282
- package/skills/patterns/ui/loading.md +0 -291
- package/skills/patterns/ui/modals.md +0 -338
- package/skills/patterns/ui/navigation.md +0 -374
- package/skills/patterns/ui/tables.md +0 -407
- package/skills/patterns/ui/toasts.md +0 -300
- package/skills/patterns/ui/tooltips.md +0 -396
- package/skills/patterns/utils/dates.md +0 -435
- package/skills/patterns/utils/errors.md +0 -451
- package/skills/patterns/utils/formatting.md +0 -345
- package/skills/patterns/utils/validation.md +0 -434
- package/templates/bootspring.config.js +0 -83
- package/templates/business/business-model-canvas.md +0 -246
- package/templates/business/business-plan.md +0 -266
- package/templates/business/competitive-analysis.md +0 -312
- package/templates/fundraising/data-room-checklist.md +0 -300
- package/templates/fundraising/investor-research.md +0 -243
- package/templates/fundraising/pitch-deck-outline.md +0 -253
- package/templates/legal/gdpr-checklist.md +0 -339
- package/templates/legal/privacy-policy.md +0 -285
- package/templates/legal/terms-of-service.md +0 -222
- package/templates/mcp.json +0 -9
|
@@ -2,17 +2,108 @@
|
|
|
2
2
|
* MCP dashboard tool module
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
const { spawn, execSync } = require('child_process');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const http = require('http');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
// PID file location for tracking the dashboard process
|
|
12
|
+
const PID_FILE = path.join(os.tmpdir(), 'bootspring-dashboard.pid');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Check if dashboard is running by trying to connect
|
|
16
|
+
*/
|
|
17
|
+
async function isDashboardRunning(port) {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
const req = http.request({ host: 'localhost', port, method: 'GET', timeout: 1000 }, (_res) => {
|
|
20
|
+
resolve(true);
|
|
21
|
+
});
|
|
22
|
+
req.on('error', () => resolve(false));
|
|
23
|
+
req.on('timeout', () => { req.destroy(); resolve(false); });
|
|
24
|
+
req.end();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get the PID of process using a specific port
|
|
30
|
+
*/
|
|
31
|
+
function getProcessOnPort(port) {
|
|
32
|
+
try {
|
|
33
|
+
// macOS/Linux: use lsof to find process on port
|
|
34
|
+
const result = execSync(`lsof -ti:${port} 2>/dev/null || true`, { encoding: 'utf-8' }).trim();
|
|
35
|
+
if (result) {
|
|
36
|
+
// Return first PID (there might be multiple lines for IPv4/IPv6)
|
|
37
|
+
return parseInt(result.split('\n')[0], 10);
|
|
38
|
+
}
|
|
39
|
+
} catch {
|
|
40
|
+
// Ignore errors
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Save PID to file for tracking
|
|
47
|
+
*/
|
|
48
|
+
function savePid(pid) {
|
|
49
|
+
try {
|
|
50
|
+
fs.writeFileSync(PID_FILE, String(pid), 'utf-8');
|
|
51
|
+
} catch {
|
|
52
|
+
// Ignore write errors
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Read saved PID from file
|
|
58
|
+
*/
|
|
59
|
+
function readSavedPid() {
|
|
60
|
+
try {
|
|
61
|
+
if (fs.existsSync(PID_FILE)) {
|
|
62
|
+
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim(), 10);
|
|
63
|
+
return isNaN(pid) ? null : pid;
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Ignore read errors
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Remove PID file
|
|
73
|
+
*/
|
|
74
|
+
function removePidFile() {
|
|
75
|
+
try {
|
|
76
|
+
if (fs.existsSync(PID_FILE)) {
|
|
77
|
+
fs.unlinkSync(PID_FILE);
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
// Ignore errors
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Kill a process by PID
|
|
86
|
+
*/
|
|
87
|
+
function killProcess(pid) {
|
|
88
|
+
try {
|
|
89
|
+
process.kill(pid, 'SIGTERM');
|
|
90
|
+
return true;
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
5
96
|
function getToolDefinition() {
|
|
6
97
|
return {
|
|
7
98
|
name: 'bootspring_dashboard',
|
|
8
|
-
description: 'Get dashboard status or
|
|
99
|
+
description: 'Get dashboard status, start the dashboard server, or get the URL.',
|
|
9
100
|
inputSchema: {
|
|
10
101
|
type: 'object',
|
|
11
102
|
properties: {
|
|
12
103
|
action: {
|
|
13
104
|
type: 'string',
|
|
14
|
-
enum: ['status', 'url'],
|
|
15
|
-
description: 'Action: status or
|
|
105
|
+
enum: ['status', 'url', 'start', 'stop'],
|
|
106
|
+
description: 'Action: status, url, start, or stop'
|
|
16
107
|
}
|
|
17
108
|
},
|
|
18
109
|
required: []
|
|
@@ -27,18 +118,20 @@ function createHandler({ config }) {
|
|
|
27
118
|
const port = cfg.dashboard?.port || 3456;
|
|
28
119
|
|
|
29
120
|
switch (action) {
|
|
30
|
-
case 'status':
|
|
121
|
+
case 'status': {
|
|
122
|
+
const running = await isDashboardRunning(port);
|
|
31
123
|
return {
|
|
32
124
|
content: [{
|
|
33
125
|
type: 'text',
|
|
34
126
|
text: JSON.stringify({
|
|
35
|
-
status: '
|
|
127
|
+
status: running ? 'running' : 'stopped',
|
|
36
128
|
port,
|
|
37
129
|
url: `http://localhost:${port}`,
|
|
38
130
|
command: 'bootspring dashboard'
|
|
39
131
|
}, null, 2)
|
|
40
132
|
}]
|
|
41
133
|
};
|
|
134
|
+
}
|
|
42
135
|
case 'url':
|
|
43
136
|
return {
|
|
44
137
|
content: [{
|
|
@@ -46,6 +139,159 @@ function createHandler({ config }) {
|
|
|
46
139
|
text: `http://localhost:${port}`
|
|
47
140
|
}]
|
|
48
141
|
};
|
|
142
|
+
case 'start': {
|
|
143
|
+
// Check if already running
|
|
144
|
+
const alreadyRunning = await isDashboardRunning(port);
|
|
145
|
+
if (alreadyRunning) {
|
|
146
|
+
const existingPid = getProcessOnPort(port);
|
|
147
|
+
return {
|
|
148
|
+
content: [{
|
|
149
|
+
type: 'text',
|
|
150
|
+
text: JSON.stringify({
|
|
151
|
+
status: 'already_running',
|
|
152
|
+
port,
|
|
153
|
+
url: `http://localhost:${port}`,
|
|
154
|
+
pid: existingPid,
|
|
155
|
+
message: 'Dashboard is already running'
|
|
156
|
+
}, null, 2)
|
|
157
|
+
}]
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Find the dashboard server script
|
|
162
|
+
const serverPath = path.join(__dirname, '..', '..', 'dashboard', 'server.js');
|
|
163
|
+
|
|
164
|
+
// Verify server script exists
|
|
165
|
+
if (!fs.existsSync(serverPath)) {
|
|
166
|
+
return {
|
|
167
|
+
content: [{
|
|
168
|
+
type: 'text',
|
|
169
|
+
text: JSON.stringify({
|
|
170
|
+
status: 'error',
|
|
171
|
+
message: `Dashboard server script not found at: ${serverPath}`,
|
|
172
|
+
hint: 'Ensure the dashboard module is installed'
|
|
173
|
+
}, null, 2)
|
|
174
|
+
}]
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Spawn the dashboard process
|
|
179
|
+
const dashboardProcess = spawn('node', [serverPath], {
|
|
180
|
+
detached: true,
|
|
181
|
+
stdio: 'ignore',
|
|
182
|
+
env: { ...process.env, BOOTSPRING_PORT: String(port) }
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Unref so parent can exit independently
|
|
186
|
+
dashboardProcess.unref();
|
|
187
|
+
|
|
188
|
+
// Save PID for later tracking
|
|
189
|
+
if (dashboardProcess.pid) {
|
|
190
|
+
savePid(dashboardProcess.pid);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Wait a moment for startup
|
|
194
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
195
|
+
|
|
196
|
+
// Verify it started
|
|
197
|
+
const started = await isDashboardRunning(port);
|
|
198
|
+
|
|
199
|
+
if (!started) {
|
|
200
|
+
removePidFile();
|
|
201
|
+
return {
|
|
202
|
+
content: [{
|
|
203
|
+
type: 'text',
|
|
204
|
+
text: JSON.stringify({
|
|
205
|
+
status: 'failed',
|
|
206
|
+
port,
|
|
207
|
+
message: 'Failed to start dashboard. The server may have crashed on startup.',
|
|
208
|
+
hint: 'Try running "node dashboard/server.js" manually to see errors'
|
|
209
|
+
}, null, 2)
|
|
210
|
+
}]
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
content: [{
|
|
216
|
+
type: 'text',
|
|
217
|
+
text: JSON.stringify({
|
|
218
|
+
status: 'started',
|
|
219
|
+
port,
|
|
220
|
+
url: `http://localhost:${port}`,
|
|
221
|
+
pid: dashboardProcess.pid,
|
|
222
|
+
message: 'Dashboard started successfully'
|
|
223
|
+
}, null, 2)
|
|
224
|
+
}]
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
case 'stop': {
|
|
228
|
+
const running = await isDashboardRunning(port);
|
|
229
|
+
if (!running) {
|
|
230
|
+
removePidFile();
|
|
231
|
+
return {
|
|
232
|
+
content: [{
|
|
233
|
+
type: 'text',
|
|
234
|
+
text: JSON.stringify({
|
|
235
|
+
status: 'not_running',
|
|
236
|
+
message: 'Dashboard is not running'
|
|
237
|
+
}, null, 2)
|
|
238
|
+
}]
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Try multiple methods to find and kill the process
|
|
243
|
+
let killed = false;
|
|
244
|
+
let killedPid = null;
|
|
245
|
+
|
|
246
|
+
// Method 1: Try to find process by port (most reliable)
|
|
247
|
+
const pidOnPort = getProcessOnPort(port);
|
|
248
|
+
if (pidOnPort) {
|
|
249
|
+
killed = killProcess(pidOnPort);
|
|
250
|
+
if (killed) killedPid = pidOnPort;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Method 2: Try saved PID file as fallback
|
|
254
|
+
if (!killed) {
|
|
255
|
+
const savedPid = readSavedPid();
|
|
256
|
+
if (savedPid) {
|
|
257
|
+
killed = killProcess(savedPid);
|
|
258
|
+
if (killed) killedPid = savedPid;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Clean up PID file
|
|
263
|
+
removePidFile();
|
|
264
|
+
|
|
265
|
+
// Wait a moment and verify it stopped
|
|
266
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
267
|
+
const stillRunning = await isDashboardRunning(port);
|
|
268
|
+
|
|
269
|
+
if (stillRunning) {
|
|
270
|
+
return {
|
|
271
|
+
content: [{
|
|
272
|
+
type: 'text',
|
|
273
|
+
text: JSON.stringify({
|
|
274
|
+
status: 'stop_failed',
|
|
275
|
+
port,
|
|
276
|
+
message: 'Failed to stop dashboard. Process may require manual termination.',
|
|
277
|
+
hint: `Try: kill $(lsof -ti:${port})`
|
|
278
|
+
}, null, 2)
|
|
279
|
+
}]
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
content: [{
|
|
285
|
+
type: 'text',
|
|
286
|
+
text: JSON.stringify({
|
|
287
|
+
status: 'stopped',
|
|
288
|
+
port,
|
|
289
|
+
pid: killedPid,
|
|
290
|
+
message: 'Dashboard stopped successfully'
|
|
291
|
+
}, null, 2)
|
|
292
|
+
}]
|
|
293
|
+
};
|
|
294
|
+
}
|
|
49
295
|
default:
|
|
50
296
|
throw new Error(`Unknown action: ${action}`);
|
|
51
297
|
}
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP MVP tool module
|
|
3
|
+
* Provides MVP operations via MCP: init, analyze, import, list, show, extract
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
function getToolDefinition() {
|
|
7
|
+
return {
|
|
8
|
+
name: 'bootspring_mvp',
|
|
9
|
+
description: 'Import and manage AI-generated MVP code from tools like Lovable, Bolt, V0, etc. Analyze code quality, transform patterns, and import into production structure.',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
action: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
enum: ['init', 'analyze', 'import', 'list', 'show', 'extract'],
|
|
16
|
+
description: 'Action to perform: init (create folders), analyze (assess code quality), import (transform and import), list (show available files), show (display specific file), extract (extract patterns)'
|
|
17
|
+
},
|
|
18
|
+
mode: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
enum: ['copy', 'inspire', 'hybrid'],
|
|
21
|
+
description: 'Import mode: copy (direct copy), inspire (extract patterns only), hybrid (copy + refactor)'
|
|
22
|
+
},
|
|
23
|
+
file: {
|
|
24
|
+
type: 'string',
|
|
25
|
+
description: 'File path for show action'
|
|
26
|
+
},
|
|
27
|
+
category: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
enum: ['components', 'hooks', 'services', 'utilities', 'pages', 'all'],
|
|
30
|
+
description: 'Category filter for list/extract actions'
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
required: ['action']
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createHandler({ mvpModule, config: _config, format }) {
|
|
39
|
+
return async (args) => {
|
|
40
|
+
const { action, mode, file, category } = args;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
switch (action) {
|
|
44
|
+
case 'init':
|
|
45
|
+
return handleInit(mvpModule, format);
|
|
46
|
+
|
|
47
|
+
case 'analyze':
|
|
48
|
+
return handleAnalyze(mvpModule, format);
|
|
49
|
+
|
|
50
|
+
case 'import':
|
|
51
|
+
return handleImport(mvpModule, mode || 'hybrid', format);
|
|
52
|
+
|
|
53
|
+
case 'list':
|
|
54
|
+
return handleList(mvpModule, category || 'all', format);
|
|
55
|
+
|
|
56
|
+
case 'show':
|
|
57
|
+
return handleShow(mvpModule, file, format);
|
|
58
|
+
|
|
59
|
+
case 'extract':
|
|
60
|
+
return handleExtract(mvpModule, category || 'all', format);
|
|
61
|
+
|
|
62
|
+
default:
|
|
63
|
+
return format.error(`Unknown action: ${action}`, [
|
|
64
|
+
'Valid actions: init, analyze, import, list, show, extract'
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
return format.error(`MVP operation failed: ${error.message}`);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function handleInit(mvpModule, format) {
|
|
74
|
+
try {
|
|
75
|
+
mvpModule.initMvpFolders();
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
content: [{
|
|
79
|
+
type: 'text',
|
|
80
|
+
text: `## MVP Folders Created
|
|
81
|
+
|
|
82
|
+
| Folder | Purpose |
|
|
83
|
+
|--------|---------|
|
|
84
|
+
| \`${mvpModule.MVP_STRUCTURE.source}/\` | Put your MVP code here |
|
|
85
|
+
| \`${mvpModule.MVP_STRUCTURE.references}/\` | Reference files kept here |
|
|
86
|
+
|
|
87
|
+
**Next Steps:**
|
|
88
|
+
1. Copy your AI-generated MVP code to \`${mvpModule.MVP_STRUCTURE.source}/\`
|
|
89
|
+
2. Run \`bootspring_mvp\` with action: "analyze" to analyze the code
|
|
90
|
+
3. Run \`bootspring_mvp\` with action: "import" to import with transformations`
|
|
91
|
+
}]
|
|
92
|
+
};
|
|
93
|
+
} catch (error) {
|
|
94
|
+
return format.error(`Failed to initialize: ${error.message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function handleAnalyze(mvpModule, format) {
|
|
99
|
+
const result = mvpModule.analyzeMvp();
|
|
100
|
+
|
|
101
|
+
if (!result.success) {
|
|
102
|
+
return format.warning(result.error, [
|
|
103
|
+
'Run bootspring_mvp with action: "init" to create the folder structure'
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const { analysis } = result;
|
|
108
|
+
|
|
109
|
+
// Build issue summary
|
|
110
|
+
const issueCount = {};
|
|
111
|
+
for (const file of analysis.files) {
|
|
112
|
+
for (const issue of file.issues) {
|
|
113
|
+
issueCount[issue.type] = (issueCount[issue.type] || 0) + 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const topIssues = Object.entries(issueCount)
|
|
118
|
+
.sort((a, b) => b[1] - a[1])
|
|
119
|
+
.slice(0, 5)
|
|
120
|
+
.map(([type, count]) => `- ${type}: ${count} occurrences`)
|
|
121
|
+
.join('\n');
|
|
122
|
+
|
|
123
|
+
// Find low quality files
|
|
124
|
+
const lowQualityFiles = analysis.files
|
|
125
|
+
.filter(f => f.quality < 70)
|
|
126
|
+
.sort((a, b) => a.quality - b.quality)
|
|
127
|
+
.slice(0, 5)
|
|
128
|
+
.map(f => `- ${f.path}: ${f.quality}%`)
|
|
129
|
+
.join('\n');
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
content: [{
|
|
133
|
+
type: 'text',
|
|
134
|
+
text: `## MVP Analysis
|
|
135
|
+
|
|
136
|
+
**Summary:**
|
|
137
|
+
| Metric | Value |
|
|
138
|
+
|--------|-------|
|
|
139
|
+
| Total Files | ${analysis.totalFiles} |
|
|
140
|
+
| Average Quality | ${analysis.summary.averageQuality}% |
|
|
141
|
+
| Total Issues | ${analysis.summary.totalIssues} |
|
|
142
|
+
| Critical Issues | ${analysis.summary.criticalIssues} |
|
|
143
|
+
| Suggested Mode | ${analysis.summary.suggestedMode} |
|
|
144
|
+
|
|
145
|
+
**File Categories:**
|
|
146
|
+
${Object.entries(analysis.categories).map(([_cat, data]) => `- ${data.description}: ${data.count} files`).join('\n')}
|
|
147
|
+
|
|
148
|
+
**Top Issues:**
|
|
149
|
+
${topIssues || 'None detected'}
|
|
150
|
+
|
|
151
|
+
**Lowest Quality Files:**
|
|
152
|
+
${lowQualityFiles || 'All files above 70%'}
|
|
153
|
+
|
|
154
|
+
**Recommended Mode:** \`${analysis.summary.suggestedMode}\`
|
|
155
|
+
- **copy**: Direct copy, minimal changes
|
|
156
|
+
- **inspire**: Extract patterns only
|
|
157
|
+
- **hybrid**: Copy with refactoring (recommended for most cases)`
|
|
158
|
+
}]
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function handleImport(mvpModule, mode, format) {
|
|
163
|
+
const result = mvpModule.importMvp({ mode });
|
|
164
|
+
|
|
165
|
+
if (!result.success) {
|
|
166
|
+
return format.error(result.error);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const { imported, skipped, transformed, referencesMoved } = result;
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
content: [{
|
|
173
|
+
type: 'text',
|
|
174
|
+
text: `## MVP Import Complete
|
|
175
|
+
|
|
176
|
+
**Mode:** ${mode}
|
|
177
|
+
|
|
178
|
+
| Metric | Count |
|
|
179
|
+
|--------|-------|
|
|
180
|
+
| Files Imported | ${imported} |
|
|
181
|
+
| Files Skipped | ${skipped} |
|
|
182
|
+
| Files Transformed | ${transformed} |
|
|
183
|
+
| References Moved | ${referencesMoved} |
|
|
184
|
+
|
|
185
|
+
**Import Summary:**
|
|
186
|
+
${result.summary || 'MVP code has been imported into your project structure.'}
|
|
187
|
+
|
|
188
|
+
**Next Steps:**
|
|
189
|
+
1. Review imported files in your project
|
|
190
|
+
2. Run \`npm install\` if new dependencies were added
|
|
191
|
+
3. Check for any TODO comments added during transformation`
|
|
192
|
+
}]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function handleList(mvpModule, category, format) {
|
|
197
|
+
const result = mvpModule.analyzeMvp();
|
|
198
|
+
|
|
199
|
+
if (!result.success) {
|
|
200
|
+
return format.warning(result.error, [
|
|
201
|
+
'Run bootspring_mvp with action: "init" to create the folder structure'
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const { analysis } = result;
|
|
206
|
+
|
|
207
|
+
let files = analysis.files;
|
|
208
|
+
if (category !== 'all') {
|
|
209
|
+
files = files.filter(f => f.category === category);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const fileList = files
|
|
213
|
+
.slice(0, 30)
|
|
214
|
+
.map(f => `- \`${f.path}\` (${f.category}, ${f.quality}%)`)
|
|
215
|
+
.join('\n');
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
content: [{
|
|
219
|
+
type: 'text',
|
|
220
|
+
text: `## MVP Files ${category !== 'all' ? `(${category})` : ''}
|
|
221
|
+
|
|
222
|
+
**Total:** ${files.length} files
|
|
223
|
+
|
|
224
|
+
${fileList}
|
|
225
|
+
${files.length > 30 ? `\n... and ${files.length - 30} more files` : ''}
|
|
226
|
+
|
|
227
|
+
**Categories:**
|
|
228
|
+
${Object.entries(analysis.categories).map(([cat, data]) => `- ${cat}: ${data.count} files`).join('\n')}`
|
|
229
|
+
}]
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function handleShow(mvpModule, filePath, format) {
|
|
234
|
+
if (!filePath) {
|
|
235
|
+
return format.error('File path required', [
|
|
236
|
+
'Use file parameter to specify which file to show'
|
|
237
|
+
]);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const fs = require('fs');
|
|
241
|
+
const path = require('path');
|
|
242
|
+
|
|
243
|
+
const result = mvpModule.analyzeMvp();
|
|
244
|
+
if (!result.success) {
|
|
245
|
+
return format.error(result.error);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Find the file in the MVP source
|
|
249
|
+
const mvpSource = mvpModule.MVP_STRUCTURE.source;
|
|
250
|
+
const fullPath = path.join(process.cwd(), mvpSource, filePath);
|
|
251
|
+
|
|
252
|
+
if (!fs.existsSync(fullPath)) {
|
|
253
|
+
return format.error(`File not found: ${filePath}`, [
|
|
254
|
+
`File should be in ${mvpSource}/`
|
|
255
|
+
]);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
259
|
+
const ext = path.extname(filePath).slice(1) || 'text';
|
|
260
|
+
|
|
261
|
+
// Get analysis for this specific file
|
|
262
|
+
const fileAnalysis = result.analysis.files.find(f => f.path === filePath);
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
content: [{
|
|
266
|
+
type: 'text',
|
|
267
|
+
text: `## ${filePath}
|
|
268
|
+
|
|
269
|
+
${fileAnalysis ? `**Quality:** ${fileAnalysis.quality}% | **Category:** ${fileAnalysis.category} | **Issues:** ${fileAnalysis.issues.length}` : ''}
|
|
270
|
+
|
|
271
|
+
\`\`\`${ext}
|
|
272
|
+
${content.slice(0, 5000)}${content.length > 5000 ? '\n... (truncated)' : ''}
|
|
273
|
+
\`\`\`
|
|
274
|
+
|
|
275
|
+
${fileAnalysis && fileAnalysis.issues.length > 0 ? `**Issues:**\n${fileAnalysis.issues.map(i => `- ${i.type}: ${i.message}`).join('\n')}` : ''}`
|
|
276
|
+
}]
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function handleExtract(mvpModule, category, format) {
|
|
281
|
+
const result = mvpModule.analyzeMvp();
|
|
282
|
+
|
|
283
|
+
if (!result.success) {
|
|
284
|
+
return format.warning(result.error);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const { analysis } = result;
|
|
288
|
+
|
|
289
|
+
// Extract patterns based on category
|
|
290
|
+
const patterns = {
|
|
291
|
+
components: [],
|
|
292
|
+
hooks: [],
|
|
293
|
+
services: [],
|
|
294
|
+
utilities: [],
|
|
295
|
+
pages: []
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
for (const file of analysis.files) {
|
|
299
|
+
if (category !== 'all' && file.category !== category) continue;
|
|
300
|
+
|
|
301
|
+
const pattern = {
|
|
302
|
+
file: file.path,
|
|
303
|
+
category: file.category,
|
|
304
|
+
exports: file.exports || [],
|
|
305
|
+
dependencies: file.dependencies || [],
|
|
306
|
+
quality: file.quality
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
if (patterns[file.category]) {
|
|
310
|
+
patterns[file.category].push(pattern);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const patternSummary = Object.entries(patterns)
|
|
315
|
+
.filter(([_, items]) => items.length > 0)
|
|
316
|
+
.map(([cat, items]) => {
|
|
317
|
+
const topItems = items
|
|
318
|
+
.sort((a, b) => b.quality - a.quality)
|
|
319
|
+
.slice(0, 5);
|
|
320
|
+
return `### ${cat.charAt(0).toUpperCase() + cat.slice(1)}
|
|
321
|
+
|
|
322
|
+
${topItems.map(p => `- \`${p.file}\` (${p.quality}%)`).join('\n')}`;
|
|
323
|
+
})
|
|
324
|
+
.join('\n\n');
|
|
325
|
+
|
|
326
|
+
return {
|
|
327
|
+
content: [{
|
|
328
|
+
type: 'text',
|
|
329
|
+
text: `## Extracted Patterns ${category !== 'all' ? `(${category})` : ''}
|
|
330
|
+
|
|
331
|
+
${patternSummary || 'No patterns found'}
|
|
332
|
+
|
|
333
|
+
**Summary:**
|
|
334
|
+
${Object.entries(patterns).map(([cat, items]) => `- ${cat}: ${items.length} patterns`).join('\n')}
|
|
335
|
+
|
|
336
|
+
Use these patterns to understand the MVP's architecture and inform your production implementation.`
|
|
337
|
+
}]
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
module.exports = {
|
|
342
|
+
getToolDefinition,
|
|
343
|
+
createHandler
|
|
344
|
+
};
|
package/mcp/tools/plugin-tool.js
CHANGED
|
@@ -57,10 +57,32 @@ function createHandler({ config }) {
|
|
|
57
57
|
case 'enable':
|
|
58
58
|
case 'disable': {
|
|
59
59
|
if (!name) throw new Error('Plugin name required');
|
|
60
|
+
|
|
61
|
+
// Initialize plugins object if it doesn't exist
|
|
62
|
+
if (!cfg.plugins) {
|
|
63
|
+
cfg.plugins = {};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Initialize the plugin if it doesn't exist
|
|
67
|
+
if (!cfg.plugins[name]) {
|
|
68
|
+
cfg.plugins[name] = { enabled: false };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Update the enabled state
|
|
72
|
+
const isEnabled = action === 'enable';
|
|
73
|
+
cfg.plugins[name].enabled = isEnabled;
|
|
74
|
+
|
|
75
|
+
// Persist the configuration
|
|
76
|
+
const saved = config.save(cfg);
|
|
77
|
+
|
|
78
|
+
if (!saved) {
|
|
79
|
+
throw new Error(`Failed to save configuration after ${action}ing plugin ${name}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
60
82
|
return {
|
|
61
83
|
content: [{
|
|
62
84
|
type: 'text',
|
|
63
|
-
text: `Plugin ${name} ${action}d
|
|
85
|
+
text: `Plugin '${name}' has been ${action}d and saved to configuration.`
|
|
64
86
|
}]
|
|
65
87
|
};
|
|
66
88
|
}
|