scanwarp 0.2.0 → 0.3.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.
@@ -14,6 +14,7 @@ const stripe_js_1 = require("../integrations/stripe.js");
14
14
  const supabase_js_1 = require("../integrations/supabase.js");
15
15
  const mcp_js_1 = require("../integrations/mcp.js");
16
16
  const notifications_js_1 = require("../integrations/notifications.js");
17
+ const instrument_js_1 = require("../integrations/instrument.js");
17
18
  const config_js_1 = require("../config.js");
18
19
  async function initCommand(options = {}) {
19
20
  console.log(chalk_1.default.bold.cyan('\nšŸš€ ScanWarp Setup\n'));
@@ -79,8 +80,22 @@ async function initCommand(options = {}) {
79
80
  const isConnected = await api.testConnection();
80
81
  if (!isConnected) {
81
82
  serverSpinner.fail(`Could not connect to ${serverUrl}`);
82
- console.log(chalk_1.default.yellow('\n⚠ Make sure the ScanWarp server is running.'));
83
- console.log(chalk_1.default.gray(' Run: cd apps/server && pnpm dev\n'));
83
+ if (options.server) {
84
+ // User explicitly provided a server URL — just report the error
85
+ console.log(chalk_1.default.yellow('\n⚠ Make sure the ScanWarp server is running at that URL.\n'));
86
+ }
87
+ else {
88
+ // No server specified, localhost failed — suggest deployment options
89
+ console.log(chalk_1.default.yellow('\n⚠ No local ScanWarp server found.\n'));
90
+ console.log(chalk_1.default.bold(' Option 1: Deploy a hosted server (60 seconds)\n'));
91
+ console.log(chalk_1.default.gray(' Railway: https://railway.com/template/scanwarp'));
92
+ console.log(chalk_1.default.gray(' Render: https://render.com/deploy?repo=https://github.com/scanwarp/scanwarp\n'));
93
+ console.log(chalk_1.default.gray(' Then run:'));
94
+ console.log(chalk_1.default.white(' npx scanwarp init --server https://your-server-url.up.railway.app\n'));
95
+ console.log(chalk_1.default.bold(' Option 2: Run locally\n'));
96
+ console.log(chalk_1.default.gray(' docker compose up -d'));
97
+ console.log(chalk_1.default.gray(' npx scanwarp init --server http://localhost:3000\n'));
98
+ }
84
99
  process.exit(1);
85
100
  }
86
101
  serverSpinner.succeed(`Connected to ${serverUrl}`);
@@ -88,9 +103,11 @@ async function initCommand(options = {}) {
88
103
  config_js_1.config.setServerUrl(serverUrl);
89
104
  // Step 4: Create project and monitor
90
105
  const setupSpinner = (0, ora_1.default)('Setting up monitoring...').start();
106
+ let projectId;
91
107
  try {
92
108
  const project = await api.createProject(detected.projectName || 'my-app');
93
109
  const monitor = await api.createMonitor(project.id, productionUrl);
110
+ projectId = project.id;
94
111
  // Save project ID to config
95
112
  config_js_1.config.setProjectId(project.id);
96
113
  setupSpinner.succeed('Monitoring configured');
@@ -117,15 +134,20 @@ async function initCommand(options = {}) {
117
134
  if (detected.services.includes('Supabase')) {
118
135
  await (0, supabase_js_1.setupSupabase)(api);
119
136
  }
120
- // Step 6: MCP Configuration
137
+ // Step 6: Request Tracing
138
+ if (!options.skipInstrumentation) {
139
+ console.log(chalk_1.default.bold('\nšŸ“” Request Tracing\n'));
140
+ await (0, instrument_js_1.setupInstrumentation)(detected, serverUrl, projectId, false); // false = don't prompt, install by default
141
+ }
142
+ // Step 7: MCP Configuration
121
143
  if (!options.skipMcp) {
122
144
  console.log(chalk_1.default.bold('\nšŸ¤– MCP Configuration\n'));
123
145
  await (0, mcp_js_1.setupMCP)(serverUrl);
124
146
  }
125
- // Step 7: Notifications
147
+ // Step 8: Notifications
126
148
  console.log(chalk_1.default.bold('\nšŸ”” Notifications\n'));
127
149
  await (0, notifications_js_1.setupNotifications)();
128
- // Step 8: Summary
150
+ // Step 9: Summary
129
151
  printSummary(api, productionUrl, detected);
130
152
  }
131
153
  function printSummary(api, url, _detected) {
@@ -0,0 +1,380 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Production MCP server command
5
+ * Connects to a running ScanWarp server and exposes monitoring data to AI coding tools
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.mcpCommand = mcpCommand;
9
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
10
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
11
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
12
+ const api_js_1 = require("../mcp/api.js");
13
+ const tools_js_1 = require("../mcp/tools.js");
14
+ async function mcpCommand(options = {}) {
15
+ const serverUrl = options.server || process.env.SCANWARP_SERVER_URL || 'http://localhost:3000';
16
+ const apiToken = options.token || process.env.SCANWARP_API_TOKEN;
17
+ const projectId = options.project || process.env.SCANWARP_PROJECT_ID;
18
+ // Initialize API client
19
+ const api = new api_js_1.ScanWarpAPI(serverUrl, apiToken);
20
+ // Create MCP server
21
+ const server = new index_js_1.Server({
22
+ name: 'scanwarp',
23
+ version: '0.3.0',
24
+ }, {
25
+ capabilities: {
26
+ tools: {},
27
+ resources: {},
28
+ },
29
+ });
30
+ // List available tools
31
+ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
32
+ return {
33
+ tools: [
34
+ {
35
+ name: 'get_app_status',
36
+ description: 'Get overall health status of your application. Shows monitor status, active incidents, and provider health. Returns a concise plain English summary.',
37
+ inputSchema: {
38
+ type: 'object',
39
+ properties: {
40
+ project_id: {
41
+ type: 'string',
42
+ description: 'Project ID to check status for',
43
+ },
44
+ },
45
+ required: ['project_id'],
46
+ },
47
+ },
48
+ {
49
+ name: 'get_incidents',
50
+ description: 'List incidents with their diagnosis and fix suggestions. Can filter by status (open/resolved) and severity (critical/warning/info).',
51
+ inputSchema: {
52
+ type: 'object',
53
+ properties: {
54
+ project_id: {
55
+ type: 'string',
56
+ description: 'Project ID',
57
+ },
58
+ status: {
59
+ type: 'string',
60
+ description: 'Filter by status: open or resolved',
61
+ enum: ['open', 'resolved'],
62
+ },
63
+ severity: {
64
+ type: 'string',
65
+ description: 'Filter by severity: critical, warning, or info',
66
+ enum: ['critical', 'warning', 'info'],
67
+ },
68
+ limit: {
69
+ type: 'number',
70
+ description: 'Maximum number of incidents to return (default: 10)',
71
+ },
72
+ },
73
+ required: ['project_id'],
74
+ },
75
+ },
76
+ {
77
+ name: 'get_incident_detail',
78
+ description: 'Get full details for a specific incident including root cause, timeline, correlated events, and the complete fix prompt ready to use.',
79
+ inputSchema: {
80
+ type: 'object',
81
+ properties: {
82
+ incident_id: {
83
+ type: 'string',
84
+ description: 'Incident ID',
85
+ },
86
+ },
87
+ required: ['incident_id'],
88
+ },
89
+ },
90
+ {
91
+ name: 'get_events',
92
+ description: 'Get recent events with optional filters. Useful for understanding patterns and troubleshooting.',
93
+ inputSchema: {
94
+ type: 'object',
95
+ properties: {
96
+ project_id: {
97
+ type: 'string',
98
+ description: 'Project ID',
99
+ },
100
+ type: {
101
+ type: 'string',
102
+ description: 'Filter by event type (error, slow, down, up)',
103
+ },
104
+ source: {
105
+ type: 'string',
106
+ description: 'Filter by source (monitor, vercel, stripe, github, supabase, provider-status)',
107
+ },
108
+ severity: {
109
+ type: 'string',
110
+ description: 'Filter by severity (critical, high, medium, low)',
111
+ },
112
+ limit: {
113
+ type: 'number',
114
+ description: 'Maximum number of events to return (default: 20)',
115
+ },
116
+ },
117
+ required: ['project_id'],
118
+ },
119
+ },
120
+ {
121
+ name: 'resolve_incident',
122
+ description: 'Mark an incident as resolved. This will trigger resolution notifications to configured channels.',
123
+ inputSchema: {
124
+ type: 'object',
125
+ properties: {
126
+ incident_id: {
127
+ type: 'string',
128
+ description: 'Incident ID to resolve',
129
+ },
130
+ },
131
+ required: ['incident_id'],
132
+ },
133
+ },
134
+ {
135
+ name: 'get_fix_prompt',
136
+ description: 'Get JUST the fix prompt for an incident - the exact text that can be used in an AI coding tool to fix the issue.',
137
+ inputSchema: {
138
+ type: 'object',
139
+ properties: {
140
+ incident_id: {
141
+ type: 'string',
142
+ description: 'Incident ID',
143
+ },
144
+ },
145
+ required: ['incident_id'],
146
+ },
147
+ },
148
+ {
149
+ name: 'get_recent_traces',
150
+ description: 'List recent request traces from OpenTelemetry instrumentation. Shows root spans with summary info (duration, span count, error status). Filter by status to find failing requests.',
151
+ inputSchema: {
152
+ type: 'object',
153
+ properties: {
154
+ project_id: {
155
+ type: 'string',
156
+ description: 'Project ID',
157
+ },
158
+ limit: {
159
+ type: 'number',
160
+ description: 'Maximum number of traces to return (default: 10)',
161
+ },
162
+ status: {
163
+ type: 'string',
164
+ description: 'Filter by trace status: "error" for traces with errors, "ok" for successful traces',
165
+ enum: ['error', 'ok'],
166
+ },
167
+ },
168
+ required: ['project_id'],
169
+ },
170
+ },
171
+ {
172
+ name: 'get_trace_detail',
173
+ description: 'Get the full request waterfall for a specific trace. Shows all spans as an indented tree with timing, status, and error details. Use this to understand exactly what happened during a request.',
174
+ inputSchema: {
175
+ type: 'object',
176
+ properties: {
177
+ trace_id: {
178
+ type: 'string',
179
+ description: 'Trace ID to inspect',
180
+ },
181
+ },
182
+ required: ['trace_id'],
183
+ },
184
+ },
185
+ {
186
+ name: 'get_trace_for_incident',
187
+ description: 'Get the most relevant trace data for an incident, combined with the AI diagnosis and fix prompt. Shows the request waterfall alongside the root cause analysis.',
188
+ inputSchema: {
189
+ type: 'object',
190
+ properties: {
191
+ incident_id: {
192
+ type: 'string',
193
+ description: 'Incident ID',
194
+ },
195
+ },
196
+ required: ['incident_id'],
197
+ },
198
+ },
199
+ ],
200
+ };
201
+ });
202
+ // Handle tool calls
203
+ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
204
+ const { name, arguments: args } = request.params;
205
+ try {
206
+ switch (name) {
207
+ case 'get_app_status': {
208
+ const projectId = args.project_id;
209
+ const result = await (0, tools_js_1.getAppStatus)(api, projectId);
210
+ return {
211
+ content: [{ type: 'text', text: result }],
212
+ };
213
+ }
214
+ case 'get_incidents': {
215
+ const { project_id, status, severity, limit } = args;
216
+ const result = await (0, tools_js_1.getIncidents)(api, project_id, {
217
+ status,
218
+ severity,
219
+ limit,
220
+ });
221
+ return {
222
+ content: [{ type: 'text', text: result }],
223
+ };
224
+ }
225
+ case 'get_incident_detail': {
226
+ const { incident_id } = args;
227
+ const result = await (0, tools_js_1.getIncidentDetail)(api, incident_id);
228
+ return {
229
+ content: [{ type: 'text', text: result }],
230
+ };
231
+ }
232
+ case 'get_events': {
233
+ const { project_id, type, source, severity, limit } = args;
234
+ const result = await (0, tools_js_1.getEvents)(api, project_id, {
235
+ type,
236
+ source,
237
+ severity,
238
+ limit,
239
+ });
240
+ return {
241
+ content: [{ type: 'text', text: result }],
242
+ };
243
+ }
244
+ case 'resolve_incident': {
245
+ const { incident_id } = args;
246
+ const result = await (0, tools_js_1.resolveIncident)(api, incident_id);
247
+ return {
248
+ content: [{ type: 'text', text: result }],
249
+ };
250
+ }
251
+ case 'get_fix_prompt': {
252
+ const { incident_id } = args;
253
+ const result = await (0, tools_js_1.getFixPrompt)(api, incident_id);
254
+ return {
255
+ content: [{ type: 'text', text: result }],
256
+ };
257
+ }
258
+ case 'get_recent_traces': {
259
+ const { project_id, limit, status } = args;
260
+ const result = await (0, tools_js_1.getRecentTraces)(api, project_id, { limit, status });
261
+ return {
262
+ content: [{ type: 'text', text: result }],
263
+ };
264
+ }
265
+ case 'get_trace_detail': {
266
+ const { trace_id } = args;
267
+ const result = await (0, tools_js_1.getTraceDetail)(api, trace_id);
268
+ return {
269
+ content: [{ type: 'text', text: result }],
270
+ };
271
+ }
272
+ case 'get_trace_for_incident': {
273
+ const { incident_id } = args;
274
+ const result = await (0, tools_js_1.getTraceForIncident)(api, incident_id);
275
+ return {
276
+ content: [{ type: 'text', text: result }],
277
+ };
278
+ }
279
+ default:
280
+ throw new Error(`Unknown tool: ${name}`);
281
+ }
282
+ }
283
+ catch (error) {
284
+ const errorMessage = error instanceof Error ? error.message : String(error);
285
+ return {
286
+ content: [{ type: 'text', text: `Error: ${errorMessage}` }],
287
+ isError: true,
288
+ };
289
+ }
290
+ });
291
+ // List resources
292
+ server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => {
293
+ if (!projectId) {
294
+ return { resources: [] };
295
+ }
296
+ return {
297
+ resources: [
298
+ {
299
+ uri: 'scanwarp://status',
300
+ name: 'Application Status',
301
+ description: 'Current health status of your application including monitors, incidents, and provider status',
302
+ mimeType: 'text/plain',
303
+ },
304
+ {
305
+ uri: 'scanwarp://incidents',
306
+ name: 'Active Incidents',
307
+ description: 'List of currently active incidents requiring attention',
308
+ mimeType: 'text/plain',
309
+ },
310
+ ],
311
+ };
312
+ });
313
+ // Handle resource reads
314
+ server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
315
+ const { uri } = request.params;
316
+ if (!projectId) {
317
+ return {
318
+ contents: [
319
+ {
320
+ uri,
321
+ mimeType: 'text/plain',
322
+ text: 'Error: SCANWARP_PROJECT_ID not configured. Pass --project or set SCANWARP_PROJECT_ID env var.',
323
+ },
324
+ ],
325
+ };
326
+ }
327
+ try {
328
+ switch (uri) {
329
+ case 'scanwarp://status': {
330
+ const status = await (0, tools_js_1.getAppStatus)(api, projectId);
331
+ return {
332
+ contents: [
333
+ {
334
+ uri,
335
+ mimeType: 'text/plain',
336
+ text: status,
337
+ },
338
+ ],
339
+ };
340
+ }
341
+ case 'scanwarp://incidents': {
342
+ const incidents = await (0, tools_js_1.getIncidents)(api, projectId, {
343
+ status: 'open',
344
+ });
345
+ return {
346
+ contents: [
347
+ {
348
+ uri,
349
+ mimeType: 'text/plain',
350
+ text: incidents,
351
+ },
352
+ ],
353
+ };
354
+ }
355
+ default:
356
+ throw new Error(`Unknown resource: ${uri}`);
357
+ }
358
+ }
359
+ catch (error) {
360
+ const errorMessage = error instanceof Error ? error.message : String(error);
361
+ return {
362
+ contents: [
363
+ {
364
+ uri,
365
+ mimeType: 'text/plain',
366
+ text: `Error: ${errorMessage}`,
367
+ },
368
+ ],
369
+ };
370
+ }
371
+ });
372
+ // Start the server
373
+ const transport = new stdio_js_1.StdioServerTransport();
374
+ await server.connect(transport);
375
+ console.error('ScanWarp MCP server running on stdio');
376
+ console.error(`Connected to: ${serverUrl}`);
377
+ if (projectId) {
378
+ console.error(`Default project: ${projectId}`);
379
+ }
380
+ }
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.serverCommand = serverCommand;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const os_1 = __importDefault(require("os"));
11
+ const child_process_1 = require("child_process");
12
+ async function serverCommand(options) {
13
+ const port = options.port || 3000;
14
+ // Determine SQLite database path
15
+ const scanwarpDir = path_1.default.join(os_1.default.homedir(), '.scanwarp');
16
+ if (!fs_1.default.existsSync(scanwarpDir)) {
17
+ fs_1.default.mkdirSync(scanwarpDir, { recursive: true });
18
+ }
19
+ const dbPath = options.dbPath || path_1.default.join(scanwarpDir, 'scanwarp.db');
20
+ // Find the server entry point
21
+ const serverEntry = findServerEntry();
22
+ if (!serverEntry) {
23
+ console.error(chalk_1.default.red('Could not find @scanwarp/server. Make sure you are running from the ScanWarp repo or have @scanwarp/server installed.'));
24
+ process.exit(1);
25
+ }
26
+ console.log('');
27
+ console.log(chalk_1.default.bold('ScanWarp Server'));
28
+ console.log('');
29
+ console.log(` ${chalk_1.default.dim('Port:')} ${port}`);
30
+ console.log(` ${chalk_1.default.dim('Database:')} SQLite → ${dbPath}`);
31
+ console.log(` ${chalk_1.default.dim('Entry:')} ${serverEntry}`);
32
+ console.log('');
33
+ // Spawn the server process with SQLite config
34
+ const env = {
35
+ ...process.env,
36
+ DATABASE_TYPE: 'sqlite',
37
+ SQLITE_PATH: dbPath,
38
+ PORT: String(port),
39
+ };
40
+ const child = (0, child_process_1.spawn)(process.execPath, [serverEntry], {
41
+ env,
42
+ stdio: 'inherit',
43
+ cwd: path_1.default.dirname(serverEntry),
44
+ });
45
+ child.on('error', (err) => {
46
+ console.error(chalk_1.default.red(`Failed to start server: ${err.message}`));
47
+ process.exit(1);
48
+ });
49
+ child.on('exit', (code) => {
50
+ if (code !== null && code !== 0) {
51
+ console.error(chalk_1.default.red(`Server exited with code ${code}`));
52
+ }
53
+ process.exit(code ?? 0);
54
+ });
55
+ // Forward signals for graceful shutdown
56
+ const signals = ['SIGINT', 'SIGTERM'];
57
+ for (const sig of signals) {
58
+ process.on(sig, () => {
59
+ child.kill(sig);
60
+ });
61
+ }
62
+ }
63
+ function findServerEntry() {
64
+ // Strategy 1: Monorepo sibling (packages/cli → apps/server)
65
+ const cliDir = path_1.default.resolve(__dirname, '..', '..');
66
+ const monorepoServer = path_1.default.resolve(cliDir, '..', '..', 'apps', 'server', 'dist', 'index.js');
67
+ if (fs_1.default.existsSync(monorepoServer)) {
68
+ return monorepoServer;
69
+ }
70
+ // Strategy 2: Check for tsx + source (dev mode)
71
+ const monorepoServerSrc = path_1.default.resolve(cliDir, '..', '..', 'apps', 'server', 'src', 'index.ts');
72
+ if (fs_1.default.existsSync(monorepoServerSrc)) {
73
+ // Check if tsx is available
74
+ const tsxPath = path_1.default.resolve(cliDir, '..', '..', 'node_modules', '.bin', 'tsx');
75
+ if (fs_1.default.existsSync(tsxPath)) {
76
+ return monorepoServerSrc;
77
+ }
78
+ }
79
+ // Strategy 3: node_modules (installed as dependency)
80
+ try {
81
+ const resolved = require.resolve('@scanwarp/server');
82
+ if (fs_1.default.existsSync(resolved)) {
83
+ return resolved;
84
+ }
85
+ }
86
+ catch {
87
+ // Module not found
88
+ }
89
+ // Strategy 4: Walk up looking for node_modules/@scanwarp/server
90
+ let dir = cliDir;
91
+ for (let i = 0; i < 5; i++) {
92
+ const candidate = path_1.default.join(dir, 'node_modules', '@scanwarp', 'server', 'dist', 'index.js');
93
+ if (fs_1.default.existsSync(candidate)) {
94
+ return candidate;
95
+ }
96
+ const parent = path_1.default.dirname(dir);
97
+ if (parent === dir)
98
+ break;
99
+ dir = parent;
100
+ }
101
+ return null;
102
+ }