deploy-mcp 0.1.1 → 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,1563 @@
1
+ // src/core/tools.ts
2
+ import { z } from "zod";
3
+ var SUPPORTED_PLATFORMS = ["vercel"];
4
+ var checkDeploymentStatusSchema = z.object({
5
+ platform: z.enum(SUPPORTED_PLATFORMS).describe("The deployment platform"),
6
+ project: z.string().describe("The project name or ID"),
7
+ token: z.string().optional().describe("API token for authentication (optional if set in environment)")
8
+ });
9
+ var watchDeploymentSchema = z.object({
10
+ platform: z.enum(SUPPORTED_PLATFORMS).describe("The deployment platform"),
11
+ project: z.string().describe("The project name or ID"),
12
+ deploymentId: z.string().optional().describe("Specific deployment ID to watch (optional, defaults to latest)"),
13
+ token: z.string().optional().describe("API token for authentication (optional if set in environment)")
14
+ });
15
+ var compareDeploymentsSchema = z.object({
16
+ platform: z.enum(SUPPORTED_PLATFORMS).describe("The deployment platform"),
17
+ project: z.string().describe("The project name or ID"),
18
+ count: z.number().default(2).describe("Number of deployments to compare (default: 2)"),
19
+ token: z.string().optional().describe("API token for authentication (optional if set in environment)")
20
+ });
21
+ var getDeploymentLogsSchema = z.object({
22
+ platform: z.enum(SUPPORTED_PLATFORMS).describe("The deployment platform"),
23
+ deploymentId: z.string().describe("The deployment ID"),
24
+ filter: z.enum(["error", "warning", "all"]).default("error").describe("Filter logs by type (default: error)"),
25
+ token: z.string().optional().describe("API token for authentication (optional if set in environment)")
26
+ });
27
+ var tools = [
28
+ {
29
+ name: "check_deployment_status",
30
+ description: "Check the latest deployment status for a project on a platform",
31
+ inputSchema: {
32
+ type: "object",
33
+ properties: {
34
+ platform: {
35
+ type: "string",
36
+ enum: ["vercel"],
37
+ description: "The deployment platform"
38
+ },
39
+ project: {
40
+ type: "string",
41
+ description: "The project name or ID"
42
+ },
43
+ token: {
44
+ type: "string",
45
+ description: "API token for authentication (optional if set in environment)"
46
+ }
47
+ },
48
+ required: ["platform", "project"]
49
+ }
50
+ },
51
+ {
52
+ name: "watch_deployment",
53
+ description: "Stream real-time deployment progress with detailed status updates and error information",
54
+ inputSchema: {
55
+ type: "object",
56
+ properties: {
57
+ platform: {
58
+ type: "string",
59
+ enum: ["vercel"],
60
+ description: "The deployment platform"
61
+ },
62
+ project: {
63
+ type: "string",
64
+ description: "The project name or ID"
65
+ },
66
+ deploymentId: {
67
+ type: "string",
68
+ description: "Specific deployment ID to watch (optional, defaults to latest)"
69
+ },
70
+ token: {
71
+ type: "string",
72
+ description: "API token for authentication (optional if set in environment)"
73
+ }
74
+ },
75
+ required: ["platform", "project"]
76
+ }
77
+ },
78
+ {
79
+ name: "compare_deployments",
80
+ description: "Compare recent deployments to identify changes, performance differences, and potential issues",
81
+ inputSchema: {
82
+ type: "object",
83
+ properties: {
84
+ platform: {
85
+ type: "string",
86
+ enum: ["vercel"],
87
+ description: "The deployment platform"
88
+ },
89
+ project: {
90
+ type: "string",
91
+ description: "The project name or ID"
92
+ },
93
+ count: {
94
+ type: "number",
95
+ default: 2,
96
+ description: "Number of deployments to compare (default: 2)"
97
+ },
98
+ token: {
99
+ type: "string",
100
+ description: "API token for authentication (optional if set in environment)"
101
+ }
102
+ },
103
+ required: ["platform", "project"]
104
+ }
105
+ },
106
+ {
107
+ name: "get_deployment_logs",
108
+ description: "Fetch detailed logs for a specific deployment, useful for debugging failed deployments",
109
+ inputSchema: {
110
+ type: "object",
111
+ properties: {
112
+ platform: {
113
+ type: "string",
114
+ enum: ["vercel"],
115
+ description: "The deployment platform"
116
+ },
117
+ deploymentId: {
118
+ type: "string",
119
+ description: "The deployment ID"
120
+ },
121
+ filter: {
122
+ type: "string",
123
+ enum: ["error", "warning", "all"],
124
+ default: "error",
125
+ description: "Filter logs by type (default: error)"
126
+ },
127
+ token: {
128
+ type: "string",
129
+ description: "API token for authentication (optional if set in environment)"
130
+ }
131
+ },
132
+ required: ["platform", "deploymentId"]
133
+ }
134
+ }
135
+ ];
136
+
137
+ // src/adapters/base/adapter.ts
138
+ var BaseAdapter = class {
139
+ formatTimestamp(date) {
140
+ return new Date(date).toISOString();
141
+ }
142
+ calculateDuration(start, end) {
143
+ const startTime = new Date(start).getTime();
144
+ const endTime = end ? new Date(end).getTime() : Date.now();
145
+ return Math.floor((endTime - startTime) / 1e3);
146
+ }
147
+ };
148
+
149
+ // src/adapters/base/types.ts
150
+ var AdapterException = class extends Error {
151
+ constructor(type, message, originalError) {
152
+ super(message);
153
+ this.type = type;
154
+ this.originalError = originalError;
155
+ this.name = "AdapterException";
156
+ }
157
+ };
158
+
159
+ // src/core/constants.ts
160
+ var MAX_DEPLOYMENT_WATCH_ATTEMPTS = 120;
161
+ var BUILD_TIME_SECONDS_DIVISOR = 1e3;
162
+ var MAX_WATCH_TIME_MS = 24e4;
163
+ var MAX_BACKOFF_DELAY_MS = 3e4;
164
+ var BACKOFF_JITTER_MS = 1e3;
165
+ var HIGH_RISK_THRESHOLD_PERCENT = 50;
166
+ var MEDIUM_RISK_THRESHOLD_PERCENT = 20;
167
+ var DEFAULT_COMPARISON_COUNT = 2;
168
+ var SINGLE_DEPLOYMENT_FETCH = 1;
169
+ var MAX_TOKENS_PER_MINUTE = 30;
170
+ var RATE_LIMITER_CLEANUP_AGE_MS = 36e5;
171
+ var DEPLOYMENT_STATES = {
172
+ INITIALIZING: "INITIALIZING",
173
+ BUILDING: "BUILDING",
174
+ UPLOADING: "UPLOADING",
175
+ DEPLOYING: "DEPLOYING",
176
+ READY: "READY",
177
+ ERROR: "ERROR",
178
+ CANCELED: "CANCELED"
179
+ };
180
+ var POLLING_INTERVALS_BY_STATE = {
181
+ INITIALIZING: 5e3,
182
+ // 5s - slower at start
183
+ BUILDING: 3e3,
184
+ // 3s - active building
185
+ UPLOADING: 2e3,
186
+ // 2s - final stages
187
+ DEPLOYING: 2e3,
188
+ // 2s - final stages
189
+ READY: 0,
190
+ // Stop polling
191
+ ERROR: 0,
192
+ // Stop polling
193
+ CANCELED: 0,
194
+ // Stop polling
195
+ UNKNOWN: 1e4
196
+ // 10s - unknown states
197
+ };
198
+ var LOG_FILTERS = {
199
+ ERROR: /error|fail|exception|critical/i,
200
+ WARNING: /warning|warn|deprecat/i
201
+ };
202
+ var DEFAULTS = {
203
+ DEPLOYMENT_ID_SLICE_LENGTH: 7,
204
+ ERROR_LINE_TRIM_INDEX: 0,
205
+ PERCENTAGE_MULTIPLIER: 100,
206
+ MAX_EVENT_BUFFER_SIZE: 100,
207
+ MAX_CACHE_SIZE: 10,
208
+ CACHE_TTL_MS: 30 * 60 * 1e3,
209
+ // 30 minutes
210
+ CACHE_CLEANUP_INTERVAL_MS: 5 * 60 * 1e3
211
+ // 5 minutes
212
+ };
213
+ var ERROR_MESSAGES = {
214
+ NO_TOKEN: "No token provided",
215
+ NO_DEPLOYMENT_FOUND: "No deployment found for this project",
216
+ DEPLOYMENT_TAKING_LONG: "\u26A0\uFE0F Deployment is taking longer than expected",
217
+ NO_LOGS_AVAILABLE: "No logs available",
218
+ NO_LOGS_MATCHING_FILTER: "No logs matching filter criteria"
219
+ };
220
+ var STATUS_ICONS = {
221
+ ROCKET: "\u{1F680}",
222
+ HOURGLASS: "\u23F3",
223
+ HAMMER: "\u{1F528}",
224
+ PACKAGE: "\u{1F4E6}",
225
+ GLOBE: "\u{1F30D}",
226
+ SUCCESS: "\u2705",
227
+ ERROR: "\u274C",
228
+ WARNING: "\u26A0\uFE0F",
229
+ CHART: "\u{1F4CA}",
230
+ LIGHTNING: "\u26A1",
231
+ TURTLE: "\u{1F422}",
232
+ LIGHTBULB: "\u{1F4A1}",
233
+ PIN: "\u{1F4CD}"
234
+ };
235
+ var STATUS_MESSAGES = {
236
+ STARTING_WATCH: (id) => `${STATUS_ICONS.ROCKET} Starting to watch deployment ${id}...`,
237
+ INITIALIZING: `${STATUS_ICONS.HOURGLASS} Initializing deployment...`,
238
+ BUILDING: `${STATUS_ICONS.HAMMER} Building application...`,
239
+ UPLOADING: `${STATUS_ICONS.PACKAGE} Uploading to edge network...`,
240
+ DEPLOYING: `${STATUS_ICONS.GLOBE} Deploying to production...`,
241
+ DEPLOYMENT_SUCCESS: `${STATUS_ICONS.SUCCESS} Deployment successful!`,
242
+ DEPLOYMENT_FAILED: (message) => `${STATUS_ICONS.ERROR} Deployment failed: ${message}`,
243
+ BUILD_TIME_SAME: (time) => `${STATUS_ICONS.CHART} Build time: ${time}s (same as previous)`,
244
+ BUILD_TIME_CHANGE: (current, delta, faster) => `${STATUS_ICONS.CHART} Build time: ${current}s (${Math.abs(delta)}s ${faster ? "faster" : "slower"} than previous) ${faster ? STATUS_ICONS.LIGHTNING : STATUS_ICONS.TURTLE}`
245
+ };
246
+ var ERROR_TEXT_PATTERNS = {
247
+ UNAUTHORIZED: ["401", "unauthorized"],
248
+ NOT_FOUND: ["404", "not found"],
249
+ RATE_LIMITED: ["429", "rate limit"],
250
+ TIMEOUT: ["timeout", "AbortError"]
251
+ };
252
+ var API_EVENT_TYPES = {
253
+ STDOUT: "stdout",
254
+ STDERR: "stderr"
255
+ };
256
+ var API_MESSAGES = {
257
+ NO_LOGS_AVAILABLE: "No logs available",
258
+ INVALID_TOKEN: "Invalid Vercel token",
259
+ PROJECT_NOT_FOUND: "Project not found",
260
+ RATE_LIMIT_EXCEEDED: "Rate limit exceeded",
261
+ REQUEST_TIMEOUT: "Request timeout",
262
+ FAILED_TO_VALIDATE_TOKEN: "Failed to validate Vercel token"
263
+ };
264
+ var API_PARAMS = {
265
+ BUILDS: 1,
266
+ LOGS: 1
267
+ };
268
+ var API_CONFIG = {
269
+ VERCEL_BASE_URL: "https://api.vercel.com",
270
+ DEFAULT_TIMEOUT_MS: 1e4,
271
+ DEFAULT_RETRY_ATTEMPTS: 3,
272
+ DEFAULT_DEPLOYMENT_LIMIT: 10,
273
+ SINGLE_DEPLOYMENT_LIMIT: 1
274
+ };
275
+ var PLATFORM_NAMES = {
276
+ VERCEL: "vercel",
277
+ NETLIFY: "netlify",
278
+ RAILWAY: "railway",
279
+ RENDER: "render"
280
+ };
281
+ var ENVIRONMENT_TYPES = {
282
+ PRODUCTION: "production",
283
+ PREVIEW: "preview",
284
+ DEVELOPMENT: "development"
285
+ };
286
+ var VERCEL_STATES = {
287
+ READY: "READY",
288
+ ERROR: "ERROR",
289
+ CANCELED: "CANCELED",
290
+ BUILDING: "BUILDING",
291
+ INITIALIZING: "INITIALIZING",
292
+ QUEUED: "QUEUED"
293
+ };
294
+ var ADAPTER_ERRORS = {
295
+ TOKEN_REQUIRED: "Vercel token required. Set VERCEL_TOKEN environment variable or pass token parameter.",
296
+ FETCH_DEPLOYMENT_FAILED: "Failed to fetch deployment status from Vercel",
297
+ UNKNOWN_STATUS: "unknown"
298
+ };
299
+
300
+ // src/adapters/base/api-client.ts
301
+ var RateLimitError = class extends Error {
302
+ constructor(retryAfter, endpoint) {
303
+ super(`Rate limit exceeded for ${endpoint}. Retry after ${retryAfter}ms`);
304
+ this.retryAfter = retryAfter;
305
+ this.endpoint = endpoint;
306
+ this.name = "RateLimitError";
307
+ }
308
+ };
309
+ var HTTPError = class _HTTPError extends Error {
310
+ constructor(statusCode, statusText, url, method) {
311
+ super(`${method} ${url} failed with ${statusCode}: ${statusText}`);
312
+ this.statusCode = statusCode;
313
+ this.statusText = statusText;
314
+ this.url = url;
315
+ this.method = method;
316
+ this.name = "HTTPError";
317
+ Error.captureStackTrace(this, _HTTPError);
318
+ }
319
+ };
320
+ var BaseAPIClient = class {
321
+ baseUrl;
322
+ defaultHeaders;
323
+ timeout;
324
+ maxRetries;
325
+ pendingRequests = /* @__PURE__ */ new Map();
326
+ rateLimiters = /* @__PURE__ */ new Map();
327
+ maxTokensPerMinute = MAX_TOKENS_PER_MINUTE;
328
+ constructor(config) {
329
+ this.baseUrl = new URL(config.baseUrl);
330
+ this.defaultHeaders = new Headers({
331
+ "Content-Type": "application/json",
332
+ "User-Agent": "deploy-mcp/1.0.0",
333
+ ...config.headers
334
+ });
335
+ this.timeout = config.timeout ?? API_CONFIG.DEFAULT_TIMEOUT_MS;
336
+ this.maxRetries = config.retry ?? API_CONFIG.DEFAULT_RETRY_ATTEMPTS;
337
+ }
338
+ async request(endpoint, options) {
339
+ if (options?.token) {
340
+ await this.checkRateLimit(options.token, endpoint.path);
341
+ }
342
+ const cacheKey = this.getCacheKey(endpoint, options);
343
+ if (endpoint.method === "GET" && !options?.body) {
344
+ const pending = this.pendingRequests.get(cacheKey);
345
+ if (pending) {
346
+ return pending;
347
+ }
348
+ }
349
+ const requestPromise = this.executeRequest(endpoint, options);
350
+ if (endpoint.method === "GET" && !options?.body) {
351
+ this.pendingRequests.set(cacheKey, requestPromise);
352
+ requestPromise.then(() => {
353
+ this.pendingRequests.delete(cacheKey);
354
+ }).catch(() => {
355
+ this.pendingRequests.delete(cacheKey);
356
+ });
357
+ }
358
+ return requestPromise;
359
+ }
360
+ async executeRequest(endpoint, options) {
361
+ const url = this.buildUrl(endpoint.path, options?.searchParams);
362
+ const headers = this.mergeHeaders(options?.headers);
363
+ let lastError = new Error("No attempts made");
364
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
365
+ try {
366
+ const response = await this.fetchWithTimeout(url, {
367
+ method: endpoint.method,
368
+ headers,
369
+ body: options?.body ? JSON.stringify(options.body) : void 0,
370
+ signal: options?.signal
371
+ });
372
+ if (!response.ok) {
373
+ throw new HTTPError(
374
+ response.status,
375
+ response.statusText,
376
+ url.toString(),
377
+ endpoint.method
378
+ );
379
+ }
380
+ const text = await response.text();
381
+ if (!text) {
382
+ return {};
383
+ }
384
+ try {
385
+ return JSON.parse(text);
386
+ } catch {
387
+ throw new Error(
388
+ `Invalid JSON response from ${endpoint.path}: ${text.slice(0, 100)}`
389
+ );
390
+ }
391
+ } catch (error) {
392
+ lastError = error instanceof Error ? error : new Error(String(error));
393
+ if (error instanceof HTTPError && error.statusCode >= 400 && error.statusCode < 500 || error instanceof Error && error.name === "AbortError") {
394
+ throw this.enhanceError(lastError, endpoint);
395
+ }
396
+ if (attempt === this.maxRetries) {
397
+ throw this.enhanceError(lastError, endpoint);
398
+ }
399
+ const baseDelay = Math.min(
400
+ 1e3 * Math.pow(2, attempt),
401
+ MAX_BACKOFF_DELAY_MS
402
+ );
403
+ const jitter = Math.random() * BACKOFF_JITTER_MS;
404
+ const totalDelay = Math.min(baseDelay + jitter, MAX_BACKOFF_DELAY_MS);
405
+ await this.delay(totalDelay);
406
+ }
407
+ }
408
+ throw this.enhanceError(lastError, endpoint);
409
+ }
410
+ async fetchWithTimeout(url, init) {
411
+ const controller = new AbortController();
412
+ if (init.signal) {
413
+ init.signal.addEventListener("abort", () => controller.abort());
414
+ }
415
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
416
+ try {
417
+ return await fetch(url, {
418
+ ...init,
419
+ signal: controller.signal,
420
+ keepalive: true
421
+ });
422
+ } finally {
423
+ clearTimeout(timeoutId);
424
+ }
425
+ }
426
+ buildUrl(path, params) {
427
+ const url = new URL(path, this.baseUrl);
428
+ if (params) {
429
+ const searchParams = new URLSearchParams();
430
+ for (const [key, value] of Object.entries(params)) {
431
+ if (value !== void 0 && value !== null) {
432
+ searchParams.set(key, String(value));
433
+ }
434
+ }
435
+ url.search = searchParams.toString();
436
+ }
437
+ return url;
438
+ }
439
+ mergeHeaders(headers) {
440
+ if (!headers) {
441
+ return this.defaultHeaders;
442
+ }
443
+ const merged = new Headers(this.defaultHeaders);
444
+ for (const [key, value] of Object.entries(headers)) {
445
+ merged.set(key, value);
446
+ }
447
+ return merged;
448
+ }
449
+ enhanceError(error, endpoint) {
450
+ const enhanced = new Error(
451
+ `API request failed for ${endpoint.path}: ${error.message}
452
+ See docs: ${endpoint.docsUrl}`
453
+ );
454
+ enhanced.stack = error.stack;
455
+ enhanced.cause = error;
456
+ return enhanced;
457
+ }
458
+ getCacheKey(endpoint, options) {
459
+ const params = options?.searchParams ? JSON.stringify(options.searchParams) : "";
460
+ return `${endpoint.method}:${endpoint.path}:${params}`;
461
+ }
462
+ delay(ms) {
463
+ return new Promise((resolve) => setTimeout(resolve, ms));
464
+ }
465
+ getRateLimiter(token) {
466
+ if (!this.rateLimiters.has(token)) {
467
+ this.rateLimiters.set(token, {
468
+ tokens: this.maxTokensPerMinute,
469
+ lastRefill: Date.now(),
470
+ refillRate: this.maxTokensPerMinute
471
+ });
472
+ }
473
+ return this.rateLimiters.get(token);
474
+ }
475
+ async checkRateLimit(token, endpoint) {
476
+ const limiter = this.getRateLimiter(token);
477
+ const now = Date.now();
478
+ const timeSinceLastRefill = now - limiter.lastRefill;
479
+ const minutesElapsed = timeSinceLastRefill / 6e4;
480
+ const tokensToAdd = minutesElapsed * limiter.refillRate;
481
+ limiter.tokens = Math.min(
482
+ this.maxTokensPerMinute,
483
+ limiter.tokens + tokensToAdd
484
+ );
485
+ limiter.lastRefill = now;
486
+ if (limiter.tokens < 1) {
487
+ const waitTime = (1 - limiter.tokens) * (6e4 / limiter.refillRate);
488
+ throw new RateLimitError(Math.ceil(waitTime), endpoint);
489
+ }
490
+ limiter.tokens -= 1;
491
+ }
492
+ cleanupRateLimiters() {
493
+ const now = Date.now();
494
+ const maxAge = RATE_LIMITER_CLEANUP_AGE_MS;
495
+ for (const [token, limiter] of this.rateLimiters.entries()) {
496
+ if (now - limiter.lastRefill > maxAge) {
497
+ this.rateLimiters.delete(token);
498
+ }
499
+ }
500
+ }
501
+ };
502
+
503
+ // src/adapters/vercel/endpoints.ts
504
+ var VercelEndpoints = {
505
+ listDeployments: {
506
+ path: "v6/deployments",
507
+ method: "GET",
508
+ docsUrl: "https://vercel.com/docs/rest-api/endpoints/deployments#list-deployments",
509
+ description: "List deployments for authenticated user or team"
510
+ },
511
+ getDeployment: {
512
+ path: "v13/deployments",
513
+ method: "GET",
514
+ docsUrl: "https://vercel.com/docs/rest-api/endpoints/deployments#get-a-deployment-by-id-or-url",
515
+ description: "Get deployment by ID or URL"
516
+ },
517
+ getDeploymentEvents: {
518
+ path: "v2/deployments",
519
+ method: "GET",
520
+ docsUrl: "https://vercel.com/docs/rest-api/endpoints/deployments#get-deployment-events",
521
+ description: "Get build logs and events for a deployment"
522
+ },
523
+ getUser: {
524
+ path: "v2/user",
525
+ method: "GET",
526
+ docsUrl: "https://vercel.com/docs/rest-api/endpoints/user#get-the-authenticated-user",
527
+ description: "Get authenticated user information"
528
+ }
529
+ };
530
+
531
+ // src/adapters/vercel/api.ts
532
+ var VercelAPI = class extends BaseAPIClient {
533
+ endpoints = VercelEndpoints;
534
+ config;
535
+ constructor(config) {
536
+ super({
537
+ baseUrl: config.baseUrl,
538
+ timeout: config.timeout,
539
+ retry: config.retryAttempts
540
+ });
541
+ this.config = config;
542
+ }
543
+ async getDeployments(projectId, token, limit = 1) {
544
+ try {
545
+ return await this.request(
546
+ this.endpoints.listDeployments,
547
+ {
548
+ searchParams: { projectId, limit },
549
+ headers: {
550
+ Authorization: `Bearer ${token}`
551
+ },
552
+ token
553
+ // Pass token for rate limiting
554
+ }
555
+ );
556
+ } catch (error) {
557
+ throw this.handleApiError(
558
+ error,
559
+ `Failed to fetch deployments for project ${projectId}`
560
+ );
561
+ }
562
+ }
563
+ async getUser(token) {
564
+ try {
565
+ return await this.request(this.endpoints.getUser, {
566
+ headers: {
567
+ Authorization: `Bearer ${token}`
568
+ },
569
+ token
570
+ // Pass token for rate limiting
571
+ });
572
+ } catch (error) {
573
+ throw this.handleApiError(error, API_MESSAGES.FAILED_TO_VALIDATE_TOKEN);
574
+ }
575
+ }
576
+ async getDeploymentById(deploymentId, token) {
577
+ try {
578
+ const endpoint = {
579
+ ...this.endpoints.getDeployment,
580
+ path: `${this.endpoints.getDeployment.path}/${deploymentId}`
581
+ };
582
+ return await this.request(endpoint, {
583
+ headers: {
584
+ Authorization: `Bearer ${token}`
585
+ },
586
+ token
587
+ // Pass token for rate limiting
588
+ });
589
+ } catch (error) {
590
+ throw this.handleApiError(
591
+ error,
592
+ `Failed to fetch deployment ${deploymentId}`
593
+ );
594
+ }
595
+ }
596
+ async getDeploymentLogs(deploymentId, token) {
597
+ try {
598
+ const endpoint = {
599
+ ...this.endpoints.getDeploymentEvents,
600
+ path: `${this.endpoints.getDeploymentEvents.path}/${deploymentId}/events`
601
+ };
602
+ const response = await this.request(endpoint, {
603
+ searchParams: {
604
+ builds: API_PARAMS.BUILDS,
605
+ logs: API_PARAMS.LOGS
606
+ },
607
+ headers: {
608
+ Authorization: `Bearer ${token}`
609
+ },
610
+ token
611
+ // Pass token for rate limiting
612
+ });
613
+ if (!response || !Array.isArray(response)) {
614
+ return API_MESSAGES.NO_LOGS_AVAILABLE;
615
+ }
616
+ const logs = response.filter(
617
+ (event) => event.type === API_EVENT_TYPES.STDOUT || event.type === API_EVENT_TYPES.STDERR
618
+ ).map((event) => event.payload?.text || event.text || "").join("\n");
619
+ const sanitizedLogs = logs.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;").replace(/\//g, "&#x2F;");
620
+ return sanitizedLogs || API_MESSAGES.NO_LOGS_AVAILABLE;
621
+ } catch (error) {
622
+ throw this.handleApiError(
623
+ error,
624
+ `Failed to fetch logs for deployment ${deploymentId}`
625
+ );
626
+ }
627
+ }
628
+ handleApiError(error, context) {
629
+ if (error instanceof Error) {
630
+ const message = error.message.toLowerCase();
631
+ if (ERROR_TEXT_PATTERNS.UNAUTHORIZED.some(
632
+ (pattern) => message.includes(pattern)
633
+ )) {
634
+ return new AdapterException(
635
+ "UNAUTHORIZED",
636
+ API_MESSAGES.INVALID_TOKEN,
637
+ error
638
+ );
639
+ }
640
+ if (ERROR_TEXT_PATTERNS.NOT_FOUND.some((pattern) => message.includes(pattern))) {
641
+ return new AdapterException(
642
+ "NOT_FOUND",
643
+ API_MESSAGES.PROJECT_NOT_FOUND,
644
+ error
645
+ );
646
+ }
647
+ if (ERROR_TEXT_PATTERNS.RATE_LIMITED.some(
648
+ (pattern) => message.includes(pattern)
649
+ )) {
650
+ return new AdapterException(
651
+ "RATE_LIMITED",
652
+ API_MESSAGES.RATE_LIMIT_EXCEEDED,
653
+ error
654
+ );
655
+ }
656
+ if (ERROR_TEXT_PATTERNS.TIMEOUT.some(
657
+ (pattern) => message.includes(pattern) || error.name === pattern
658
+ )) {
659
+ return new AdapterException(
660
+ "NETWORK_ERROR",
661
+ API_MESSAGES.REQUEST_TIMEOUT,
662
+ error
663
+ );
664
+ }
665
+ return new AdapterException("NETWORK_ERROR", context, error);
666
+ }
667
+ return new AdapterException("UNKNOWN_ERROR", context);
668
+ }
669
+ };
670
+
671
+ // src/adapters/vercel/index.ts
672
+ var VercelAdapter = class extends BaseAdapter {
673
+ name = PLATFORM_NAMES.VERCEL;
674
+ api;
675
+ constructor(config) {
676
+ super();
677
+ const defaultConfig = {
678
+ baseUrl: API_CONFIG.VERCEL_BASE_URL,
679
+ timeout: API_CONFIG.DEFAULT_TIMEOUT_MS,
680
+ retryAttempts: API_CONFIG.DEFAULT_RETRY_ATTEMPTS
681
+ };
682
+ this.api = new VercelAPI({ ...defaultConfig, ...config });
683
+ }
684
+ async getLatestDeployment(project, token) {
685
+ const apiToken = token || process.env.VERCEL_TOKEN;
686
+ if (!apiToken) {
687
+ throw new Error(ADAPTER_ERRORS.TOKEN_REQUIRED);
688
+ }
689
+ try {
690
+ const data = await this.api.getDeployments(
691
+ project,
692
+ apiToken,
693
+ API_CONFIG.SINGLE_DEPLOYMENT_LIMIT
694
+ );
695
+ if (!data.deployments || data.deployments.length === 0) {
696
+ return {
697
+ status: ADAPTER_ERRORS.UNKNOWN_STATUS,
698
+ projectName: project,
699
+ platform: PLATFORM_NAMES.VERCEL
700
+ };
701
+ }
702
+ return this.transformDeployment(data.deployments[0]);
703
+ } catch (error) {
704
+ if (error instanceof Error) {
705
+ throw error;
706
+ }
707
+ throw new Error(ADAPTER_ERRORS.FETCH_DEPLOYMENT_FAILED);
708
+ }
709
+ }
710
+ async authenticate(token) {
711
+ try {
712
+ await this.api.getUser(token);
713
+ return true;
714
+ } catch {
715
+ return false;
716
+ }
717
+ }
718
+ transformDeployment(deployment) {
719
+ const status = this.mapState(deployment.state);
720
+ return {
721
+ id: deployment.uid,
722
+ status,
723
+ url: deployment.url ? `https://${deployment.url}` : void 0,
724
+ projectName: deployment.name,
725
+ platform: PLATFORM_NAMES.VERCEL,
726
+ timestamp: this.formatTimestamp(deployment.createdAt),
727
+ duration: deployment.ready ? this.calculateDuration(deployment.createdAt, deployment.ready) : void 0,
728
+ environment: deployment.target || ENVIRONMENT_TYPES.PRODUCTION,
729
+ commit: deployment.meta ? {
730
+ sha: deployment.meta.githubCommitSha,
731
+ message: deployment.meta.githubCommitMessage,
732
+ author: deployment.meta.githubCommitAuthorName
733
+ } : void 0
734
+ };
735
+ }
736
+ mapState(state) {
737
+ switch (state) {
738
+ case VERCEL_STATES.READY:
739
+ return "success";
740
+ case VERCEL_STATES.ERROR:
741
+ case VERCEL_STATES.CANCELED:
742
+ return "failed";
743
+ case VERCEL_STATES.BUILDING:
744
+ case VERCEL_STATES.INITIALIZING:
745
+ case VERCEL_STATES.QUEUED:
746
+ return "building";
747
+ default:
748
+ return ADAPTER_ERRORS.UNKNOWN_STATUS;
749
+ }
750
+ }
751
+ async getDeploymentById(deploymentId, token) {
752
+ return this.api.getDeploymentById(deploymentId, token);
753
+ }
754
+ async getRecentDeployments(project, token, limit = API_CONFIG.DEFAULT_DEPLOYMENT_LIMIT) {
755
+ const data = await this.api.getDeployments(project, token, limit);
756
+ return data.deployments || [];
757
+ }
758
+ async getDeploymentLogs(deploymentId, token) {
759
+ return this.api.getDeploymentLogs(deploymentId, token);
760
+ }
761
+ async getDeploymentStatus(project, token) {
762
+ return this.getLatestDeployment(project, token);
763
+ }
764
+ };
765
+
766
+ // src/core/deployment-intelligence.ts
767
+ var DeploymentIntelligence = class {
768
+ adapter;
769
+ constructor(platform) {
770
+ this.adapter = this.createAdapter(platform);
771
+ }
772
+ getPollingInterval(state) {
773
+ return POLLING_INTERVALS_BY_STATE[state] || POLLING_INTERVALS_BY_STATE.UNKNOWN;
774
+ }
775
+ getTimeAgo(timestamp) {
776
+ const now = Date.now();
777
+ const diff = now - timestamp;
778
+ const seconds = Math.floor(diff / 1e3);
779
+ const minutes = Math.floor(seconds / 60);
780
+ const hours = Math.floor(minutes / 60);
781
+ const days = Math.floor(hours / 24);
782
+ if (days > 0) return `${days} day${days > 1 ? "s" : ""} ago`;
783
+ if (hours > 0) return `${hours} hour${hours > 1 ? "s" : ""} ago`;
784
+ if (minutes > 0) return `${minutes} minute${minutes > 1 ? "s" : ""} ago`;
785
+ return `${seconds} second${seconds > 1 ? "s" : ""} ago`;
786
+ }
787
+ mapDeploymentState(state) {
788
+ switch (state) {
789
+ case "READY":
790
+ return "success";
791
+ case "ERROR":
792
+ case "CANCELED":
793
+ return "failed";
794
+ case "BUILDING":
795
+ case "INITIALIZING":
796
+ case "QUEUED":
797
+ return "building";
798
+ default:
799
+ return "unknown";
800
+ }
801
+ }
802
+ createAdapter(platform) {
803
+ switch (platform) {
804
+ case "vercel":
805
+ return new VercelAdapter();
806
+ // Ready for future platforms
807
+ // case "netlify":
808
+ // return new NetlifyAdapter();
809
+ // case "railway":
810
+ // return new RailwayAdapter();
811
+ // case "render":
812
+ // return new RenderAdapter();
813
+ default:
814
+ throw new Error(`Unsupported platform: ${platform}`);
815
+ }
816
+ }
817
+ async *watchDeployment(args) {
818
+ const token = args.token || process.env.VERCEL_TOKEN;
819
+ if (!token) {
820
+ yield {
821
+ type: "error",
822
+ message: ERROR_MESSAGES.NO_TOKEN,
823
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
824
+ };
825
+ return;
826
+ }
827
+ try {
828
+ let deploymentId = args.deploymentId;
829
+ if (!deploymentId) {
830
+ const deployments = await this.adapter.getRecentDeployments(
831
+ args.project,
832
+ token,
833
+ SINGLE_DEPLOYMENT_FETCH
834
+ );
835
+ if (deployments.length > 0) {
836
+ deploymentId = deployments[0].uid || deployments[0].id;
837
+ }
838
+ }
839
+ if (!deploymentId) {
840
+ yield {
841
+ type: "error",
842
+ message: ERROR_MESSAGES.NO_DEPLOYMENT_FOUND,
843
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
844
+ };
845
+ return;
846
+ }
847
+ yield {
848
+ type: "progress",
849
+ message: STATUS_MESSAGES.STARTING_WATCH(
850
+ deploymentId.slice(0, DEFAULTS.DEPLOYMENT_ID_SLICE_LENGTH)
851
+ ),
852
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
853
+ };
854
+ let lastState = "";
855
+ let attempts = 0;
856
+ const maxAttempts = MAX_DEPLOYMENT_WATCH_ATTEMPTS;
857
+ const startTime = Date.now();
858
+ const maxWatchTime = MAX_WATCH_TIME_MS;
859
+ while (attempts < maxAttempts && Date.now() - startTime < maxWatchTime) {
860
+ try {
861
+ const deployment = await this.adapter.getDeploymentById(
862
+ deploymentId,
863
+ token
864
+ );
865
+ if (deployment.readyState !== lastState) {
866
+ lastState = deployment.readyState;
867
+ switch (deployment.readyState) {
868
+ case DEPLOYMENT_STATES.INITIALIZING:
869
+ yield {
870
+ type: "progress",
871
+ message: STATUS_MESSAGES.INITIALIZING,
872
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
873
+ };
874
+ break;
875
+ case DEPLOYMENT_STATES.BUILDING:
876
+ yield {
877
+ type: "progress",
878
+ message: STATUS_MESSAGES.BUILDING,
879
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
880
+ };
881
+ break;
882
+ case DEPLOYMENT_STATES.UPLOADING:
883
+ yield {
884
+ type: "progress",
885
+ message: STATUS_MESSAGES.UPLOADING,
886
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
887
+ };
888
+ break;
889
+ case DEPLOYMENT_STATES.DEPLOYING:
890
+ yield {
891
+ type: "progress",
892
+ message: STATUS_MESSAGES.DEPLOYING,
893
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
894
+ };
895
+ break;
896
+ case DEPLOYMENT_STATES.READY: {
897
+ const duration = deployment.buildingAt && deployment.ready ? deployment.ready - deployment.buildingAt : void 0;
898
+ yield {
899
+ type: "success",
900
+ message: STATUS_MESSAGES.DEPLOYMENT_SUCCESS,
901
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
902
+ details: {
903
+ url: deployment.url,
904
+ duration: duration ? Math.round(duration / BUILD_TIME_SECONDS_DIVISOR) : void 0
905
+ }
906
+ };
907
+ try {
908
+ const comparison = await this.compareWithPrevious(
909
+ args.project,
910
+ deploymentId,
911
+ token
912
+ );
913
+ if (comparison) {
914
+ yield {
915
+ type: "progress",
916
+ message: this.formatComparison(comparison),
917
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
918
+ };
919
+ }
920
+ } catch (e) {
921
+ console.error("Failed to compare deployments:", e);
922
+ }
923
+ return;
924
+ }
925
+ case DEPLOYMENT_STATES.ERROR:
926
+ case DEPLOYMENT_STATES.CANCELED: {
927
+ const errorDetails = await this.analyzeError(
928
+ deploymentId,
929
+ token
930
+ );
931
+ yield {
932
+ type: "error",
933
+ message: STATUS_MESSAGES.DEPLOYMENT_FAILED(
934
+ errorDetails.message
935
+ ),
936
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
937
+ details: {
938
+ suggestion: errorDetails.suggestion,
939
+ file: errorDetails.location
940
+ }
941
+ };
942
+ return;
943
+ }
944
+ }
945
+ }
946
+ if (deployment.readyState === DEPLOYMENT_STATES.READY || deployment.readyState === DEPLOYMENT_STATES.ERROR || deployment.readyState === DEPLOYMENT_STATES.CANCELED) {
947
+ break;
948
+ }
949
+ const pollInterval = this.getPollingInterval(lastState || "UNKNOWN");
950
+ if (pollInterval === 0) {
951
+ break;
952
+ }
953
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
954
+ attempts++;
955
+ } catch (error) {
956
+ console.error("Error checking deployment status:", error);
957
+ attempts++;
958
+ const pollInterval = this.getPollingInterval(lastState || "UNKNOWN");
959
+ if (pollInterval === 0) {
960
+ break;
961
+ }
962
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
963
+ }
964
+ }
965
+ if (attempts >= maxAttempts || Date.now() - startTime >= MAX_WATCH_TIME_MS) {
966
+ const timeoutSeconds = Math.round((Date.now() - startTime) / 1e3);
967
+ yield {
968
+ type: "warning",
969
+ message: `Deployment watch timed out after ${timeoutSeconds} seconds. The deployment may still be running.`,
970
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
971
+ details: {
972
+ suggestion: "Check the platform dashboard for the latest status"
973
+ }
974
+ };
975
+ }
976
+ } catch (error) {
977
+ yield {
978
+ type: "error",
979
+ message: `Error watching deployment: ${error instanceof Error ? error.message : "Unknown error"}`,
980
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
981
+ };
982
+ }
983
+ }
984
+ async compareDeployments(args) {
985
+ const token = args.token || process.env.VERCEL_TOKEN;
986
+ if (!token) {
987
+ throw new Error("No Vercel token provided");
988
+ }
989
+ try {
990
+ const deployments = await this.adapter.getRecentDeployments(
991
+ args.project,
992
+ token,
993
+ args.count
994
+ );
995
+ if (deployments.length < 2) {
996
+ return null;
997
+ }
998
+ const [current, previous] = deployments;
999
+ const currentBuildTime = current.buildingAt && current.ready ? Math.round(
1000
+ (current.ready - current.buildingAt) / BUILD_TIME_SECONDS_DIVISOR
1001
+ ) : 0;
1002
+ const previousBuildTime = previous.buildingAt && previous.ready ? Math.round(
1003
+ (previous.ready - previous.buildingAt) / BUILD_TIME_SECONDS_DIVISOR
1004
+ ) : 0;
1005
+ const comparison = {
1006
+ deployments: {
1007
+ current: {
1008
+ id: current.uid || current.id,
1009
+ url: current.url ? `https://${current.url}` : void 0,
1010
+ timestamp: new Date(current.createdAt).toISOString(),
1011
+ commit: current.meta ? {
1012
+ sha: current.meta.githubCommitSha,
1013
+ message: current.meta.githubCommitMessage,
1014
+ author: current.meta.githubCommitAuthorName
1015
+ } : void 0,
1016
+ buildTime: currentBuildTime,
1017
+ status: this.mapDeploymentState(
1018
+ current.state || current.readyState
1019
+ ),
1020
+ timeAgo: this.getTimeAgo(current.createdAt)
1021
+ },
1022
+ previous: {
1023
+ id: previous.uid || previous.id,
1024
+ url: previous.url ? `https://${previous.url}` : void 0,
1025
+ timestamp: new Date(previous.createdAt).toISOString(),
1026
+ commit: previous.meta ? {
1027
+ sha: previous.meta.githubCommitSha,
1028
+ message: previous.meta.githubCommitMessage,
1029
+ author: previous.meta.githubCommitAuthorName
1030
+ } : void 0,
1031
+ buildTime: previousBuildTime,
1032
+ status: this.mapDeploymentState(
1033
+ previous.state || previous.readyState
1034
+ ),
1035
+ timeAgo: this.getTimeAgo(previous.createdAt)
1036
+ }
1037
+ },
1038
+ performance: {
1039
+ buildTime: {
1040
+ current: currentBuildTime,
1041
+ previous: previousBuildTime,
1042
+ delta: 0,
1043
+ percentage: 0
1044
+ }
1045
+ },
1046
+ changes: {
1047
+ filesChanged: 0
1048
+ },
1049
+ risk: "LOW"
1050
+ };
1051
+ comparison.performance.buildTime.delta = comparison.performance.buildTime.current - comparison.performance.buildTime.previous;
1052
+ comparison.performance.buildTime.percentage = comparison.performance.buildTime.previous > 0 ? Math.round(
1053
+ comparison.performance.buildTime.delta / comparison.performance.buildTime.previous * DEFAULTS.PERCENTAGE_MULTIPLIER
1054
+ ) : 0;
1055
+ if (Math.abs(comparison.performance.buildTime.percentage) > HIGH_RISK_THRESHOLD_PERCENT) {
1056
+ comparison.risk = "HIGH";
1057
+ } else if (Math.abs(comparison.performance.buildTime.percentage) > MEDIUM_RISK_THRESHOLD_PERCENT) {
1058
+ comparison.risk = "MEDIUM";
1059
+ }
1060
+ return comparison;
1061
+ } catch (error) {
1062
+ console.error("Error comparing deployments:", error);
1063
+ return null;
1064
+ }
1065
+ }
1066
+ async getDeploymentLogs(args) {
1067
+ const token = args.token || process.env.VERCEL_TOKEN;
1068
+ if (!token) {
1069
+ throw new Error("No Vercel token provided");
1070
+ }
1071
+ try {
1072
+ const logs = await this.adapter.getDeploymentLogs(
1073
+ args.deploymentId,
1074
+ token
1075
+ );
1076
+ let filteredLogs = logs;
1077
+ if (args.filter === "error") {
1078
+ const lines = logs.split("\n");
1079
+ filteredLogs = lines.filter((line) => LOG_FILTERS.ERROR.test(line)).join("\n");
1080
+ } else if (args.filter === "warning") {
1081
+ const lines = logs.split("\n");
1082
+ filteredLogs = lines.filter((line) => LOG_FILTERS.WARNING.test(line)).join("\n");
1083
+ }
1084
+ const analysis = this.analyzeLogs(filteredLogs);
1085
+ return {
1086
+ logs: filteredLogs || ERROR_MESSAGES.NO_LOGS_MATCHING_FILTER,
1087
+ analysis
1088
+ };
1089
+ } catch (error) {
1090
+ throw new Error(
1091
+ `Failed to get deployment logs: ${error instanceof Error ? error.message : "Unknown error"}`
1092
+ );
1093
+ }
1094
+ }
1095
+ async analyzeError(deploymentId, token) {
1096
+ try {
1097
+ const logs = await this.adapter.getDeploymentLogs(deploymentId, token);
1098
+ return this.analyzeLogs(logs);
1099
+ } catch {
1100
+ return {
1101
+ type: "UNKNOWN",
1102
+ message: "Deployment failed - unable to fetch logs",
1103
+ suggestion: "Check deployment platform dashboard"
1104
+ };
1105
+ }
1106
+ }
1107
+ analyzeLogs(logs) {
1108
+ const lowerLogs = logs.toLowerCase();
1109
+ const lines = logs.split("\n");
1110
+ let type = "UNKNOWN";
1111
+ let quickContext = "";
1112
+ let location;
1113
+ let errorLine;
1114
+ for (const line of lines) {
1115
+ const fileMatch = line.match(/([^\s:]+\.(tsx?|jsx?|js|ts)):(\d+):(\d+)/);
1116
+ if (fileMatch && line.toLowerCase().includes("error")) {
1117
+ location = `${fileMatch[1]}:${fileMatch[3]}:${fileMatch[4]}`;
1118
+ errorLine = line.trim();
1119
+ break;
1120
+ }
1121
+ }
1122
+ if (!errorLine) {
1123
+ errorLine = lines.find((line) => line.toLowerCase().includes("error"))?.trim() || "Check logs for details";
1124
+ }
1125
+ if (lowerLogs.includes("cannot find module") || lowerLogs.includes("modulenotfounderror")) {
1126
+ type = "MISSING_DEPENDENCY";
1127
+ quickContext = "Missing package/dependency";
1128
+ } else if (lowerLogs.includes("environment variable") || lowerLogs.includes("env var")) {
1129
+ type = "ENV_VAR";
1130
+ quickContext = "Environment variable issue";
1131
+ } else if (lowerLogs.includes("timeout") || lowerLogs.includes("time limit")) {
1132
+ type = "TIMEOUT";
1133
+ quickContext = "Build timeout exceeded";
1134
+ } else if (lowerLogs.includes("error") || lowerLogs.includes("failed")) {
1135
+ type = "BUILD";
1136
+ quickContext = "Build failed";
1137
+ }
1138
+ return {
1139
+ type,
1140
+ message: quickContext || errorLine,
1141
+ location,
1142
+ suggestion: location ? `Error at ${location} - check the file for issues` : "See full logs below for detailed analysis"
1143
+ };
1144
+ }
1145
+ async compareWithPrevious(project, currentDeploymentId, token) {
1146
+ try {
1147
+ const deployments = await this.adapter.getRecentDeployments(
1148
+ project,
1149
+ token,
1150
+ DEFAULT_COMPARISON_COUNT
1151
+ );
1152
+ if (deployments.length < 2) {
1153
+ return null;
1154
+ }
1155
+ const current = deployments.find((d) => d.id === currentDeploymentId) || deployments[0];
1156
+ const previous = deployments.find((d) => d.id !== currentDeploymentId) || deployments[1];
1157
+ const currentBuildTime = current.buildingAt && current.ready ? Math.round(
1158
+ (current.ready - current.buildingAt) / BUILD_TIME_SECONDS_DIVISOR
1159
+ ) : 0;
1160
+ const previousBuildTime = previous.buildingAt && previous.ready ? Math.round(
1161
+ (previous.ready - previous.buildingAt) / BUILD_TIME_SECONDS_DIVISOR
1162
+ ) : 0;
1163
+ const delta = currentBuildTime - previousBuildTime;
1164
+ const percentage = previousBuildTime > 0 ? Math.round(
1165
+ delta / previousBuildTime * DEFAULTS.PERCENTAGE_MULTIPLIER
1166
+ ) : 0;
1167
+ return {
1168
+ deployments: {
1169
+ current: {
1170
+ id: current.uid || current.id || "",
1171
+ url: current.url,
1172
+ timestamp: current.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
1173
+ status: current.state || "unknown",
1174
+ buildTime: currentBuildTime,
1175
+ timeAgo: this.getTimeAgo(new Date(current.createdAt).getTime())
1176
+ },
1177
+ previous: {
1178
+ id: previous.uid || previous.id || "",
1179
+ url: previous.url,
1180
+ timestamp: previous.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
1181
+ status: previous.state || "unknown",
1182
+ buildTime: previousBuildTime,
1183
+ timeAgo: this.getTimeAgo(new Date(previous.createdAt).getTime())
1184
+ }
1185
+ },
1186
+ performance: {
1187
+ buildTime: {
1188
+ current: currentBuildTime,
1189
+ previous: previousBuildTime,
1190
+ delta,
1191
+ percentage
1192
+ }
1193
+ },
1194
+ changes: {},
1195
+ risk: Math.abs(percentage) > HIGH_RISK_THRESHOLD_PERCENT ? "HIGH" : Math.abs(percentage) > MEDIUM_RISK_THRESHOLD_PERCENT ? "MEDIUM" : "LOW"
1196
+ };
1197
+ } catch (error) {
1198
+ console.error("Error comparing with previous deployment:", error);
1199
+ return null;
1200
+ }
1201
+ }
1202
+ formatComparison(comparison) {
1203
+ const { buildTime } = comparison.performance;
1204
+ if (buildTime.delta === 0) {
1205
+ return STATUS_MESSAGES.BUILD_TIME_SAME(buildTime.current);
1206
+ }
1207
+ const faster = buildTime.delta < 0;
1208
+ return STATUS_MESSAGES.BUILD_TIME_CHANGE(
1209
+ buildTime.current,
1210
+ buildTime.delta,
1211
+ faster
1212
+ );
1213
+ }
1214
+ };
1215
+
1216
+ // src/core/response-formatter.ts
1217
+ var ResponseFormatter = class {
1218
+ static formatComparison(data) {
1219
+ const current = data.deployments.current;
1220
+ const previous = data.deployments.previous;
1221
+ const perf = data.performance.buildTime;
1222
+ const display = `## Deployment Comparison
1223
+
1224
+ ### Current Deployment
1225
+ **Status:** ${current.status === "success" ? "\u2705 Success" : "\u274C Failed"}
1226
+ **URL:** ${current.url || "N/A"}
1227
+ **Time:** ${current.timeAgo}
1228
+ **Build Duration:** ${current.buildTime}s
1229
+
1230
+ **Commit:** \`${current.commit?.sha?.slice(0, 7) || "N/A"}\` ${current.commit?.message || "No message"}
1231
+ **Author:** ${current.commit?.author || "Unknown"}
1232
+
1233
+ ### Previous Deployment
1234
+ **Status:** ${previous.status === "success" ? "\u2705 Success" : "\u274C Failed"}
1235
+ **URL:** ${previous.url || "N/A"}
1236
+ **Time:** ${previous.timeAgo}
1237
+ **Build Duration:** ${previous.buildTime}s
1238
+
1239
+ **Commit:** \`${previous.commit?.sha?.slice(0, 7) || "N/A"}\` ${previous.commit?.message || "No message"}
1240
+ **Author:** ${previous.commit?.author || "Unknown"}
1241
+
1242
+ ### Performance Analysis
1243
+ **Build Time Change:** ${Math.abs(perf.delta)}s ${perf.delta < 0 ? "faster" : "slower"} (${perf.percentage}% ${perf.delta < 0 ? "improvement" : "increase"})
1244
+ **Risk Level:** ${data.risk === "LOW" ? "\u{1F7E2} Low" : data.risk === "MEDIUM" ? "\u{1F7E1} Medium" : "\u{1F534} High"}
1245
+ `;
1246
+ return {
1247
+ version: "1.0",
1248
+ tool: "compare_deployments",
1249
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1250
+ display,
1251
+ data,
1252
+ highlights: {
1253
+ url: current.url,
1254
+ status: current.status,
1255
+ duration: `${perf.delta}s ${perf.delta < 0 ? "faster" : "slower"}`
1256
+ }
1257
+ };
1258
+ }
1259
+ static formatLogs(logs, analysis, summary) {
1260
+ const truncatedLogs = logs.split("\n").slice(0, 30).join("\n");
1261
+ const isTruncated = logs.split("\n").length > 30;
1262
+ const display = `## Deployment Logs
1263
+
1264
+ ### Summary
1265
+ **Errors:** ${summary.errorCount === 0 ? "\u2705" : "\u274C"} ${summary.errorCount} error${summary.errorCount !== 1 ? "s" : ""}
1266
+ **Warnings:** ${summary.warningCount === 0 ? "\u2705" : "\u26A0\uFE0F"} ${summary.warningCount} warning${summary.warningCount !== 1 ? "s" : ""}
1267
+ **URL:** ${summary.deploymentUrl || "Not available"}
1268
+
1269
+ ${analysis.type !== "UNKNOWN" ? `### Error Analysis
1270
+ **Type:** ${analysis.type}
1271
+ **Message:** ${analysis.message}
1272
+ **Location:** ${analysis.location ? `\`${analysis.location}\`` : "Unknown"}
1273
+ **Suggested Fix:** ${analysis.suggestion || "Check the logs below for details"}
1274
+
1275
+ ` : ""}
1276
+ ### Logs
1277
+ \`\`\`
1278
+ ${truncatedLogs}
1279
+ ${isTruncated ? "\n... (truncated - showing first 30 lines)" : ""}
1280
+ \`\`\`
1281
+ `;
1282
+ return {
1283
+ version: "1.0",
1284
+ tool: "get_deployment_logs",
1285
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1286
+ display,
1287
+ data: { logs, analysis, summary },
1288
+ highlights: {
1289
+ url: summary.deploymentUrl,
1290
+ error: analysis.message,
1291
+ status: summary.hasErrors ? "error" : "success"
1292
+ }
1293
+ };
1294
+ }
1295
+ static formatStatus(status) {
1296
+ const statusIcon = status.status === "success" ? "\u2705 Success" : status.status === "building" ? "\u{1F504} Building" : status.status === "error" ? "\u274C Failed" : status.status;
1297
+ const display = `## Deployment Status
1298
+
1299
+ ### Current Status
1300
+ **Project:** ${status.projectName}
1301
+ **Platform:** ${status.platform}
1302
+ **Status:** ${statusIcon}
1303
+ **Environment:** ${status.environment || "production"}
1304
+ **URL:** ${status.url || "Not available"}
1305
+ **Duration:** ${status.duration ? `${status.duration}s` : "N/A"}
1306
+ **Deployed:** ${status.timestamp}
1307
+
1308
+ ${status.commit ? `### Commit Info
1309
+ **SHA:** \`${status.commit.sha}\`
1310
+ **Message:** ${status.commit.message}
1311
+ **Author:** ${status.commit.author}
1312
+ ` : ""}`;
1313
+ return {
1314
+ version: "1.0",
1315
+ tool: "check_deployment_status",
1316
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1317
+ display,
1318
+ data: status,
1319
+ highlights: {
1320
+ url: status.url,
1321
+ status: status.status,
1322
+ duration: status.duration ? `${status.duration}s` : void 0
1323
+ }
1324
+ };
1325
+ }
1326
+ static formatWatchEvent(event) {
1327
+ const icons = {
1328
+ progress: "\u{1F504}",
1329
+ success: "\u2705",
1330
+ error: "\u274C",
1331
+ warning: "\u26A0\uFE0F"
1332
+ };
1333
+ let formatted = `${icons[event.type] || ""} **${event.message}**`;
1334
+ if (event.details) {
1335
+ if (event.details.url) {
1336
+ formatted += `
1337
+ URL: ${event.details.url}`;
1338
+ }
1339
+ if (event.details.duration) {
1340
+ formatted += `
1341
+ Duration: ${event.details.duration}s`;
1342
+ }
1343
+ if (event.details.suggestion) {
1344
+ formatted += `
1345
+ \u{1F4A1} ${event.details.suggestion}`;
1346
+ }
1347
+ if (event.details.file) {
1348
+ formatted += `
1349
+ File: \`${event.details.file}\``;
1350
+ }
1351
+ }
1352
+ return formatted;
1353
+ }
1354
+ };
1355
+
1356
+ // src/core/mcp-handler.ts
1357
+ var MCPHandler = class {
1358
+ constructor(adapters) {
1359
+ this.adapters = adapters;
1360
+ this.startCacheCleanup();
1361
+ }
1362
+ deploymentIntelligenceCache = /* @__PURE__ */ new Map();
1363
+ cacheTimestamps = /* @__PURE__ */ new Map();
1364
+ maxCacheAge = DEFAULTS.CACHE_TTL_MS;
1365
+ cleanupTimer;
1366
+ getDeploymentIntelligence(platform) {
1367
+ const now = Date.now();
1368
+ const cached = this.deploymentIntelligenceCache.get(platform);
1369
+ const timestamp = this.cacheTimestamps.get(platform);
1370
+ if (cached && timestamp && now - timestamp < this.maxCacheAge) {
1371
+ this.cacheTimestamps.set(platform, now);
1372
+ return cached;
1373
+ }
1374
+ if (cached) {
1375
+ this.deploymentIntelligenceCache.delete(platform);
1376
+ this.cacheTimestamps.delete(platform);
1377
+ }
1378
+ const instance = new DeploymentIntelligence(platform);
1379
+ this.deploymentIntelligenceCache.set(platform, instance);
1380
+ this.cacheTimestamps.set(platform, now);
1381
+ if (this.deploymentIntelligenceCache.size > DEFAULTS.MAX_CACHE_SIZE) {
1382
+ this.evictOldestEntry();
1383
+ }
1384
+ return instance;
1385
+ }
1386
+ evictOldestEntry() {
1387
+ let oldestPlatform;
1388
+ let oldestTime = Date.now();
1389
+ for (const [platform, timestamp] of this.cacheTimestamps.entries()) {
1390
+ if (timestamp < oldestTime) {
1391
+ oldestTime = timestamp;
1392
+ oldestPlatform = platform;
1393
+ }
1394
+ }
1395
+ if (oldestPlatform) {
1396
+ this.deploymentIntelligenceCache.delete(oldestPlatform);
1397
+ this.cacheTimestamps.delete(oldestPlatform);
1398
+ }
1399
+ }
1400
+ startCacheCleanup() {
1401
+ this.cleanupTimer = setInterval(() => {
1402
+ const now = Date.now();
1403
+ const platformsToDelete = [];
1404
+ for (const [platform, timestamp] of this.cacheTimestamps.entries()) {
1405
+ if (now - timestamp > this.maxCacheAge) {
1406
+ platformsToDelete.push(platform);
1407
+ }
1408
+ }
1409
+ for (const platform of platformsToDelete) {
1410
+ this.deploymentIntelligenceCache.delete(platform);
1411
+ this.cacheTimestamps.delete(platform);
1412
+ }
1413
+ }, DEFAULTS.CACHE_CLEANUP_INTERVAL_MS);
1414
+ }
1415
+ dispose() {
1416
+ if (this.cleanupTimer) {
1417
+ clearInterval(this.cleanupTimer);
1418
+ }
1419
+ this.deploymentIntelligenceCache.clear();
1420
+ this.cacheTimestamps.clear();
1421
+ }
1422
+ async handleToolCall(tool, args) {
1423
+ switch (tool) {
1424
+ case "check_deployment_status":
1425
+ return this.checkDeploymentStatus(args);
1426
+ case "watch_deployment":
1427
+ return this.watchDeployment(args);
1428
+ case "compare_deployments":
1429
+ return this.compareDeployments(args);
1430
+ case "get_deployment_logs":
1431
+ return this.getDeploymentLogs(args);
1432
+ default:
1433
+ throw new Error(`Unknown tool: ${tool}`);
1434
+ }
1435
+ }
1436
+ async handleRequest(request) {
1437
+ if (request.method === "tools/call") {
1438
+ const { name, arguments: args } = request.params;
1439
+ const result = await this.handleToolCall(name, args);
1440
+ const text = result.display ? result.display : JSON.stringify(result, null, 2);
1441
+ return {
1442
+ content: [
1443
+ {
1444
+ type: "text",
1445
+ text
1446
+ }
1447
+ ]
1448
+ };
1449
+ }
1450
+ if (request.method === "tools/list") {
1451
+ return {
1452
+ tools: tools.map((tool) => ({
1453
+ ...tool,
1454
+ inputSchema: this.schemaToJsonSchema(tool.inputSchema)
1455
+ }))
1456
+ };
1457
+ }
1458
+ throw new Error(`Unknown method: ${request.method}`);
1459
+ }
1460
+ async checkDeploymentStatus(args) {
1461
+ const validated = checkDeploymentStatusSchema.parse(args);
1462
+ const adapter = this.adapters.get(validated.platform);
1463
+ if (!adapter) {
1464
+ throw new Error(`Unsupported platform: ${validated.platform}`);
1465
+ }
1466
+ try {
1467
+ const status = await adapter.getLatestDeployment(
1468
+ validated.project,
1469
+ validated.token
1470
+ );
1471
+ const formattedStatus = this.formatResponse(status, validated.platform);
1472
+ return ResponseFormatter.formatStatus(formattedStatus);
1473
+ } catch (error) {
1474
+ console.error(`Error checking deployment status: ${error}`);
1475
+ const errorStatus = {
1476
+ status: "error",
1477
+ platform: validated.platform,
1478
+ projectName: validated.project,
1479
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1480
+ };
1481
+ return ResponseFormatter.formatStatus(errorStatus);
1482
+ }
1483
+ }
1484
+ formatResponse(status, platform) {
1485
+ return {
1486
+ ...status,
1487
+ platform,
1488
+ timestamp: status.timestamp || (/* @__PURE__ */ new Date()).toISOString()
1489
+ };
1490
+ }
1491
+ async watchDeployment(args) {
1492
+ const validated = watchDeploymentSchema.parse(args);
1493
+ const intelligence = this.getDeploymentIntelligence(validated.platform);
1494
+ const messages = [];
1495
+ const events = [];
1496
+ const generator = intelligence.watchDeployment(validated);
1497
+ for await (const event of generator) {
1498
+ const formatted = ResponseFormatter.formatWatchEvent(event);
1499
+ messages.push(formatted);
1500
+ events.push(event);
1501
+ }
1502
+ const display = `## Deployment Watch
1503
+
1504
+ ### Real-time Updates
1505
+ ${messages.join("\n\n")}
1506
+ `;
1507
+ const finalEvent = events[events.length - 1];
1508
+ const status = finalEvent?.type === "success" ? "success" : finalEvent?.type === "error" ? "error" : "in_progress";
1509
+ return {
1510
+ version: "1.0",
1511
+ tool: "watch_deployment",
1512
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1513
+ display,
1514
+ data: { events },
1515
+ highlights: {
1516
+ status,
1517
+ url: finalEvent?.details?.url,
1518
+ duration: finalEvent?.details?.duration ? `${finalEvent.details.duration}s` : void 0
1519
+ }
1520
+ };
1521
+ }
1522
+ async compareDeployments(args) {
1523
+ const validated = compareDeploymentsSchema.parse(args);
1524
+ const intelligence = this.getDeploymentIntelligence(validated.platform);
1525
+ const comparison = await intelligence.compareDeployments(validated);
1526
+ if (!comparison) {
1527
+ return {
1528
+ version: "1.0",
1529
+ tool: "compare_deployments",
1530
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1531
+ display: "Not enough deployments to compare",
1532
+ data: { message: "Not enough deployments to compare" },
1533
+ highlights: { status: "error" }
1534
+ };
1535
+ }
1536
+ return ResponseFormatter.formatComparison(comparison);
1537
+ }
1538
+ async getDeploymentLogs(args) {
1539
+ const validated = getDeploymentLogsSchema.parse(args);
1540
+ const intelligence = this.getDeploymentIntelligence(validated.platform);
1541
+ const result = await intelligence.getDeploymentLogs(validated);
1542
+ const summary = {
1543
+ errorCount: (result.logs.match(/error/gi) || []).length,
1544
+ warningCount: (result.logs.match(/warning/gi) || []).length,
1545
+ deploymentUrl: null,
1546
+ hasErrors: result.analysis?.type !== "UNKNOWN"
1547
+ };
1548
+ return ResponseFormatter.formatLogs(
1549
+ result.logs,
1550
+ result.analysis || { type: "UNKNOWN", message: "No errors detected" },
1551
+ summary
1552
+ );
1553
+ }
1554
+ schemaToJsonSchema(zodSchema) {
1555
+ return JSON.parse(JSON.stringify(zodSchema));
1556
+ }
1557
+ };
1558
+
1559
+ export {
1560
+ tools,
1561
+ VercelAdapter,
1562
+ MCPHandler
1563
+ };