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.
@@ -0,0 +1,487 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAppStatus = getAppStatus;
4
+ exports.getIncidents = getIncidents;
5
+ exports.getIncidentDetail = getIncidentDetail;
6
+ exports.getEvents = getEvents;
7
+ exports.resolveIncident = resolveIncident;
8
+ exports.getFixPrompt = getFixPrompt;
9
+ exports.getRecentTraces = getRecentTraces;
10
+ exports.getTraceDetail = getTraceDetail;
11
+ exports.getTraceForIncident = getTraceForIncident;
12
+ function formatTimeSince(date) {
13
+ const now = new Date();
14
+ const diffMs = now.getTime() - new Date(date).getTime();
15
+ const diffMins = Math.floor(diffMs / 1000 / 60);
16
+ if (diffMins < 1)
17
+ return 'just now';
18
+ if (diffMins === 1)
19
+ return '1 minute ago';
20
+ if (diffMins < 60)
21
+ return `${diffMins} minutes ago`;
22
+ const diffHours = Math.floor(diffMins / 60);
23
+ if (diffHours === 1)
24
+ return '1 hour ago';
25
+ if (diffHours < 24)
26
+ return `${diffHours} hours ago`;
27
+ const diffDays = Math.floor(diffHours / 24);
28
+ if (diffDays === 1)
29
+ return '1 day ago';
30
+ return `${diffDays} days ago`;
31
+ }
32
+ async function getAppStatus(api, projectId) {
33
+ try {
34
+ // Fetch all relevant data
35
+ const [monitors, incidents, providerStatus] = await Promise.all([
36
+ api.getMonitors(projectId),
37
+ api.getIncidents({ projectId, status: 'open' }),
38
+ api.getProviderStatus().catch(() => []),
39
+ ]);
40
+ // Check for active incidents
41
+ if (incidents.length > 0) {
42
+ const criticalIncidents = incidents.filter((i) => i.severity === 'critical');
43
+ const warningIncidents = incidents.filter((i) => i.severity === 'warning');
44
+ let summary = `⚠️ ${incidents.length} active incident${incidents.length > 1 ? 's' : ''}. `;
45
+ if (criticalIncidents.length > 0) {
46
+ const incident = criticalIncidents[0];
47
+ const timeSince = formatTimeSince(incident.created_at);
48
+ summary += `CRITICAL: ${incident.diagnosis_text || 'Investigating issue'} (started ${timeSince}). `;
49
+ }
50
+ else if (warningIncidents.length > 0) {
51
+ const incident = warningIncidents[0];
52
+ const timeSince = formatTimeSince(incident.created_at);
53
+ summary += `${incident.diagnosis_text || 'Investigating issue'} (started ${timeSince}). `;
54
+ }
55
+ if (incidents.length > 1) {
56
+ summary += `Plus ${incidents.length - 1} other incident${incidents.length - 1 > 1 ? 's' : ''}. `;
57
+ }
58
+ summary += `\n\nUse get_incident_detail to see full diagnosis and fix prompts.`;
59
+ return summary;
60
+ }
61
+ // No incidents - check monitor health
62
+ const downMonitors = monitors.filter((m) => m.status === 'down');
63
+ if (downMonitors.length > 0) {
64
+ return `⚠️ ${downMonitors.length} monitor${downMonitors.length > 1 ? 's' : ''} down: ${downMonitors.map((m) => m.url).join(', ')}. No incidents created yet.`;
65
+ }
66
+ // Check provider status
67
+ const degradedProviders = providerStatus.filter((p) => p.status !== 'operational');
68
+ let providerMessage = '';
69
+ if (degradedProviders.length > 0) {
70
+ providerMessage = ` ${degradedProviders.map((p) => `${p.provider}: ${p.status}`).join(', ')}.`;
71
+ }
72
+ // All good!
73
+ return `✅ Your app is healthy. ${monitors.length} monitor${monitors.length !== 1 ? 's' : ''} all passing. No active incidents.${providerMessage || ' All providers operational.'}`;
74
+ }
75
+ catch (error) {
76
+ if (error instanceof Error) {
77
+ return `❌ Error fetching status: ${error.message}`;
78
+ }
79
+ return `❌ Error fetching status`;
80
+ }
81
+ }
82
+ async function getIncidents(api, projectId, options = {}) {
83
+ try {
84
+ const incidents = await api.getIncidents({
85
+ projectId,
86
+ ...options,
87
+ limit: options.limit || 10,
88
+ });
89
+ if (incidents.length === 0) {
90
+ return options.status === 'resolved'
91
+ ? 'No resolved incidents found.'
92
+ : 'No active incidents. Your app is running smoothly!';
93
+ }
94
+ let output = `Found ${incidents.length} incident${incidents.length !== 1 ? 's' : ''}:\n\n`;
95
+ for (const incident of incidents) {
96
+ const timeSince = formatTimeSince(incident.created_at);
97
+ const statusEmoji = incident.status === 'resolved'
98
+ ? '✅'
99
+ : incident.severity === 'critical'
100
+ ? '🔴'
101
+ : incident.severity === 'warning'
102
+ ? '🟡'
103
+ : '🔵';
104
+ output += `${statusEmoji} Incident #${incident.id.substring(0, 8)}\n`;
105
+ output += ` Severity: ${incident.severity.toUpperCase()}\n`;
106
+ output += ` Status: ${incident.status}\n`;
107
+ output += ` Started: ${timeSince}\n`;
108
+ if (incident.diagnosis_text) {
109
+ output += ` Issue: ${incident.diagnosis_text}\n`;
110
+ }
111
+ if (incident.diagnosis_fix) {
112
+ output += ` Fix: ${incident.diagnosis_fix.substring(0, 150)}${incident.diagnosis_fix.length > 150 ? '...' : ''}\n`;
113
+ }
114
+ if (incident.resolved_at) {
115
+ const duration = Math.floor((new Date(incident.resolved_at).getTime() -
116
+ new Date(incident.created_at).getTime()) /
117
+ 1000 /
118
+ 60);
119
+ output += ` Duration: ${duration} minutes\n`;
120
+ }
121
+ output += `\n`;
122
+ }
123
+ output += `Use get_incident_detail with an incident ID to see full diagnosis and fix prompts.`;
124
+ return output;
125
+ }
126
+ catch (error) {
127
+ if (error instanceof Error) {
128
+ return `❌ Error fetching incidents: ${error.message}`;
129
+ }
130
+ return `❌ Error fetching incidents`;
131
+ }
132
+ }
133
+ async function getIncidentDetail(api, incidentId) {
134
+ try {
135
+ const { incident, events } = await api.getIncident(incidentId);
136
+ const timeSince = formatTimeSince(incident.created_at);
137
+ const statusEmoji = incident.status === 'resolved'
138
+ ? '✅'
139
+ : incident.severity === 'critical'
140
+ ? '🔴'
141
+ : incident.severity === 'warning'
142
+ ? '🟡'
143
+ : '🔵';
144
+ let output = `${statusEmoji} Incident #${incident.id}\n\n`;
145
+ output += `Severity: ${incident.severity.toUpperCase()}\n`;
146
+ output += `Status: ${incident.status}\n`;
147
+ output += `Started: ${timeSince}\n\n`;
148
+ if (incident.diagnosis_text) {
149
+ output += `ROOT CAUSE:\n${incident.diagnosis_text}\n\n`;
150
+ }
151
+ if (incident.diagnosis_fix) {
152
+ output += `SUGGESTED FIX:\n${incident.diagnosis_fix}\n\n`;
153
+ }
154
+ if (incident.fix_prompt) {
155
+ output += `FIX PROMPT (ready to use in your AI tool):\n${'─'.repeat(50)}\n${incident.fix_prompt}\n${'─'.repeat(50)}\n\n`;
156
+ }
157
+ // Event timeline
158
+ if (events.length > 0) {
159
+ output += `EVENT TIMELINE (${events.length} events):\n`;
160
+ const sortedEvents = [...events].sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime());
161
+ for (const event of sortedEvents.slice(0, 10)) {
162
+ const eventTime = formatTimeSince(event.created_at);
163
+ output += ` • [${event.source}] ${event.type}: ${event.message.substring(0, 100)} (${eventTime})\n`;
164
+ }
165
+ if (events.length > 10) {
166
+ output += ` ... and ${events.length - 10} more events\n`;
167
+ }
168
+ output += `\n`;
169
+ }
170
+ // Trace data
171
+ try {
172
+ const spans = await api.getIncidentTraces(incidentId);
173
+ if (spans.length > 0) {
174
+ output += `TRACE DATA:\n`;
175
+ output += buildTraceWaterfall(spans);
176
+ output += `\n`;
177
+ }
178
+ }
179
+ catch {
180
+ // Trace data is optional — don't fail the whole response
181
+ }
182
+ if (incident.correlation_group) {
183
+ output += `Correlation Group: ${incident.correlation_group}\n`;
184
+ output += `This incident groups related events that happened together.\n\n`;
185
+ }
186
+ if (incident.resolved_at) {
187
+ const duration = Math.floor((new Date(incident.resolved_at).getTime() -
188
+ new Date(incident.created_at).getTime()) /
189
+ 1000 /
190
+ 60);
191
+ output += `Resolved ${formatTimeSince(incident.resolved_at)} (duration: ${duration} minutes)\n`;
192
+ }
193
+ return output;
194
+ }
195
+ catch (error) {
196
+ if (error instanceof Error) {
197
+ return `❌ Error fetching incident: ${error.message}`;
198
+ }
199
+ return `❌ Error fetching incident`;
200
+ }
201
+ }
202
+ async function getEvents(api, projectId, options = {}) {
203
+ try {
204
+ const events = await api.getEvents({
205
+ projectId,
206
+ ...options,
207
+ limit: options.limit || 20,
208
+ });
209
+ if (events.length === 0) {
210
+ return 'No events found matching your criteria.';
211
+ }
212
+ let output = `Found ${events.length} recent event${events.length !== 1 ? 's' : ''}:\n\n`;
213
+ for (const event of events) {
214
+ const timeSince = formatTimeSince(event.created_at);
215
+ const severityEmoji = event.severity === 'critical'
216
+ ? '🔴'
217
+ : event.severity === 'high'
218
+ ? '🟠'
219
+ : event.severity === 'medium'
220
+ ? '🟡'
221
+ : '🔵';
222
+ output += `${severityEmoji} [${event.source}] ${event.type} - ${timeSince}\n`;
223
+ output += ` ${event.message}\n`;
224
+ if (event.raw_data && Object.keys(event.raw_data).length > 0) {
225
+ const keys = Object.keys(event.raw_data).slice(0, 3);
226
+ output += ` Data: ${keys.join(', ')}${Object.keys(event.raw_data).length > 3 ? ', ...' : ''}\n`;
227
+ }
228
+ output += `\n`;
229
+ }
230
+ return output;
231
+ }
232
+ catch (error) {
233
+ if (error instanceof Error) {
234
+ return `❌ Error fetching events: ${error.message}`;
235
+ }
236
+ return `❌ Error fetching events`;
237
+ }
238
+ }
239
+ async function resolveIncident(api, incidentId) {
240
+ try {
241
+ await api.resolveIncident(incidentId);
242
+ return `✅ Incident #${incidentId.substring(0, 8)} has been marked as resolved. Resolution notifications have been sent to configured channels.`;
243
+ }
244
+ catch (error) {
245
+ if (error instanceof Error) {
246
+ return `❌ Error resolving incident: ${error.message}`;
247
+ }
248
+ return `❌ Error resolving incident`;
249
+ }
250
+ }
251
+ async function getFixPrompt(api, incidentId) {
252
+ try {
253
+ const { incident } = await api.getIncident(incidentId);
254
+ if (!incident.fix_prompt) {
255
+ return `No fix prompt available for incident #${incidentId.substring(0, 8)}. The AI diagnosis may still be in progress.`;
256
+ }
257
+ return incident.fix_prompt;
258
+ }
259
+ catch (error) {
260
+ if (error instanceof Error) {
261
+ return `❌ Error fetching fix prompt: ${error.message}`;
262
+ }
263
+ return `❌ Error fetching fix prompt`;
264
+ }
265
+ }
266
+ async function getRecentTraces(api, projectId, options = {}) {
267
+ try {
268
+ const traces = await api.getRecentTraces({
269
+ projectId,
270
+ limit: options.limit || 10,
271
+ status: options.status,
272
+ });
273
+ if (traces.length === 0) {
274
+ if (options.status === 'error') {
275
+ return 'No traces with errors found. Your requests are completing successfully!';
276
+ }
277
+ return 'No traces found. Make sure @scanwarp/instrument is configured and your app is receiving traffic.';
278
+ }
279
+ let output = `Found ${traces.length} recent trace${traces.length !== 1 ? 's' : ''}`;
280
+ if (options.status) {
281
+ output += ` (filtered: ${options.status})`;
282
+ }
283
+ output += ':\n\n';
284
+ for (const trace of traces) {
285
+ const root = trace.root_span;
286
+ const statusIcon = trace.has_errors ? '✗' : '✓';
287
+ const time = new Date(Number(root.start_time)).toISOString();
288
+ // Format the root span label
289
+ const label = formatSpanLabel(root);
290
+ output += `${statusIcon} ${label} (${root.duration_ms}ms)\n`;
291
+ output += ` Trace: ${trace.trace_id}\n`;
292
+ output += ` Service: ${root.service_name}\n`;
293
+ output += ` Spans: ${trace.span_count} | Max duration: ${trace.max_duration_ms}ms\n`;
294
+ output += ` Time: ${time}\n`;
295
+ if (trace.has_errors) {
296
+ output += ` ⚠ Contains errors\n`;
297
+ }
298
+ output += '\n';
299
+ }
300
+ output += 'Use get_trace_detail with a trace_id to see the full request waterfall.';
301
+ return output;
302
+ }
303
+ catch (error) {
304
+ if (error instanceof Error) {
305
+ return `❌ Error fetching traces: ${error.message}`;
306
+ }
307
+ return `❌ Error fetching traces`;
308
+ }
309
+ }
310
+ async function getTraceDetail(api, traceId) {
311
+ try {
312
+ const spans = await api.getTraceDetail(traceId);
313
+ if (spans.length === 0) {
314
+ return `No spans found for trace ${traceId}. The trace may have expired or the ID may be incorrect.`;
315
+ }
316
+ const root = spans.find((s) => !s.parent_span_id);
317
+ const hasErrors = spans.some((s) => s.status_code === 'ERROR');
318
+ const totalDuration = root?.duration_ms || Math.max(...spans.map((s) => s.duration_ms));
319
+ let output = `Trace ${traceId}\n`;
320
+ output += `${'─'.repeat(50)}\n`;
321
+ output += `Service: ${root?.service_name || spans[0].service_name}\n`;
322
+ output += `Total duration: ${totalDuration}ms\n`;
323
+ output += `Spans: ${spans.length}\n`;
324
+ output += `Status: ${hasErrors ? '✗ Has errors' : '✓ OK'}\n\n`;
325
+ output += `REQUEST WATERFALL:\n`;
326
+ output += buildTraceWaterfall(spans);
327
+ // List error spans explicitly
328
+ const errorSpans = spans.filter((s) => s.status_code === 'ERROR');
329
+ if (errorSpans.length > 0) {
330
+ output += `\nERROR SPANS (${errorSpans.length}):\n`;
331
+ for (const span of errorSpans) {
332
+ output += ` ✗ ${formatSpanLabel(span)} (${span.duration_ms}ms)\n`;
333
+ if (span.status_message) {
334
+ output += ` Message: ${span.status_message}\n`;
335
+ }
336
+ // Check for exception events
337
+ const exceptionEvent = span.events.find((e) => e.name === 'exception');
338
+ if (exceptionEvent?.attributes) {
339
+ const msg = exceptionEvent.attributes['exception.message'];
340
+ if (typeof msg === 'string') {
341
+ output += ` Exception: ${msg.length > 200 ? msg.substring(0, 200) + '...' : msg}\n`;
342
+ }
343
+ }
344
+ }
345
+ }
346
+ // Find slowest spans
347
+ const sortedByDuration = [...spans].sort((a, b) => b.duration_ms - a.duration_ms);
348
+ const slowest = sortedByDuration.slice(0, 3);
349
+ output += `\nSLOWEST OPERATIONS:\n`;
350
+ for (const span of slowest) {
351
+ const pct = totalDuration > 0 ? Math.round((span.duration_ms / totalDuration) * 100) : 0;
352
+ output += ` ${formatSpanLabel(span)}: ${span.duration_ms}ms (${pct}% of total)\n`;
353
+ }
354
+ return output;
355
+ }
356
+ catch (error) {
357
+ if (error instanceof Error) {
358
+ return `❌ Error fetching trace: ${error.message}`;
359
+ }
360
+ return `❌ Error fetching trace`;
361
+ }
362
+ }
363
+ async function getTraceForIncident(api, incidentId) {
364
+ try {
365
+ const [incidentData, spans] = await Promise.all([
366
+ api.getIncident(incidentId),
367
+ api.getIncidentTraces(incidentId),
368
+ ]);
369
+ const { incident } = incidentData;
370
+ let output = `Traces for Incident #${incidentId.substring(0, 8)}\n`;
371
+ output += `${'─'.repeat(50)}\n\n`;
372
+ // Show diagnosis summary
373
+ if (incident.diagnosis_text) {
374
+ output += `ROOT CAUSE: ${incident.diagnosis_text}\n\n`;
375
+ }
376
+ if (spans.length === 0) {
377
+ output += 'No trace data available for this incident.\n';
378
+ if (incident.diagnosis_fix) {
379
+ output += `\nSUGGESTED FIX: ${incident.diagnosis_fix}\n`;
380
+ }
381
+ return output;
382
+ }
383
+ // Group spans by trace_id
384
+ const traceIds = [...new Set(spans.map((s) => s.trace_id))];
385
+ output += `Found ${spans.length} spans across ${traceIds.length} trace${traceIds.length !== 1 ? 's' : ''}.\n\n`;
386
+ output += `REQUEST WATERFALL:\n`;
387
+ output += buildTraceWaterfall(spans);
388
+ // Highlight the bottleneck
389
+ const errorSpans = spans.filter((s) => s.status_code === 'ERROR');
390
+ if (errorSpans.length > 0) {
391
+ output += `\nERROR SPANS:\n`;
392
+ for (const span of errorSpans) {
393
+ output += ` ✗ ${formatSpanLabel(span)} (${span.duration_ms}ms) — ${span.service_name}\n`;
394
+ if (span.status_message) {
395
+ output += ` ${span.status_message}\n`;
396
+ }
397
+ }
398
+ }
399
+ if (incident.diagnosis_fix) {
400
+ output += `\nSUGGESTED FIX:\n${incident.diagnosis_fix}\n`;
401
+ }
402
+ if (incident.fix_prompt) {
403
+ output += `\nFIX PROMPT (ready to use):\n${'─'.repeat(50)}\n${incident.fix_prompt}\n${'─'.repeat(50)}\n`;
404
+ }
405
+ return output;
406
+ }
407
+ catch (error) {
408
+ if (error instanceof Error) {
409
+ return `❌ Error fetching traces for incident: ${error.message}`;
410
+ }
411
+ return `❌ Error fetching traces for incident`;
412
+ }
413
+ }
414
+ // ─── Trace formatting helpers ───
415
+ function formatSpanLabel(span) {
416
+ const attrs = span.attributes;
417
+ // Database spans
418
+ if (attrs['db.system']) {
419
+ const stmt = attrs['db.statement'];
420
+ if (typeof stmt === 'string') {
421
+ const truncated = stmt.length > 80 ? stmt.substring(0, 80) + '...' : stmt;
422
+ return `${attrs['db.system']}: ${truncated}`;
423
+ }
424
+ return `${attrs['db.system']}: ${span.operation_name}`;
425
+ }
426
+ // HTTP spans
427
+ const method = attrs['http.method'] || attrs['http.request.method'];
428
+ const route = attrs['http.route'] || attrs['http.target'] || attrs['url.path'];
429
+ if (method && route) {
430
+ return `${method} ${route}`;
431
+ }
432
+ return span.operation_name;
433
+ }
434
+ /**
435
+ * Build a human-readable waterfall view from a list of spans.
436
+ */
437
+ function buildTraceWaterfall(spans) {
438
+ // Group by trace_id
439
+ const traceMap = new Map();
440
+ for (const span of spans) {
441
+ const group = traceMap.get(span.trace_id) || [];
442
+ group.push(span);
443
+ traceMap.set(span.trace_id, group);
444
+ }
445
+ const sections = [];
446
+ for (const [traceId, traceSpans] of traceMap) {
447
+ traceSpans.sort((a, b) => Number(a.start_time) - Number(b.start_time));
448
+ // Build parent → children index
449
+ const childrenMap = new Map();
450
+ for (const span of traceSpans) {
451
+ const parentKey = span.parent_span_id;
452
+ const siblings = childrenMap.get(parentKey) || [];
453
+ siblings.push(span);
454
+ childrenMap.set(parentKey, siblings);
455
+ }
456
+ // Find root spans
457
+ const spanIds = new Set(traceSpans.map((s) => s.span_id));
458
+ const roots = traceSpans.filter((s) => !s.parent_span_id || !spanIds.has(s.parent_span_id));
459
+ if (roots.length === 0)
460
+ continue;
461
+ let section = `\`\`\`\nTrace ${traceId}\n`;
462
+ for (const root of roots) {
463
+ section += renderSpanTree(root, childrenMap, '', true);
464
+ }
465
+ section += '```\n';
466
+ sections.push(section);
467
+ if (sections.length >= 5)
468
+ break;
469
+ }
470
+ return sections.join('\n');
471
+ }
472
+ function renderSpanTree(span, childrenMap, prefix, isLast) {
473
+ const status = span.status_code === 'ERROR'
474
+ ? `✗ ${span.status_message || 'error'}`
475
+ : '✓';
476
+ const label = formatSpanLabel(span);
477
+ const connector = prefix === '' ? '' : isLast ? '└─ ' : '├─ ';
478
+ let line = `${prefix}${connector}${label} (${span.duration_ms}ms) ${status}\n`;
479
+ // Render children
480
+ const children = childrenMap.get(span.span_id) || [];
481
+ children.sort((a, b) => Number(a.start_time) - Number(b.start_time));
482
+ const childPrefix = prefix === '' ? '' : prefix + (isLast ? ' ' : '│ ');
483
+ for (let i = 0; i < children.length; i++) {
484
+ line += renderSpanTree(children[i], childrenMap, childPrefix, i === children.length - 1);
485
+ }
486
+ return line;
487
+ }
package/package.json CHANGED
@@ -1,57 +1,60 @@
1
- {
2
- "name": "scanwarp",
3
- "version": "0.2.0",
4
- "description": "Production monitoring built for developers who ship fast. Auto-diagnoses issues with Claude AI.",
5
- "main": "dist/index.js",
6
- "bin": {
7
- "scanwarp": "./dist/index.js"
8
- },
9
- "files": [
10
- "dist"
11
- ],
12
- "scripts": {
13
- "build": "tsc",
14
- "dev": "tsc --watch",
15
- "typecheck": "tsc --noEmit",
16
- "clean": "rm -rf dist",
17
- "prepublishOnly": "npm run build"
18
- },
19
- "keywords": [
20
- "monitoring",
21
- "observability",
22
- "cli",
23
- "production",
24
- "ai",
25
- "claude",
26
- "cursor",
27
- "diagnosis",
28
- "incidents",
29
- "vercel",
30
- "stripe",
31
- "alerts"
32
- ],
33
- "author": "ScanWarp",
34
- "license": "MIT",
35
- "repository": {
36
- "type": "git",
37
- "url": "https://github.com/scanwarp/scanwarp.git",
38
- "directory": "packages/cli"
39
- },
40
- "bugs": {
41
- "url": "https://github.com/scanwarp/scanwarp/issues"
42
- },
43
- "homepage": "https://scanwarp.com",
44
- "dependencies": {
45
- "@anthropic-ai/sdk": "^0.74.0",
46
- "axios": "^1.13.5",
47
- "chalk": "^5.6.2",
48
- "commander": "^11.1.0",
49
- "inquirer": "^13.2.2",
50
- "ora": "^9.3.0"
51
- },
52
- "devDependencies": {
53
- "@types/inquirer": "^9.0.9",
54
- "@types/node": "^20.11.0",
55
- "typescript": "^5.3.3"
56
- }
57
- }
1
+ {
2
+ "name": "scanwarp",
3
+ "version": "0.3.0",
4
+ "description": "Production monitoring built for developers who ship fast. Auto-diagnoses issues with Claude AI.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "scanwarp": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc --watch",
15
+ "typecheck": "tsc --noEmit",
16
+ "clean": "rm -rf dist",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "monitoring",
21
+ "observability",
22
+ "cli",
23
+ "production",
24
+ "ai",
25
+ "claude",
26
+ "cursor",
27
+ "diagnosis",
28
+ "incidents",
29
+ "vercel",
30
+ "stripe",
31
+ "alerts"
32
+ ],
33
+ "author": "ScanWarp",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/scanwarp/scanwarp.git",
38
+ "directory": "packages/cli"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/scanwarp/scanwarp/issues"
42
+ },
43
+ "homepage": "https://scanwarp.com",
44
+ "dependencies": {
45
+ "@anthropic-ai/sdk": "^0.74.0",
46
+ "@modelcontextprotocol/sdk": "^1.26.0",
47
+ "@scanwarp/core": "workspace:*",
48
+ "axios": "^1.13.5",
49
+ "chalk": "^5.6.2",
50
+ "chokidar": "^5.0.0",
51
+ "commander": "^11.1.0",
52
+ "inquirer": "^13.2.2",
53
+ "ora": "^9.3.0"
54
+ },
55
+ "devDependencies": {
56
+ "@types/inquirer": "^9.0.9",
57
+ "@types/node": "^20.11.0",
58
+ "typescript": "^5.3.3"
59
+ }
60
+ }