@scanwarp/core 0.1.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.
- package/dist/diagnoser.d.ts +10 -2
- package/dist/diagnoser.js +203 -34
- package/dist/types.d.ts +21 -2
- package/package.json +47 -44
package/dist/diagnoser.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
import type { Event, Monitor, DiagnosisResult } from './types.js';
|
|
1
|
+
import type { Event, Monitor, DiagnosisResult, TraceSpan } from './types.js';
|
|
2
2
|
interface DiagnoserConfig {
|
|
3
3
|
apiKey: string;
|
|
4
4
|
model?: string;
|
|
5
5
|
}
|
|
6
|
+
interface ProviderStatusContext {
|
|
7
|
+
provider: string;
|
|
8
|
+
displayName: string;
|
|
9
|
+
status: string;
|
|
10
|
+
description: string | null;
|
|
11
|
+
}
|
|
6
12
|
interface DiagnosisContext {
|
|
7
13
|
events: Event[];
|
|
8
14
|
monitor?: Monitor;
|
|
@@ -11,7 +17,10 @@ interface DiagnosisContext {
|
|
|
11
17
|
status: string;
|
|
12
18
|
message: string;
|
|
13
19
|
}>;
|
|
20
|
+
traces?: TraceSpan[];
|
|
21
|
+
providerStatuses?: ProviderStatusContext[];
|
|
14
22
|
}
|
|
23
|
+
export { type DiagnosisContext };
|
|
15
24
|
export declare class Diagnoser {
|
|
16
25
|
private client;
|
|
17
26
|
private model;
|
|
@@ -23,4 +32,3 @@ export declare class Diagnoser {
|
|
|
23
32
|
private parseResponse;
|
|
24
33
|
private normalizeSeverity;
|
|
25
34
|
}
|
|
26
|
-
export {};
|
package/dist/diagnoser.js
CHANGED
|
@@ -20,7 +20,7 @@ class Diagnoser {
|
|
|
20
20
|
model: this.model,
|
|
21
21
|
max_tokens: 2000,
|
|
22
22
|
temperature: 0.3,
|
|
23
|
-
system: this.getSystemPrompt(),
|
|
23
|
+
system: this.getSystemPrompt(context),
|
|
24
24
|
messages: [
|
|
25
25
|
{
|
|
26
26
|
role: 'user',
|
|
@@ -34,43 +34,69 @@ class Diagnoser {
|
|
|
34
34
|
}
|
|
35
35
|
return this.parseResponse(content.text);
|
|
36
36
|
}
|
|
37
|
-
getSystemPrompt() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
37
|
+
getSystemPrompt(context) {
|
|
38
|
+
const hasTraces = context.traces && context.traces.length > 0;
|
|
39
|
+
const hasProviderIssues = context.providerStatuses && context.providerStatuses.length > 0;
|
|
40
|
+
let prompt = `You are a senior engineering mentor helping developers who built their application using AI coding tools like Cursor or Claude Code. These developers may not have deep infrastructure knowledge or be familiar with reading stack traces.
|
|
41
|
+
|
|
42
|
+
Your job is to:
|
|
43
|
+
1. Explain what went wrong in plain, conversational English (no jargon)
|
|
44
|
+
2. Explain WHY it happened in a way a non-expert can understand
|
|
45
|
+
3. Provide a clear, actionable fix in plain language
|
|
46
|
+
4. Write a ready-to-paste prompt they can give to their AI coding assistant to fix the issue${hasTraces ? '\n5. Identify the specific span (operation) that is the bottleneck or root cause' : ''}
|
|
47
|
+
|
|
48
|
+
Think of yourself as a patient mentor who's explaining a production issue to someone smart but new to production systems.
|
|
49
|
+
|
|
50
|
+
IMPORTANT RULES:
|
|
51
|
+
- NO technical jargon without explanation
|
|
52
|
+
- NO raw stack traces in your response
|
|
53
|
+
- Use analogies when helpful
|
|
54
|
+
- Be encouraging, not condescending
|
|
55
|
+
- Focus on "what to do" not "what you did wrong"${hasTraces ? '\n- When trace data is available, use it to pinpoint the EXACT operation that failed or is slow' : ''}`;
|
|
56
|
+
if (hasProviderIssues) {
|
|
57
|
+
prompt += `
|
|
58
|
+
|
|
59
|
+
PROVIDER OUTAGE RULES (CRITICAL — follow these when provider status data shows a non-operational provider):
|
|
60
|
+
- If the issue correlates with a provider that is currently experiencing an outage or degraded performance, clearly state that the issue is CAUSED BY the provider outage, NOT a bug in the user's code
|
|
61
|
+
- The root_cause MUST mention the provider by name and their current status (e.g. "This is caused by a Vercel outage, not a bug in your code")
|
|
62
|
+
- The suggested_fix should focus on: (1) waiting for the provider to recover, (2) checking the provider's status page, and (3) any temporary workarounds
|
|
63
|
+
- Do NOT suggest code fixes for issues caused by provider outages — it's not the user's fault
|
|
64
|
+
- The fix_prompt should suggest adding resilience improvements (retry logic, fallbacks, circuit breakers) as an OPTIONAL improvement, not as a bug fix`;
|
|
65
|
+
}
|
|
66
|
+
prompt += `
|
|
67
|
+
|
|
68
|
+
Respond in this exact JSON format:
|
|
69
|
+
{
|
|
70
|
+
"root_cause": "1-2 sentence plain English explanation of what broke",
|
|
71
|
+
"severity": "critical|warning|info",
|
|
72
|
+
"suggested_fix": "Plain English explanation of how to fix it (2-4 sentences)",
|
|
73
|
+
"fix_prompt": "A complete, copy-pasteable prompt for Cursor/Claude Code that will fix this issue"${hasTraces ? `,
|
|
74
|
+
"bottleneck_span": "name of the span that is the root cause (e.g. 'stripe: payment_intents.create' or 'pg: SELECT * FROM users')",
|
|
75
|
+
"trace_id": "the trace_id of the most relevant trace"` : ''}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
The fix_prompt should be detailed and include:
|
|
79
|
+
- What file(s) to modify
|
|
80
|
+
- What specific changes to make
|
|
81
|
+
- Any environment variables or config needed
|
|
82
|
+
- How to test the fix
|
|
83
|
+
|
|
69
84
|
Make the fix_prompt actionable enough that an AI coding assistant can implement it without asking follow-up questions.`;
|
|
85
|
+
return prompt;
|
|
70
86
|
}
|
|
71
87
|
buildPrompt(context) {
|
|
72
|
-
const { events, monitor, recentHistory } = context;
|
|
88
|
+
const { events, monitor, recentHistory, traces, providerStatuses } = context;
|
|
73
89
|
let prompt = '## Production Issue Detected\n\n';
|
|
90
|
+
// Provider status section — show this first so the AI sees it immediately
|
|
91
|
+
if (providerStatuses && providerStatuses.length > 0) {
|
|
92
|
+
prompt += '**⚠️ Provider Status (current):**\n';
|
|
93
|
+
for (const ps of providerStatuses) {
|
|
94
|
+
const statusLabel = ps.status === 'operational' ? '✅ operational' : `🔴 ${ps.status}`;
|
|
95
|
+
const detail = ps.description ? ` — ${ps.description}` : '';
|
|
96
|
+
prompt += `- ${ps.displayName}: ${statusLabel}${detail}\n`;
|
|
97
|
+
}
|
|
98
|
+
prompt += '\nNote: One or more infrastructure providers are experiencing issues. Consider whether this incident is caused by the provider outage rather than a code bug.\n\n';
|
|
99
|
+
}
|
|
74
100
|
// Add monitor context if available
|
|
75
101
|
if (monitor) {
|
|
76
102
|
prompt += `**Service:** ${monitor.url}\n`;
|
|
@@ -89,6 +115,16 @@ Make the fix_prompt actionable enough that an AI coding assistant can implement
|
|
|
89
115
|
}
|
|
90
116
|
prompt += '\n';
|
|
91
117
|
}
|
|
118
|
+
// Add trace waterfall if available
|
|
119
|
+
if (traces && traces.length > 0) {
|
|
120
|
+
const waterfall = buildTraceWaterfall(traces);
|
|
121
|
+
if (waterfall) {
|
|
122
|
+
prompt += `\n**Request Traces (from OpenTelemetry instrumentation):**\n`;
|
|
123
|
+
prompt += `These traces show the exact sequence of operations your app performed during the failing request(s).\n\n`;
|
|
124
|
+
prompt += waterfall;
|
|
125
|
+
prompt += '\n';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
92
128
|
// Add recent history if available
|
|
93
129
|
if (recentHistory && recentHistory.length > 0) {
|
|
94
130
|
prompt += `\n**Recent History (last 24 hours):**\n`;
|
|
@@ -113,6 +149,14 @@ Make the fix_prompt actionable enough that an AI coding assistant can implement
|
|
|
113
149
|
'message',
|
|
114
150
|
'type',
|
|
115
151
|
'source',
|
|
152
|
+
'trace_id',
|
|
153
|
+
'span_id',
|
|
154
|
+
'service_name',
|
|
155
|
+
'operation_name',
|
|
156
|
+
'duration_ms',
|
|
157
|
+
'status_message',
|
|
158
|
+
'db_system',
|
|
159
|
+
'db_statement',
|
|
116
160
|
];
|
|
117
161
|
for (const field of relevantFields) {
|
|
118
162
|
if (field in data) {
|
|
@@ -134,6 +178,8 @@ Make the fix_prompt actionable enough that an AI coding assistant can implement
|
|
|
134
178
|
severity: this.normalizeSeverity(parsed.severity),
|
|
135
179
|
suggested_fix: parsed.suggested_fix || 'No fix suggested',
|
|
136
180
|
fix_prompt: parsed.fix_prompt || 'No fix prompt provided',
|
|
181
|
+
bottleneck_span: parsed.bottleneck_span || undefined,
|
|
182
|
+
trace_id: parsed.trace_id || undefined,
|
|
137
183
|
};
|
|
138
184
|
}
|
|
139
185
|
catch (error) {
|
|
@@ -157,3 +203,126 @@ Make the fix_prompt actionable enough that an AI coding assistant can implement
|
|
|
157
203
|
}
|
|
158
204
|
}
|
|
159
205
|
exports.Diagnoser = Diagnoser;
|
|
206
|
+
/**
|
|
207
|
+
* Build a human-readable waterfall view of traces, grouped by trace_id.
|
|
208
|
+
* Each trace shows the root span and its children as an indented tree.
|
|
209
|
+
*/
|
|
210
|
+
function buildTraceWaterfall(spans) {
|
|
211
|
+
// Group spans by trace_id
|
|
212
|
+
const traceMap = new Map();
|
|
213
|
+
for (const span of spans) {
|
|
214
|
+
const group = traceMap.get(span.trace_id) || [];
|
|
215
|
+
group.push(span);
|
|
216
|
+
traceMap.set(span.trace_id, group);
|
|
217
|
+
}
|
|
218
|
+
const sections = [];
|
|
219
|
+
for (const [traceId, traceSpans] of traceMap) {
|
|
220
|
+
// Sort by start_time
|
|
221
|
+
traceSpans.sort((a, b) => a.start_time - b.start_time);
|
|
222
|
+
// Build a parent → children index
|
|
223
|
+
const childrenMap = new Map();
|
|
224
|
+
for (const span of traceSpans) {
|
|
225
|
+
const parentKey = span.parent_span_id;
|
|
226
|
+
const siblings = childrenMap.get(parentKey) || [];
|
|
227
|
+
siblings.push(span);
|
|
228
|
+
childrenMap.set(parentKey, siblings);
|
|
229
|
+
}
|
|
230
|
+
// Find root spans (no parent or parent not in this trace)
|
|
231
|
+
const spanIds = new Set(traceSpans.map((s) => s.span_id));
|
|
232
|
+
const roots = traceSpans.filter((s) => !s.parent_span_id || !spanIds.has(s.parent_span_id));
|
|
233
|
+
if (roots.length === 0)
|
|
234
|
+
continue;
|
|
235
|
+
let section = `\`\`\`\nTrace ${traceId}\n`;
|
|
236
|
+
for (const root of roots) {
|
|
237
|
+
section += renderSpanTree(root, childrenMap, '', true);
|
|
238
|
+
}
|
|
239
|
+
section += '```\n';
|
|
240
|
+
sections.push(section);
|
|
241
|
+
// Limit to 5 traces to keep prompt size reasonable
|
|
242
|
+
if (sections.length >= 5)
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
return sections.join('\n');
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Render a single span and its children as an indented tree.
|
|
249
|
+
*/
|
|
250
|
+
function renderSpanTree(span, childrenMap, prefix, isLast) {
|
|
251
|
+
const status = formatSpanStatus(span);
|
|
252
|
+
const label = formatSpanLabel(span);
|
|
253
|
+
const connector = prefix === '' ? '' : isLast ? '└─ ' : '├─ ';
|
|
254
|
+
let line = `${prefix}${connector}${label} (${span.duration_ms}ms) ${status}\n`;
|
|
255
|
+
// Add key attributes on a sub-line for context
|
|
256
|
+
const detail = formatSpanDetail(span);
|
|
257
|
+
if (detail) {
|
|
258
|
+
const detailPrefix = prefix === '' ? ' ' : prefix + (isLast ? ' ' : '│ ');
|
|
259
|
+
line += `${detailPrefix}${detail}\n`;
|
|
260
|
+
}
|
|
261
|
+
// Render children
|
|
262
|
+
const children = childrenMap.get(span.span_id) || [];
|
|
263
|
+
children.sort((a, b) => a.start_time - b.start_time);
|
|
264
|
+
const childPrefix = prefix === '' ? '' : prefix + (isLast ? ' ' : '│ ');
|
|
265
|
+
for (let i = 0; i < children.length; i++) {
|
|
266
|
+
line += renderSpanTree(children[i], childrenMap, childPrefix, i === children.length - 1);
|
|
267
|
+
}
|
|
268
|
+
return line;
|
|
269
|
+
}
|
|
270
|
+
function formatSpanLabel(span) {
|
|
271
|
+
const attrs = span.attributes;
|
|
272
|
+
// Database spans: show db.system + operation
|
|
273
|
+
if (attrs['db.system']) {
|
|
274
|
+
const stmt = attrs['db.statement'];
|
|
275
|
+
if (typeof stmt === 'string') {
|
|
276
|
+
const truncated = stmt.length > 80 ? stmt.substring(0, 80) + '...' : stmt;
|
|
277
|
+
return `${attrs['db.system']}: ${truncated}`;
|
|
278
|
+
}
|
|
279
|
+
return `${attrs['db.system']}: ${span.operation_name}`;
|
|
280
|
+
}
|
|
281
|
+
// HTTP spans: show method + route/target
|
|
282
|
+
const method = attrs['http.method'] || attrs['http.request.method'];
|
|
283
|
+
const route = attrs['http.route'] || attrs['http.target'] || attrs['url.path'];
|
|
284
|
+
if (method && route) {
|
|
285
|
+
return `${method} ${route}`;
|
|
286
|
+
}
|
|
287
|
+
return span.operation_name;
|
|
288
|
+
}
|
|
289
|
+
function formatSpanStatus(span) {
|
|
290
|
+
if (span.status_code === 'ERROR') {
|
|
291
|
+
const msg = span.status_message || 'error';
|
|
292
|
+
return `✗ ${msg}`;
|
|
293
|
+
}
|
|
294
|
+
if (span.status_code === 'OK') {
|
|
295
|
+
return '✓';
|
|
296
|
+
}
|
|
297
|
+
// UNSET — infer from http.status_code if available
|
|
298
|
+
const httpStatus = span.attributes['http.status_code'] || span.attributes['http.response.status_code'];
|
|
299
|
+
if (typeof httpStatus === 'number' && httpStatus >= 400) {
|
|
300
|
+
return `✗ HTTP ${httpStatus}`;
|
|
301
|
+
}
|
|
302
|
+
return '✓';
|
|
303
|
+
}
|
|
304
|
+
function formatSpanDetail(span) {
|
|
305
|
+
const parts = [];
|
|
306
|
+
const attrs = span.attributes;
|
|
307
|
+
// Error message from span events
|
|
308
|
+
if (span.status_code === 'ERROR') {
|
|
309
|
+
const exceptionEvent = span.events.find((e) => e.name === 'exception');
|
|
310
|
+
if (exceptionEvent?.attributes) {
|
|
311
|
+
const msg = exceptionEvent.attributes['exception.message'];
|
|
312
|
+
if (typeof msg === 'string') {
|
|
313
|
+
parts.push(`error: ${msg.length > 120 ? msg.substring(0, 120) + '...' : msg}`);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// HTTP status
|
|
318
|
+
const httpStatus = attrs['http.status_code'] || attrs['http.response.status_code'];
|
|
319
|
+
if (httpStatus) {
|
|
320
|
+
parts.push(`status: ${httpStatus}`);
|
|
321
|
+
}
|
|
322
|
+
// DB statement (if not already in label, show here for children)
|
|
323
|
+
if (attrs['db.statement'] && !attrs['db.system']) {
|
|
324
|
+
const stmt = String(attrs['db.statement']);
|
|
325
|
+
parts.push(stmt.length > 80 ? stmt.substring(0, 80) + '...' : stmt);
|
|
326
|
+
}
|
|
327
|
+
return parts.join(' | ');
|
|
328
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -7,12 +7,12 @@ export interface Monitor {
|
|
|
7
7
|
status: 'up' | 'down' | 'unknown';
|
|
8
8
|
created_at: Date;
|
|
9
9
|
}
|
|
10
|
-
export type EventSource = 'monitor' | 'vercel' | 'stripe' | 'supabase' | 'github' | 'provider-status';
|
|
10
|
+
export type EventSource = 'monitor' | 'vercel' | 'stripe' | 'supabase' | 'github' | 'provider-status' | 'otel';
|
|
11
11
|
export interface Event {
|
|
12
12
|
id: string;
|
|
13
13
|
project_id: string;
|
|
14
14
|
monitor_id?: string;
|
|
15
|
-
type: 'error' | 'slow' | 'down' | 'up';
|
|
15
|
+
type: 'error' | 'slow' | 'down' | 'up' | 'trace_error' | 'slow_query';
|
|
16
16
|
source: EventSource;
|
|
17
17
|
message: string;
|
|
18
18
|
raw_data?: Record<string, unknown>;
|
|
@@ -60,6 +60,25 @@ export interface DiagnosisResult {
|
|
|
60
60
|
severity: 'critical' | 'warning' | 'info';
|
|
61
61
|
suggested_fix: string;
|
|
62
62
|
fix_prompt: string;
|
|
63
|
+
bottleneck_span?: string;
|
|
64
|
+
trace_id?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface TraceSpan {
|
|
67
|
+
trace_id: string;
|
|
68
|
+
span_id: string;
|
|
69
|
+
parent_span_id: string | null;
|
|
70
|
+
service_name: string;
|
|
71
|
+
operation_name: string;
|
|
72
|
+
kind: string;
|
|
73
|
+
start_time: number;
|
|
74
|
+
duration_ms: number;
|
|
75
|
+
status_code: string | null;
|
|
76
|
+
status_message: string | null;
|
|
77
|
+
attributes: Record<string, unknown>;
|
|
78
|
+
events: Array<{
|
|
79
|
+
name: string;
|
|
80
|
+
attributes?: Record<string, unknown>;
|
|
81
|
+
}>;
|
|
63
82
|
}
|
|
64
83
|
export interface ProviderStatus {
|
|
65
84
|
provider: string;
|
package/package.json
CHANGED
|
@@ -1,44 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@scanwarp/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Shared types and logic for ScanWarp monitoring",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
"dist"
|
|
9
|
-
],
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
12
|
-
"dev": "tsc --watch",
|
|
13
|
-
"typecheck": "tsc --noEmit",
|
|
14
|
-
"clean": "rm -rf dist",
|
|
15
|
-
"prepublishOnly": "npm run build"
|
|
16
|
-
},
|
|
17
|
-
"keywords": [
|
|
18
|
-
"monitoring",
|
|
19
|
-
"observability",
|
|
20
|
-
"ai",
|
|
21
|
-
"claude",
|
|
22
|
-
"diagnosis",
|
|
23
|
-
"production",
|
|
24
|
-
"incidents"
|
|
25
|
-
],
|
|
26
|
-
"author": "ScanWarp",
|
|
27
|
-
"license": "MIT",
|
|
28
|
-
"repository": {
|
|
29
|
-
"type": "git",
|
|
30
|
-
"url": "https://github.com/scanwarp/scanwarp.git",
|
|
31
|
-
"directory": "packages/core"
|
|
32
|
-
},
|
|
33
|
-
"bugs": {
|
|
34
|
-
"url": "https://github.com/scanwarp/scanwarp/issues"
|
|
35
|
-
},
|
|
36
|
-
"homepage": "https://scanwarp.com",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
}
|
|
44
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@scanwarp/core",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Shared types and logic for ScanWarp monitoring",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"clean": "rm -rf dist",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"monitoring",
|
|
19
|
+
"observability",
|
|
20
|
+
"ai",
|
|
21
|
+
"claude",
|
|
22
|
+
"diagnosis",
|
|
23
|
+
"production",
|
|
24
|
+
"incidents"
|
|
25
|
+
],
|
|
26
|
+
"author": "ScanWarp",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/scanwarp/scanwarp.git",
|
|
31
|
+
"directory": "packages/core"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/scanwarp/scanwarp/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://scanwarp.com",
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^20.11.0",
|
|
42
|
+
"typescript": "^5.3.3"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@anthropic-ai/sdk": "^0.74.0"
|
|
46
|
+
}
|
|
47
|
+
}
|