From cf28822a1c7cb36983b93fc5b566449a3a01d383 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 18 Feb 2026 14:00:25 -0800 Subject: [PATCH 1/4] fix(shortlink): remove isHosted guard from redirects, not available at build time on ECS (#3251) * fix(shortlink): remove isHosted guard from redirects, not available at build time on ECS * fix(shortlink): use rewrite instead of redirect for Beluga tracking --- apps/sim/next.config.ts | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/apps/sim/next.config.ts b/apps/sim/next.config.ts index 9ec7ab6fe..d9dd9809f 100644 --- a/apps/sim/next.config.ts +++ b/apps/sim/next.config.ts @@ -1,6 +1,6 @@ import type { NextConfig } from 'next' import { env, getEnv, isTruthy } from './lib/core/config/env' -import { isDev, isHosted } from './lib/core/config/feature-flags' +import { isDev } from './lib/core/config/feature-flags' import { getFormEmbedCSPPolicy, getMainCSPPolicy, @@ -306,34 +306,15 @@ const nextConfig: NextConfig = { } ) - // Only enable domain redirects for the hosted version - if (isHosted) { - redirects.push( - { - source: '/((?!api|_next|_vercel|favicon|static|ingest|.*\\..*).*)', - destination: 'https://www.sim.ai/$1', - permanent: true, - has: [{ type: 'host' as const, value: 'simstudio.ai' }], - }, - { - source: '/((?!api|_next|_vercel|favicon|static|ingest|.*\\..*).*)', - destination: 'https://www.sim.ai/$1', - permanent: true, - has: [{ type: 'host' as const, value: 'www.simstudio.ai' }], - } - ) - } - - // Beluga campaign short link tracking - if (isHosted) { - redirects.push({ + return redirects + }, + async rewrites() { + return [ + { source: '/r/:shortCode', destination: 'https://go.trybeluga.ai/:shortCode', - permanent: false, - }) - } - - return redirects + }, + ] }, } From 2979269ac38cc58bd211c5b3b5f4dc5d3c8887a0 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 18 Feb 2026 14:41:55 -0800 Subject: [PATCH 2/4] fix(sidebar): unify workflow and folder insertion ordering (#3250) * fix(sidebar): unify workflow and folder insertion ordering * ack comments * ack comments * ack * ack comment * upgrade turbo * fix build --- .../app/api/folders/[id]/duplicate/route.ts | 48 ++-- apps/sim/app/api/folders/route.test.ts | 212 +++++++----------- apps/sim/app/api/folders/route.ts | 42 ++-- apps/sim/app/api/workflows/route.test.ts | 137 +++++++++++ apps/sim/app/api/workflows/route.ts | 35 ++- apps/sim/hooks/queries/folders.test.ts | 177 +++++++++++++++ apps/sim/hooks/queries/folders.ts | 65 +++--- .../queries/utils/top-insertion-sort-order.ts | 44 ++++ apps/sim/hooks/queries/workflows.ts | 26 ++- .../workflows/persistence/duplicate.test.ts | 197 ++++++++++++++++ .../lib/workflows/persistence/duplicate.ts | 36 ++- bun.lock | 16 +- package.json | 2 +- 13 files changed, 806 insertions(+), 231 deletions(-) create mode 100644 apps/sim/app/api/workflows/route.test.ts create mode 100644 apps/sim/hooks/queries/folders.test.ts create mode 100644 apps/sim/hooks/queries/utils/top-insertion-sort-order.ts create mode 100644 apps/sim/lib/workflows/persistence/duplicate.test.ts diff --git a/apps/sim/app/api/folders/[id]/duplicate/route.ts b/apps/sim/app/api/folders/[id]/duplicate/route.ts index 92bc5ed75..3cbaa1b43 100644 --- a/apps/sim/app/api/folders/[id]/duplicate/route.ts +++ b/apps/sim/app/api/folders/[id]/duplicate/route.ts @@ -1,7 +1,7 @@ import { db } from '@sim/db' import { workflow, workflowFolder } from '@sim/db/schema' import { createLogger } from '@sim/logger' -import { and, eq } from 'drizzle-orm' +import { and, eq, isNull, min } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' @@ -37,7 +37,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: logger.info(`[${requestId}] Duplicating folder ${sourceFolderId} for user ${session.user.id}`) - // Verify the source folder exists const sourceFolder = await db .select() .from(workflowFolder) @@ -48,7 +47,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: throw new Error('Source folder not found') } - // Check if user has permission to access the source folder const userPermission = await getUserEntityPermissions( session.user.id, 'workspace', @@ -61,26 +59,51 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: const targetWorkspaceId = workspaceId || sourceFolder.workspaceId - // Step 1: Duplicate folder structure const { newFolderId, folderMapping } = await db.transaction(async (tx) => { const newFolderId = crypto.randomUUID() const now = new Date() + const targetParentId = parentId ?? sourceFolder.parentId + + const folderParentCondition = targetParentId + ? eq(workflowFolder.parentId, targetParentId) + : isNull(workflowFolder.parentId) + const workflowParentCondition = targetParentId + ? eq(workflow.folderId, targetParentId) + : isNull(workflow.folderId) + + const [[folderResult], [workflowResult]] = await Promise.all([ + tx + .select({ minSortOrder: min(workflowFolder.sortOrder) }) + .from(workflowFolder) + .where(and(eq(workflowFolder.workspaceId, targetWorkspaceId), folderParentCondition)), + tx + .select({ minSortOrder: min(workflow.sortOrder) }) + .from(workflow) + .where(and(eq(workflow.workspaceId, targetWorkspaceId), workflowParentCondition)), + ]) + + const minSortOrder = [folderResult?.minSortOrder, workflowResult?.minSortOrder].reduce< + number | null + >((currentMin, candidate) => { + if (candidate == null) return currentMin + if (currentMin == null) return candidate + return Math.min(currentMin, candidate) + }, null) + const sortOrder = minSortOrder != null ? minSortOrder - 1 : 0 - // Create the new root folder await tx.insert(workflowFolder).values({ id: newFolderId, userId: session.user.id, workspaceId: targetWorkspaceId, name, color: color || sourceFolder.color, - parentId: parentId || sourceFolder.parentId, - sortOrder: sourceFolder.sortOrder, + parentId: targetParentId, + sortOrder, isExpanded: false, createdAt: now, updatedAt: now, }) - // Recursively duplicate child folders const folderMapping = new Map([[sourceFolderId, newFolderId]]) await duplicateFolderStructure( tx, @@ -96,7 +119,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: return { newFolderId, folderMapping } }) - // Step 2: Duplicate workflows const workflowStats = await duplicateWorkflowsInFolderTree( sourceFolder.workspaceId, targetWorkspaceId, @@ -173,7 +195,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: } } -// Helper to recursively duplicate folder structure async function duplicateFolderStructure( tx: any, sourceFolderId: string, @@ -184,7 +205,6 @@ async function duplicateFolderStructure( timestamp: Date, folderMapping: Map ): Promise { - // Get all child folders const childFolders = await tx .select() .from(workflowFolder) @@ -195,7 +215,6 @@ async function duplicateFolderStructure( ) ) - // Create each child folder and recurse for (const childFolder of childFolders) { const newChildFolderId = crypto.randomUUID() folderMapping.set(childFolder.id, newChildFolderId) @@ -213,7 +232,6 @@ async function duplicateFolderStructure( updatedAt: timestamp, }) - // Recurse for this child's children await duplicateFolderStructure( tx, childFolder.id, @@ -227,7 +245,6 @@ async function duplicateFolderStructure( } } -// Helper to duplicate all workflows in a folder tree async function duplicateWorkflowsInFolderTree( sourceWorkspaceId: string, targetWorkspaceId: string, @@ -237,9 +254,7 @@ async function duplicateWorkflowsInFolderTree( ): Promise<{ total: number; succeeded: number; failed: number }> { const stats = { total: 0, succeeded: 0, failed: 0 } - // Process each folder in the mapping for (const [oldFolderId, newFolderId] of folderMapping.entries()) { - // Get workflows in this folder const workflowsInFolder = await db .select() .from(workflow) @@ -247,7 +262,6 @@ async function duplicateWorkflowsInFolderTree( stats.total += workflowsInFolder.length - // Duplicate each workflow for (const sourceWorkflow of workflowsInFolder) { try { await duplicateWorkflow({ diff --git a/apps/sim/app/api/folders/route.test.ts b/apps/sim/app/api/folders/route.test.ts index 89c0ad451..92f71796e 100644 --- a/apps/sim/app/api/folders/route.test.ts +++ b/apps/sim/app/api/folders/route.test.ts @@ -10,9 +10,14 @@ import { mockConsoleLogger, setupCommonApiMocks, } from '@sim/testing' +import { drizzleOrmMock } from '@sim/testing/mocks' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' vi.mock('@/lib/audit/log', () => auditMock) +vi.mock('drizzle-orm', () => ({ + ...drizzleOrmMock, + min: vi.fn((field) => ({ type: 'min', field })), +})) interface CapturedFolderValues { name?: string @@ -24,29 +29,35 @@ interface CapturedFolderValues { } function createMockTransaction(mockData: { - selectData?: Array<{ id: string; [key: string]: unknown }> + selectResults?: Array> insertResult?: Array<{ id: string; [key: string]: unknown }> + onInsertValues?: (values: CapturedFolderValues) => void }) { - const { selectData = [], insertResult = [] } = mockData - return vi.fn().mockImplementation(async (callback: (tx: unknown) => Promise) => { + const { selectResults = [[], []], insertResult = [], onInsertValues } = mockData + return async (callback: (tx: unknown) => Promise) => { + const where = vi.fn() + for (const result of selectResults) { + where.mockReturnValueOnce(result) + } + where.mockReturnValue([]) + const tx = { select: vi.fn().mockReturnValue({ from: vi.fn().mockReturnValue({ - where: vi.fn().mockReturnValue({ - orderBy: vi.fn().mockReturnValue({ - limit: vi.fn().mockReturnValue(selectData), - }), - }), + where, }), }), insert: vi.fn().mockReturnValue({ - values: vi.fn().mockReturnValue({ - returning: vi.fn().mockReturnValue(insertResult), + values: vi.fn().mockImplementation((values: CapturedFolderValues) => { + onInsertValues?.(values) + return { + returning: vi.fn().mockReturnValue(insertResult), + } }), }), } return await callback(tx) - }) + } } describe('Folders API Route', () => { @@ -257,25 +268,12 @@ describe('Folders API Route', () => { it('should create a new folder successfully', async () => { mockAuthenticatedUser() - mockTransaction.mockImplementationOnce(async (callback: any) => { - const tx = { - select: vi.fn().mockReturnValue({ - from: vi.fn().mockReturnValue({ - where: vi.fn().mockReturnValue({ - orderBy: vi.fn().mockReturnValue({ - limit: vi.fn().mockReturnValue([]), // No existing folders - }), - }), - }), - }), - insert: vi.fn().mockReturnValue({ - values: vi.fn().mockReturnValue({ - returning: vi.fn().mockReturnValue([mockFolders[0]]), - }), - }), - } - return await callback(tx) - }) + mockTransaction.mockImplementationOnce( + createMockTransaction({ + selectResults: [[], []], + insertResult: [mockFolders[0]], + }) + ) const req = createMockRequest('POST', { name: 'New Test Folder', @@ -285,12 +283,11 @@ describe('Folders API Route', () => { const { POST } = await import('@/app/api/folders/route') const response = await POST(req) + const responseBody = await response.json() expect(response.status).toBe(200) - - const data = await response.json() - expect(data).toHaveProperty('folder') - expect(data.folder).toMatchObject({ + expect(responseBody).toHaveProperty('folder') + expect(responseBody.folder).toMatchObject({ id: 'folder-1', name: 'Test Folder 1', workspaceId: 'workspace-123', @@ -299,26 +296,17 @@ describe('Folders API Route', () => { it('should create folder with correct sort order', async () => { mockAuthenticatedUser() + let capturedValues: CapturedFolderValues | null = null - mockTransaction.mockImplementationOnce(async (callback: any) => { - const tx = { - select: vi.fn().mockReturnValue({ - from: vi.fn().mockReturnValue({ - where: vi.fn().mockReturnValue({ - orderBy: vi.fn().mockReturnValue({ - limit: vi.fn().mockReturnValue([{ sortOrder: 5 }]), // Existing folder with sort order 5 - }), - }), - }), - }), - insert: vi.fn().mockReturnValue({ - values: vi.fn().mockReturnValue({ - returning: vi.fn().mockReturnValue([{ ...mockFolders[0], sortOrder: 6 }]), - }), - }), - } - return await callback(tx) - }) + mockTransaction.mockImplementationOnce( + createMockTransaction({ + selectResults: [[{ minSortOrder: 5 }], [{ minSortOrder: 2 }]], + insertResult: [{ ...mockFolders[0], sortOrder: 1 }], + onInsertValues: (values) => { + capturedValues = values + }, + }) + ) const req = createMockRequest('POST', { name: 'New Test Folder', @@ -332,8 +320,10 @@ describe('Folders API Route', () => { const data = await response.json() expect(data.folder).toMatchObject({ - sortOrder: 6, + sortOrder: 1, }) + expect(capturedValues).not.toBeNull() + expect(capturedValues!.sortOrder).toBe(1) }) it('should create subfolder with parent reference', async () => { @@ -341,7 +331,7 @@ describe('Folders API Route', () => { mockTransaction.mockImplementationOnce( createMockTransaction({ - selectData: [], // No existing folders + selectResults: [[], []], insertResult: [{ ...mockFolders[1] }], }) ) @@ -402,25 +392,12 @@ describe('Folders API Route', () => { mockAuthenticatedUser() mockGetUserEntityPermissions.mockResolvedValue('write') // Write permissions - mockTransaction.mockImplementationOnce(async (callback: any) => { - const tx = { - select: vi.fn().mockReturnValue({ - from: vi.fn().mockReturnValue({ - where: vi.fn().mockReturnValue({ - orderBy: vi.fn().mockReturnValue({ - limit: vi.fn().mockReturnValue([]), // No existing folders - }), - }), - }), - }), - insert: vi.fn().mockReturnValue({ - values: vi.fn().mockReturnValue({ - returning: vi.fn().mockReturnValue([mockFolders[0]]), - }), - }), - } - return await callback(tx) - }) + mockTransaction.mockImplementationOnce( + createMockTransaction({ + selectResults: [[], []], + insertResult: [mockFolders[0]], + }) + ) const req = createMockRequest('POST', { name: 'Test Folder', @@ -440,25 +417,12 @@ describe('Folders API Route', () => { mockAuthenticatedUser() mockGetUserEntityPermissions.mockResolvedValue('admin') // Admin permissions - mockTransaction.mockImplementationOnce(async (callback: any) => { - const tx = { - select: vi.fn().mockReturnValue({ - from: vi.fn().mockReturnValue({ - where: vi.fn().mockReturnValue({ - orderBy: vi.fn().mockReturnValue({ - limit: vi.fn().mockReturnValue([]), // No existing folders - }), - }), - }), - }), - insert: vi.fn().mockReturnValue({ - values: vi.fn().mockReturnValue({ - returning: vi.fn().mockReturnValue([mockFolders[0]]), - }), - }), - } - return await callback(tx) - }) + mockTransaction.mockImplementationOnce( + createMockTransaction({ + selectResults: [[], []], + insertResult: [mockFolders[0]], + }) + ) const req = createMockRequest('POST', { name: 'Test Folder', @@ -527,28 +491,15 @@ describe('Folders API Route', () => { let capturedValues: CapturedFolderValues | null = null - mockTransaction.mockImplementationOnce(async (callback: any) => { - const tx = { - select: vi.fn().mockReturnValue({ - from: vi.fn().mockReturnValue({ - where: vi.fn().mockReturnValue({ - orderBy: vi.fn().mockReturnValue({ - limit: vi.fn().mockReturnValue([]), - }), - }), - }), - }), - insert: vi.fn().mockReturnValue({ - values: vi.fn().mockImplementation((values) => { - capturedValues = values - return { - returning: vi.fn().mockReturnValue([mockFolders[0]]), - } - }), - }), - } - return await callback(tx) - }) + mockTransaction.mockImplementationOnce( + createMockTransaction({ + selectResults: [[], []], + insertResult: [mockFolders[0]], + onInsertValues: (values) => { + capturedValues = values + }, + }) + ) const req = createMockRequest('POST', { name: ' Test Folder With Spaces ', @@ -567,28 +518,15 @@ describe('Folders API Route', () => { let capturedValues: CapturedFolderValues | null = null - mockTransaction.mockImplementationOnce(async (callback: any) => { - const tx = { - select: vi.fn().mockReturnValue({ - from: vi.fn().mockReturnValue({ - where: vi.fn().mockReturnValue({ - orderBy: vi.fn().mockReturnValue({ - limit: vi.fn().mockReturnValue([]), - }), - }), - }), - }), - insert: vi.fn().mockReturnValue({ - values: vi.fn().mockImplementation((values) => { - capturedValues = values - return { - returning: vi.fn().mockReturnValue([mockFolders[0]]), - } - }), - }), - } - return await callback(tx) - }) + mockTransaction.mockImplementationOnce( + createMockTransaction({ + selectResults: [[], []], + insertResult: [mockFolders[0]], + onInsertValues: (values) => { + capturedValues = values + }, + }) + ) const req = createMockRequest('POST', { name: 'Test Folder', diff --git a/apps/sim/app/api/folders/route.ts b/apps/sim/app/api/folders/route.ts index 19aef4ca4..835231d31 100644 --- a/apps/sim/app/api/folders/route.ts +++ b/apps/sim/app/api/folders/route.ts @@ -1,7 +1,7 @@ import { db } from '@sim/db' -import { workflowFolder } from '@sim/db/schema' +import { workflow, workflowFolder } from '@sim/db/schema' import { createLogger } from '@sim/logger' -import { and, asc, desc, eq, isNull } from 'drizzle-orm' +import { and, asc, eq, isNull, min } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' @@ -87,19 +87,33 @@ export async function POST(request: NextRequest) { if (providedSortOrder !== undefined) { sortOrder = providedSortOrder } else { - const existingFolders = await tx - .select({ sortOrder: workflowFolder.sortOrder }) - .from(workflowFolder) - .where( - and( - eq(workflowFolder.workspaceId, workspaceId), - parentId ? eq(workflowFolder.parentId, parentId) : isNull(workflowFolder.parentId) - ) - ) - .orderBy(desc(workflowFolder.sortOrder)) - .limit(1) + const folderParentCondition = parentId + ? eq(workflowFolder.parentId, parentId) + : isNull(workflowFolder.parentId) + const workflowParentCondition = parentId + ? eq(workflow.folderId, parentId) + : isNull(workflow.folderId) - sortOrder = existingFolders.length > 0 ? existingFolders[0].sortOrder + 1 : 0 + const [[folderResult], [workflowResult]] = await Promise.all([ + tx + .select({ minSortOrder: min(workflowFolder.sortOrder) }) + .from(workflowFolder) + .where(and(eq(workflowFolder.workspaceId, workspaceId), folderParentCondition)), + tx + .select({ minSortOrder: min(workflow.sortOrder) }) + .from(workflow) + .where(and(eq(workflow.workspaceId, workspaceId), workflowParentCondition)), + ]) + + const minSortOrder = [folderResult?.minSortOrder, workflowResult?.minSortOrder].reduce< + number | null + >((currentMin, candidate) => { + if (candidate == null) return currentMin + if (currentMin == null) return candidate + return Math.min(currentMin, candidate) + }, null) + + sortOrder = minSortOrder != null ? minSortOrder - 1 : 0 } const [folder] = await tx diff --git a/apps/sim/app/api/workflows/route.test.ts b/apps/sim/app/api/workflows/route.test.ts new file mode 100644 index 000000000..ddef020dc --- /dev/null +++ b/apps/sim/app/api/workflows/route.test.ts @@ -0,0 +1,137 @@ +/** + * @vitest-environment node + */ +import { auditMock, createMockRequest, mockConsoleLogger, setupCommonApiMocks } from '@sim/testing' +import { drizzleOrmMock } from '@sim/testing/mocks' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const mockCheckSessionOrInternalAuth = vi.fn() +const mockGetUserEntityPermissions = vi.fn() +const mockDbSelect = vi.fn() +const mockDbInsert = vi.fn() +const mockWorkflowCreated = vi.fn() + +vi.mock('drizzle-orm', () => ({ + ...drizzleOrmMock, + min: vi.fn((field) => ({ type: 'min', field })), +})) + +vi.mock('@/lib/audit/log', () => auditMock) + +describe('Workflows API Route - POST ordering', () => { + beforeEach(() => { + vi.resetModules() + vi.clearAllMocks() + + setupCommonApiMocks() + mockConsoleLogger() + + vi.stubGlobal('crypto', { + randomUUID: vi.fn().mockReturnValue('workflow-new-id'), + }) + + mockCheckSessionOrInternalAuth.mockResolvedValue({ + success: true, + userId: 'user-123', + userName: 'Test User', + userEmail: 'test@example.com', + }) + mockGetUserEntityPermissions.mockResolvedValue('write') + + vi.doMock('@sim/db', () => ({ + db: { + select: (...args: unknown[]) => mockDbSelect(...args), + insert: (...args: unknown[]) => mockDbInsert(...args), + }, + })) + + vi.doMock('@/lib/auth/hybrid', () => ({ + checkSessionOrInternalAuth: (...args: unknown[]) => mockCheckSessionOrInternalAuth(...args), + })) + + vi.doMock('@/lib/workspaces/permissions/utils', () => ({ + getUserEntityPermissions: (...args: unknown[]) => mockGetUserEntityPermissions(...args), + workspaceExists: vi.fn(), + })) + + vi.doMock('@/app/api/workflows/utils', () => ({ + verifyWorkspaceMembership: vi.fn(), + })) + + vi.doMock('@/lib/core/telemetry', () => ({ + PlatformEvents: { + workflowCreated: (...args: unknown[]) => mockWorkflowCreated(...args), + }, + })) + }) + + it('uses top insertion against mixed siblings (folders + workflows)', async () => { + const minResultsQueue: Array> = [ + [{ minOrder: 5 }], + [{ minOrder: 2 }], + ] + + mockDbSelect.mockImplementation(() => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockImplementation(() => Promise.resolve(minResultsQueue.shift() ?? [])), + }), + })) + + let insertedValues: Record | null = null + mockDbInsert.mockReturnValue({ + values: vi.fn().mockImplementation((values: Record) => { + insertedValues = values + return Promise.resolve(undefined) + }), + }) + + const req = createMockRequest('POST', { + name: 'New Workflow', + description: 'desc', + color: '#3972F6', + workspaceId: 'workspace-123', + folderId: null, + }) + + const { POST } = await import('@/app/api/workflows/route') + const response = await POST(req) + const data = await response.json() + expect(response.status).toBe(200) + expect(data.sortOrder).toBe(1) + expect(insertedValues).not.toBeNull() + expect(insertedValues?.sortOrder).toBe(1) + }) + + it('defaults to sortOrder 0 when there are no siblings', async () => { + const minResultsQueue: Array> = [[], []] + + mockDbSelect.mockImplementation(() => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockImplementation(() => Promise.resolve(minResultsQueue.shift() ?? [])), + }), + })) + + let insertedValues: Record | null = null + mockDbInsert.mockReturnValue({ + values: vi.fn().mockImplementation((values: Record) => { + insertedValues = values + return Promise.resolve(undefined) + }), + }) + + const req = createMockRequest('POST', { + name: 'New Workflow', + description: 'desc', + color: '#3972F6', + workspaceId: 'workspace-123', + folderId: null, + }) + + const { POST } = await import('@/app/api/workflows/route') + const response = await POST(req) + const data = await response.json() + expect(response.status).toBe(200) + expect(data.sortOrder).toBe(0) + expect(insertedValues?.sortOrder).toBe(0) + }) +}) diff --git a/apps/sim/app/api/workflows/route.ts b/apps/sim/app/api/workflows/route.ts index 003c9fc63..611d808cf 100644 --- a/apps/sim/app/api/workflows/route.ts +++ b/apps/sim/app/api/workflows/route.ts @@ -1,5 +1,5 @@ import { db } from '@sim/db' -import { permissions, workflow } from '@sim/db/schema' +import { permissions, workflow, workflowFolder } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, asc, eq, inArray, isNull, min } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' @@ -162,12 +162,33 @@ export async function POST(req: NextRequest) { if (providedSortOrder !== undefined) { sortOrder = providedSortOrder } else { - const folderCondition = folderId ? eq(workflow.folderId, folderId) : isNull(workflow.folderId) - const [minResult] = await db - .select({ minOrder: min(workflow.sortOrder) }) - .from(workflow) - .where(and(eq(workflow.workspaceId, workspaceId), folderCondition)) - sortOrder = (minResult?.minOrder ?? 1) - 1 + const workflowParentCondition = folderId + ? eq(workflow.folderId, folderId) + : isNull(workflow.folderId) + const folderParentCondition = folderId + ? eq(workflowFolder.parentId, folderId) + : isNull(workflowFolder.parentId) + + const [[workflowMinResult], [folderMinResult]] = await Promise.all([ + db + .select({ minOrder: min(workflow.sortOrder) }) + .from(workflow) + .where(and(eq(workflow.workspaceId, workspaceId), workflowParentCondition)), + db + .select({ minOrder: min(workflowFolder.sortOrder) }) + .from(workflowFolder) + .where(and(eq(workflowFolder.workspaceId, workspaceId), folderParentCondition)), + ]) + + const minSortOrder = [workflowMinResult?.minOrder, folderMinResult?.minOrder].reduce< + number | null + >((currentMin, candidate) => { + if (candidate == null) return currentMin + if (currentMin == null) return candidate + return Math.min(currentMin, candidate) + }, null) + + sortOrder = minSortOrder != null ? minSortOrder - 1 : 0 } await db.insert(workflow).values({ diff --git a/apps/sim/hooks/queries/folders.test.ts b/apps/sim/hooks/queries/folders.test.ts new file mode 100644 index 000000000..9af6eaa5b --- /dev/null +++ b/apps/sim/hooks/queries/folders.test.ts @@ -0,0 +1,177 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockLogger, queryClient, useFolderStoreMock, useWorkflowRegistryMock } = vi.hoisted(() => ({ + mockLogger: { + info: vi.fn(), + warn: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + }, + queryClient: { + cancelQueries: vi.fn().mockResolvedValue(undefined), + invalidateQueries: vi.fn().mockResolvedValue(undefined), + }, + useFolderStoreMock: Object.assign(vi.fn(), { + getState: vi.fn(), + setState: vi.fn(), + }), + useWorkflowRegistryMock: Object.assign(vi.fn(), { + getState: vi.fn(), + setState: vi.fn(), + }), +})) + +let folderState: { + folders: Record +} + +let workflowRegistryState: { + workflows: Record +} + +vi.mock('@sim/logger', () => ({ + createLogger: vi.fn(() => mockLogger), +})) + +vi.mock('@tanstack/react-query', () => ({ + keepPreviousData: {}, + useQuery: vi.fn(), + useQueryClient: vi.fn(() => queryClient), + useMutation: vi.fn((options) => options), +})) + +vi.mock('@/stores/folders/store', () => ({ + useFolderStore: useFolderStoreMock, +})) + +vi.mock('@/stores/workflows/registry/store', () => ({ + useWorkflowRegistry: useWorkflowRegistryMock, +})) + +vi.mock('@/hooks/queries/workflows', () => ({ + workflowKeys: { + list: (workspaceId: string | undefined) => ['workflows', 'list', workspaceId ?? ''], + }, +})) + +import { useCreateFolder, useDuplicateFolderMutation } from '@/hooks/queries/folders' + +function getOptimisticFolderByName(name: string) { + return Object.values(folderState.folders).find((folder: any) => folder.name === name) as + | { sortOrder: number } + | undefined +} + +describe('folder optimistic top insertion ordering', () => { + beforeEach(() => { + vi.clearAllMocks() + useFolderStoreMock.getState.mockImplementation(() => folderState) + useFolderStoreMock.setState.mockImplementation((updater: any) => { + if (typeof updater === 'function') { + const next = updater(folderState) + if (next) { + folderState = { ...folderState, ...next } + } + return + } + + folderState = { ...folderState, ...updater } + }) + useWorkflowRegistryMock.getState.mockImplementation(() => workflowRegistryState) + + folderState = { + folders: { + 'folder-parent-match': { + id: 'folder-parent-match', + name: 'Existing sibling folder', + userId: 'user-1', + workspaceId: 'ws-1', + parentId: 'parent-1', + color: '#808080', + isExpanded: false, + sortOrder: 5, + createdAt: new Date(), + updatedAt: new Date(), + }, + 'folder-other-parent': { + id: 'folder-other-parent', + name: 'Other parent folder', + userId: 'user-1', + workspaceId: 'ws-1', + parentId: 'parent-2', + color: '#808080', + isExpanded: false, + sortOrder: -100, + createdAt: new Date(), + updatedAt: new Date(), + }, + }, + } + + workflowRegistryState = { + workflows: { + 'workflow-parent-match': { + id: 'workflow-parent-match', + name: 'Existing sibling workflow', + workspaceId: 'ws-1', + folderId: 'parent-1', + sortOrder: 2, + }, + 'workflow-other-parent': { + id: 'workflow-other-parent', + name: 'Other parent workflow', + workspaceId: 'ws-1', + folderId: 'parent-2', + sortOrder: -50, + }, + }, + } + }) + + it('creates folders at top of mixed non-root siblings', async () => { + const mutation = useCreateFolder() + + await mutation.onMutate({ + workspaceId: 'ws-1', + name: 'New child folder', + parentId: 'parent-1', + }) + + const optimisticFolder = getOptimisticFolderByName('New child folder') + expect(optimisticFolder).toBeDefined() + expect(optimisticFolder?.sortOrder).toBe(1) + }) + + it('duplicates folders at top of mixed non-root siblings', async () => { + const mutation = useDuplicateFolderMutation() + + await mutation.onMutate({ + workspaceId: 'ws-1', + id: 'folder-parent-match', + name: 'Duplicated child folder', + parentId: 'parent-1', + }) + + const optimisticFolder = getOptimisticFolderByName('Duplicated child folder') + expect(optimisticFolder).toBeDefined() + expect(optimisticFolder?.sortOrder).toBe(1) + }) + + it('uses source parent scope when duplicate parentId is undefined', async () => { + const mutation = useDuplicateFolderMutation() + + await mutation.onMutate({ + workspaceId: 'ws-1', + id: 'folder-parent-match', + name: 'Duplicated with inherited parent', + // parentId intentionally omitted to mirror duplicate fallback behavior + }) + + const optimisticFolder = getOptimisticFolderByName('Duplicated with inherited parent') as + | { parentId: string | null; sortOrder: number } + | undefined + expect(optimisticFolder).toBeDefined() + expect(optimisticFolder?.parentId).toBe('parent-1') + expect(optimisticFolder?.sortOrder).toBe(1) + }) +}) diff --git a/apps/sim/hooks/queries/folders.ts b/apps/sim/hooks/queries/folders.ts index 61289ed65..b369795ac 100644 --- a/apps/sim/hooks/queries/folders.ts +++ b/apps/sim/hooks/queries/folders.ts @@ -5,9 +5,11 @@ import { createOptimisticMutationHandlers, generateTempId, } from '@/hooks/queries/utils/optimistic-mutation' +import { getTopInsertionSortOrder } from '@/hooks/queries/utils/top-insertion-sort-order' import { workflowKeys } from '@/hooks/queries/workflows' import { useFolderStore } from '@/stores/folders/store' import type { WorkflowFolder } from '@/stores/folders/types' +import { useWorkflowRegistry } from '@/stores/workflows/registry/store' const logger = createLogger('FolderQueries') @@ -133,40 +135,35 @@ function createFolderMutationHandlers, - workspaceId: string, - parentId: string | null | undefined -): number { - const siblingFolders = Object.values(folders).filter( - (f) => f.workspaceId === workspaceId && f.parentId === (parentId || null) - ) - return siblingFolders.reduce((max, f) => Math.max(max, f.sortOrder), -1) + 1 -} - export function useCreateFolder() { const queryClient = useQueryClient() const handlers = createFolderMutationHandlers( queryClient, 'CreateFolder', - (variables, tempId, previousFolders) => ({ - id: tempId, - name: variables.name, - userId: '', - workspaceId: variables.workspaceId, - parentId: variables.parentId || null, - color: variables.color || '#808080', - isExpanded: false, - sortOrder: - variables.sortOrder ?? - getNextSortOrder(previousFolders, variables.workspaceId, variables.parentId), - createdAt: new Date(), - updatedAt: new Date(), - }) + (variables, tempId, previousFolders) => { + const currentWorkflows = useWorkflowRegistry.getState().workflows + + return { + id: tempId, + name: variables.name, + userId: '', + workspaceId: variables.workspaceId, + parentId: variables.parentId || null, + color: variables.color || '#808080', + isExpanded: false, + sortOrder: + variables.sortOrder ?? + getTopInsertionSortOrder( + currentWorkflows, + previousFolders, + variables.workspaceId, + variables.parentId + ), + createdAt: new Date(), + updatedAt: new Date(), + } + } ) return useMutation({ @@ -242,17 +239,25 @@ export function useDuplicateFolderMutation() { queryClient, 'DuplicateFolder', (variables, tempId, previousFolders) => { + const currentWorkflows = useWorkflowRegistry.getState().workflows + // Get source folder info if available const sourceFolder = previousFolders[variables.id] + const targetParentId = variables.parentId ?? sourceFolder?.parentId ?? null return { id: tempId, name: variables.name, userId: sourceFolder?.userId || '', workspaceId: variables.workspaceId, - parentId: variables.parentId ?? sourceFolder?.parentId ?? null, + parentId: targetParentId, color: variables.color || sourceFolder?.color || '#808080', isExpanded: false, - sortOrder: getNextSortOrder(previousFolders, variables.workspaceId, variables.parentId), + sortOrder: getTopInsertionSortOrder( + currentWorkflows, + previousFolders, + variables.workspaceId, + targetParentId + ), createdAt: new Date(), updatedAt: new Date(), } diff --git a/apps/sim/hooks/queries/utils/top-insertion-sort-order.ts b/apps/sim/hooks/queries/utils/top-insertion-sort-order.ts new file mode 100644 index 000000000..fec629ca9 --- /dev/null +++ b/apps/sim/hooks/queries/utils/top-insertion-sort-order.ts @@ -0,0 +1,44 @@ +interface SortableWorkflow { + workspaceId?: string + folderId?: string | null + sortOrder?: number +} + +interface SortableFolder { + workspaceId?: string + parentId?: string | null + sortOrder: number +} + +/** + * Calculates the insertion sort order that places a new item at the top of a + * mixed list of folders and workflows within the same parent scope. + */ +export function getTopInsertionSortOrder( + workflows: Record, + folders: Record, + workspaceId: string, + parentId: string | null | undefined +): number { + const normalizedParentId = parentId ?? null + + const siblingWorkflows = Object.values(workflows).filter( + (workflow) => + workflow.workspaceId === workspaceId && (workflow.folderId ?? null) === normalizedParentId + ) + const siblingFolders = Object.values(folders).filter( + (folder) => + folder.workspaceId === workspaceId && (folder.parentId ?? null) === normalizedParentId + ) + + const siblingOrders = [ + ...siblingWorkflows.map((workflow) => workflow.sortOrder ?? 0), + ...siblingFolders.map((folder) => folder.sortOrder), + ] + + if (siblingOrders.length === 0) { + return 0 + } + + return Math.min(...siblingOrders) - 1 +} diff --git a/apps/sim/hooks/queries/workflows.ts b/apps/sim/hooks/queries/workflows.ts index 5e50194c4..9664cf864 100644 --- a/apps/sim/hooks/queries/workflows.ts +++ b/apps/sim/hooks/queries/workflows.ts @@ -8,6 +8,8 @@ import { createOptimisticMutationHandlers, generateTempId, } from '@/hooks/queries/utils/optimistic-mutation' +import { getTopInsertionSortOrder } from '@/hooks/queries/utils/top-insertion-sort-order' +import { useFolderStore } from '@/stores/folders/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import type { WorkflowMetadata } from '@/stores/workflows/registry/types' import { generateCreativeWorkflowName } from '@/stores/workflows/registry/utils' @@ -223,11 +225,13 @@ export function useCreateWorkflow() { sortOrder = variables.sortOrder } else { const currentWorkflows = useWorkflowRegistry.getState().workflows - const targetFolderId = variables.folderId || null - const workflowsInFolder = Object.values(currentWorkflows).filter( - (w) => w.folderId === targetFolderId + const currentFolders = useFolderStore.getState().folders + sortOrder = getTopInsertionSortOrder( + currentWorkflows, + currentFolders, + variables.workspaceId, + variables.folderId ) - sortOrder = workflowsInFolder.reduce((min, w) => Math.min(min, w.sortOrder ?? 0), 1) - 1 } return { @@ -323,11 +327,8 @@ export function useDuplicateWorkflowMutation() { 'DuplicateWorkflow', (variables, tempId) => { const currentWorkflows = useWorkflowRegistry.getState().workflows - const targetFolderId = variables.folderId || null - const workflowsInFolder = Object.values(currentWorkflows).filter( - (w) => w.folderId === targetFolderId - ) - const minSortOrder = workflowsInFolder.reduce((min, w) => Math.min(min, w.sortOrder ?? 0), 1) + const currentFolders = useFolderStore.getState().folders + const targetFolderId = variables.folderId ?? null return { id: tempId, @@ -338,7 +339,12 @@ export function useDuplicateWorkflowMutation() { color: variables.color, workspaceId: variables.workspaceId, folderId: targetFolderId, - sortOrder: minSortOrder - 1, + sortOrder: getTopInsertionSortOrder( + currentWorkflows, + currentFolders, + variables.workspaceId, + targetFolderId + ), } } ) diff --git a/apps/sim/lib/workflows/persistence/duplicate.test.ts b/apps/sim/lib/workflows/persistence/duplicate.test.ts new file mode 100644 index 000000000..ec710ce10 --- /dev/null +++ b/apps/sim/lib/workflows/persistence/duplicate.test.ts @@ -0,0 +1,197 @@ +/** + * @vitest-environment node + */ +import { mockConsoleLogger, setupCommonApiMocks } from '@sim/testing' +import { drizzleOrmMock } from '@sim/testing/mocks' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const mockAuthorizeWorkflowByWorkspacePermission = vi.fn() +const mockGetUserEntityPermissions = vi.fn() + +const { mockDb } = vi.hoisted(() => ({ + mockDb: { + transaction: vi.fn(), + }, +})) + +vi.mock('drizzle-orm', () => ({ + ...drizzleOrmMock, + min: vi.fn((field) => ({ type: 'min', field })), +})) +vi.mock('@/lib/workflows/utils', () => ({ + authorizeWorkflowByWorkspacePermission: (...args: unknown[]) => + mockAuthorizeWorkflowByWorkspacePermission(...args), +})) + +vi.mock('@/lib/workspaces/permissions/utils', () => ({ + getUserEntityPermissions: (...args: unknown[]) => mockGetUserEntityPermissions(...args), +})) + +vi.mock('@sim/db/schema', () => ({ + workflow: { + id: 'id', + workspaceId: 'workspaceId', + folderId: 'folderId', + sortOrder: 'sortOrder', + variables: 'variables', + }, + workflowFolder: { + workspaceId: 'workspaceId', + parentId: 'parentId', + sortOrder: 'sortOrder', + }, + workflowBlocks: { + workflowId: 'workflowId', + }, + workflowEdges: { + workflowId: 'workflowId', + }, + workflowSubflows: { + workflowId: 'workflowId', + }, +})) + +vi.mock('@sim/db', () => ({ + db: mockDb, +})) + +import { duplicateWorkflow } from './duplicate' + +function createMockTx( + selectResults: unknown[], + onWorkflowInsert?: (values: Record) => void +) { + let selectCallCount = 0 + + const select = vi.fn().mockImplementation(() => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockImplementation(() => { + const result = selectResults[selectCallCount++] ?? [] + if (selectCallCount === 1) { + return { + limit: vi.fn().mockResolvedValue(result), + } + } + return Promise.resolve(result) + }), + }), + })) + + const insert = vi.fn().mockReturnValue({ + values: vi.fn().mockImplementation((values: Record) => { + onWorkflowInsert?.(values) + return Promise.resolve(undefined) + }), + }) + + const update = vi.fn().mockReturnValue({ + set: vi.fn().mockReturnValue({ + where: vi.fn().mockResolvedValue(undefined), + }), + }) + + return { + select, + insert, + update, + } +} + +describe('duplicateWorkflow ordering', () => { + beforeEach(() => { + setupCommonApiMocks() + mockConsoleLogger() + vi.clearAllMocks() + + vi.stubGlobal('crypto', { + randomUUID: vi.fn().mockReturnValue('new-workflow-id'), + }) + + mockAuthorizeWorkflowByWorkspacePermission.mockResolvedValue({ allowed: true }) + mockGetUserEntityPermissions.mockResolvedValue('write') + }) + + it('uses mixed-sibling top insertion sort order', async () => { + let insertedWorkflowValues: Record | null = null + const tx = createMockTx( + [ + [ + { + id: 'source-workflow-id', + workspaceId: 'workspace-123', + folderId: null, + description: 'source', + color: '#000000', + variables: {}, + }, + ], + [{ minOrder: 5 }], + [{ minOrder: 2 }], + [], + [], + [], + ], + (values) => { + insertedWorkflowValues = values + } + ) + + mockDb.transaction.mockImplementation(async (callback: (txArg: unknown) => Promise) => + callback(tx) + ) + + const result = await duplicateWorkflow({ + sourceWorkflowId: 'source-workflow-id', + userId: 'user-123', + name: 'Duplicated', + workspaceId: 'workspace-123', + folderId: null, + requestId: 'req-1', + }) + + expect(result.sortOrder).toBe(1) + expect(insertedWorkflowValues?.sortOrder).toBe(1) + }) + + it('defaults to sortOrder 0 when target has no siblings', async () => { + let insertedWorkflowValues: Record | null = null + const tx = createMockTx( + [ + [ + { + id: 'source-workflow-id', + workspaceId: 'workspace-123', + folderId: null, + description: 'source', + color: '#000000', + variables: {}, + }, + ], + [], + [], + [], + [], + [], + ], + (values) => { + insertedWorkflowValues = values + } + ) + + mockDb.transaction.mockImplementation(async (callback: (txArg: unknown) => Promise) => + callback(tx) + ) + + const result = await duplicateWorkflow({ + sourceWorkflowId: 'source-workflow-id', + userId: 'user-123', + name: 'Duplicated', + workspaceId: 'workspace-123', + folderId: null, + requestId: 'req-2', + }) + + expect(result.sortOrder).toBe(0) + expect(insertedWorkflowValues?.sortOrder).toBe(0) + }) +}) diff --git a/apps/sim/lib/workflows/persistence/duplicate.ts b/apps/sim/lib/workflows/persistence/duplicate.ts index 329d077e9..cee5f467a 100644 --- a/apps/sim/lib/workflows/persistence/duplicate.ts +++ b/apps/sim/lib/workflows/persistence/duplicate.ts @@ -1,5 +1,11 @@ import { db } from '@sim/db' -import { workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@sim/db/schema' +import { + workflow, + workflowBlocks, + workflowEdges, + workflowFolder, + workflowSubflows, +} from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, eq, isNull, min } from 'drizzle-orm' import { authorizeWorkflowByWorkspacePermission } from '@/lib/workflows/utils' @@ -132,15 +138,31 @@ export async function duplicateWorkflow( throw new Error('Write or admin access required for target workspace') } const targetFolderId = folderId !== undefined ? folderId : source.folderId - const folderCondition = targetFolderId + const workflowParentCondition = targetFolderId ? eq(workflow.folderId, targetFolderId) : isNull(workflow.folderId) + const folderParentCondition = targetFolderId + ? eq(workflowFolder.parentId, targetFolderId) + : isNull(workflowFolder.parentId) - const [minResult] = await tx - .select({ minOrder: min(workflow.sortOrder) }) - .from(workflow) - .where(and(eq(workflow.workspaceId, targetWorkspaceId), folderCondition)) - const sortOrder = (minResult?.minOrder ?? 1) - 1 + const [[workflowMinResult], [folderMinResult]] = await Promise.all([ + tx + .select({ minOrder: min(workflow.sortOrder) }) + .from(workflow) + .where(and(eq(workflow.workspaceId, targetWorkspaceId), workflowParentCondition)), + tx + .select({ minOrder: min(workflowFolder.sortOrder) }) + .from(workflowFolder) + .where(and(eq(workflowFolder.workspaceId, targetWorkspaceId), folderParentCondition)), + ]) + const minSortOrder = [workflowMinResult?.minOrder, folderMinResult?.minOrder].reduce< + number | null + >((currentMin, candidate) => { + if (candidate == null) return currentMin + if (currentMin == null) return candidate + return Math.min(currentMin, candidate) + }, null) + const sortOrder = minSortOrder != null ? minSortOrder - 1 : 0 // Mapping from old variable IDs to new variable IDs (populated during variable duplication) const varIdMapping = new Map() diff --git a/bun.lock b/bun.lock index d46a5090c..bae82ffca 100644 --- a/bun.lock +++ b/bun.lock @@ -13,7 +13,7 @@ "glob": "13.0.0", "husky": "9.1.7", "lint-staged": "16.0.0", - "turbo": "2.8.9", + "turbo": "2.8.10", }, }, "apps/docs": { @@ -3437,19 +3437,19 @@ "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], - "turbo": ["turbo@2.8.9", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.9", "turbo-darwin-arm64": "2.8.9", "turbo-linux-64": "2.8.9", "turbo-linux-arm64": "2.8.9", "turbo-windows-64": "2.8.9", "turbo-windows-arm64": "2.8.9" }, "bin": { "turbo": "bin/turbo" } }, "sha512-G+Mq8VVQAlpz/0HTsxiNNk/xywaHGl+dk1oiBREgOEVCCDjXInDlONWUn5srRnC9s5tdHTFD1bx1N19eR4hI+g=="], + "turbo": ["turbo@2.8.10", "", { "optionalDependencies": { "turbo-darwin-64": "2.8.10", "turbo-darwin-arm64": "2.8.10", "turbo-linux-64": "2.8.10", "turbo-linux-arm64": "2.8.10", "turbo-windows-64": "2.8.10", "turbo-windows-arm64": "2.8.10" }, "bin": { "turbo": "bin/turbo" } }, "sha512-OxbzDES66+x7nnKGg2MwBA1ypVsZoDTLHpeaP4giyiHSixbsiTaMyeJqbEyvBdp5Cm28fc+8GG6RdQtic0ijwQ=="], - "turbo-darwin-64": ["turbo-darwin-64@2.8.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-KnCw1ZI9KTnEAhdI9avZrnZ/z4wsM++flMA1w8s8PKOqi5daGpFV36qoPafg4S8TmYMe52JPWEoFr0L+lQ5JIw=="], + "turbo-darwin-64": ["turbo-darwin-64@2.8.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-A03fXh+B7S8mL3PbdhTd+0UsaGrhfyPkODvzBDpKRY7bbeac4MDFpJ7I+Slf2oSkCEeSvHKR7Z4U71uKRUfX7g=="], - "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CbD5Y2NKJKBXTOZ7z7Cc7vGlFPZkYjApA7ri9lH4iFwKV1X7MoZswh9gyRLetXYWImVX1BqIvP8KftulJg/wIA=="], + "turbo-darwin-arm64": ["turbo-darwin-arm64@2.8.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-sidzowgWL3s5xCHLeqwC9M3s9M0i16W1nuQF3Mc7fPHpZ+YPohvcbVFBB2uoRRHYZg6yBnwD4gyUHKTeXfwtXA=="], - "turbo-linux-64": ["turbo-linux-64@2.8.9", "", { "os": "linux", "cpu": "x64" }, "sha512-OXC9HdCtsHvyH+5KUoH8ds+p5WU13vdif0OPbsFzZca4cUXMwKA3HWwUuCgQetk0iAE4cscXpi/t8A263n3VTg=="], + "turbo-linux-64": ["turbo-linux-64@2.8.10", "", { "os": "linux", "cpu": "x64" }, "sha512-YK9vcpL3TVtqonB021XwgaQhY9hJJbKKUhLv16osxV0HkcQASQWUqR56yMge7puh6nxU67rQlTq1b7ksR1T3KA=="], - "turbo-linux-arm64": ["turbo-linux-arm64@2.8.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-yI5n8jNXiFA6+CxnXG0gO7h5ZF1+19K8uO3/kXPQmyl37AdiA7ehKJQOvf9OPAnmkGDHcF2HSCPltabERNRmug=="], + "turbo-linux-arm64": ["turbo-linux-arm64@2.8.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-3+j2tL0sG95iBJTm+6J8/45JsETQABPqtFyYjVjBbi6eVGdtNTiBmHNKrbvXRlQ3ZbUG75bKLaSSDHSEEN+btQ=="], - "turbo-windows-64": ["turbo-windows-64@2.8.9", "", { "os": "win32", "cpu": "x64" }, "sha512-/OztzeGftJAg258M/9vK2ZCkUKUzqrWXJIikiD2pm8TlqHcIYUmepDbyZSDfOiUjMy6NzrLFahpNLnY7b5vNgg=="], + "turbo-windows-64": ["turbo-windows-64@2.8.10", "", { "os": "win32", "cpu": "x64" }, "sha512-hdeF5qmVY/NFgiucf8FW0CWJWtyT2QPm5mIsX0W1DXAVzqKVXGq+Zf+dg4EUngAFKjDzoBeN6ec2Fhajwfztkw=="], - "turbo-windows-arm64": ["turbo-windows-arm64@2.8.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-xZ2VTwVTjIqpFZKN4UBxDHCPM3oJ2J5cpRzCBSmRpJ/Pn33wpiYjs+9FB2E03svKaD04/lSSLlEUej0UYsugfg=="], + "turbo-windows-arm64": ["turbo-windows-arm64@2.8.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-QGdr/Q8LWmj+ITMkSvfiz2glf0d7JG0oXVzGL3jxkGqiBI1zXFj20oqVY0qWi+112LO9SVrYdpHS0E/oGFrMbQ=="], "tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="], diff --git a/package.json b/package.json index 169f31272..0d4b6d78a 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "glob": "13.0.0", "husky": "9.1.7", "lint-staged": "16.0.0", - "turbo": "2.8.9" + "turbo": "2.8.10" }, "lint-staged": { "*.{js,jsx,ts,tsx,json,css,scss}": [ From 91aa1f9a52eb328b5d5cfdc97cd1740097cb5199 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 18 Feb 2026 15:54:49 -0800 Subject: [PATCH 3/4] feat(tools): added vercel block & tools (#3252) * feat(vercel): add complete Vercel integration with 42 API tools Add Vercel platform management integration covering deployments, projects, environment variables, domains, DNS records, aliases, edge configs, and team/user management. All tools use API key authentication with Bearer tokens. Co-Authored-By: Claude Opus 4.6 * feat(vercel): add webhook and deployment check tools Add 8 new Vercel API tools: - Webhooks: list, create, delete - Deployment Checks: create, get, list, update, rerequest Brings total Vercel tools to 50. * fix(vercel): expand all object and array output definitions Expand unexpanded output types: - get_deployment: meta and gitSource objects now have properties - list_deployment_files: children array now has items definition - get_team: teamRoles and teamPermissions arrays now have items Co-Authored-By: Claude Opus 4.6 * update icon size, update docs --------- Co-authored-by: Claude Opus 4.6 --- apps/docs/components/icons.tsx | 15 + apps/docs/components/ui/icon-mapping.ts | 2 + apps/docs/content/docs/en/tools/meta.json | 1 + apps/docs/content/docs/en/tools/vercel.mdx | 1391 +++++++++++++++++ apps/sim/blocks/blocks/vercel.ts | 1007 ++++++++++++ apps/sim/blocks/registry.ts | 2 + apps/sim/components/icons.tsx | 15 + apps/sim/tools/registry.ts | 112 ++ apps/sim/tools/vercel/add_domain.ts | 84 + apps/sim/tools/vercel/add_project_domain.ts | 113 ++ apps/sim/tools/vercel/cancel_deployment.ts | 83 + apps/sim/tools/vercel/create_alias.ts | 87 ++ apps/sim/tools/vercel/create_check.ts | 141 ++ apps/sim/tools/vercel/create_deployment.ts | 136 ++ apps/sim/tools/vercel/create_dns_record.ts | 107 ++ apps/sim/tools/vercel/create_edge_config.ts | 106 ++ apps/sim/tools/vercel/create_env_var.ts | 145 ++ apps/sim/tools/vercel/create_project.ts | 108 ++ apps/sim/tools/vercel/create_webhook.ts | 105 ++ apps/sim/tools/vercel/delete_alias.ts | 62 + apps/sim/tools/vercel/delete_deployment.ts | 77 + apps/sim/tools/vercel/delete_dns_record.ts | 69 + apps/sim/tools/vercel/delete_domain.ts | 64 + apps/sim/tools/vercel/delete_env_var.ts | 69 + apps/sim/tools/vercel/delete_project.ts | 60 + apps/sim/tools/vercel/delete_webhook.ts | 63 + apps/sim/tools/vercel/get_alias.ts | 97 ++ apps/sim/tools/vercel/get_check.ts | 99 ++ apps/sim/tools/vercel/get_deployment.ts | 176 +++ .../sim/tools/vercel/get_deployment_events.ts | 135 ++ apps/sim/tools/vercel/get_domain.ts | 94 ++ apps/sim/tools/vercel/get_domain_config.ts | 107 ++ apps/sim/tools/vercel/get_edge_config.ts | 100 ++ .../sim/tools/vercel/get_edge_config_items.ts | 93 ++ apps/sim/tools/vercel/get_env_vars.ts | 99 ++ apps/sim/tools/vercel/get_project.ts | 89 ++ apps/sim/tools/vercel/get_team.ts | 98 ++ apps/sim/tools/vercel/get_user.ts | 73 + apps/sim/tools/vercel/index.ts | 126 ++ apps/sim/tools/vercel/list_aliases.ts | 107 ++ apps/sim/tools/vercel/list_checks.ts | 99 ++ .../sim/tools/vercel/list_deployment_files.ts | 114 ++ apps/sim/tools/vercel/list_deployments.ts | 170 ++ apps/sim/tools/vercel/list_dns_records.ts | 116 ++ apps/sim/tools/vercel/list_domains.ts | 109 ++ apps/sim/tools/vercel/list_edge_configs.ts | 91 ++ apps/sim/tools/vercel/list_project_domains.ts | 116 ++ apps/sim/tools/vercel/list_projects.ts | 106 ++ apps/sim/tools/vercel/list_team_members.ts | 151 ++ apps/sim/tools/vercel/list_teams.ts | 132 ++ apps/sim/tools/vercel/list_webhooks.ts | 100 ++ apps/sim/tools/vercel/pause_project.ts | 65 + .../sim/tools/vercel/remove_project_domain.ts | 69 + apps/sim/tools/vercel/rerequest_check.ts | 65 + apps/sim/tools/vercel/types.ts | 1025 ++++++++++++ apps/sim/tools/vercel/unpause_project.ts | 65 + apps/sim/tools/vercel/update_check.ts | 159 ++ .../tools/vercel/update_edge_config_items.ts | 77 + apps/sim/tools/vercel/update_env_var.ts | 149 ++ apps/sim/tools/vercel/update_project.ts | 106 ++ 60 files changed, 8801 insertions(+) create mode 100644 apps/docs/content/docs/en/tools/vercel.mdx create mode 100644 apps/sim/blocks/blocks/vercel.ts create mode 100644 apps/sim/tools/vercel/add_domain.ts create mode 100644 apps/sim/tools/vercel/add_project_domain.ts create mode 100644 apps/sim/tools/vercel/cancel_deployment.ts create mode 100644 apps/sim/tools/vercel/create_alias.ts create mode 100644 apps/sim/tools/vercel/create_check.ts create mode 100644 apps/sim/tools/vercel/create_deployment.ts create mode 100644 apps/sim/tools/vercel/create_dns_record.ts create mode 100644 apps/sim/tools/vercel/create_edge_config.ts create mode 100644 apps/sim/tools/vercel/create_env_var.ts create mode 100644 apps/sim/tools/vercel/create_project.ts create mode 100644 apps/sim/tools/vercel/create_webhook.ts create mode 100644 apps/sim/tools/vercel/delete_alias.ts create mode 100644 apps/sim/tools/vercel/delete_deployment.ts create mode 100644 apps/sim/tools/vercel/delete_dns_record.ts create mode 100644 apps/sim/tools/vercel/delete_domain.ts create mode 100644 apps/sim/tools/vercel/delete_env_var.ts create mode 100644 apps/sim/tools/vercel/delete_project.ts create mode 100644 apps/sim/tools/vercel/delete_webhook.ts create mode 100644 apps/sim/tools/vercel/get_alias.ts create mode 100644 apps/sim/tools/vercel/get_check.ts create mode 100644 apps/sim/tools/vercel/get_deployment.ts create mode 100644 apps/sim/tools/vercel/get_deployment_events.ts create mode 100644 apps/sim/tools/vercel/get_domain.ts create mode 100644 apps/sim/tools/vercel/get_domain_config.ts create mode 100644 apps/sim/tools/vercel/get_edge_config.ts create mode 100644 apps/sim/tools/vercel/get_edge_config_items.ts create mode 100644 apps/sim/tools/vercel/get_env_vars.ts create mode 100644 apps/sim/tools/vercel/get_project.ts create mode 100644 apps/sim/tools/vercel/get_team.ts create mode 100644 apps/sim/tools/vercel/get_user.ts create mode 100644 apps/sim/tools/vercel/index.ts create mode 100644 apps/sim/tools/vercel/list_aliases.ts create mode 100644 apps/sim/tools/vercel/list_checks.ts create mode 100644 apps/sim/tools/vercel/list_deployment_files.ts create mode 100644 apps/sim/tools/vercel/list_deployments.ts create mode 100644 apps/sim/tools/vercel/list_dns_records.ts create mode 100644 apps/sim/tools/vercel/list_domains.ts create mode 100644 apps/sim/tools/vercel/list_edge_configs.ts create mode 100644 apps/sim/tools/vercel/list_project_domains.ts create mode 100644 apps/sim/tools/vercel/list_projects.ts create mode 100644 apps/sim/tools/vercel/list_team_members.ts create mode 100644 apps/sim/tools/vercel/list_teams.ts create mode 100644 apps/sim/tools/vercel/list_webhooks.ts create mode 100644 apps/sim/tools/vercel/pause_project.ts create mode 100644 apps/sim/tools/vercel/remove_project_domain.ts create mode 100644 apps/sim/tools/vercel/rerequest_check.ts create mode 100644 apps/sim/tools/vercel/types.ts create mode 100644 apps/sim/tools/vercel/unpause_project.ts create mode 100644 apps/sim/tools/vercel/update_check.ts create mode 100644 apps/sim/tools/vercel/update_edge_config_items.ts create mode 100644 apps/sim/tools/vercel/update_env_var.ts create mode 100644 apps/sim/tools/vercel/update_project.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index dfb95dab2..c31380d81 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -5532,3 +5532,18 @@ export function OnePasswordIcon(props: SVGProps) { ) } + +export function VercelIcon(props: SVGProps) { + return ( + + + + + + ) +} diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index dc6b16f37..171d8461b 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -125,6 +125,7 @@ import { TTSIcon, TwilioIcon, TypeformIcon, + VercelIcon, VideoIcon, WealthboxIcon, WebflowIcon, @@ -262,6 +263,7 @@ export const blockTypeToIconMap: Record = { twilio_sms: TwilioIcon, twilio_voice: TwilioIcon, typeform: TypeformIcon, + vercel: VercelIcon, video_generator_v2: VideoIcon, vision_v2: EyeIcon, wealthbox: WealthboxIcon, diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index c10640a96..e19b01b3d 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -122,6 +122,7 @@ "twilio_sms", "twilio_voice", "typeform", + "vercel", "video_generator", "vision", "wealthbox", diff --git a/apps/docs/content/docs/en/tools/vercel.mdx b/apps/docs/content/docs/en/tools/vercel.mdx new file mode 100644 index 000000000..0baacc3f6 --- /dev/null +++ b/apps/docs/content/docs/en/tools/vercel.mdx @@ -0,0 +1,1391 @@ +--- +title: Vercel +description: Manage Vercel deployments, projects, and infrastructure +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Vercel](https://vercel.com/) describes itself as **the AI Cloud**: a platform that provides developer tools and cloud infrastructure to build, scale, and secure faster, more personalized web experiences. It is widely used to build and ship modern web apps and agentic workloads, with built-in support for Git-based workflows, preview deployments, and production delivery. + +With Vercel, you can: + +- **Automate deployments**: Deploy from Git and manage preview and production releases with minimal operational overhead +- **Manage projects and teams**: Organize projects, team access, and settings across multiple environments +- **Control infrastructure settings**: Configure domains, DNS, aliases, environment variables, and edge settings in one place +- **Monitor and troubleshoot**: Track deployment status, inspect logs, and debug build or runtime issues + +In Sim, the Vercel integration lets your agents programmatically manage deployments, projects, domains, DNS records, aliases, environment variables, edge configs, and teams directly from workflows. You can automate deployment operations, react to status changes, and run infrastructure tasks as part of reliable, end-to-end delivery workflows. +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate with Vercel to manage deployments, projects, domains, DNS records, environment variables, aliases, edge configs, teams, and more. + + + +## Tools + +### `vercel_list_deployments` + +List deployments for a Vercel project or team + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | No | Filter deployments by project ID or name | +| `target` | string | No | Filter by environment: production or staging | +| `state` | string | No | Filter by state: BUILDING, ERROR, INITIALIZING, QUEUED, READY, CANCELED | +| `app` | string | No | Filter by deployment name | +| `since` | number | No | Get deployments created after this JavaScript timestamp | +| `until` | number | No | Get deployments created before this JavaScript timestamp | +| `limit` | number | No | Maximum number of deployments to return per request | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deployments` | array | List of deployments | +| ↳ `uid` | string | Unique deployment identifier | +| ↳ `name` | string | Deployment name | +| ↳ `url` | string | Deployment URL | +| ↳ `state` | string | Deployment state: BUILDING, ERROR, INITIALIZING, QUEUED, READY, CANCELED, DELETED | +| ↳ `target` | string | Target environment | +| ↳ `created` | number | Creation timestamp | +| ↳ `projectId` | string | Associated project ID | +| ↳ `source` | string | Deployment source: api-trigger-git-deploy, cli, clone/repo, git, import, import/repo, redeploy, v0-web | +| ↳ `inspectorUrl` | string | Vercel inspector URL | +| ↳ `creator` | object | Creator information | +| ↳ `uid` | string | Creator user ID | +| ↳ `email` | string | Creator email | +| ↳ `username` | string | Creator username | +| ↳ `meta` | object | Git provider metadata \(key-value strings\) | +| `count` | number | Number of deployments returned | +| `hasMore` | boolean | Whether more deployments are available | + +### `vercel_get_deployment` + +Get details of a specific Vercel deployment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `deploymentId` | string | Yes | The unique deployment identifier or hostname | +| `withGitRepoInfo` | string | No | Whether to add in gitRepo information \(true/false\) | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Deployment ID | +| `name` | string | Deployment name | +| `url` | string | Unique deployment URL | +| `readyState` | string | Deployment ready state: QUEUED, BUILDING, ERROR, INITIALIZING, READY, CANCELED | +| `status` | string | Deployment status | +| `target` | string | Target environment | +| `createdAt` | number | Creation timestamp in milliseconds | +| `buildingAt` | number | Build start timestamp | +| `ready` | number | Ready timestamp | +| `source` | string | Deployment source: cli, git, redeploy, import, v0-web, etc. | +| `alias` | array | Assigned aliases | +| `regions` | array | Deployment regions | +| `inspectorUrl` | string | Vercel inspector URL | +| `projectId` | string | Associated project ID | +| `creator` | object | Creator information | +| ↳ `uid` | string | Creator user ID | +| ↳ `username` | string | Creator username | +| `project` | object | Associated project | +| ↳ `id` | string | Project ID | +| ↳ `name` | string | Project name | +| ↳ `framework` | string | Project framework | +| `meta` | object | Deployment metadata \(key-value strings\) | +| ↳ `githubCommitSha` | string | GitHub commit SHA | +| ↳ `githubCommitMessage` | string | GitHub commit message | +| ↳ `githubCommitRef` | string | GitHub branch/ref | +| ↳ `githubRepo` | string | GitHub repository | +| ↳ `githubOrg` | string | GitHub organization | +| ↳ `githubCommitAuthorName` | string | Commit author name | +| `gitSource` | object | Git source information | +| ↳ `type` | string | Git provider type \(e.g., github, gitlab, bitbucket\) | +| ↳ `ref` | string | Git ref \(branch or tag\) | +| ↳ `sha` | string | Git commit SHA | +| ↳ `repoId` | string | Repository ID | + +### `vercel_create_deployment` + +Create a new deployment or redeploy an existing one + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `name` | string | Yes | Project name for the deployment | +| `project` | string | No | Project ID \(overrides name for project lookup\) | +| `deploymentId` | string | No | Existing deployment ID to redeploy | +| `target` | string | No | Target environment: production, staging, or a custom environment identifier | +| `gitSource` | string | No | JSON string defining the Git Repository source to deploy \(e.g. \{"type":"github","repo":"owner/repo","ref":"main"\}\) | +| `forceNew` | string | No | Forces a new deployment even if there is a previous similar deployment \(0 or 1\) | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Deployment ID | +| `name` | string | Deployment name | +| `url` | string | Unique deployment URL | +| `readyState` | string | Deployment ready state: QUEUED, BUILDING, ERROR, INITIALIZING, READY, CANCELED | +| `projectId` | string | Associated project ID | +| `createdAt` | number | Creation timestamp in milliseconds | +| `alias` | array | Assigned aliases | +| `target` | string | Target environment | +| `inspectorUrl` | string | Vercel inspector URL | + +### `vercel_cancel_deployment` + +Cancel a running Vercel deployment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `deploymentId` | string | Yes | The deployment ID to cancel | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Deployment ID | +| `name` | string | Deployment name | +| `state` | string | Deployment state after cancellation | +| `url` | string | Deployment URL | + +### `vercel_delete_deployment` + +Delete a Vercel deployment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `deploymentId` | string | Yes | The deployment ID or URL to delete | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `uid` | string | The removed deployment ID | +| `state` | string | Deployment state after deletion \(DELETED\) | + +### `vercel_get_deployment_events` + +Get build and runtime events for a Vercel deployment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `deploymentId` | string | Yes | The unique deployment identifier or hostname | +| `direction` | string | No | Order of events by timestamp: backward or forward \(default: forward\) | +| `follow` | number | No | When set to 1, returns live events as they happen | +| `limit` | number | No | Maximum number of events to return \(-1 for all\) | +| `since` | number | No | Timestamp to start pulling build logs from | +| `until` | number | No | Timestamp to stop pulling build logs at | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `events` | array | List of deployment events | +| ↳ `type` | string | Event type: delimiter, command, stdout, stderr, exit, deployment-state, middleware, middleware-invocation, edge-function-invocation, metric, report, fatal | +| ↳ `created` | number | Event creation timestamp | +| ↳ `date` | number | Event date timestamp | +| ↳ `text` | string | Event text content | +| ↳ `serial` | string | Event serial identifier | +| ↳ `deploymentId` | string | Associated deployment ID | +| ↳ `id` | string | Event unique identifier | +| ↳ `level` | string | Event level: error or warning | +| `count` | number | Number of events returned | + +### `vercel_list_deployment_files` + +List files in a Vercel deployment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `deploymentId` | string | Yes | The deployment ID to list files for | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `files` | array | List of deployment files | +| ↳ `name` | string | The name of the file tree entry | +| ↳ `type` | string | File type: directory, file, symlink, lambda, middleware, or invalid | +| ↳ `uid` | string | Unique file identifier \(only valid for file type\) | +| ↳ `mode` | number | File mode indicating file type and permissions | +| ↳ `contentType` | string | Content-type of the file \(only valid for file type\) | +| ↳ `children` | array | Child files of the directory \(only valid for directory type\) | +| ↳ `name` | string | File name | +| ↳ `type` | string | Entry type | +| ↳ `uid` | string | File identifier | +| `count` | number | Number of files returned | + +### `vercel_list_projects` + +List all projects in a Vercel team or account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `search` | string | No | Search projects by name | +| `limit` | number | No | Maximum number of projects to return | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `projects` | array | List of projects | +| ↳ `id` | string | Project ID | +| ↳ `name` | string | Project name | +| ↳ `framework` | string | Framework | +| ↳ `createdAt` | number | Creation timestamp | +| ↳ `updatedAt` | number | Last updated timestamp | +| ↳ `domains` | array | Project domains | +| `count` | number | Number of projects returned | +| `hasMore` | boolean | Whether more projects are available | + +### `vercel_get_project` + +Get details of a specific Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | Yes | Project ID or name | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Project ID | +| `name` | string | Project name | +| `framework` | string | Project framework | +| `createdAt` | number | Creation timestamp | +| `updatedAt` | number | Last updated timestamp | +| `domains` | array | Project domains | +| `link` | object | Git repository connection | +| ↳ `type` | string | Repository type \(github, gitlab, bitbucket\) | +| ↳ `repo` | string | Repository name | +| ↳ `org` | string | Organization or owner | + +### `vercel_create_project` + +Create a new Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `name` | string | Yes | Project name | +| `framework` | string | No | Project framework \(e.g. nextjs, remix, vite\) | +| `gitRepository` | json | No | Git repository connection object with type and repo | +| `buildCommand` | string | No | Custom build command | +| `outputDirectory` | string | No | Custom output directory | +| `installCommand` | string | No | Custom install command | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Project ID | +| `name` | string | Project name | +| `framework` | string | Project framework | +| `createdAt` | number | Creation timestamp | +| `updatedAt` | number | Last updated timestamp | + +### `vercel_update_project` + +Update an existing Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | Yes | Project ID or name | +| `name` | string | No | New project name | +| `framework` | string | No | Project framework \(e.g. nextjs, remix, vite\) | +| `buildCommand` | string | No | Custom build command | +| `outputDirectory` | string | No | Custom output directory | +| `installCommand` | string | No | Custom install command | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Project ID | +| `name` | string | Project name | +| `framework` | string | Project framework | +| `updatedAt` | number | Last updated timestamp | + +### `vercel_delete_project` + +Delete a Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | Yes | Project ID or name | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the project was successfully deleted | + +### `vercel_pause_project` + +Pause a Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | Yes | Project ID or name | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Project ID | +| `name` | string | Project name | +| `paused` | boolean | Whether the project is paused | + +### `vercel_unpause_project` + +Unpause a Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | Yes | Project ID or name | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Project ID | +| `name` | string | Project name | +| `paused` | boolean | Whether the project is paused | + +### `vercel_list_project_domains` + +List all domains for a Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | Yes | Project ID or name | +| `teamId` | string | No | Team ID to scope the request | +| `limit` | number | No | Maximum number of domains to return | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `domains` | array | List of project domains | +| ↳ `name` | string | Domain name | +| ↳ `apexName` | string | Apex domain name | +| ↳ `redirect` | string | Redirect target | +| ↳ `redirectStatusCode` | number | Redirect status code | +| ↳ `verified` | boolean | Whether the domain is verified | +| ↳ `gitBranch` | string | Git branch for the domain | +| ↳ `createdAt` | number | Creation timestamp | +| ↳ `updatedAt` | number | Last updated timestamp | +| `count` | number | Number of domains returned | +| `hasMore` | boolean | Whether more domains are available | + +### `vercel_add_project_domain` + +Add a domain to a Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | Yes | Project ID or name | +| `domain` | string | Yes | Domain name to add | +| `redirect` | string | No | Target domain for redirect | +| `redirectStatusCode` | number | No | HTTP status code for redirect \(301, 302, 307, 308\) | +| `gitBranch` | string | No | Git branch to link the domain to | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `name` | string | Domain name | +| `apexName` | string | Apex domain name | +| `verified` | boolean | Whether the domain is verified | +| `gitBranch` | string | Git branch for the domain | +| `redirect` | string | Redirect target domain | +| `redirectStatusCode` | number | HTTP status code for redirect \(301, 302, 307, 308\) | +| `createdAt` | number | Creation timestamp | +| `updatedAt` | number | Last updated timestamp | + +### `vercel_remove_project_domain` + +Remove a domain from a Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | Yes | Project ID or name | +| `domain` | string | Yes | Domain name to remove | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the domain was successfully removed | + +### `vercel_get_env_vars` + +Retrieve environment variables for a Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | Yes | Project ID or name | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `envs` | array | List of environment variables | +| ↳ `id` | string | Environment variable ID | +| ↳ `key` | string | Variable name | +| ↳ `value` | string | Variable value | +| ↳ `type` | string | Variable type \(secret, system, encrypted, plain, sensitive\) | +| ↳ `target` | array | Target environments | +| ↳ `gitBranch` | string | Git branch filter | +| ↳ `comment` | string | Comment providing context for the variable | +| `count` | number | Number of environment variables returned | + +### `vercel_create_env_var` + +Create an environment variable for a Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | Yes | Project ID or name | +| `key` | string | Yes | Environment variable name | +| `value` | string | Yes | Environment variable value | +| `target` | string | Yes | Comma-separated list of target environments \(production, preview, development\) | +| `type` | string | No | Variable type: system, secret, encrypted, plain, or sensitive \(default: plain\) | +| `gitBranch` | string | No | Git branch to associate with the variable \(requires target to include preview\) | +| `comment` | string | No | Comment to add context to the variable \(max 500 characters\) | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Environment variable ID | +| `key` | string | Variable name | +| `value` | string | Variable value | +| `type` | string | Variable type \(secret, system, encrypted, plain, sensitive\) | +| `target` | array | Target environments | +| `gitBranch` | string | Git branch filter | +| `comment` | string | Comment providing context for the variable | + +### `vercel_update_env_var` + +Update an environment variable for a Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | Yes | Project ID or name | +| `envId` | string | Yes | Environment variable ID to update | +| `key` | string | No | New variable name | +| `value` | string | No | New variable value | +| `target` | string | No | Comma-separated list of target environments \(production, preview, development\) | +| `type` | string | No | Variable type: system, secret, encrypted, plain, or sensitive | +| `gitBranch` | string | No | Git branch to associate with the variable \(requires target to include preview\) | +| `comment` | string | No | Comment to add context to the variable \(max 500 characters\) | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Environment variable ID | +| `key` | string | Variable name | +| `value` | string | Variable value | +| `type` | string | Variable type \(secret, system, encrypted, plain, sensitive\) | +| `target` | array | Target environments | +| `gitBranch` | string | Git branch filter | +| `comment` | string | Comment providing context for the variable | + +### `vercel_delete_env_var` + +Delete an environment variable from a Vercel project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | Yes | Project ID or name | +| `envId` | string | Yes | Environment variable ID to delete | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the environment variable was successfully deleted | + +### `vercel_list_domains` + +List all domains in a Vercel account or team + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `limit` | number | No | Maximum number of domains to return \(default 20\) | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `domains` | array | List of domains | +| ↳ `id` | string | Domain ID | +| ↳ `name` | string | Domain name | +| ↳ `verified` | boolean | Whether domain is verified | +| ↳ `createdAt` | number | Creation timestamp | +| ↳ `expiresAt` | number | Expiration timestamp | +| ↳ `serviceType` | string | Service type \(zeit.world, external, na\) | +| ↳ `nameservers` | array | Current nameservers | +| ↳ `intendedNameservers` | array | Intended nameservers | +| ↳ `renew` | boolean | Whether auto-renewal is enabled | +| ↳ `boughtAt` | number | Purchase timestamp | +| `count` | number | Number of domains returned | +| `hasMore` | boolean | Whether more domains are available | + +### `vercel_get_domain` + +Get information about a specific domain in a Vercel account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `domain` | string | Yes | The domain name to retrieve | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Domain ID | +| `name` | string | Domain name | +| `verified` | boolean | Whether domain is verified | +| `createdAt` | number | Creation timestamp | +| `expiresAt` | number | Expiration timestamp | +| `serviceType` | string | Service type \(zeit.world, external, na\) | +| `nameservers` | array | Current nameservers | +| `intendedNameservers` | array | Intended nameservers | +| `customNameservers` | array | Custom nameservers | +| `renew` | boolean | Whether auto-renewal is enabled | +| `boughtAt` | number | Purchase timestamp | +| `transferredAt` | number | Transfer completion timestamp | + +### `vercel_add_domain` + +Add a new domain to a Vercel account or team + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `name` | string | Yes | The domain name to add | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Domain ID | +| `name` | string | Domain name | +| `verified` | boolean | Whether domain is verified | +| `createdAt` | number | Creation timestamp | +| `serviceType` | string | Service type \(zeit.world, external, na\) | +| `nameservers` | array | Current nameservers | +| `intendedNameservers` | array | Intended nameservers | + +### `vercel_delete_domain` + +Delete a domain from a Vercel account or team + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `domain` | string | Yes | The domain name to delete | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `uid` | string | The ID of the deleted domain | +| `deleted` | boolean | Whether the domain was deleted | + +### `vercel_get_domain_config` + +Get the configuration for a domain in a Vercel account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `domain` | string | Yes | The domain name to get configuration for | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `configuredBy` | string | How the domain is configured \(CNAME, A, http, dns-01, or null\) | +| `acceptedChallenges` | array | Accepted challenge types for certificate issuance \(dns-01, http-01\) | +| `misconfigured` | boolean | Whether the domain is misconfigured for TLS certificate generation | +| `recommendedIPv4` | array | Recommended IPv4 addresses with rank values | +| ↳ `rank` | number | Priority rank \(1 is preferred\) | +| ↳ `value` | array | IPv4 addresses | +| `recommendedCNAME` | array | Recommended CNAME records with rank values | +| ↳ `rank` | number | Priority rank \(1 is preferred\) | +| ↳ `value` | string | CNAME value | + +### `vercel_list_dns_records` + +List all DNS records for a domain in a Vercel account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `domain` | string | Yes | The domain name to list records for | +| `limit` | number | No | Maximum number of records to return | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `records` | array | List of DNS records | +| ↳ `id` | string | Record ID | +| ↳ `slug` | string | Record slug | +| ↳ `name` | string | Record name | +| ↳ `type` | string | Record type \(A, AAAA, ALIAS, CAA, CNAME, HTTPS, MX, SRV, TXT, NS\) | +| ↳ `value` | string | Record value | +| ↳ `ttl` | number | Time to live in seconds | +| ↳ `mxPriority` | number | MX record priority | +| ↳ `priority` | number | Record priority | +| ↳ `creator` | string | Creator identifier | +| ↳ `createdAt` | number | Creation timestamp | +| ↳ `updatedAt` | number | Last update timestamp | +| ↳ `comment` | string | Record comment | +| `count` | number | Number of records returned | +| `hasMore` | boolean | Whether more records are available | + +### `vercel_create_dns_record` + +Create a DNS record for a domain in a Vercel account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `domain` | string | Yes | The domain name to create the record for | +| `recordName` | string | Yes | The subdomain or record name | +| `recordType` | string | Yes | DNS record type \(A, AAAA, ALIAS, CAA, CNAME, HTTPS, MX, SRV, TXT, NS\) | +| `value` | string | Yes | The value of the DNS record | +| `ttl` | number | No | Time to live in seconds | +| `mxPriority` | number | No | Priority for MX records | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `uid` | string | The DNS record ID | +| `updated` | number | Timestamp of the update | + +### `vercel_delete_dns_record` + +Delete a DNS record for a domain in a Vercel account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `domain` | string | Yes | The domain name the record belongs to | +| `recordId` | string | Yes | The ID of the DNS record to delete | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the record was deleted | + +### `vercel_list_aliases` + +List aliases for a Vercel project or team + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | No | Filter aliases by project ID | +| `domain` | string | No | Filter aliases by domain | +| `limit` | number | No | Maximum number of aliases to return | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `aliases` | array | List of aliases | +| ↳ `uid` | string | Alias ID | +| ↳ `alias` | string | Alias hostname | +| ↳ `deploymentId` | string | Associated deployment ID | +| ↳ `projectId` | string | Associated project ID | +| ↳ `createdAt` | number | Creation timestamp in milliseconds | +| ↳ `updatedAt` | number | Last update timestamp in milliseconds | +| `count` | number | Number of aliases returned | +| `hasMore` | boolean | Whether more aliases are available | + +### `vercel_get_alias` + +Get details about a specific alias by ID or hostname + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `aliasId` | string | Yes | Alias ID or hostname to look up | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `uid` | string | Alias ID | +| `alias` | string | Alias hostname | +| `deploymentId` | string | Associated deployment ID | +| `projectId` | string | Associated project ID | +| `createdAt` | number | Creation timestamp in milliseconds | +| `updatedAt` | number | Last update timestamp in milliseconds | +| `redirect` | string | Target domain for redirect aliases | +| `redirectStatusCode` | number | HTTP status code for redirect \(301, 302, 307, or 308\) | + +### `vercel_create_alias` + +Assign an alias (domain/subdomain) to a deployment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `deploymentId` | string | Yes | Deployment ID to assign the alias to | +| `alias` | string | Yes | The domain or subdomain to assign as an alias | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `uid` | string | Alias ID | +| `alias` | string | Alias hostname | +| `created` | string | Creation timestamp as ISO 8601 date-time string | +| `oldDeploymentId` | string | ID of the previously aliased deployment, if the alias was reassigned | + +### `vercel_delete_alias` + +Delete an alias by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `aliasId` | string | Yes | Alias ID to delete | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | string | Deletion status \(SUCCESS\) | + +### `vercel_list_edge_configs` + +List all Edge Config stores for a team + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `edgeConfigs` | array | List of Edge Config stores | +| ↳ `id` | string | Edge Config ID | +| ↳ `slug` | string | Edge Config slug | +| ↳ `ownerId` | string | Owner ID | +| ↳ `digest` | string | Content digest hash | +| ↳ `createdAt` | number | Creation timestamp | +| ↳ `updatedAt` | number | Last update timestamp | +| ↳ `itemCount` | number | Number of items | +| ↳ `sizeInBytes` | number | Size in bytes | +| `count` | number | Number of Edge Configs returned | + +### `vercel_get_edge_config` + +Get details about a specific Edge Config store + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `edgeConfigId` | string | Yes | Edge Config ID to look up | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Edge Config ID | +| `slug` | string | Edge Config slug | +| `ownerId` | string | Owner ID | +| `digest` | string | Content digest hash | +| `createdAt` | number | Creation timestamp | +| `updatedAt` | number | Last update timestamp | +| `itemCount` | number | Number of items | +| `sizeInBytes` | number | Size in bytes | + +### `vercel_create_edge_config` + +Create a new Edge Config store + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `slug` | string | Yes | The name/slug for the new Edge Config | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Edge Config ID | +| `slug` | string | Edge Config slug | +| `ownerId` | string | Owner ID | +| `digest` | string | Content digest hash | +| `createdAt` | number | Creation timestamp | +| `updatedAt` | number | Last update timestamp | +| `itemCount` | number | Number of items | +| `sizeInBytes` | number | Size in bytes | + +### `vercel_get_edge_config_items` + +Get all items in an Edge Config store + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `edgeConfigId` | string | Yes | Edge Config ID to get items from | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `items` | array | List of Edge Config items | +| ↳ `key` | string | Item key | +| ↳ `value` | json | Item value | +| ↳ `description` | string | Item description | +| ↳ `edgeConfigId` | string | Parent Edge Config ID | +| ↳ `createdAt` | number | Creation timestamp | +| ↳ `updatedAt` | number | Last update timestamp | +| `count` | number | Number of items returned | + +### `vercel_update_edge_config_items` + +Create, update, upsert, or delete items in an Edge Config store + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `edgeConfigId` | string | Yes | Edge Config ID to update items in | +| `items` | json | Yes | Array of operations: \[\{operation: "create"\|"update"\|"upsert"\|"delete", key: string, value?: any\}\] | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `status` | string | Operation status | + +### `vercel_list_webhooks` + +List webhooks for a Vercel project or team + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `projectId` | string | No | Filter webhooks by project ID | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `webhooks` | array | List of webhooks | +| ↳ `id` | string | Webhook ID | +| ↳ `url` | string | Webhook URL | +| ↳ `events` | array | Events the webhook listens to | +| ↳ `ownerId` | string | Owner ID | +| ↳ `projectIds` | array | Associated project IDs | +| ↳ `createdAt` | number | Creation timestamp | +| ↳ `updatedAt` | number | Last updated timestamp | +| `count` | number | Number of webhooks returned | + +### `vercel_create_webhook` + +Create a new webhook for a Vercel team + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `url` | string | Yes | Webhook URL \(must be https\) | +| `events` | string | Yes | Comma-separated event names to subscribe to | +| `projectIds` | string | No | Comma-separated project IDs to scope the webhook to | +| `teamId` | string | Yes | Team ID to create the webhook for | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Webhook ID | +| `url` | string | Webhook URL | +| `secret` | string | Webhook signing secret | +| `events` | array | Events the webhook listens to | +| `ownerId` | string | Owner ID | +| `projectIds` | array | Associated project IDs | +| `createdAt` | number | Creation timestamp | +| `updatedAt` | number | Last updated timestamp | + +### `vercel_delete_webhook` + +Delete a webhook from a Vercel team + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `webhookId` | string | Yes | The webhook ID to delete | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the webhook was successfully deleted | + +### `vercel_create_check` + +Create a new deployment check + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `deploymentId` | string | Yes | Deployment ID to create the check for | +| `name` | string | Yes | Name of the check \(max 100 characters\) | +| `blocking` | boolean | Yes | Whether the check blocks the deployment | +| `path` | string | No | Page path being checked | +| `detailsUrl` | string | No | URL with details about the check | +| `externalId` | string | No | External identifier for the check | +| `rerequestable` | boolean | No | Whether the check can be rerequested | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Check ID | +| `name` | string | Check name | +| `status` | string | Check status: registered, running, or completed | +| `conclusion` | string | Check conclusion: canceled, failed, neutral, succeeded, skipped, or stale | +| `blocking` | boolean | Whether the check blocks the deployment | +| `deploymentId` | string | Associated deployment ID | +| `integrationId` | string | Associated integration ID | +| `externalId` | string | External identifier | +| `detailsUrl` | string | URL with details about the check | +| `path` | string | Page path being checked | +| `rerequestable` | boolean | Whether the check can be rerequested | +| `createdAt` | number | Creation timestamp in milliseconds | +| `updatedAt` | number | Last update timestamp in milliseconds | +| `startedAt` | number | Start timestamp in milliseconds | +| `completedAt` | number | Completion timestamp in milliseconds | + +### `vercel_get_check` + +Get details of a specific deployment check + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `deploymentId` | string | Yes | Deployment ID the check belongs to | +| `checkId` | string | Yes | Check ID to retrieve | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Check ID | +| `name` | string | Check name | +| `status` | string | Check status: registered, running, or completed | +| `conclusion` | string | Check conclusion: canceled, failed, neutral, succeeded, skipped, or stale | +| `blocking` | boolean | Whether the check blocks the deployment | +| `deploymentId` | string | Associated deployment ID | +| `integrationId` | string | Associated integration ID | +| `externalId` | string | External identifier | +| `detailsUrl` | string | URL with details about the check | +| `path` | string | Page path being checked | +| `rerequestable` | boolean | Whether the check can be rerequested | +| `createdAt` | number | Creation timestamp in milliseconds | +| `updatedAt` | number | Last update timestamp in milliseconds | +| `startedAt` | number | Start timestamp in milliseconds | +| `completedAt` | number | Completion timestamp in milliseconds | + +### `vercel_list_checks` + +List all checks for a deployment + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `deploymentId` | string | Yes | Deployment ID to list checks for | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `checks` | array | List of deployment checks | +| ↳ `id` | string | Check ID | +| ↳ `name` | string | Check name | +| ↳ `status` | string | Check status | +| ↳ `conclusion` | string | Check conclusion | +| ↳ `blocking` | boolean | Whether the check blocks the deployment | +| ↳ `deploymentId` | string | Associated deployment ID | +| ↳ `integrationId` | string | Associated integration ID | +| ↳ `externalId` | string | External identifier | +| ↳ `detailsUrl` | string | URL with details about the check | +| ↳ `path` | string | Page path being checked | +| ↳ `rerequestable` | boolean | Whether the check can be rerequested | +| ↳ `createdAt` | number | Creation timestamp | +| ↳ `updatedAt` | number | Last update timestamp | +| ↳ `startedAt` | number | Start timestamp | +| ↳ `completedAt` | number | Completion timestamp | +| `count` | number | Total number of checks | + +### `vercel_update_check` + +Update an existing deployment check + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `deploymentId` | string | Yes | Deployment ID the check belongs to | +| `checkId` | string | Yes | Check ID to update | +| `name` | string | No | Updated name of the check | +| `status` | string | No | Updated status: running or completed | +| `conclusion` | string | No | Check conclusion: canceled, failed, neutral, succeeded, or skipped | +| `detailsUrl` | string | No | URL with details about the check | +| `externalId` | string | No | External identifier for the check | +| `path` | string | No | Page path being checked | +| `output` | string | No | JSON string with check output metrics | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Check ID | +| `name` | string | Check name | +| `status` | string | Check status: registered, running, or completed | +| `conclusion` | string | Check conclusion: canceled, failed, neutral, succeeded, skipped, or stale | +| `blocking` | boolean | Whether the check blocks the deployment | +| `deploymentId` | string | Associated deployment ID | +| `integrationId` | string | Associated integration ID | +| `externalId` | string | External identifier | +| `detailsUrl` | string | URL with details about the check | +| `path` | string | Page path being checked | +| `rerequestable` | boolean | Whether the check can be rerequested | +| `createdAt` | number | Creation timestamp in milliseconds | +| `updatedAt` | number | Last update timestamp in milliseconds | +| `startedAt` | number | Start timestamp in milliseconds | +| `completedAt` | number | Completion timestamp in milliseconds | + +### `vercel_rerequest_check` + +Rerequest a deployment check + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `deploymentId` | string | Yes | Deployment ID the check belongs to | +| `checkId` | string | Yes | Check ID to rerequest | +| `teamId` | string | No | Team ID to scope the request | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `rerequested` | boolean | Whether the check was successfully rerequested | + +### `vercel_list_teams` + +List all teams in a Vercel account + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `limit` | number | No | Maximum number of teams to return | +| `since` | number | No | Timestamp in milliseconds to only include teams created since then | +| `until` | number | No | Timestamp in milliseconds to only include teams created until then | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `teams` | array | List of teams | +| ↳ `id` | string | Team ID | +| ↳ `slug` | string | Team slug | +| ↳ `name` | string | Team name | +| ↳ `avatar` | string | Avatar file ID | +| ↳ `createdAt` | number | Creation timestamp in milliseconds | +| ↳ `updatedAt` | number | Last update timestamp in milliseconds | +| ↳ `creatorId` | string | User ID of team creator | +| ↳ `membership` | object | Current user membership details | +| ↳ `role` | string | Membership role | +| ↳ `confirmed` | boolean | Whether membership is confirmed | +| ↳ `created` | number | Membership creation timestamp | +| ↳ `uid` | string | User ID of the member | +| ↳ `teamId` | string | Team ID | +| `count` | number | Number of teams returned | +| `pagination` | object | Pagination information | +| ↳ `count` | number | Items in current page | +| ↳ `next` | number | Timestamp for next page request | +| ↳ `prev` | number | Timestamp for previous page request | + +### `vercel_get_team` + +Get information about a specific Vercel team + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `teamId` | string | Yes | The team ID to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | Team ID | +| `slug` | string | Team slug | +| `name` | string | Team name | +| `avatar` | string | Avatar file ID | +| `description` | string | Short team description | +| `createdAt` | number | Creation timestamp in milliseconds | +| `updatedAt` | number | Last update timestamp in milliseconds | +| `creatorId` | string | User ID of team creator | +| `membership` | object | Current user membership details | +| ↳ `uid` | string | User ID of the member | +| ↳ `teamId` | string | Team ID | +| ↳ `role` | string | Membership role | +| ↳ `confirmed` | boolean | Whether membership is confirmed | +| ↳ `created` | number | Membership creation timestamp | +| ↳ `createdAt` | number | Membership creation timestamp \(milliseconds\) | +| ↳ `accessRequestedAt` | number | When access was requested | +| ↳ `teamRoles` | array | Team role assignments | +| ↳ `teamPermissions` | array | Team permission assignments | + +### `vercel_list_team_members` + +List all members of a Vercel team + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | +| `teamId` | string | Yes | The team ID to list members for | +| `limit` | number | No | Maximum number of members to return | +| `role` | string | No | Filter by role \(OWNER, MEMBER, DEVELOPER, SECURITY, BILLING, VIEWER, VIEWER_FOR_PLUS, CONTRIBUTOR\) | +| `since` | number | No | Timestamp in milliseconds to only include members added since then | +| `until` | number | No | Timestamp in milliseconds to only include members added until then | +| `search` | string | No | Search team members by their name, username, and email | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `members` | array | List of team members | +| ↳ `uid` | string | Member user ID | +| ↳ `email` | string | Member email | +| ↳ `username` | string | Member username | +| ↳ `name` | string | Member full name | +| ↳ `avatar` | string | Avatar file ID | +| ↳ `role` | string | Member role | +| ↳ `confirmed` | boolean | Whether membership is confirmed | +| ↳ `createdAt` | number | Join timestamp in milliseconds | +| ↳ `joinedFrom` | object | Origin of how the member joined | +| ↳ `origin` | string | Join origin identifier | +| `count` | number | Number of members returned | +| `pagination` | object | Pagination information | +| ↳ `hasNext` | boolean | Whether there are more pages | +| ↳ `count` | number | Items in current page | + +### `vercel_get_user` + +Get information about the authenticated Vercel user + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Vercel Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `id` | string | User ID | +| `email` | string | User email | +| `username` | string | Username | +| `name` | string | Display name | +| `avatar` | string | SHA1 hash of the avatar | +| `defaultTeamId` | string | Default team ID | +| `createdAt` | number | Account creation timestamp in milliseconds | +| `stagingPrefix` | string | Prefix for preview deployment URLs | +| `softBlock` | object | Account restriction details if blocked | +| ↳ `blockedAt` | number | When the account was blocked | +| ↳ `reason` | string | Reason for the block | +| `hasTrialAvailable` | boolean | Whether a trial is available | + + diff --git a/apps/sim/blocks/blocks/vercel.ts b/apps/sim/blocks/blocks/vercel.ts new file mode 100644 index 000000000..138b2c23a --- /dev/null +++ b/apps/sim/blocks/blocks/vercel.ts @@ -0,0 +1,1007 @@ +import { VercelIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode } from '@/blocks/types' + +export const VercelBlock: BlockConfig = { + type: 'vercel', + name: 'Vercel', + description: 'Manage Vercel deployments, projects, and infrastructure', + longDescription: + 'Integrate with Vercel to manage deployments, projects, domains, DNS records, environment variables, aliases, edge configs, teams, and more.', + docsLink: 'https://docs.sim.ai/tools/vercel', + category: 'tools', + bgColor: '#171717', + icon: VercelIcon, + authMode: AuthMode.ApiKey, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + // Deployments + { label: 'List Deployments', id: 'list_deployments' }, + { label: 'Get Deployment', id: 'get_deployment' }, + { label: 'Create Deployment', id: 'create_deployment' }, + { label: 'Cancel Deployment', id: 'cancel_deployment' }, + { label: 'Delete Deployment', id: 'delete_deployment' }, + { label: 'Get Deployment Logs', id: 'get_deployment_events' }, + { label: 'List Deployment Files', id: 'list_deployment_files' }, + // Projects + { label: 'List Projects', id: 'list_projects' }, + { label: 'Get Project', id: 'get_project' }, + { label: 'Create Project', id: 'create_project' }, + { label: 'Update Project', id: 'update_project' }, + { label: 'Delete Project', id: 'delete_project' }, + { label: 'Pause Project', id: 'pause_project' }, + { label: 'Unpause Project', id: 'unpause_project' }, + // Project Domains + { label: 'List Project Domains', id: 'list_project_domains' }, + { label: 'Add Project Domain', id: 'add_project_domain' }, + { label: 'Remove Project Domain', id: 'remove_project_domain' }, + // Environment Variables + { label: 'Get Environment Variables', id: 'get_env_vars' }, + { label: 'Create Environment Variable', id: 'create_env_var' }, + { label: 'Update Environment Variable', id: 'update_env_var' }, + { label: 'Delete Environment Variable', id: 'delete_env_var' }, + // Domains + { label: 'List Domains', id: 'list_domains' }, + { label: 'Get Domain', id: 'get_domain' }, + { label: 'Add Domain', id: 'add_domain' }, + { label: 'Delete Domain', id: 'delete_domain' }, + { label: 'Get Domain Config', id: 'get_domain_config' }, + // DNS + { label: 'List DNS Records', id: 'list_dns_records' }, + { label: 'Create DNS Record', id: 'create_dns_record' }, + { label: 'Delete DNS Record', id: 'delete_dns_record' }, + // Aliases + { label: 'List Aliases', id: 'list_aliases' }, + { label: 'Get Alias', id: 'get_alias' }, + { label: 'Create Alias', id: 'create_alias' }, + { label: 'Delete Alias', id: 'delete_alias' }, + // Edge Config + { label: 'List Edge Configs', id: 'list_edge_configs' }, + { label: 'Get Edge Config', id: 'get_edge_config' }, + { label: 'Create Edge Config', id: 'create_edge_config' }, + { label: 'Get Edge Config Items', id: 'get_edge_config_items' }, + { label: 'Update Edge Config Items', id: 'update_edge_config_items' }, + // Webhooks + { label: 'List Webhooks', id: 'list_webhooks' }, + { label: 'Create Webhook', id: 'create_webhook' }, + { label: 'Delete Webhook', id: 'delete_webhook' }, + // Checks + { label: 'List Checks', id: 'list_checks' }, + { label: 'Get Check', id: 'get_check' }, + { label: 'Create Check', id: 'create_check' }, + { label: 'Update Check', id: 'update_check' }, + { label: 'Rerequest Check', id: 'rerequest_check' }, + // Teams & User + { label: 'List Teams', id: 'list_teams' }, + { label: 'Get Team', id: 'get_team' }, + { label: 'List Team Members', id: 'list_team_members' }, + { label: 'Get User', id: 'get_user' }, + ], + value: () => 'list_deployments', + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + placeholder: 'Enter Vercel Access Token', + required: true, + password: true, + }, + + // === Deployment fields === + { + id: 'projectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'Filter by project ID or name (optional)', + condition: { field: 'operation', value: 'list_deployments' }, + }, + { + id: 'target', + title: 'Target', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'Production', id: 'production' }, + { label: 'Staging', id: 'staging' }, + ], + condition: { field: 'operation', value: 'list_deployments' }, + }, + { + id: 'state', + title: 'State', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'Ready', id: 'READY' }, + { label: 'Building', id: 'BUILDING' }, + { label: 'Error', id: 'ERROR' }, + { label: 'Queued', id: 'QUEUED' }, + { label: 'Canceled', id: 'CANCELED' }, + ], + condition: { field: 'operation', value: 'list_deployments' }, + }, + { + id: 'deploymentId', + title: 'Deployment ID', + type: 'short-input', + placeholder: 'Enter deployment ID or hostname', + condition: { + field: 'operation', + value: [ + 'get_deployment', + 'cancel_deployment', + 'delete_deployment', + 'get_deployment_events', + 'list_deployment_files', + ], + }, + required: { + field: 'operation', + value: [ + 'get_deployment', + 'cancel_deployment', + 'delete_deployment', + 'get_deployment_events', + 'list_deployment_files', + ], + }, + }, + // Create Deployment + { + id: 'name', + title: 'Project Name', + type: 'short-input', + placeholder: 'Project name for the deployment', + condition: { field: 'operation', value: 'create_deployment' }, + required: { field: 'operation', value: 'create_deployment' }, + }, + { + id: 'project', + title: 'Project ID', + type: 'short-input', + placeholder: 'Project ID (optional, overrides name)', + condition: { field: 'operation', value: 'create_deployment' }, + }, + { + id: 'redeployId', + title: 'Redeploy From', + type: 'short-input', + placeholder: 'Existing deployment ID to redeploy (optional)', + condition: { field: 'operation', value: 'create_deployment' }, + }, + { + id: 'deployTarget', + title: 'Target', + type: 'dropdown', + options: [ + { label: 'Preview', id: '' }, + { label: 'Production', id: 'production' }, + { label: 'Staging', id: 'staging' }, + ], + condition: { field: 'operation', value: 'create_deployment' }, + }, + + // === Project fields === + { + id: 'search', + title: 'Search', + type: 'short-input', + placeholder: 'Search projects by name (optional)', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'projectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'Enter project ID or name', + condition: { + field: 'operation', + value: [ + 'get_project', + 'update_project', + 'delete_project', + 'pause_project', + 'unpause_project', + 'list_project_domains', + 'add_project_domain', + 'remove_project_domain', + 'get_env_vars', + 'create_env_var', + 'update_env_var', + 'delete_env_var', + ], + }, + required: { + field: 'operation', + value: [ + 'get_project', + 'update_project', + 'delete_project', + 'pause_project', + 'unpause_project', + 'list_project_domains', + 'add_project_domain', + 'remove_project_domain', + 'get_env_vars', + 'create_env_var', + 'update_env_var', + 'delete_env_var', + ], + }, + }, + // Create Project + { + id: 'projectName', + title: 'Project Name', + type: 'short-input', + placeholder: 'Name for the new project', + condition: { field: 'operation', value: 'create_project' }, + required: { field: 'operation', value: 'create_project' }, + }, + { + id: 'framework', + title: 'Framework', + type: 'dropdown', + options: [ + { label: 'Auto-detect', id: '' }, + { label: 'Next.js', id: 'nextjs' }, + { label: 'Remix', id: 'remix' }, + { label: 'Vite', id: 'vite' }, + { label: 'Nuxt', id: 'nuxtjs' }, + { label: 'SvelteKit', id: 'sveltekit' }, + { label: 'Astro', id: 'astro' }, + { label: 'Gatsby', id: 'gatsby' }, + { label: 'Other', id: 'other' }, + ], + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + }, + { + id: 'buildCommand', + title: 'Build Command', + type: 'short-input', + placeholder: 'Custom build command (optional)', + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + }, + { + id: 'outputDirectory', + title: 'Output Directory', + type: 'short-input', + placeholder: 'Output directory (optional)', + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + }, + { + id: 'installCommand', + title: 'Install Command', + type: 'short-input', + placeholder: 'Install command (optional)', + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + }, + + // === Project Domain fields === + { + id: 'domainName', + title: 'Domain', + type: 'short-input', + placeholder: 'Enter domain name (e.g., example.com)', + condition: { + field: 'operation', + value: [ + 'add_project_domain', + 'remove_project_domain', + 'get_domain', + 'delete_domain', + 'get_domain_config', + 'list_dns_records', + 'create_dns_record', + 'delete_dns_record', + 'add_domain', + ], + }, + required: { + field: 'operation', + value: [ + 'add_project_domain', + 'remove_project_domain', + 'get_domain', + 'delete_domain', + 'get_domain_config', + 'list_dns_records', + 'create_dns_record', + 'delete_dns_record', + 'add_domain', + ], + }, + }, + + // === Environment Variable fields === + { + id: 'envId', + title: 'Env Variable ID', + type: 'short-input', + placeholder: 'Environment variable ID', + condition: { field: 'operation', value: ['update_env_var', 'delete_env_var'] }, + required: { field: 'operation', value: ['update_env_var', 'delete_env_var'] }, + }, + { + id: 'envKey', + title: 'Key', + type: 'short-input', + placeholder: 'Variable name (e.g., DATABASE_URL)', + condition: { field: 'operation', value: ['create_env_var', 'update_env_var'] }, + required: { field: 'operation', value: 'create_env_var' }, + }, + { + id: 'envValue', + title: 'Value', + type: 'short-input', + placeholder: 'Variable value', + condition: { field: 'operation', value: ['create_env_var', 'update_env_var'] }, + required: { field: 'operation', value: 'create_env_var' }, + }, + { + id: 'envTarget', + title: 'Target Environments', + type: 'short-input', + placeholder: 'production,preview,development', + condition: { field: 'operation', value: ['create_env_var', 'update_env_var'] }, + required: { field: 'operation', value: 'create_env_var' }, + }, + { + id: 'envType', + title: 'Variable Type', + type: 'dropdown', + options: [ + { label: 'Plain', id: 'plain' }, + { label: 'Secret', id: 'secret' }, + { label: 'Encrypted', id: 'encrypted' }, + { label: 'Sensitive', id: 'sensitive' }, + ], + condition: { field: 'operation', value: ['create_env_var', 'update_env_var'] }, + }, + + // === DNS fields === + { + id: 'recordName', + title: 'Record Name', + type: 'short-input', + placeholder: 'Subdomain (e.g., www)', + condition: { field: 'operation', value: 'create_dns_record' }, + required: { field: 'operation', value: 'create_dns_record' }, + }, + { + id: 'recordType', + title: 'Record Type', + type: 'dropdown', + options: [ + { label: 'A', id: 'A' }, + { label: 'AAAA', id: 'AAAA' }, + { label: 'CNAME', id: 'CNAME' }, + { label: 'TXT', id: 'TXT' }, + { label: 'MX', id: 'MX' }, + { label: 'NS', id: 'NS' }, + { label: 'ALIAS', id: 'ALIAS' }, + { label: 'SRV', id: 'SRV' }, + { label: 'CAA', id: 'CAA' }, + ], + condition: { field: 'operation', value: 'create_dns_record' }, + required: { field: 'operation', value: 'create_dns_record' }, + }, + { + id: 'recordValue', + title: 'Value', + type: 'short-input', + placeholder: 'Record value (e.g., IP address)', + condition: { field: 'operation', value: 'create_dns_record' }, + required: { field: 'operation', value: 'create_dns_record' }, + }, + { + id: 'recordId', + title: 'Record ID', + type: 'short-input', + placeholder: 'DNS record ID', + condition: { field: 'operation', value: 'delete_dns_record' }, + required: { field: 'operation', value: 'delete_dns_record' }, + }, + + // === Alias fields === + { + id: 'aliasId', + title: 'Alias ID', + type: 'short-input', + placeholder: 'Alias ID or hostname', + condition: { field: 'operation', value: ['get_alias', 'delete_alias'] }, + required: { field: 'operation', value: ['get_alias', 'delete_alias'] }, + }, + { + id: 'aliasDeploymentId', + title: 'Deployment ID', + type: 'short-input', + placeholder: 'Deployment ID to assign alias to', + condition: { field: 'operation', value: 'create_alias' }, + required: { field: 'operation', value: 'create_alias' }, + }, + { + id: 'aliasName', + title: 'Alias', + type: 'short-input', + placeholder: 'Domain or subdomain to assign (e.g., my-app.vercel.app)', + condition: { field: 'operation', value: 'create_alias' }, + required: { field: 'operation', value: 'create_alias' }, + }, + + // === Edge Config fields === + { + id: 'edgeConfigId', + title: 'Edge Config ID', + type: 'short-input', + placeholder: 'Edge Config ID', + condition: { + field: 'operation', + value: ['get_edge_config', 'get_edge_config_items', 'update_edge_config_items'], + }, + required: { + field: 'operation', + value: ['get_edge_config', 'get_edge_config_items', 'update_edge_config_items'], + }, + }, + { + id: 'edgeConfigSlug', + title: 'Slug', + type: 'short-input', + placeholder: 'Name/slug for the Edge Config', + condition: { field: 'operation', value: 'create_edge_config' }, + required: { field: 'operation', value: 'create_edge_config' }, + }, + { + id: 'edgeConfigItems', + title: 'Items', + type: 'code', + placeholder: '[{"operation":"upsert","key":"my-key","value":"my-value"}]', + condition: { field: 'operation', value: 'update_edge_config_items' }, + required: { field: 'operation', value: 'update_edge_config_items' }, + }, + + // === Webhook fields === + { + id: 'webhookUrl', + title: 'Webhook URL', + type: 'short-input', + placeholder: 'https://example.com/webhook', + condition: { field: 'operation', value: 'create_webhook' }, + required: { field: 'operation', value: 'create_webhook' }, + }, + { + id: 'webhookEvents', + title: 'Events', + type: 'short-input', + placeholder: 'deployment.created,deployment.succeeded', + condition: { field: 'operation', value: 'create_webhook' }, + required: { field: 'operation', value: 'create_webhook' }, + }, + { + id: 'webhookProjectIds', + title: 'Project IDs', + type: 'short-input', + placeholder: 'Comma-separated project IDs (optional)', + condition: { field: 'operation', value: 'create_webhook' }, + }, + { + id: 'webhookId', + title: 'Webhook ID', + type: 'short-input', + placeholder: 'Webhook ID', + condition: { field: 'operation', value: 'delete_webhook' }, + required: { field: 'operation', value: 'delete_webhook' }, + }, + + // === Check fields === + { + id: 'checkDeploymentId', + title: 'Deployment ID', + type: 'short-input', + placeholder: 'Deployment ID', + condition: { + field: 'operation', + value: ['create_check', 'get_check', 'list_checks', 'update_check', 'rerequest_check'], + }, + required: { + field: 'operation', + value: ['create_check', 'get_check', 'list_checks', 'update_check', 'rerequest_check'], + }, + }, + { + id: 'checkId', + title: 'Check ID', + type: 'short-input', + placeholder: 'Check ID', + condition: { + field: 'operation', + value: ['get_check', 'update_check', 'rerequest_check'], + }, + required: { + field: 'operation', + value: ['get_check', 'update_check', 'rerequest_check'], + }, + }, + { + id: 'checkName', + title: 'Check Name', + type: 'short-input', + placeholder: 'Name of the check (max 100 chars)', + condition: { field: 'operation', value: ['create_check', 'update_check'] }, + required: { field: 'operation', value: 'create_check' }, + }, + { + id: 'checkBlocking', + title: 'Blocking', + type: 'dropdown', + options: [ + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + condition: { field: 'operation', value: 'create_check' }, + required: { field: 'operation', value: 'create_check' }, + }, + { + id: 'checkPath', + title: 'Path', + type: 'short-input', + placeholder: 'Page path being checked (optional)', + condition: { field: 'operation', value: ['create_check', 'update_check'] }, + }, + { + id: 'checkDetailsUrl', + title: 'Details URL', + type: 'short-input', + placeholder: 'URL for more details (optional)', + condition: { field: 'operation', value: ['create_check', 'update_check'] }, + }, + { + id: 'checkStatus', + title: 'Status', + type: 'dropdown', + options: [ + { label: 'Running', id: 'running' }, + { label: 'Completed', id: 'completed' }, + ], + condition: { field: 'operation', value: 'update_check' }, + }, + { + id: 'checkConclusion', + title: 'Conclusion', + type: 'dropdown', + options: [ + { label: 'Succeeded', id: 'succeeded' }, + { label: 'Failed', id: 'failed' }, + { label: 'Canceled', id: 'canceled' }, + { label: 'Neutral', id: 'neutral' }, + { label: 'Skipped', id: 'skipped' }, + ], + condition: { field: 'operation', value: 'update_check' }, + }, + + // === Team fields === + { + id: 'teamIdParam', + title: 'Team ID', + type: 'short-input', + placeholder: 'Team ID', + condition: { field: 'operation', value: ['get_team', 'list_team_members'] }, + required: { field: 'operation', value: ['get_team', 'list_team_members'] }, + }, + { + id: 'memberRole', + title: 'Role Filter', + type: 'dropdown', + options: [ + { label: 'All', id: '' }, + { label: 'Owner', id: 'OWNER' }, + { label: 'Member', id: 'MEMBER' }, + { label: 'Developer', id: 'DEVELOPER' }, + { label: 'Viewer', id: 'VIEWER' }, + { label: 'Billing', id: 'BILLING' }, + ], + condition: { field: 'operation', value: 'list_team_members' }, + }, + + // === Shared optional Team ID (for scoping requests) === + { + id: 'teamId', + title: 'Team ID (Scope)', + type: 'short-input', + placeholder: 'Team ID to scope request (optional)', + condition: { + field: 'operation', + value: [ + 'get_team', + 'list_team_members', + 'get_user', + 'create_check', + 'get_check', + 'list_checks', + 'update_check', + 'rerequest_check', + ], + not: true, + }, + }, + ], + tools: { + access: [ + // Deployments + 'vercel_list_deployments', + 'vercel_get_deployment', + 'vercel_create_deployment', + 'vercel_cancel_deployment', + 'vercel_delete_deployment', + 'vercel_get_deployment_events', + 'vercel_list_deployment_files', + // Projects + 'vercel_list_projects', + 'vercel_get_project', + 'vercel_create_project', + 'vercel_update_project', + 'vercel_delete_project', + 'vercel_pause_project', + 'vercel_unpause_project', + 'vercel_list_project_domains', + 'vercel_add_project_domain', + 'vercel_remove_project_domain', + // Environment Variables + 'vercel_get_env_vars', + 'vercel_create_env_var', + 'vercel_update_env_var', + 'vercel_delete_env_var', + // Domains + 'vercel_list_domains', + 'vercel_get_domain', + 'vercel_add_domain', + 'vercel_delete_domain', + 'vercel_get_domain_config', + // DNS + 'vercel_list_dns_records', + 'vercel_create_dns_record', + 'vercel_delete_dns_record', + // Aliases + 'vercel_list_aliases', + 'vercel_get_alias', + 'vercel_create_alias', + 'vercel_delete_alias', + // Edge Config + 'vercel_list_edge_configs', + 'vercel_get_edge_config', + 'vercel_create_edge_config', + 'vercel_get_edge_config_items', + 'vercel_update_edge_config_items', + // Webhooks + 'vercel_list_webhooks', + 'vercel_create_webhook', + 'vercel_delete_webhook', + // Checks + 'vercel_create_check', + 'vercel_get_check', + 'vercel_list_checks', + 'vercel_update_check', + 'vercel_rerequest_check', + // Teams & User + 'vercel_list_teams', + 'vercel_get_team', + 'vercel_list_team_members', + 'vercel_get_user', + ], + config: { + tool: (params) => `vercel_${params.operation}`, + params: (params) => { + const { + apiKey, + operation, + redeployId, + deployTarget, + projectName, + domainName, + envKey, + envValue, + envTarget, + envType, + recordName, + recordType, + recordValue, + recordId, + aliasId, + aliasDeploymentId, + aliasName, + edgeConfigId, + edgeConfigSlug, + edgeConfigItems, + webhookId, + webhookUrl, + webhookEvents, + webhookProjectIds, + checkDeploymentId, + checkId, + checkName, + checkBlocking, + checkPath, + checkDetailsUrl, + checkStatus, + checkConclusion, + teamIdParam, + memberRole, + ...rest + } = params + + const base = { ...rest, apiKey } + + switch (operation) { + case 'create_deployment': + return { + ...base, + ...(redeployId ? { deploymentId: redeployId } : {}), + ...(deployTarget ? { target: deployTarget } : {}), + } + case 'create_project': + return { ...base, name: projectName } + case 'update_project': + return base + case 'add_project_domain': + case 'remove_project_domain': + return { ...base, domain: domainName } + case 'get_domain': + case 'delete_domain': + case 'get_domain_config': + return { ...base, domain: domainName } + case 'add_domain': + return { ...base, name: domainName } + case 'list_dns_records': + return { ...base, domain: domainName } + case 'create_dns_record': + return { ...base, domain: domainName, recordName, recordType, value: recordValue } + case 'delete_dns_record': + return { ...base, domain: domainName, recordId } + case 'create_env_var': + return { ...base, key: envKey, value: envValue, target: envTarget, type: envType } + case 'update_env_var': + return { + ...base, + ...(envKey ? { key: envKey } : {}), + ...(envValue ? { value: envValue } : {}), + ...(envTarget ? { target: envTarget } : {}), + ...(envType ? { type: envType } : {}), + } + case 'get_alias': + case 'delete_alias': + return { ...base, aliasId } + case 'create_alias': + return { ...base, deploymentId: aliasDeploymentId, alias: aliasName } + case 'get_edge_config': + case 'get_edge_config_items': + return { ...base, edgeConfigId } + case 'create_edge_config': + return { ...base, slug: edgeConfigSlug } + case 'update_edge_config_items': + return { ...base, edgeConfigId, items: edgeConfigItems } + case 'create_webhook': + return { + ...base, + url: webhookUrl, + events: webhookEvents, + ...(webhookProjectIds ? { projectIds: webhookProjectIds } : {}), + } + case 'delete_webhook': + return { ...base, webhookId } + case 'create_check': + return { + ...base, + deploymentId: checkDeploymentId, + name: checkName, + blocking: checkBlocking === 'true', + ...(checkPath ? { path: checkPath } : {}), + ...(checkDetailsUrl ? { detailsUrl: checkDetailsUrl } : {}), + } + case 'get_check': + case 'rerequest_check': + return { ...base, deploymentId: checkDeploymentId, checkId } + case 'list_checks': + return { ...base, deploymentId: checkDeploymentId } + case 'update_check': + return { + ...base, + deploymentId: checkDeploymentId, + checkId, + ...(checkName ? { name: checkName } : {}), + ...(checkStatus ? { status: checkStatus } : {}), + ...(checkConclusion ? { conclusion: checkConclusion } : {}), + ...(checkPath ? { path: checkPath } : {}), + ...(checkDetailsUrl ? { detailsUrl: checkDetailsUrl } : {}), + } + case 'get_team': + return { ...base, teamId: teamIdParam } + case 'list_team_members': + return { ...base, teamId: teamIdParam, ...(memberRole ? { role: memberRole } : {}) } + default: + return base + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'Vercel access token' }, + projectId: { type: 'string', description: 'Project ID or name' }, + deploymentId: { type: 'string', description: 'Deployment ID or hostname' }, + name: { type: 'string', description: 'Project name' }, + projectName: { type: 'string', description: 'New project name' }, + project: { type: 'string', description: 'Project ID override' }, + redeployId: { type: 'string', description: 'Deployment ID to redeploy' }, + target: { type: 'string', description: 'Target environment filter' }, + deployTarget: { type: 'string', description: 'Deployment target environment' }, + state: { type: 'string', description: 'Deployment state filter' }, + search: { type: 'string', description: 'Project search query' }, + framework: { type: 'string', description: 'Project framework' }, + buildCommand: { type: 'string', description: 'Build command' }, + outputDirectory: { type: 'string', description: 'Output directory' }, + installCommand: { type: 'string', description: 'Install command' }, + domainName: { type: 'string', description: 'Domain name' }, + envId: { type: 'string', description: 'Environment variable ID' }, + envKey: { type: 'string', description: 'Environment variable key' }, + envValue: { type: 'string', description: 'Environment variable value' }, + envTarget: { type: 'string', description: 'Target environments' }, + envType: { type: 'string', description: 'Variable type' }, + recordName: { type: 'string', description: 'DNS record name' }, + recordType: { type: 'string', description: 'DNS record type' }, + recordValue: { type: 'string', description: 'DNS record value' }, + recordId: { type: 'string', description: 'DNS record ID' }, + aliasId: { type: 'string', description: 'Alias ID' }, + aliasDeploymentId: { type: 'string', description: 'Deployment ID for alias' }, + aliasName: { type: 'string', description: 'Alias domain' }, + edgeConfigId: { type: 'string', description: 'Edge Config ID' }, + edgeConfigSlug: { type: 'string', description: 'Edge Config slug' }, + edgeConfigItems: { type: 'string', description: 'Edge Config items JSON' }, + teamId: { type: 'string', description: 'Team ID for scoping' }, + teamIdParam: { type: 'string', description: 'Team ID parameter' }, + memberRole: { type: 'string', description: 'Team member role filter' }, + webhookId: { type: 'string', description: 'Webhook ID' }, + webhookUrl: { type: 'string', description: 'Webhook URL' }, + webhookEvents: { type: 'string', description: 'Comma-separated event names' }, + webhookProjectIds: { type: 'string', description: 'Comma-separated project IDs' }, + checkDeploymentId: { type: 'string', description: 'Deployment ID for checks' }, + checkId: { type: 'string', description: 'Check ID' }, + checkName: { type: 'string', description: 'Check name' }, + checkBlocking: { type: 'string', description: 'Whether check blocks deployment' }, + checkPath: { type: 'string', description: 'Page path being checked' }, + checkDetailsUrl: { type: 'string', description: 'URL for check details' }, + checkStatus: { type: 'string', description: 'Check status' }, + checkConclusion: { type: 'string', description: 'Check conclusion' }, + }, + outputs: { + // List results + deployments: { + type: 'array', + description: 'List of deployments', + condition: { field: 'operation', value: 'list_deployments' }, + }, + projects: { + type: 'array', + description: 'List of projects', + condition: { field: 'operation', value: 'list_projects' }, + }, + domains: { + type: 'array', + description: 'List of domains', + condition: { + field: 'operation', + value: ['list_domains', 'list_project_domains'], + }, + }, + envs: { + type: 'array', + description: 'List of environment variables', + condition: { field: 'operation', value: 'get_env_vars' }, + }, + records: { + type: 'array', + description: 'List of DNS records', + condition: { field: 'operation', value: 'list_dns_records' }, + }, + aliases: { + type: 'array', + description: 'List of aliases', + condition: { field: 'operation', value: 'list_aliases' }, + }, + edgeConfigs: { + type: 'array', + description: 'List of edge configs', + condition: { field: 'operation', value: 'list_edge_configs' }, + }, + items: { + type: 'array', + description: 'Edge config items', + condition: { field: 'operation', value: 'get_edge_config_items' }, + }, + teams: { + type: 'array', + description: 'List of teams', + condition: { field: 'operation', value: 'list_teams' }, + }, + members: { + type: 'array', + description: 'List of team members', + condition: { field: 'operation', value: 'list_team_members' }, + }, + events: { + type: 'array', + description: 'Deployment build log events', + condition: { field: 'operation', value: 'get_deployment_events' }, + }, + files: { + type: 'array', + description: 'Deployment file tree', + condition: { field: 'operation', value: 'list_deployment_files' }, + }, + webhooks: { + type: 'array', + description: 'List of webhooks', + condition: { field: 'operation', value: 'list_webhooks' }, + }, + checks: { + type: 'array', + description: 'List of deployment checks', + condition: { field: 'operation', value: 'list_checks' }, + }, + // Single resource outputs + id: { + type: 'string', + description: 'Resource ID', + }, + name: { + type: 'string', + description: 'Resource name', + }, + url: { + type: 'string', + description: 'Deployment URL', + condition: { + field: 'operation', + value: ['get_deployment', 'create_deployment', 'cancel_deployment'], + }, + }, + state: { + type: 'string', + description: 'Deployment state', + condition: { + field: 'operation', + value: ['get_deployment', 'create_deployment', 'cancel_deployment', 'delete_deployment'], + }, + }, + deleted: { + type: 'boolean', + description: 'Whether the resource was deleted', + condition: { + field: 'operation', + value: [ + 'delete_deployment', + 'delete_project', + 'remove_project_domain', + 'delete_domain', + 'delete_dns_record', + 'delete_alias', + 'delete_env_var', + 'delete_webhook', + ], + }, + }, + count: { + type: 'number', + description: 'Number of items returned', + }, + hasMore: { + type: 'boolean', + description: 'Whether more results are available', + }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index f51019da2..bb3193fd1 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -146,6 +146,7 @@ import { TwilioSMSBlock } from '@/blocks/blocks/twilio' import { TwilioVoiceBlock } from '@/blocks/blocks/twilio_voice' import { TypeformBlock } from '@/blocks/blocks/typeform' import { VariablesBlock } from '@/blocks/blocks/variables' +import { VercelBlock } from '@/blocks/blocks/vercel' import { VideoGeneratorBlock, VideoGeneratorV2Block } from '@/blocks/blocks/video_generator' import { VisionBlock, VisionV2Block } from '@/blocks/blocks/vision' import { WaitBlock } from '@/blocks/blocks/wait' @@ -330,6 +331,7 @@ export const registry: Record = { twilio_sms: TwilioSMSBlock, twilio_voice: TwilioVoiceBlock, typeform: TypeformBlock, + vercel: VercelBlock, variables: VariablesBlock, video_generator: VideoGeneratorBlock, video_generator_v2: VideoGeneratorV2Block, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index dfb95dab2..c31380d81 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -5532,3 +5532,18 @@ export function OnePasswordIcon(props: SVGProps) { ) } + +export function VercelIcon(props: SVGProps) { + return ( + + + + + + ) +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 991c7e140..f931313a1 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1693,6 +1693,58 @@ import { typeformUpdateFormTool, } from '@/tools/typeform' import type { ToolConfig } from '@/tools/types' +import { + vercelAddDomainTool, + vercelAddProjectDomainTool, + vercelCancelDeploymentTool, + vercelCreateAliasTool, + vercelCreateCheckTool, + vercelCreateDeploymentTool, + vercelCreateDnsRecordTool, + vercelCreateEdgeConfigTool, + vercelCreateEnvVarTool, + vercelCreateProjectTool, + vercelCreateWebhookTool, + vercelDeleteAliasTool, + vercelDeleteDeploymentTool, + vercelDeleteDnsRecordTool, + vercelDeleteDomainTool, + vercelDeleteEnvVarTool, + vercelDeleteProjectTool, + vercelDeleteWebhookTool, + vercelGetAliasTool, + vercelGetCheckTool, + vercelGetDeploymentEventsTool, + vercelGetDeploymentTool, + vercelGetDomainConfigTool, + vercelGetDomainTool, + vercelGetEdgeConfigItemsTool, + vercelGetEdgeConfigTool, + vercelGetEnvVarsTool, + vercelGetProjectTool, + vercelGetTeamTool, + vercelGetUserTool, + vercelListAliasesTool, + vercelListChecksTool, + vercelListDeploymentFilesTool, + vercelListDeploymentsTool, + vercelListDnsRecordsTool, + vercelListDomainsTool, + vercelListEdgeConfigsTool, + vercelListProjectDomainsTool, + vercelListProjectsTool, + vercelListTeamMembersTool, + vercelListTeamsTool, + vercelListWebhooksTool, + vercelPauseProjectTool, + vercelRemoveProjectDomainTool, + vercelRerequestCheckTool, + vercelUnpauseProjectTool, + vercelUpdateCheckTool, + vercelUpdateEdgeConfigItemsTool, + vercelUpdateEnvVarTool, + vercelUpdateProjectTool, +} from '@/tools/vercel' import { falaiVideoTool, lumaVideoTool, @@ -2700,6 +2752,66 @@ export const tools: Record = { trello_update_card: trelloUpdateCardTool, trello_get_actions: trelloGetActionsTool, trello_add_comment: trelloAddCommentTool, + // Vercel - Deployments + vercel_list_deployments: vercelListDeploymentsTool, + vercel_get_deployment: vercelGetDeploymentTool, + vercel_create_deployment: vercelCreateDeploymentTool, + vercel_cancel_deployment: vercelCancelDeploymentTool, + vercel_delete_deployment: vercelDeleteDeploymentTool, + vercel_get_deployment_events: vercelGetDeploymentEventsTool, + vercel_list_deployment_files: vercelListDeploymentFilesTool, + // Vercel - Projects + vercel_list_projects: vercelListProjectsTool, + vercel_get_project: vercelGetProjectTool, + vercel_create_project: vercelCreateProjectTool, + vercel_update_project: vercelUpdateProjectTool, + vercel_delete_project: vercelDeleteProjectTool, + vercel_pause_project: vercelPauseProjectTool, + vercel_unpause_project: vercelUnpauseProjectTool, + vercel_list_project_domains: vercelListProjectDomainsTool, + vercel_add_project_domain: vercelAddProjectDomainTool, + vercel_remove_project_domain: vercelRemoveProjectDomainTool, + // Vercel - Environment Variables + vercel_get_env_vars: vercelGetEnvVarsTool, + vercel_create_env_var: vercelCreateEnvVarTool, + vercel_update_env_var: vercelUpdateEnvVarTool, + vercel_delete_env_var: vercelDeleteEnvVarTool, + // Vercel - Domains + vercel_list_domains: vercelListDomainsTool, + vercel_get_domain: vercelGetDomainTool, + vercel_add_domain: vercelAddDomainTool, + vercel_delete_domain: vercelDeleteDomainTool, + vercel_get_domain_config: vercelGetDomainConfigTool, + // Vercel - DNS + vercel_list_dns_records: vercelListDnsRecordsTool, + vercel_create_dns_record: vercelCreateDnsRecordTool, + vercel_delete_dns_record: vercelDeleteDnsRecordTool, + // Vercel - Aliases + vercel_list_aliases: vercelListAliasesTool, + vercel_get_alias: vercelGetAliasTool, + vercel_create_alias: vercelCreateAliasTool, + vercel_delete_alias: vercelDeleteAliasTool, + // Vercel - Edge Config + vercel_list_edge_configs: vercelListEdgeConfigsTool, + vercel_get_edge_config: vercelGetEdgeConfigTool, + vercel_create_edge_config: vercelCreateEdgeConfigTool, + vercel_get_edge_config_items: vercelGetEdgeConfigItemsTool, + vercel_update_edge_config_items: vercelUpdateEdgeConfigItemsTool, + // Vercel - Teams & User + vercel_list_teams: vercelListTeamsTool, + vercel_get_team: vercelGetTeamTool, + vercel_list_team_members: vercelListTeamMembersTool, + vercel_get_user: vercelGetUserTool, + // Webhooks + vercel_list_webhooks: vercelListWebhooksTool, + vercel_create_webhook: vercelCreateWebhookTool, + vercel_delete_webhook: vercelDeleteWebhookTool, + // Checks + vercel_create_check: vercelCreateCheckTool, + vercel_get_check: vercelGetCheckTool, + vercel_list_checks: vercelListChecksTool, + vercel_update_check: vercelUpdateCheckTool, + vercel_rerequest_check: vercelRerequestCheckTool, twilio_send_sms: sendSMSTool, twilio_voice_make_call: makeCallTool, twilio_voice_list_calls: listCallsTool, diff --git a/apps/sim/tools/vercel/add_domain.ts b/apps/sim/tools/vercel/add_domain.ts new file mode 100644 index 000000000..7e2796459 --- /dev/null +++ b/apps/sim/tools/vercel/add_domain.ts @@ -0,0 +1,84 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelAddDomainParams, VercelAddDomainResponse } from '@/tools/vercel/types' + +export const vercelAddDomainTool: ToolConfig = { + id: 'vercel_add_domain', + name: 'Vercel Add Domain', + description: 'Add a new domain to a Vercel account or team', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The domain name to add', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelAddDomainParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v7/domains${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelAddDomainParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelAddDomainParams) => ({ + method: 'add', + name: params.name.trim(), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const d = data.domain ?? data + + return { + success: true, + output: { + id: d.id ?? null, + name: d.name ?? null, + verified: d.verified ?? false, + createdAt: d.createdAt ?? null, + serviceType: d.serviceType ?? null, + nameservers: d.nameservers ?? [], + intendedNameservers: d.intendedNameservers ?? [], + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Domain ID' }, + name: { type: 'string', description: 'Domain name' }, + verified: { type: 'boolean', description: 'Whether domain is verified' }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + serviceType: { type: 'string', description: 'Service type (zeit.world, external, na)' }, + nameservers: { + type: 'array', + description: 'Current nameservers', + items: { type: 'string' }, + }, + intendedNameservers: { + type: 'array', + description: 'Intended nameservers', + items: { type: 'string' }, + }, + }, +} diff --git a/apps/sim/tools/vercel/add_project_domain.ts b/apps/sim/tools/vercel/add_project_domain.ts new file mode 100644 index 000000000..c25949831 --- /dev/null +++ b/apps/sim/tools/vercel/add_project_domain.ts @@ -0,0 +1,113 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelAddProjectDomainParams, + VercelAddProjectDomainResponse, +} from '@/tools/vercel/types' + +export const vercelAddProjectDomainTool: ToolConfig< + VercelAddProjectDomainParams, + VercelAddProjectDomainResponse +> = { + id: 'vercel_add_project_domain', + name: 'Vercel Add Project Domain', + description: 'Add a domain to a Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or name', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Domain name to add', + }, + redirect: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Target domain for redirect', + }, + redirectStatusCode: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'HTTP status code for redirect (301, 302, 307, 308)', + }, + gitBranch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Git branch to link the domain to', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelAddProjectDomainParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v10/projects/${params.projectId.trim()}/domains${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelAddProjectDomainParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelAddProjectDomainParams) => { + const body: Record = { name: params.domain.trim() } + if (params.redirect) body.redirect = params.redirect.trim() + if (params.redirectStatusCode) body.redirectStatusCode = params.redirectStatusCode + if (params.gitBranch) body.gitBranch = params.gitBranch.trim() + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + name: data.name, + apexName: data.apexName, + verified: data.verified, + gitBranch: data.gitBranch ?? null, + redirect: data.redirect ?? null, + redirectStatusCode: data.redirectStatusCode ?? null, + createdAt: data.createdAt, + updatedAt: data.updatedAt, + }, + } + }, + + outputs: { + name: { type: 'string', description: 'Domain name' }, + apexName: { type: 'string', description: 'Apex domain name' }, + verified: { type: 'boolean', description: 'Whether the domain is verified' }, + gitBranch: { type: 'string', description: 'Git branch for the domain', optional: true }, + redirect: { type: 'string', description: 'Redirect target domain', optional: true }, + redirectStatusCode: { + type: 'number', + description: 'HTTP status code for redirect (301, 302, 307, 308)', + optional: true, + }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + updatedAt: { type: 'number', description: 'Last updated timestamp' }, + }, +} diff --git a/apps/sim/tools/vercel/cancel_deployment.ts b/apps/sim/tools/vercel/cancel_deployment.ts new file mode 100644 index 000000000..c9a5b2a7a --- /dev/null +++ b/apps/sim/tools/vercel/cancel_deployment.ts @@ -0,0 +1,83 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelCancelDeploymentParams, + VercelCancelDeploymentResponse, +} from '@/tools/vercel/types' + +export const vercelCancelDeploymentTool: ToolConfig< + VercelCancelDeploymentParams, + VercelCancelDeploymentResponse +> = { + id: 'vercel_cancel_deployment', + name: 'Vercel Cancel Deployment', + description: 'Cancel a running Vercel deployment', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + deploymentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The deployment ID to cancel', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelCancelDeploymentParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v12/deployments/${params.deploymentId.trim()}/cancel${qs ? `?${qs}` : ''}` + }, + method: 'PATCH', + headers: (params: VercelCancelDeploymentParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? data.uid, + name: data.name ?? null, + state: data.readyState ?? data.state ?? 'CANCELED', + url: data.url ?? null, + }, + } + }, + + outputs: { + id: { + type: 'string', + description: 'Deployment ID', + }, + name: { + type: 'string', + description: 'Deployment name', + }, + state: { + type: 'string', + description: 'Deployment state after cancellation', + }, + url: { + type: 'string', + description: 'Deployment URL', + }, + }, +} diff --git a/apps/sim/tools/vercel/create_alias.ts b/apps/sim/tools/vercel/create_alias.ts new file mode 100644 index 000000000..8146005c0 --- /dev/null +++ b/apps/sim/tools/vercel/create_alias.ts @@ -0,0 +1,87 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelCreateAliasParams, VercelCreateAliasResponse } from '@/tools/vercel/types' + +export const vercelCreateAliasTool: ToolConfig = + { + id: 'vercel_create_alias', + name: 'Vercel Create Alias', + description: 'Assign an alias (domain/subdomain) to a deployment', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + deploymentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Deployment ID to assign the alias to', + }, + alias: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The domain or subdomain to assign as an alias', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelCreateAliasParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v2/deployments/${params.deploymentId.trim()}/aliases${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelCreateAliasParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelCreateAliasParams) => ({ + alias: params.alias.trim(), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + uid: data.uid ?? null, + alias: data.alias ?? null, + created: data.created ?? null, + oldDeploymentId: data.oldDeploymentId ?? null, + }, + } + }, + + outputs: { + uid: { + type: 'string', + description: 'Alias ID', + }, + alias: { + type: 'string', + description: 'Alias hostname', + }, + created: { + type: 'string', + description: 'Creation timestamp as ISO 8601 date-time string', + }, + oldDeploymentId: { + type: 'string', + description: 'ID of the previously aliased deployment, if the alias was reassigned', + }, + }, + } diff --git a/apps/sim/tools/vercel/create_check.ts b/apps/sim/tools/vercel/create_check.ts new file mode 100644 index 000000000..06da99591 --- /dev/null +++ b/apps/sim/tools/vercel/create_check.ts @@ -0,0 +1,141 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelCheckResponse, VercelCreateCheckParams } from '@/tools/vercel/types' + +export const vercelCreateCheckTool: ToolConfig = { + id: 'vercel_create_check', + name: 'Vercel Create Check', + description: 'Create a new deployment check', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + deploymentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Deployment ID to create the check for', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the check (max 100 characters)', + }, + blocking: { + type: 'boolean', + required: true, + visibility: 'user-or-llm', + description: 'Whether the check blocks the deployment', + }, + path: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page path being checked', + }, + detailsUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'URL with details about the check', + }, + externalId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'External identifier for the check', + }, + rerequestable: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the check can be rerequested', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelCreateCheckParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/deployments/${params.deploymentId.trim()}/checks${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelCreateCheckParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelCreateCheckParams) => { + const body: Record = { + name: params.name.trim(), + blocking: params.blocking, + } + if (params.path) body.path = params.path + if (params.detailsUrl) body.detailsUrl = params.detailsUrl + if (params.externalId) body.externalId = params.externalId + if (params.rerequestable !== undefined) body.rerequestable = params.rerequestable + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id, + name: data.name, + status: data.status ?? 'registered', + conclusion: data.conclusion ?? null, + blocking: data.blocking ?? false, + deploymentId: data.deploymentId, + integrationId: data.integrationId ?? null, + externalId: data.externalId ?? null, + detailsUrl: data.detailsUrl ?? null, + path: data.path ?? null, + rerequestable: data.rerequestable ?? false, + createdAt: data.createdAt, + updatedAt: data.updatedAt, + startedAt: data.startedAt ?? null, + completedAt: data.completedAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Check ID' }, + name: { type: 'string', description: 'Check name' }, + status: { type: 'string', description: 'Check status: registered, running, or completed' }, + conclusion: { + type: 'string', + description: 'Check conclusion: canceled, failed, neutral, succeeded, skipped, or stale', + optional: true, + }, + blocking: { type: 'boolean', description: 'Whether the check blocks the deployment' }, + deploymentId: { type: 'string', description: 'Associated deployment ID' }, + integrationId: { type: 'string', description: 'Associated integration ID', optional: true }, + externalId: { type: 'string', description: 'External identifier', optional: true }, + detailsUrl: { type: 'string', description: 'URL with details about the check', optional: true }, + path: { type: 'string', description: 'Page path being checked', optional: true }, + rerequestable: { type: 'boolean', description: 'Whether the check can be rerequested' }, + createdAt: { type: 'number', description: 'Creation timestamp in milliseconds' }, + updatedAt: { type: 'number', description: 'Last update timestamp in milliseconds' }, + startedAt: { type: 'number', description: 'Start timestamp in milliseconds', optional: true }, + completedAt: { + type: 'number', + description: 'Completion timestamp in milliseconds', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/vercel/create_deployment.ts b/apps/sim/tools/vercel/create_deployment.ts new file mode 100644 index 000000000..2f5b5b36d --- /dev/null +++ b/apps/sim/tools/vercel/create_deployment.ts @@ -0,0 +1,136 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelCreateDeploymentParams, + VercelCreateDeploymentResponse, +} from '@/tools/vercel/types' + +export const vercelCreateDeploymentTool: ToolConfig< + VercelCreateDeploymentParams, + VercelCreateDeploymentResponse +> = { + id: 'vercel_create_deployment', + name: 'Vercel Create Deployment', + description: 'Create a new deployment or redeploy an existing one', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project name for the deployment', + }, + project: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Project ID (overrides name for project lookup)', + }, + deploymentId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Existing deployment ID to redeploy', + }, + target: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Target environment: production, staging, or a custom environment identifier', + }, + gitSource: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'JSON string defining the Git Repository source to deploy (e.g. {"type":"github","repo":"owner/repo","ref":"main"})', + }, + forceNew: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Forces a new deployment even if there is a previous similar deployment (0 or 1)', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelCreateDeploymentParams) => { + const query = new URLSearchParams() + if (params.forceNew) query.set('forceNew', params.forceNew) + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v13/deployments${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelCreateDeploymentParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelCreateDeploymentParams) => { + const body: Record = { + name: params.name.trim(), + } + if (params.project) body.project = params.project.trim() + if (params.deploymentId) body.deploymentId = params.deploymentId.trim() + if (params.target) body.target = params.target + if (params.gitSource) { + try { + body.gitSource = JSON.parse(params.gitSource) + } catch { + body.gitSource = params.gitSource + } + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id, + name: data.name, + url: data.url ?? '', + readyState: data.readyState ?? 'QUEUED', + projectId: data.projectId ?? '', + createdAt: data.createdAt ?? data.created, + alias: data.alias ?? [], + target: data.target ?? null, + inspectorUrl: data.inspectorUrl ?? '', + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Deployment ID' }, + name: { type: 'string', description: 'Deployment name' }, + url: { type: 'string', description: 'Unique deployment URL' }, + readyState: { + type: 'string', + description: 'Deployment ready state: QUEUED, BUILDING, ERROR, INITIALIZING, READY, CANCELED', + }, + projectId: { type: 'string', description: 'Associated project ID' }, + createdAt: { type: 'number', description: 'Creation timestamp in milliseconds' }, + alias: { + type: 'array', + description: 'Assigned aliases', + items: { type: 'string', description: 'Alias domain' }, + }, + target: { type: 'string', description: 'Target environment', optional: true }, + inspectorUrl: { type: 'string', description: 'Vercel inspector URL' }, + }, +} diff --git a/apps/sim/tools/vercel/create_dns_record.ts b/apps/sim/tools/vercel/create_dns_record.ts new file mode 100644 index 000000000..fdb64c93c --- /dev/null +++ b/apps/sim/tools/vercel/create_dns_record.ts @@ -0,0 +1,107 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelCreateDnsRecordParams, + VercelCreateDnsRecordResponse, +} from '@/tools/vercel/types' + +export const vercelCreateDnsRecordTool: ToolConfig< + VercelCreateDnsRecordParams, + VercelCreateDnsRecordResponse +> = { + id: 'vercel_create_dns_record', + name: 'Vercel Create DNS Record', + description: 'Create a DNS record for a domain in a Vercel account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The domain name to create the record for', + }, + recordName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The subdomain or record name', + }, + recordType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'DNS record type (A, AAAA, ALIAS, CAA, CNAME, HTTPS, MX, SRV, TXT, NS)', + }, + value: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The value of the DNS record', + }, + ttl: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Time to live in seconds', + }, + mxPriority: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Priority for MX records', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelCreateDnsRecordParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v2/domains/${params.domain.trim()}/records${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelCreateDnsRecordParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelCreateDnsRecordParams) => { + const body: Record = { + name: params.recordName.trim(), + type: params.recordType.trim(), + value: params.value.trim(), + } + if (params.ttl != null) body.ttl = params.ttl + if (params.mxPriority != null) body.mxPriority = params.mxPriority + return body + }, + }, + + transformResponse: async (response: Response) => { + const d = await response.json() + + return { + success: true, + output: { + uid: d.uid ?? null, + updated: d.updated ?? null, + }, + } + }, + + outputs: { + uid: { type: 'string', description: 'The DNS record ID' }, + updated: { type: 'number', description: 'Timestamp of the update' }, + }, +} diff --git a/apps/sim/tools/vercel/create_edge_config.ts b/apps/sim/tools/vercel/create_edge_config.ts new file mode 100644 index 000000000..631353340 --- /dev/null +++ b/apps/sim/tools/vercel/create_edge_config.ts @@ -0,0 +1,106 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelCreateEdgeConfigParams, + VercelCreateEdgeConfigResponse, +} from '@/tools/vercel/types' + +export const vercelCreateEdgeConfigTool: ToolConfig< + VercelCreateEdgeConfigParams, + VercelCreateEdgeConfigResponse +> = { + id: 'vercel_create_edge_config', + name: 'Vercel Create Edge Config', + description: 'Create a new Edge Config store', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + slug: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The name/slug for the new Edge Config', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelCreateEdgeConfigParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/edge-config${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelCreateEdgeConfigParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelCreateEdgeConfigParams) => ({ + slug: params.slug.trim(), + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? null, + slug: data.slug ?? null, + ownerId: data.ownerId ?? null, + digest: data.digest ?? null, + createdAt: data.createdAt ?? null, + updatedAt: data.updatedAt ?? null, + itemCount: data.itemCount ?? 0, + sizeInBytes: data.sizeInBytes ?? 0, + }, + } + }, + + outputs: { + id: { + type: 'string', + description: 'Edge Config ID', + }, + slug: { + type: 'string', + description: 'Edge Config slug', + }, + ownerId: { + type: 'string', + description: 'Owner ID', + }, + digest: { + type: 'string', + description: 'Content digest hash', + }, + createdAt: { + type: 'number', + description: 'Creation timestamp', + }, + updatedAt: { + type: 'number', + description: 'Last update timestamp', + }, + itemCount: { + type: 'number', + description: 'Number of items', + }, + sizeInBytes: { + type: 'number', + description: 'Size in bytes', + }, + }, +} diff --git a/apps/sim/tools/vercel/create_env_var.ts b/apps/sim/tools/vercel/create_env_var.ts new file mode 100644 index 000000000..c174b9fb1 --- /dev/null +++ b/apps/sim/tools/vercel/create_env_var.ts @@ -0,0 +1,145 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelCreateEnvVarParams, VercelCreateEnvVarResponse } from '@/tools/vercel/types' + +export const vercelCreateEnvVarTool: ToolConfig< + VercelCreateEnvVarParams, + VercelCreateEnvVarResponse +> = { + id: 'vercel_create_env_var', + name: 'Vercel Create Environment Variable', + description: 'Create an environment variable for a Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or name', + }, + key: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Environment variable name', + }, + value: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Environment variable value', + }, + target: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Comma-separated list of target environments (production, preview, development)', + }, + type: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Variable type: system, secret, encrypted, plain, or sensitive (default: plain)', + }, + gitBranch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Git branch to associate with the variable (requires target to include preview)', + }, + comment: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comment to add context to the variable (max 500 characters)', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelCreateEnvVarParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v10/projects/${params.projectId.trim()}/env${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelCreateEnvVarParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelCreateEnvVarParams) => { + const body: Record = { + key: params.key, + value: params.value, + target: params.target.split(',').map((t) => t.trim()), + type: params.type || 'plain', + } + if (params.gitBranch) body.gitBranch = params.gitBranch + if (params.comment) body.comment = params.comment + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const env = data.created ?? data + return { + success: true, + output: { + id: env.id, + key: env.key, + value: env.value ?? '', + type: env.type ?? 'plain', + target: env.target ?? [], + gitBranch: env.gitBranch ?? null, + comment: env.comment ?? null, + }, + } + }, + + outputs: { + id: { + type: 'string', + description: 'Environment variable ID', + }, + key: { + type: 'string', + description: 'Variable name', + }, + value: { + type: 'string', + description: 'Variable value', + }, + type: { + type: 'string', + description: 'Variable type (secret, system, encrypted, plain, sensitive)', + }, + target: { + type: 'array', + description: 'Target environments', + items: { type: 'string', description: 'Environment name' }, + }, + gitBranch: { + type: 'string', + description: 'Git branch filter', + optional: true, + }, + comment: { + type: 'string', + description: 'Comment providing context for the variable', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/vercel/create_project.ts b/apps/sim/tools/vercel/create_project.ts new file mode 100644 index 000000000..b05e4b836 --- /dev/null +++ b/apps/sim/tools/vercel/create_project.ts @@ -0,0 +1,108 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelCreateProjectParams, VercelCreateProjectResponse } from '@/tools/vercel/types' + +export const vercelCreateProjectTool: ToolConfig< + VercelCreateProjectParams, + VercelCreateProjectResponse +> = { + id: 'vercel_create_project', + name: 'Vercel Create Project', + description: 'Create a new Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + name: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project name', + }, + framework: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Project framework (e.g. nextjs, remix, vite)', + }, + gitRepository: { + type: 'json', + required: false, + visibility: 'user-or-llm', + description: 'Git repository connection object with type and repo', + }, + buildCommand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Custom build command', + }, + outputDirectory: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Custom output directory', + }, + installCommand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Custom install command', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelCreateProjectParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v11/projects${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelCreateProjectParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelCreateProjectParams) => { + const body: Record = { name: params.name.trim() } + if (params.framework) body.framework = params.framework.trim() + if (params.gitRepository) body.gitRepository = params.gitRepository + if (params.buildCommand) body.buildCommand = params.buildCommand.trim() + if (params.outputDirectory) body.outputDirectory = params.outputDirectory.trim() + if (params.installCommand) body.installCommand = params.installCommand.trim() + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id, + name: data.name, + framework: data.framework ?? null, + createdAt: data.createdAt, + updatedAt: data.updatedAt, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Project ID' }, + name: { type: 'string', description: 'Project name' }, + framework: { type: 'string', description: 'Project framework', optional: true }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + updatedAt: { type: 'number', description: 'Last updated timestamp' }, + }, +} diff --git a/apps/sim/tools/vercel/create_webhook.ts b/apps/sim/tools/vercel/create_webhook.ts new file mode 100644 index 000000000..e61993e85 --- /dev/null +++ b/apps/sim/tools/vercel/create_webhook.ts @@ -0,0 +1,105 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelCreateWebhookParams, VercelCreateWebhookResponse } from '@/tools/vercel/types' + +export const vercelCreateWebhookTool: ToolConfig< + VercelCreateWebhookParams, + VercelCreateWebhookResponse +> = { + id: 'vercel_create_webhook', + name: 'Vercel Create Webhook', + description: 'Create a new webhook for a Vercel team', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + url: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Webhook URL (must be https)', + }, + events: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Comma-separated event names to subscribe to', + }, + projectIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated project IDs to scope the webhook to', + }, + teamId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Team ID to create the webhook for', + }, + }, + + request: { + url: (params: VercelCreateWebhookParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/webhooks${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelCreateWebhookParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelCreateWebhookParams) => { + const body: Record = { + url: params.url.trim(), + events: params.events.split(',').map((e) => e.trim()), + } + if (params.projectIds) { + body.projectIds = params.projectIds.split(',').map((p) => p.trim()) + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id ?? null, + url: data.url ?? null, + secret: data.secret ?? null, + events: data.events ?? [], + ownerId: data.ownerId ?? null, + projectIds: data.projectIds ?? [], + createdAt: data.createdAt ?? null, + updatedAt: data.updatedAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Webhook ID' }, + url: { type: 'string', description: 'Webhook URL' }, + secret: { type: 'string', description: 'Webhook signing secret' }, + events: { + type: 'array', + description: 'Events the webhook listens to', + items: { type: 'string', description: 'Event name' }, + }, + ownerId: { type: 'string', description: 'Owner ID' }, + projectIds: { + type: 'array', + description: 'Associated project IDs', + items: { type: 'string', description: 'Project ID' }, + }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + updatedAt: { type: 'number', description: 'Last updated timestamp' }, + }, +} diff --git a/apps/sim/tools/vercel/delete_alias.ts b/apps/sim/tools/vercel/delete_alias.ts new file mode 100644 index 000000000..cc4761993 --- /dev/null +++ b/apps/sim/tools/vercel/delete_alias.ts @@ -0,0 +1,62 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelDeleteAliasParams, VercelDeleteAliasResponse } from '@/tools/vercel/types' + +export const vercelDeleteAliasTool: ToolConfig = + { + id: 'vercel_delete_alias', + name: 'Vercel Delete Alias', + description: 'Delete an alias by its ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + aliasId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Alias ID to delete', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelDeleteAliasParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v2/aliases/${params.aliasId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'DELETE', + headers: (params: VercelDeleteAliasParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + status: data.status ?? 'SUCCESS', + }, + } + }, + + outputs: { + status: { + type: 'string', + description: 'Deletion status (SUCCESS)', + }, + }, + } diff --git a/apps/sim/tools/vercel/delete_deployment.ts b/apps/sim/tools/vercel/delete_deployment.ts new file mode 100644 index 000000000..b2989b7f8 --- /dev/null +++ b/apps/sim/tools/vercel/delete_deployment.ts @@ -0,0 +1,77 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelDeleteDeploymentParams, + VercelDeleteDeploymentResponse, +} from '@/tools/vercel/types' + +export const vercelDeleteDeploymentTool: ToolConfig< + VercelDeleteDeploymentParams, + VercelDeleteDeploymentResponse +> = { + id: 'vercel_delete_deployment', + name: 'Vercel Delete Deployment', + description: 'Delete a Vercel deployment', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + deploymentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The deployment ID or URL to delete', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelDeleteDeploymentParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const id = params.deploymentId.trim() + if (id.includes('.')) { + query.set('url', id) + } + const qs = query.toString() + return `https://api.vercel.com/v13/deployments/${id}${qs ? `?${qs}` : ''}` + }, + method: 'DELETE', + headers: (params: VercelDeleteDeploymentParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + uid: data.uid ?? data.id ?? null, + state: data.state ?? 'DELETED', + }, + } + }, + + outputs: { + uid: { + type: 'string', + description: 'The removed deployment ID', + }, + state: { + type: 'string', + description: 'Deployment state after deletion (DELETED)', + }, + }, +} diff --git a/apps/sim/tools/vercel/delete_dns_record.ts b/apps/sim/tools/vercel/delete_dns_record.ts new file mode 100644 index 000000000..313df6192 --- /dev/null +++ b/apps/sim/tools/vercel/delete_dns_record.ts @@ -0,0 +1,69 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelDeleteDnsRecordParams, + VercelDeleteDnsRecordResponse, +} from '@/tools/vercel/types' + +export const vercelDeleteDnsRecordTool: ToolConfig< + VercelDeleteDnsRecordParams, + VercelDeleteDnsRecordResponse +> = { + id: 'vercel_delete_dns_record', + name: 'Vercel Delete DNS Record', + description: 'Delete a DNS record for a domain in a Vercel account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The domain name the record belongs to', + }, + recordId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The ID of the DNS record to delete', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelDeleteDnsRecordParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v2/domains/${params.domain.trim()}/records/${params.recordId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'DELETE', + headers: (params: VercelDeleteDnsRecordParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async () => { + return { + success: true, + output: { + deleted: true, + }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the record was deleted' }, + }, +} diff --git a/apps/sim/tools/vercel/delete_domain.ts b/apps/sim/tools/vercel/delete_domain.ts new file mode 100644 index 000000000..dc2ab080c --- /dev/null +++ b/apps/sim/tools/vercel/delete_domain.ts @@ -0,0 +1,64 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelDeleteDomainParams, VercelDeleteDomainResponse } from '@/tools/vercel/types' + +export const vercelDeleteDomainTool: ToolConfig< + VercelDeleteDomainParams, + VercelDeleteDomainResponse +> = { + id: 'vercel_delete_domain', + name: 'Vercel Delete Domain', + description: 'Delete a domain from a Vercel account or team', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The domain name to delete', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelDeleteDomainParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v6/domains/${params.domain.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'DELETE', + headers: (params: VercelDeleteDomainParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const d = await response.json() + + return { + success: true, + output: { + uid: d.uid ?? null, + deleted: true, + }, + } + }, + + outputs: { + uid: { type: 'string', description: 'The ID of the deleted domain' }, + deleted: { type: 'boolean', description: 'Whether the domain was deleted' }, + }, +} diff --git a/apps/sim/tools/vercel/delete_env_var.ts b/apps/sim/tools/vercel/delete_env_var.ts new file mode 100644 index 000000000..1c2f7f0ec --- /dev/null +++ b/apps/sim/tools/vercel/delete_env_var.ts @@ -0,0 +1,69 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelDeleteEnvVarParams, VercelDeleteEnvVarResponse } from '@/tools/vercel/types' + +export const vercelDeleteEnvVarTool: ToolConfig< + VercelDeleteEnvVarParams, + VercelDeleteEnvVarResponse +> = { + id: 'vercel_delete_env_var', + name: 'Vercel Delete Environment Variable', + description: 'Delete an environment variable from a Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or name', + }, + envId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Environment variable ID to delete', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelDeleteEnvVarParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v9/projects/${params.projectId.trim()}/env/${params.envId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'DELETE', + headers: (params: VercelDeleteEnvVarParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async () => { + return { + success: true, + output: { + deleted: true, + }, + } + }, + + outputs: { + deleted: { + type: 'boolean', + description: 'Whether the environment variable was successfully deleted', + }, + }, +} diff --git a/apps/sim/tools/vercel/delete_project.ts b/apps/sim/tools/vercel/delete_project.ts new file mode 100644 index 000000000..7e04e41cd --- /dev/null +++ b/apps/sim/tools/vercel/delete_project.ts @@ -0,0 +1,60 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelDeleteProjectParams, VercelDeleteProjectResponse } from '@/tools/vercel/types' + +export const vercelDeleteProjectTool: ToolConfig< + VercelDeleteProjectParams, + VercelDeleteProjectResponse +> = { + id: 'vercel_delete_project', + name: 'Vercel Delete Project', + description: 'Delete a Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or name', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelDeleteProjectParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v9/projects/${params.projectId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'DELETE', + headers: (params: VercelDeleteProjectParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async () => { + return { + success: true, + output: { + deleted: true, + }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the project was successfully deleted' }, + }, +} diff --git a/apps/sim/tools/vercel/delete_webhook.ts b/apps/sim/tools/vercel/delete_webhook.ts new file mode 100644 index 000000000..03d483a51 --- /dev/null +++ b/apps/sim/tools/vercel/delete_webhook.ts @@ -0,0 +1,63 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelDeleteWebhookParams, VercelDeleteWebhookResponse } from '@/tools/vercel/types' + +export const vercelDeleteWebhookTool: ToolConfig< + VercelDeleteWebhookParams, + VercelDeleteWebhookResponse +> = { + id: 'vercel_delete_webhook', + name: 'Vercel Delete Webhook', + description: 'Delete a webhook from a Vercel team', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + webhookId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The webhook ID to delete', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelDeleteWebhookParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/webhooks/${params.webhookId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'DELETE', + headers: (params: VercelDeleteWebhookParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async () => { + return { + success: true, + output: { + deleted: true, + }, + } + }, + + outputs: { + deleted: { + type: 'boolean', + description: 'Whether the webhook was successfully deleted', + }, + }, +} diff --git a/apps/sim/tools/vercel/get_alias.ts b/apps/sim/tools/vercel/get_alias.ts new file mode 100644 index 000000000..2d211cff6 --- /dev/null +++ b/apps/sim/tools/vercel/get_alias.ts @@ -0,0 +1,97 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelGetAliasParams, VercelGetAliasResponse } from '@/tools/vercel/types' + +export const vercelGetAliasTool: ToolConfig = { + id: 'vercel_get_alias', + name: 'Vercel Get Alias', + description: 'Get details about a specific alias by ID or hostname', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + aliasId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Alias ID or hostname to look up', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelGetAliasParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v4/aliases/${params.aliasId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelGetAliasParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + uid: data.uid ?? null, + alias: data.alias ?? null, + deploymentId: data.deploymentId ?? null, + projectId: data.projectId ?? null, + createdAt: data.createdAt ?? null, + updatedAt: data.updatedAt ?? null, + redirect: data.redirect ?? null, + redirectStatusCode: data.redirectStatusCode ?? null, + }, + } + }, + + outputs: { + uid: { + type: 'string', + description: 'Alias ID', + }, + alias: { + type: 'string', + description: 'Alias hostname', + }, + deploymentId: { + type: 'string', + description: 'Associated deployment ID', + }, + projectId: { + type: 'string', + description: 'Associated project ID', + }, + createdAt: { + type: 'number', + description: 'Creation timestamp in milliseconds', + }, + updatedAt: { + type: 'number', + description: 'Last update timestamp in milliseconds', + }, + redirect: { + type: 'string', + description: 'Target domain for redirect aliases', + }, + redirectStatusCode: { + type: 'number', + description: 'HTTP status code for redirect (301, 302, 307, or 308)', + }, + }, +} diff --git a/apps/sim/tools/vercel/get_check.ts b/apps/sim/tools/vercel/get_check.ts new file mode 100644 index 000000000..064b12779 --- /dev/null +++ b/apps/sim/tools/vercel/get_check.ts @@ -0,0 +1,99 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelCheckResponse, VercelGetCheckParams } from '@/tools/vercel/types' + +export const vercelGetCheckTool: ToolConfig = { + id: 'vercel_get_check', + name: 'Vercel Get Check', + description: 'Get details of a specific deployment check', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + deploymentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Deployment ID the check belongs to', + }, + checkId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Check ID to retrieve', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelGetCheckParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/deployments/${params.deploymentId.trim()}/checks/${params.checkId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelGetCheckParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id, + name: data.name, + status: data.status ?? 'registered', + conclusion: data.conclusion ?? null, + blocking: data.blocking ?? false, + deploymentId: data.deploymentId, + integrationId: data.integrationId ?? null, + externalId: data.externalId ?? null, + detailsUrl: data.detailsUrl ?? null, + path: data.path ?? null, + rerequestable: data.rerequestable ?? false, + createdAt: data.createdAt, + updatedAt: data.updatedAt, + startedAt: data.startedAt ?? null, + completedAt: data.completedAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Check ID' }, + name: { type: 'string', description: 'Check name' }, + status: { type: 'string', description: 'Check status: registered, running, or completed' }, + conclusion: { + type: 'string', + description: 'Check conclusion: canceled, failed, neutral, succeeded, skipped, or stale', + optional: true, + }, + blocking: { type: 'boolean', description: 'Whether the check blocks the deployment' }, + deploymentId: { type: 'string', description: 'Associated deployment ID' }, + integrationId: { type: 'string', description: 'Associated integration ID', optional: true }, + externalId: { type: 'string', description: 'External identifier', optional: true }, + detailsUrl: { type: 'string', description: 'URL with details about the check', optional: true }, + path: { type: 'string', description: 'Page path being checked', optional: true }, + rerequestable: { type: 'boolean', description: 'Whether the check can be rerequested' }, + createdAt: { type: 'number', description: 'Creation timestamp in milliseconds' }, + updatedAt: { type: 'number', description: 'Last update timestamp in milliseconds' }, + startedAt: { type: 'number', description: 'Start timestamp in milliseconds', optional: true }, + completedAt: { + type: 'number', + description: 'Completion timestamp in milliseconds', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/vercel/get_deployment.ts b/apps/sim/tools/vercel/get_deployment.ts new file mode 100644 index 000000000..d56891448 --- /dev/null +++ b/apps/sim/tools/vercel/get_deployment.ts @@ -0,0 +1,176 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelGetDeploymentParams, VercelGetDeploymentResponse } from '@/tools/vercel/types' + +export const vercelGetDeploymentTool: ToolConfig< + VercelGetDeploymentParams, + VercelGetDeploymentResponse +> = { + id: 'vercel_get_deployment', + name: 'Vercel Get Deployment', + description: 'Get details of a specific Vercel deployment', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + deploymentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique deployment identifier or hostname', + }, + withGitRepoInfo: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Whether to add in gitRepo information (true/false)', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelGetDeploymentParams) => { + const query = new URLSearchParams() + if (params.withGitRepoInfo) query.set('withGitRepoInfo', params.withGitRepoInfo) + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v13/deployments/${params.deploymentId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelGetDeploymentParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id, + name: data.name, + url: data.url ?? '', + readyState: data.readyState ?? 'UNKNOWN', + status: data.status ?? data.readyState ?? 'UNKNOWN', + target: data.target ?? null, + createdAt: data.createdAt ?? data.created, + buildingAt: data.buildingAt ?? null, + ready: data.ready ?? null, + source: data.source ?? '', + alias: data.alias ?? [], + regions: data.regions ?? [], + inspectorUrl: data.inspectorUrl ?? '', + projectId: data.projectId ?? '', + creator: { + uid: data.creator?.uid ?? '', + username: data.creator?.username ?? '', + }, + project: data.project + ? { + id: data.project.id, + name: data.project.name, + framework: data.project.framework ?? null, + } + : null, + meta: data.meta ?? {}, + gitSource: data.gitSource ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Deployment ID' }, + name: { type: 'string', description: 'Deployment name' }, + url: { type: 'string', description: 'Unique deployment URL' }, + readyState: { + type: 'string', + description: 'Deployment ready state: QUEUED, BUILDING, ERROR, INITIALIZING, READY, CANCELED', + }, + status: { + type: 'string', + description: 'Deployment status', + }, + target: { type: 'string', description: 'Target environment', optional: true }, + createdAt: { type: 'number', description: 'Creation timestamp in milliseconds' }, + buildingAt: { type: 'number', description: 'Build start timestamp', optional: true }, + ready: { type: 'number', description: 'Ready timestamp', optional: true }, + source: { + type: 'string', + description: 'Deployment source: cli, git, redeploy, import, v0-web, etc.', + }, + alias: { + type: 'array', + description: 'Assigned aliases', + items: { type: 'string', description: 'Alias domain' }, + }, + regions: { + type: 'array', + description: 'Deployment regions', + items: { type: 'string', description: 'Region code' }, + }, + inspectorUrl: { type: 'string', description: 'Vercel inspector URL' }, + projectId: { type: 'string', description: 'Associated project ID' }, + creator: { + type: 'object', + description: 'Creator information', + properties: { + uid: { type: 'string', description: 'Creator user ID' }, + username: { type: 'string', description: 'Creator username' }, + }, + }, + project: { + type: 'object', + description: 'Associated project', + optional: true, + properties: { + id: { type: 'string', description: 'Project ID' }, + name: { type: 'string', description: 'Project name' }, + framework: { type: 'string', description: 'Project framework', optional: true }, + }, + }, + meta: { + type: 'object', + description: 'Deployment metadata (key-value strings)', + properties: { + githubCommitSha: { type: 'string', description: 'GitHub commit SHA', optional: true }, + githubCommitMessage: { + type: 'string', + description: 'GitHub commit message', + optional: true, + }, + githubCommitRef: { type: 'string', description: 'GitHub branch/ref', optional: true }, + githubRepo: { type: 'string', description: 'GitHub repository', optional: true }, + githubOrg: { type: 'string', description: 'GitHub organization', optional: true }, + githubCommitAuthorName: { + type: 'string', + description: 'Commit author name', + optional: true, + }, + }, + }, + gitSource: { + type: 'object', + description: 'Git source information', + optional: true, + properties: { + type: { + type: 'string', + description: 'Git provider type (e.g., github, gitlab, bitbucket)', + }, + ref: { type: 'string', description: 'Git ref (branch or tag)' }, + sha: { type: 'string', description: 'Git commit SHA' }, + repoId: { type: 'string', description: 'Repository ID', optional: true }, + }, + }, + }, +} diff --git a/apps/sim/tools/vercel/get_deployment_events.ts b/apps/sim/tools/vercel/get_deployment_events.ts new file mode 100644 index 000000000..3e3d8cbd0 --- /dev/null +++ b/apps/sim/tools/vercel/get_deployment_events.ts @@ -0,0 +1,135 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelGetDeploymentEventsParams, + VercelGetDeploymentEventsResponse, +} from '@/tools/vercel/types' + +export const vercelGetDeploymentEventsTool: ToolConfig< + VercelGetDeploymentEventsParams, + VercelGetDeploymentEventsResponse +> = { + id: 'vercel_get_deployment_events', + name: 'Vercel Get Deployment Events', + description: 'Get build and runtime events for a Vercel deployment', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + deploymentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The unique deployment identifier or hostname', + }, + direction: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Order of events by timestamp: backward or forward (default: forward)', + }, + follow: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'When set to 1, returns live events as they happen', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of events to return (-1 for all)', + }, + since: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Timestamp to start pulling build logs from', + }, + until: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Timestamp to stop pulling build logs at', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelGetDeploymentEventsParams) => { + const query = new URLSearchParams() + if (params.direction) query.set('direction', params.direction) + if (params.follow !== undefined) query.set('follow', String(params.follow)) + if (params.limit !== undefined) query.set('limit', String(params.limit)) + if (params.since !== undefined) query.set('since', String(params.since)) + if (params.until !== undefined) query.set('until', String(params.until)) + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v3/deployments/${params.deploymentId.trim()}/events${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelGetDeploymentEventsParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const events = (Array.isArray(data) ? data : (data.events ?? [])).map((e: any) => ({ + type: e.type ?? null, + created: e.created ?? null, + date: e.date ?? null, + text: e.text ?? e.payload?.text ?? null, + serial: e.serial ?? null, + deploymentId: e.deploymentId ?? e.payload?.deploymentId ?? null, + id: e.id ?? null, + level: e.level ?? null, + })) + + return { + success: true, + output: { + events, + count: events.length, + }, + } + }, + + outputs: { + events: { + type: 'array', + description: 'List of deployment events', + items: { + type: 'object', + properties: { + type: { + type: 'string', + description: + 'Event type: delimiter, command, stdout, stderr, exit, deployment-state, middleware, middleware-invocation, edge-function-invocation, metric, report, fatal', + }, + created: { type: 'number', description: 'Event creation timestamp' }, + date: { type: 'number', description: 'Event date timestamp' }, + text: { type: 'string', description: 'Event text content' }, + serial: { type: 'string', description: 'Event serial identifier' }, + deploymentId: { type: 'string', description: 'Associated deployment ID' }, + id: { type: 'string', description: 'Event unique identifier' }, + level: { type: 'string', description: 'Event level: error or warning' }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of events returned', + }, + }, +} diff --git a/apps/sim/tools/vercel/get_domain.ts b/apps/sim/tools/vercel/get_domain.ts new file mode 100644 index 000000000..8f4c216e9 --- /dev/null +++ b/apps/sim/tools/vercel/get_domain.ts @@ -0,0 +1,94 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelGetDomainParams, VercelGetDomainResponse } from '@/tools/vercel/types' + +export const vercelGetDomainTool: ToolConfig = { + id: 'vercel_get_domain', + name: 'Vercel Get Domain', + description: 'Get information about a specific domain in a Vercel account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The domain name to retrieve', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelGetDomainParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v5/domains/${params.domain.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelGetDomainParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const d = data.domain ?? data + + return { + success: true, + output: { + id: d.id ?? null, + name: d.name ?? null, + verified: d.verified ?? false, + createdAt: d.createdAt ?? null, + expiresAt: d.expiresAt ?? null, + serviceType: d.serviceType ?? null, + nameservers: d.nameservers ?? [], + intendedNameservers: d.intendedNameservers ?? [], + customNameservers: d.customNameservers ?? [], + renew: d.renew ?? false, + boughtAt: d.boughtAt ?? null, + transferredAt: d.transferredAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Domain ID' }, + name: { type: 'string', description: 'Domain name' }, + verified: { type: 'boolean', description: 'Whether domain is verified' }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + expiresAt: { type: 'number', description: 'Expiration timestamp' }, + serviceType: { type: 'string', description: 'Service type (zeit.world, external, na)' }, + nameservers: { + type: 'array', + description: 'Current nameservers', + items: { type: 'string' }, + }, + intendedNameservers: { + type: 'array', + description: 'Intended nameservers', + items: { type: 'string' }, + }, + customNameservers: { + type: 'array', + description: 'Custom nameservers', + items: { type: 'string' }, + }, + renew: { type: 'boolean', description: 'Whether auto-renewal is enabled' }, + boughtAt: { type: 'number', description: 'Purchase timestamp' }, + transferredAt: { type: 'number', description: 'Transfer completion timestamp' }, + }, +} diff --git a/apps/sim/tools/vercel/get_domain_config.ts b/apps/sim/tools/vercel/get_domain_config.ts new file mode 100644 index 000000000..14cbeb5d4 --- /dev/null +++ b/apps/sim/tools/vercel/get_domain_config.ts @@ -0,0 +1,107 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelGetDomainConfigParams, + VercelGetDomainConfigResponse, +} from '@/tools/vercel/types' + +export const vercelGetDomainConfigTool: ToolConfig< + VercelGetDomainConfigParams, + VercelGetDomainConfigResponse +> = { + id: 'vercel_get_domain_config', + name: 'Vercel Get Domain Config', + description: 'Get the configuration for a domain in a Vercel account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The domain name to get configuration for', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelGetDomainConfigParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v6/domains/${params.domain.trim()}/config${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelGetDomainConfigParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const d = await response.json() + + return { + success: true, + output: { + configuredBy: d.configuredBy ?? null, + acceptedChallenges: d.acceptedChallenges ?? [], + misconfigured: d.misconfigured ?? false, + recommendedIPv4: d.recommendedIPv4 ?? [], + recommendedCNAME: d.recommendedCNAME ?? [], + }, + } + }, + + outputs: { + configuredBy: { + type: 'string', + description: 'How the domain is configured (CNAME, A, http, dns-01, or null)', + }, + acceptedChallenges: { + type: 'array', + description: 'Accepted challenge types for certificate issuance (dns-01, http-01)', + items: { type: 'string' }, + }, + misconfigured: { + type: 'boolean', + description: 'Whether the domain is misconfigured for TLS certificate generation', + }, + recommendedIPv4: { + type: 'array', + description: 'Recommended IPv4 addresses with rank values', + items: { + type: 'object', + properties: { + rank: { type: 'number', description: 'Priority rank (1 is preferred)' }, + value: { + type: 'array', + description: 'IPv4 addresses', + items: { type: 'string' }, + }, + }, + }, + }, + recommendedCNAME: { + type: 'array', + description: 'Recommended CNAME records with rank values', + items: { + type: 'object', + properties: { + rank: { type: 'number', description: 'Priority rank (1 is preferred)' }, + value: { type: 'string', description: 'CNAME value' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/vercel/get_edge_config.ts b/apps/sim/tools/vercel/get_edge_config.ts new file mode 100644 index 000000000..0d15d6298 --- /dev/null +++ b/apps/sim/tools/vercel/get_edge_config.ts @@ -0,0 +1,100 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelGetEdgeConfigParams, VercelGetEdgeConfigResponse } from '@/tools/vercel/types' + +export const vercelGetEdgeConfigTool: ToolConfig< + VercelGetEdgeConfigParams, + VercelGetEdgeConfigResponse +> = { + id: 'vercel_get_edge_config', + name: 'Vercel Get Edge Config', + description: 'Get details about a specific Edge Config store', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + edgeConfigId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Edge Config ID to look up', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelGetEdgeConfigParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/edge-config/${params.edgeConfigId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelGetEdgeConfigParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + return { + success: true, + output: { + id: data.id ?? null, + slug: data.slug ?? null, + ownerId: data.ownerId ?? null, + digest: data.digest ?? null, + createdAt: data.createdAt ?? null, + updatedAt: data.updatedAt ?? null, + itemCount: data.itemCount ?? 0, + sizeInBytes: data.sizeInBytes ?? 0, + }, + } + }, + + outputs: { + id: { + type: 'string', + description: 'Edge Config ID', + }, + slug: { + type: 'string', + description: 'Edge Config slug', + }, + ownerId: { + type: 'string', + description: 'Owner ID', + }, + digest: { + type: 'string', + description: 'Content digest hash', + }, + createdAt: { + type: 'number', + description: 'Creation timestamp', + }, + updatedAt: { + type: 'number', + description: 'Last update timestamp', + }, + itemCount: { + type: 'number', + description: 'Number of items', + }, + sizeInBytes: { + type: 'number', + description: 'Size in bytes', + }, + }, +} diff --git a/apps/sim/tools/vercel/get_edge_config_items.ts b/apps/sim/tools/vercel/get_edge_config_items.ts new file mode 100644 index 000000000..252e59c33 --- /dev/null +++ b/apps/sim/tools/vercel/get_edge_config_items.ts @@ -0,0 +1,93 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelGetEdgeConfigItemsParams, + VercelGetEdgeConfigItemsResponse, +} from '@/tools/vercel/types' + +export const vercelGetEdgeConfigItemsTool: ToolConfig< + VercelGetEdgeConfigItemsParams, + VercelGetEdgeConfigItemsResponse +> = { + id: 'vercel_get_edge_config_items', + name: 'Vercel Get Edge Config Items', + description: 'Get all items in an Edge Config store', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + edgeConfigId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Edge Config ID to get items from', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelGetEdgeConfigItemsParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/edge-config/${params.edgeConfigId.trim()}/items${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelGetEdgeConfigItemsParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const rawItems = Array.isArray(data) ? data : (data.items ?? []) + const items = rawItems.map((item: any) => ({ + key: item.key ?? null, + value: item.value ?? null, + description: item.description ?? null, + edgeConfigId: item.edgeConfigId ?? null, + createdAt: item.createdAt ?? null, + updatedAt: item.updatedAt ?? null, + })) + + return { + success: true, + output: { + items, + count: items.length, + }, + } + }, + + outputs: { + items: { + type: 'array', + description: 'List of Edge Config items', + items: { + type: 'object', + properties: { + key: { type: 'string', description: 'Item key' }, + value: { type: 'json', description: 'Item value' }, + description: { type: 'string', description: 'Item description' }, + edgeConfigId: { type: 'string', description: 'Parent Edge Config ID' }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + updatedAt: { type: 'number', description: 'Last update timestamp' }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of items returned', + }, + }, +} diff --git a/apps/sim/tools/vercel/get_env_vars.ts b/apps/sim/tools/vercel/get_env_vars.ts new file mode 100644 index 000000000..e6b0cca7b --- /dev/null +++ b/apps/sim/tools/vercel/get_env_vars.ts @@ -0,0 +1,99 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelGetEnvVarsParams, VercelGetEnvVarsResponse } from '@/tools/vercel/types' + +export const vercelGetEnvVarsTool: ToolConfig = { + id: 'vercel_get_env_vars', + name: 'Vercel Get Environment Variables', + description: 'Retrieve environment variables for a Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or name', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelGetEnvVarsParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v10/projects/${params.projectId.trim()}/env${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelGetEnvVarsParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const envs = (data.envs ?? []).map((e: any) => ({ + id: e.id, + key: e.key, + value: e.value ?? '', + type: e.type ?? 'plain', + target: e.target ?? [], + gitBranch: e.gitBranch ?? null, + comment: e.comment ?? null, + })) + + return { + success: true, + output: { + envs, + count: envs.length, + }, + } + }, + + outputs: { + envs: { + type: 'array', + description: 'List of environment variables', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Environment variable ID' }, + key: { type: 'string', description: 'Variable name' }, + value: { type: 'string', description: 'Variable value' }, + type: { + type: 'string', + description: 'Variable type (secret, system, encrypted, plain, sensitive)', + }, + target: { + type: 'array', + description: 'Target environments', + items: { type: 'string', description: 'Environment name' }, + }, + gitBranch: { type: 'string', description: 'Git branch filter', optional: true }, + comment: { + type: 'string', + description: 'Comment providing context for the variable', + optional: true, + }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of environment variables returned', + }, + }, +} diff --git a/apps/sim/tools/vercel/get_project.ts b/apps/sim/tools/vercel/get_project.ts new file mode 100644 index 000000000..4a89554e1 --- /dev/null +++ b/apps/sim/tools/vercel/get_project.ts @@ -0,0 +1,89 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelGetProjectParams, VercelGetProjectResponse } from '@/tools/vercel/types' + +export const vercelGetProjectTool: ToolConfig = { + id: 'vercel_get_project', + name: 'Vercel Get Project', + description: 'Get details of a specific Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or name', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelGetProjectParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v9/projects/${params.projectId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelGetProjectParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id, + name: data.name, + framework: data.framework ?? null, + createdAt: data.createdAt, + updatedAt: data.updatedAt, + domains: data.domains ?? [], + link: data.link + ? { + type: data.link.type, + repo: data.link.repo, + org: data.link.org, + } + : null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Project ID' }, + name: { type: 'string', description: 'Project name' }, + framework: { type: 'string', description: 'Project framework', optional: true }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + updatedAt: { type: 'number', description: 'Last updated timestamp' }, + domains: { + type: 'array', + description: 'Project domains', + items: { type: 'string', description: 'Domain' }, + }, + link: { + type: 'object', + description: 'Git repository connection', + optional: true, + properties: { + type: { type: 'string', description: 'Repository type (github, gitlab, bitbucket)' }, + repo: { type: 'string', description: 'Repository name' }, + org: { type: 'string', description: 'Organization or owner' }, + }, + }, + }, +} diff --git a/apps/sim/tools/vercel/get_team.ts b/apps/sim/tools/vercel/get_team.ts new file mode 100644 index 000000000..0d4018231 --- /dev/null +++ b/apps/sim/tools/vercel/get_team.ts @@ -0,0 +1,98 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelGetTeamParams, VercelGetTeamResponse } from '@/tools/vercel/types' + +export const vercelGetTeamTool: ToolConfig = { + id: 'vercel_get_team', + name: 'Vercel Get Team', + description: 'Get information about a specific Vercel team', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + teamId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The team ID to retrieve', + }, + }, + + request: { + url: (params: VercelGetTeamParams) => `https://api.vercel.com/v2/teams/${params.teamId.trim()}`, + method: 'GET', + headers: (params: VercelGetTeamParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const d = await response.json() + + return { + success: true, + output: { + id: d.id ?? null, + slug: d.slug ?? null, + name: d.name ?? null, + avatar: d.avatar ?? null, + description: d.description ?? null, + createdAt: d.createdAt ?? null, + updatedAt: d.updatedAt ?? null, + creatorId: d.creatorId ?? null, + membership: d.membership + ? { + uid: d.membership.uid ?? null, + teamId: d.membership.teamId ?? null, + role: d.membership.role ?? null, + confirmed: d.membership.confirmed ?? false, + created: d.membership.created ?? null, + createdAt: d.membership.createdAt ?? null, + accessRequestedAt: d.membership.accessRequestedAt ?? null, + teamRoles: d.membership.teamRoles ?? [], + teamPermissions: d.membership.teamPermissions ?? [], + } + : null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Team ID' }, + slug: { type: 'string', description: 'Team slug' }, + name: { type: 'string', description: 'Team name' }, + avatar: { type: 'string', description: 'Avatar file ID' }, + description: { type: 'string', description: 'Short team description' }, + createdAt: { type: 'number', description: 'Creation timestamp in milliseconds' }, + updatedAt: { type: 'number', description: 'Last update timestamp in milliseconds' }, + creatorId: { type: 'string', description: 'User ID of team creator' }, + membership: { + type: 'object', + description: 'Current user membership details', + properties: { + uid: { type: 'string', description: 'User ID of the member' }, + teamId: { type: 'string', description: 'Team ID' }, + role: { type: 'string', description: 'Membership role' }, + confirmed: { type: 'boolean', description: 'Whether membership is confirmed' }, + created: { type: 'number', description: 'Membership creation timestamp' }, + createdAt: { type: 'number', description: 'Membership creation timestamp (milliseconds)' }, + accessRequestedAt: { type: 'number', description: 'When access was requested' }, + teamRoles: { + type: 'array', + description: 'Team role assignments', + items: { type: 'string', description: 'Role name' }, + }, + teamPermissions: { + type: 'array', + description: 'Team permission assignments', + items: { type: 'string', description: 'Permission name' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/vercel/get_user.ts b/apps/sim/tools/vercel/get_user.ts new file mode 100644 index 000000000..115678c02 --- /dev/null +++ b/apps/sim/tools/vercel/get_user.ts @@ -0,0 +1,73 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelGetUserParams, VercelGetUserResponse } from '@/tools/vercel/types' + +export const vercelGetUserTool: ToolConfig = { + id: 'vercel_get_user', + name: 'Vercel Get User', + description: 'Get information about the authenticated Vercel user', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + }, + + request: { + url: () => 'https://api.vercel.com/v2/user', + method: 'GET', + headers: (params: VercelGetUserParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const d = data.user ?? data + + return { + success: true, + output: { + id: d.id ?? null, + email: d.email ?? null, + username: d.username ?? null, + name: d.name ?? null, + avatar: d.avatar ?? null, + defaultTeamId: d.defaultTeamId ?? null, + createdAt: d.createdAt ?? null, + stagingPrefix: d.stagingPrefix ?? null, + softBlock: d.softBlock + ? { + blockedAt: d.softBlock.blockedAt ?? null, + reason: d.softBlock.reason ?? null, + } + : null, + hasTrialAvailable: d.hasTrialAvailable ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'User ID' }, + email: { type: 'string', description: 'User email' }, + username: { type: 'string', description: 'Username' }, + name: { type: 'string', description: 'Display name' }, + avatar: { type: 'string', description: 'SHA1 hash of the avatar' }, + defaultTeamId: { type: 'string', description: 'Default team ID' }, + createdAt: { type: 'number', description: 'Account creation timestamp in milliseconds' }, + stagingPrefix: { type: 'string', description: 'Prefix for preview deployment URLs' }, + softBlock: { + type: 'object', + description: 'Account restriction details if blocked', + properties: { + blockedAt: { type: 'number', description: 'When the account was blocked' }, + reason: { type: 'string', description: 'Reason for the block' }, + }, + }, + hasTrialAvailable: { type: 'boolean', description: 'Whether a trial is available' }, + }, +} diff --git a/apps/sim/tools/vercel/index.ts b/apps/sim/tools/vercel/index.ts new file mode 100644 index 000000000..c25ff0710 --- /dev/null +++ b/apps/sim/tools/vercel/index.ts @@ -0,0 +1,126 @@ +// Deployment tools + +// Domain tools +import { vercelAddDomainTool } from '@/tools/vercel/add_domain' +// Project tools +import { vercelAddProjectDomainTool } from '@/tools/vercel/add_project_domain' +import { vercelCancelDeploymentTool } from '@/tools/vercel/cancel_deployment' +// Alias tools +import { vercelCreateAliasTool } from '@/tools/vercel/create_alias' +// Check tools +import { vercelCreateCheckTool } from '@/tools/vercel/create_check' +import { vercelCreateDeploymentTool } from '@/tools/vercel/create_deployment' +// DNS tools +import { vercelCreateDnsRecordTool } from '@/tools/vercel/create_dns_record' +// Edge Config tools +import { vercelCreateEdgeConfigTool } from '@/tools/vercel/create_edge_config' +// Environment variable tools +import { vercelCreateEnvVarTool } from '@/tools/vercel/create_env_var' +import { vercelCreateProjectTool } from '@/tools/vercel/create_project' +// Webhook tools +import { vercelCreateWebhookTool } from '@/tools/vercel/create_webhook' +import { vercelDeleteAliasTool } from '@/tools/vercel/delete_alias' +import { vercelDeleteDeploymentTool } from '@/tools/vercel/delete_deployment' +import { vercelDeleteDnsRecordTool } from '@/tools/vercel/delete_dns_record' +import { vercelDeleteDomainTool } from '@/tools/vercel/delete_domain' +import { vercelDeleteEnvVarTool } from '@/tools/vercel/delete_env_var' +import { vercelDeleteProjectTool } from '@/tools/vercel/delete_project' +import { vercelDeleteWebhookTool } from '@/tools/vercel/delete_webhook' +import { vercelGetAliasTool } from '@/tools/vercel/get_alias' +import { vercelGetCheckTool } from '@/tools/vercel/get_check' +import { vercelGetDeploymentTool } from '@/tools/vercel/get_deployment' +import { vercelGetDeploymentEventsTool } from '@/tools/vercel/get_deployment_events' +import { vercelGetDomainTool } from '@/tools/vercel/get_domain' +import { vercelGetDomainConfigTool } from '@/tools/vercel/get_domain_config' +import { vercelGetEdgeConfigTool } from '@/tools/vercel/get_edge_config' +import { vercelGetEdgeConfigItemsTool } from '@/tools/vercel/get_edge_config_items' +import { vercelGetEnvVarsTool } from '@/tools/vercel/get_env_vars' +import { vercelGetProjectTool } from '@/tools/vercel/get_project' +// Team & User tools +import { vercelGetTeamTool } from '@/tools/vercel/get_team' +import { vercelGetUserTool } from '@/tools/vercel/get_user' +import { vercelListAliasesTool } from '@/tools/vercel/list_aliases' +import { vercelListChecksTool } from '@/tools/vercel/list_checks' +import { vercelListDeploymentFilesTool } from '@/tools/vercel/list_deployment_files' +import { vercelListDeploymentsTool } from '@/tools/vercel/list_deployments' +import { vercelListDnsRecordsTool } from '@/tools/vercel/list_dns_records' +import { vercelListDomainsTool } from '@/tools/vercel/list_domains' +import { vercelListEdgeConfigsTool } from '@/tools/vercel/list_edge_configs' +import { vercelListProjectDomainsTool } from '@/tools/vercel/list_project_domains' +import { vercelListProjectsTool } from '@/tools/vercel/list_projects' +import { vercelListTeamMembersTool } from '@/tools/vercel/list_team_members' +import { vercelListTeamsTool } from '@/tools/vercel/list_teams' +import { vercelListWebhooksTool } from '@/tools/vercel/list_webhooks' +import { vercelPauseProjectTool } from '@/tools/vercel/pause_project' +import { vercelRemoveProjectDomainTool } from '@/tools/vercel/remove_project_domain' +import { vercelRerequestCheckTool } from '@/tools/vercel/rerequest_check' +import { vercelUnpauseProjectTool } from '@/tools/vercel/unpause_project' +import { vercelUpdateCheckTool } from '@/tools/vercel/update_check' +import { vercelUpdateEdgeConfigItemsTool } from '@/tools/vercel/update_edge_config_items' +import { vercelUpdateEnvVarTool } from '@/tools/vercel/update_env_var' +import { vercelUpdateProjectTool } from '@/tools/vercel/update_project' + +export { + // Deployments + vercelListDeploymentsTool, + vercelGetDeploymentTool, + vercelCreateDeploymentTool, + vercelCancelDeploymentTool, + vercelDeleteDeploymentTool, + vercelGetDeploymentEventsTool, + vercelListDeploymentFilesTool, + // Projects + vercelListProjectsTool, + vercelGetProjectTool, + vercelCreateProjectTool, + vercelUpdateProjectTool, + vercelDeleteProjectTool, + vercelPauseProjectTool, + vercelUnpauseProjectTool, + vercelListProjectDomainsTool, + vercelAddProjectDomainTool, + vercelRemoveProjectDomainTool, + // Environment Variables + vercelGetEnvVarsTool, + vercelCreateEnvVarTool, + vercelUpdateEnvVarTool, + vercelDeleteEnvVarTool, + // Domains + vercelListDomainsTool, + vercelGetDomainTool, + vercelAddDomainTool, + vercelDeleteDomainTool, + vercelGetDomainConfigTool, + // DNS + vercelListDnsRecordsTool, + vercelCreateDnsRecordTool, + vercelDeleteDnsRecordTool, + // Aliases + vercelListAliasesTool, + vercelGetAliasTool, + vercelCreateAliasTool, + vercelDeleteAliasTool, + // Edge Config + vercelListEdgeConfigsTool, + vercelGetEdgeConfigTool, + vercelCreateEdgeConfigTool, + vercelGetEdgeConfigItemsTool, + vercelUpdateEdgeConfigItemsTool, + // Teams & User + vercelListTeamsTool, + vercelGetTeamTool, + vercelListTeamMembersTool, + vercelGetUserTool, + // Webhooks + vercelListWebhooksTool, + vercelCreateWebhookTool, + vercelDeleteWebhookTool, + // Checks + vercelCreateCheckTool, + vercelGetCheckTool, + vercelListChecksTool, + vercelUpdateCheckTool, + vercelRerequestCheckTool, +} + +export * from './types' diff --git a/apps/sim/tools/vercel/list_aliases.ts b/apps/sim/tools/vercel/list_aliases.ts new file mode 100644 index 000000000..fc4f77574 --- /dev/null +++ b/apps/sim/tools/vercel/list_aliases.ts @@ -0,0 +1,107 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelListAliasesParams, VercelListAliasesResponse } from '@/tools/vercel/types' + +export const vercelListAliasesTool: ToolConfig = + { + id: 'vercel_list_aliases', + name: 'Vercel List Aliases', + description: 'List aliases for a Vercel project or team', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter aliases by project ID', + }, + domain: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter aliases by domain', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of aliases to return', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelListAliasesParams) => { + const query = new URLSearchParams() + if (params.projectId) query.set('projectId', params.projectId.trim()) + if (params.domain) query.set('domain', params.domain.trim()) + if (params.limit) query.set('limit', String(params.limit)) + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v4/aliases${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelListAliasesParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const aliases = (data.aliases ?? []).map((a: any) => ({ + uid: a.uid ?? null, + alias: a.alias ?? null, + deploymentId: a.deploymentId ?? null, + projectId: a.projectId ?? null, + createdAt: a.createdAt ?? null, + updatedAt: a.updatedAt ?? null, + })) + + return { + success: true, + output: { + aliases, + count: aliases.length, + hasMore: data.pagination?.next != null, + }, + } + }, + + outputs: { + aliases: { + type: 'array', + description: 'List of aliases', + items: { + type: 'object', + properties: { + uid: { type: 'string', description: 'Alias ID' }, + alias: { type: 'string', description: 'Alias hostname' }, + deploymentId: { type: 'string', description: 'Associated deployment ID' }, + projectId: { type: 'string', description: 'Associated project ID' }, + createdAt: { type: 'number', description: 'Creation timestamp in milliseconds' }, + updatedAt: { type: 'number', description: 'Last update timestamp in milliseconds' }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of aliases returned', + }, + hasMore: { + type: 'boolean', + description: 'Whether more aliases are available', + }, + }, + } diff --git a/apps/sim/tools/vercel/list_checks.ts b/apps/sim/tools/vercel/list_checks.ts new file mode 100644 index 000000000..fec286ca6 --- /dev/null +++ b/apps/sim/tools/vercel/list_checks.ts @@ -0,0 +1,99 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelListChecksParams, VercelListChecksResponse } from '@/tools/vercel/types' + +export const vercelListChecksTool: ToolConfig = { + id: 'vercel_list_checks', + name: 'Vercel List Checks', + description: 'List all checks for a deployment', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + deploymentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Deployment ID to list checks for', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelListChecksParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/deployments/${params.deploymentId.trim()}/checks${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelListChecksParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const checks = (data.checks ?? []).map((check: Record) => ({ + id: check.id, + name: check.name, + status: check.status ?? 'registered', + conclusion: check.conclusion ?? null, + blocking: check.blocking ?? false, + deploymentId: check.deploymentId, + integrationId: check.integrationId ?? null, + externalId: check.externalId ?? null, + detailsUrl: check.detailsUrl ?? null, + path: check.path ?? null, + rerequestable: check.rerequestable ?? false, + createdAt: check.createdAt, + updatedAt: check.updatedAt, + startedAt: check.startedAt ?? null, + completedAt: check.completedAt ?? null, + })) + return { + success: true, + output: { + checks, + count: checks.length, + }, + } + }, + + outputs: { + checks: { + type: 'array', + description: 'List of deployment checks', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Check ID' }, + name: { type: 'string', description: 'Check name' }, + status: { type: 'string', description: 'Check status' }, + conclusion: { type: 'string', description: 'Check conclusion' }, + blocking: { type: 'boolean', description: 'Whether the check blocks the deployment' }, + deploymentId: { type: 'string', description: 'Associated deployment ID' }, + integrationId: { type: 'string', description: 'Associated integration ID' }, + externalId: { type: 'string', description: 'External identifier' }, + detailsUrl: { type: 'string', description: 'URL with details about the check' }, + path: { type: 'string', description: 'Page path being checked' }, + rerequestable: { type: 'boolean', description: 'Whether the check can be rerequested' }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + updatedAt: { type: 'number', description: 'Last update timestamp' }, + startedAt: { type: 'number', description: 'Start timestamp' }, + completedAt: { type: 'number', description: 'Completion timestamp' }, + }, + }, + }, + count: { type: 'number', description: 'Total number of checks' }, + }, +} diff --git a/apps/sim/tools/vercel/list_deployment_files.ts b/apps/sim/tools/vercel/list_deployment_files.ts new file mode 100644 index 000000000..c44c8e01b --- /dev/null +++ b/apps/sim/tools/vercel/list_deployment_files.ts @@ -0,0 +1,114 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelListDeploymentFilesParams, + VercelListDeploymentFilesResponse, +} from '@/tools/vercel/types' + +export const vercelListDeploymentFilesTool: ToolConfig< + VercelListDeploymentFilesParams, + VercelListDeploymentFilesResponse +> = { + id: 'vercel_list_deployment_files', + name: 'Vercel List Deployment Files', + description: 'List files in a Vercel deployment', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + deploymentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The deployment ID to list files for', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelListDeploymentFilesParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v6/deployments/${params.deploymentId.trim()}/files${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelListDeploymentFilesParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const files = (Array.isArray(data) ? data : (data.files ?? [])).map((f: any) => ({ + name: f.name ?? null, + type: f.type ?? null, + uid: f.uid ?? null, + mode: f.mode ?? null, + contentType: f.contentType ?? null, + children: f.children ?? [], + })) + + return { + success: true, + output: { + files, + count: files.length, + }, + } + }, + + outputs: { + files: { + type: 'array', + description: 'List of deployment files', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'The name of the file tree entry' }, + type: { + type: 'string', + description: 'File type: directory, file, symlink, lambda, middleware, or invalid', + }, + uid: { + type: 'string', + description: 'Unique file identifier (only valid for file type)', + optional: true, + }, + mode: { type: 'number', description: 'File mode indicating file type and permissions' }, + contentType: { + type: 'string', + description: 'Content-type of the file (only valid for file type)', + optional: true, + }, + children: { + type: 'array', + description: 'Child files of the directory (only valid for directory type)', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'File name' }, + type: { type: 'string', description: 'Entry type' }, + uid: { type: 'string', description: 'File identifier', optional: true }, + }, + }, + }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of files returned', + }, + }, +} diff --git a/apps/sim/tools/vercel/list_deployments.ts b/apps/sim/tools/vercel/list_deployments.ts new file mode 100644 index 000000000..51278ba0e --- /dev/null +++ b/apps/sim/tools/vercel/list_deployments.ts @@ -0,0 +1,170 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelListDeploymentsParams, + VercelListDeploymentsResponse, +} from '@/tools/vercel/types' + +export const vercelListDeploymentsTool: ToolConfig< + VercelListDeploymentsParams, + VercelListDeploymentsResponse +> = { + id: 'vercel_list_deployments', + name: 'Vercel List Deployments', + description: 'List deployments for a Vercel project or team', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter deployments by project ID or name', + }, + target: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by environment: production or staging', + }, + state: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by state: BUILDING, ERROR, INITIALIZING, QUEUED, READY, CANCELED', + }, + app: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by deployment name', + }, + since: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Get deployments created after this JavaScript timestamp', + }, + until: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Get deployments created before this JavaScript timestamp', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of deployments to return per request', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelListDeploymentsParams) => { + const query = new URLSearchParams() + if (params.projectId) query.set('projectId', params.projectId.trim()) + if (params.target) query.set('target', params.target) + if (params.state) query.set('state', params.state) + if (params.app) query.set('app', params.app.trim()) + if (params.since) query.set('since', String(params.since)) + if (params.until) query.set('until', String(params.until)) + if (params.limit) query.set('limit', String(params.limit)) + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v6/deployments${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelListDeploymentsParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const deployments = (data.deployments ?? []).map((d: any) => ({ + uid: d.uid, + name: d.name, + url: d.url ?? null, + state: d.state ?? d.readyState ?? 'UNKNOWN', + target: d.target ?? null, + created: d.created ?? d.createdAt, + projectId: d.projectId ?? '', + source: d.source ?? '', + inspectorUrl: d.inspectorUrl ?? '', + creator: { + uid: d.creator?.uid ?? '', + email: d.creator?.email ?? '', + username: d.creator?.username ?? '', + }, + meta: d.meta ?? {}, + })) + + return { + success: true, + output: { + deployments, + count: deployments.length, + hasMore: data.pagination?.next != null, + }, + } + }, + + outputs: { + deployments: { + type: 'array', + description: 'List of deployments', + items: { + type: 'object', + properties: { + uid: { type: 'string', description: 'Unique deployment identifier' }, + name: { type: 'string', description: 'Deployment name' }, + url: { type: 'string', description: 'Deployment URL', optional: true }, + state: { + type: 'string', + description: + 'Deployment state: BUILDING, ERROR, INITIALIZING, QUEUED, READY, CANCELED, DELETED', + }, + target: { type: 'string', description: 'Target environment', optional: true }, + created: { type: 'number', description: 'Creation timestamp' }, + projectId: { type: 'string', description: 'Associated project ID' }, + source: { + type: 'string', + description: + 'Deployment source: api-trigger-git-deploy, cli, clone/repo, git, import, import/repo, redeploy, v0-web', + }, + inspectorUrl: { type: 'string', description: 'Vercel inspector URL' }, + creator: { + type: 'object', + description: 'Creator information', + properties: { + uid: { type: 'string', description: 'Creator user ID' }, + email: { type: 'string', description: 'Creator email' }, + username: { type: 'string', description: 'Creator username' }, + }, + }, + meta: { type: 'object', description: 'Git provider metadata (key-value strings)' }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of deployments returned', + }, + hasMore: { + type: 'boolean', + description: 'Whether more deployments are available', + }, + }, +} diff --git a/apps/sim/tools/vercel/list_dns_records.ts b/apps/sim/tools/vercel/list_dns_records.ts new file mode 100644 index 000000000..81672abab --- /dev/null +++ b/apps/sim/tools/vercel/list_dns_records.ts @@ -0,0 +1,116 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelListDnsRecordsParams, VercelListDnsRecordsResponse } from '@/tools/vercel/types' + +export const vercelListDnsRecordsTool: ToolConfig< + VercelListDnsRecordsParams, + VercelListDnsRecordsResponse +> = { + id: 'vercel_list_dns_records', + name: 'Vercel List DNS Records', + description: 'List all DNS records for a domain in a Vercel account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The domain name to list records for', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of records to return', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelListDnsRecordsParams) => { + const query = new URLSearchParams() + if (params.limit) query.set('limit', String(params.limit)) + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v4/domains/${params.domain.trim()}/records${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelListDnsRecordsParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const records = (data.records ?? []).map((r: any) => ({ + id: r.id ?? null, + slug: r.slug ?? null, + name: r.name ?? null, + type: r.type ?? null, + value: r.value ?? null, + ttl: r.ttl ?? null, + mxPriority: r.mxPriority ?? null, + priority: r.priority ?? null, + creator: r.creator ?? null, + createdAt: r.createdAt ?? null, + updatedAt: r.updatedAt ?? null, + comment: r.comment ?? null, + })) + + return { + success: true, + output: { + records, + count: records.length, + hasMore: data.pagination?.next != null, + }, + } + }, + + outputs: { + records: { + type: 'array', + description: 'List of DNS records', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Record ID' }, + slug: { type: 'string', description: 'Record slug' }, + name: { type: 'string', description: 'Record name' }, + type: { + type: 'string', + description: 'Record type (A, AAAA, ALIAS, CAA, CNAME, HTTPS, MX, SRV, TXT, NS)', + }, + value: { type: 'string', description: 'Record value' }, + ttl: { type: 'number', description: 'Time to live in seconds' }, + mxPriority: { type: 'number', description: 'MX record priority' }, + priority: { type: 'number', description: 'Record priority' }, + creator: { type: 'string', description: 'Creator identifier' }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + updatedAt: { type: 'number', description: 'Last update timestamp' }, + comment: { type: 'string', description: 'Record comment' }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of records returned', + }, + hasMore: { + type: 'boolean', + description: 'Whether more records are available', + }, + }, +} diff --git a/apps/sim/tools/vercel/list_domains.ts b/apps/sim/tools/vercel/list_domains.ts new file mode 100644 index 000000000..55b2da509 --- /dev/null +++ b/apps/sim/tools/vercel/list_domains.ts @@ -0,0 +1,109 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelListDomainsParams, VercelListDomainsResponse } from '@/tools/vercel/types' + +export const vercelListDomainsTool: ToolConfig = + { + id: 'vercel_list_domains', + name: 'Vercel List Domains', + description: 'List all domains in a Vercel account or team', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of domains to return (default 20)', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelListDomainsParams) => { + const query = new URLSearchParams() + if (params.limit) query.set('limit', String(params.limit)) + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v5/domains${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelListDomainsParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const domains = (data.domains ?? []).map((d: any) => ({ + id: d.id, + name: d.name, + verified: d.verified ?? false, + createdAt: d.createdAt, + expiresAt: d.expiresAt ?? null, + serviceType: d.serviceType ?? 'external', + nameservers: d.nameservers ?? [], + intendedNameservers: d.intendedNameservers ?? [], + renew: d.renew ?? false, + boughtAt: d.boughtAt ?? null, + })) + + return { + success: true, + output: { + domains, + count: domains.length, + hasMore: data.pagination?.next != null, + }, + } + }, + + outputs: { + domains: { + type: 'array', + description: 'List of domains', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Domain ID' }, + name: { type: 'string', description: 'Domain name' }, + verified: { type: 'boolean', description: 'Whether domain is verified' }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + expiresAt: { type: 'number', description: 'Expiration timestamp' }, + serviceType: { type: 'string', description: 'Service type (zeit.world, external, na)' }, + nameservers: { + type: 'array', + description: 'Current nameservers', + items: { type: 'string' }, + }, + intendedNameservers: { + type: 'array', + description: 'Intended nameservers', + items: { type: 'string' }, + }, + renew: { type: 'boolean', description: 'Whether auto-renewal is enabled' }, + boughtAt: { type: 'number', description: 'Purchase timestamp' }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of domains returned', + }, + hasMore: { + type: 'boolean', + description: 'Whether more domains are available', + }, + }, + } diff --git a/apps/sim/tools/vercel/list_edge_configs.ts b/apps/sim/tools/vercel/list_edge_configs.ts new file mode 100644 index 000000000..9a025ada7 --- /dev/null +++ b/apps/sim/tools/vercel/list_edge_configs.ts @@ -0,0 +1,91 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelListEdgeConfigsParams, + VercelListEdgeConfigsResponse, +} from '@/tools/vercel/types' + +export const vercelListEdgeConfigsTool: ToolConfig< + VercelListEdgeConfigsParams, + VercelListEdgeConfigsResponse +> = { + id: 'vercel_list_edge_configs', + name: 'Vercel List Edge Configs', + description: 'List all Edge Config stores for a team', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelListEdgeConfigsParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/edge-config${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelListEdgeConfigsParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const items = Array.isArray(data) ? data : (data.edgeConfigs ?? []) + const edgeConfigs = items.map((ec: any) => ({ + id: ec.id ?? null, + slug: ec.slug ?? null, + ownerId: ec.ownerId ?? null, + digest: ec.digest ?? null, + createdAt: ec.createdAt ?? null, + updatedAt: ec.updatedAt ?? null, + itemCount: ec.itemCount ?? 0, + sizeInBytes: ec.sizeInBytes ?? 0, + })) + + return { + success: true, + output: { + edgeConfigs, + count: edgeConfigs.length, + }, + } + }, + + outputs: { + edgeConfigs: { + type: 'array', + description: 'List of Edge Config stores', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Edge Config ID' }, + slug: { type: 'string', description: 'Edge Config slug' }, + ownerId: { type: 'string', description: 'Owner ID' }, + digest: { type: 'string', description: 'Content digest hash' }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + updatedAt: { type: 'number', description: 'Last update timestamp' }, + itemCount: { type: 'number', description: 'Number of items' }, + sizeInBytes: { type: 'number', description: 'Size in bytes' }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of Edge Configs returned', + }, + }, +} diff --git a/apps/sim/tools/vercel/list_project_domains.ts b/apps/sim/tools/vercel/list_project_domains.ts new file mode 100644 index 000000000..8f8c370ef --- /dev/null +++ b/apps/sim/tools/vercel/list_project_domains.ts @@ -0,0 +1,116 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelListProjectDomainsParams, + VercelListProjectDomainsResponse, +} from '@/tools/vercel/types' + +export const vercelListProjectDomainsTool: ToolConfig< + VercelListProjectDomainsParams, + VercelListProjectDomainsResponse +> = { + id: 'vercel_list_project_domains', + name: 'Vercel List Project Domains', + description: 'List all domains for a Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or name', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of domains to return', + }, + }, + + request: { + url: (params: VercelListProjectDomainsParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + if (params.limit) query.set('limit', String(params.limit)) + const qs = query.toString() + return `https://api.vercel.com/v9/projects/${params.projectId.trim()}/domains${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelListProjectDomainsParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const domains = (data.domains ?? []).map( + (d: { + name: string + apexName: string + redirect: string | null + redirectStatusCode: number | null + verified: boolean + gitBranch: string | null + createdAt: number + updatedAt: number + }) => ({ + name: d.name, + apexName: d.apexName, + redirect: d.redirect ?? null, + redirectStatusCode: d.redirectStatusCode ?? null, + verified: d.verified, + gitBranch: d.gitBranch ?? null, + createdAt: d.createdAt, + updatedAt: d.updatedAt, + }) + ) + return { + success: true, + output: { + domains, + count: domains.length, + hasMore: data.pagination?.next != null, + }, + } + }, + + outputs: { + domains: { + type: 'array', + description: 'List of project domains', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Domain name' }, + apexName: { type: 'string', description: 'Apex domain name' }, + redirect: { type: 'string', description: 'Redirect target', optional: true }, + redirectStatusCode: { + type: 'number', + description: 'Redirect status code', + optional: true, + }, + verified: { type: 'boolean', description: 'Whether the domain is verified' }, + gitBranch: { type: 'string', description: 'Git branch for the domain', optional: true }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + updatedAt: { type: 'number', description: 'Last updated timestamp' }, + }, + }, + }, + count: { type: 'number', description: 'Number of domains returned' }, + hasMore: { type: 'boolean', description: 'Whether more domains are available' }, + }, +} diff --git a/apps/sim/tools/vercel/list_projects.ts b/apps/sim/tools/vercel/list_projects.ts new file mode 100644 index 000000000..edec3835d --- /dev/null +++ b/apps/sim/tools/vercel/list_projects.ts @@ -0,0 +1,106 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelListProjectsParams, VercelListProjectsResponse } from '@/tools/vercel/types' + +export const vercelListProjectsTool: ToolConfig< + VercelListProjectsParams, + VercelListProjectsResponse +> = { + id: 'vercel_list_projects', + name: 'Vercel List Projects', + description: 'List all projects in a Vercel team or account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search projects by name', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of projects to return', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelListProjectsParams) => { + const query = new URLSearchParams() + if (params.search) query.set('search', params.search) + if (params.limit) query.set('limit', String(params.limit)) + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v10/projects${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelListProjectsParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const projects = (data.projects ?? []).map((p: any) => ({ + id: p.id, + name: p.name, + framework: p.framework ?? null, + createdAt: p.createdAt, + updatedAt: p.updatedAt, + domains: p.domains ?? [], + })) + + return { + success: true, + output: { + projects, + count: projects.length, + hasMore: data.pagination?.next != null, + }, + } + }, + + outputs: { + projects: { + type: 'array', + description: 'List of projects', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Project ID' }, + name: { type: 'string', description: 'Project name' }, + framework: { type: 'string', description: 'Framework', optional: true }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + updatedAt: { type: 'number', description: 'Last updated timestamp' }, + domains: { + type: 'array', + description: 'Project domains', + items: { type: 'string', description: 'Domain' }, + }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of projects returned', + }, + hasMore: { + type: 'boolean', + description: 'Whether more projects are available', + }, + }, +} diff --git a/apps/sim/tools/vercel/list_team_members.ts b/apps/sim/tools/vercel/list_team_members.ts new file mode 100644 index 000000000..78dd2ebd2 --- /dev/null +++ b/apps/sim/tools/vercel/list_team_members.ts @@ -0,0 +1,151 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelListTeamMembersParams, + VercelListTeamMembersResponse, +} from '@/tools/vercel/types' + +export const vercelListTeamMembersTool: ToolConfig< + VercelListTeamMembersParams, + VercelListTeamMembersResponse +> = { + id: 'vercel_list_team_members', + name: 'Vercel List Team Members', + description: 'List all members of a Vercel team', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + teamId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The team ID to list members for', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of members to return', + }, + role: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Filter by role (OWNER, MEMBER, DEVELOPER, SECURITY, BILLING, VIEWER, VIEWER_FOR_PLUS, CONTRIBUTOR)', + }, + since: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Timestamp in milliseconds to only include members added since then', + }, + until: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Timestamp in milliseconds to only include members added until then', + }, + search: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search team members by their name, username, and email', + }, + }, + + request: { + url: (params: VercelListTeamMembersParams) => { + const query = new URLSearchParams() + if (params.limit) query.set('limit', String(params.limit)) + if (params.role) query.set('role', params.role.trim()) + if (params.since) query.set('since', String(params.since)) + if (params.until) query.set('until', String(params.until)) + if (params.search) query.set('search', params.search.trim()) + const qs = query.toString() + return `https://api.vercel.com/v3/teams/${params.teamId.trim()}/members${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelListTeamMembersParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const members = (data.members ?? []).map((m: any) => ({ + uid: m.uid ?? null, + email: m.email ?? null, + username: m.username ?? null, + name: m.name ?? null, + avatar: m.avatar ?? null, + role: m.role ?? null, + confirmed: m.confirmed ?? false, + createdAt: m.createdAt ?? null, + joinedFrom: m.joinedFrom + ? { + origin: m.joinedFrom.origin ?? null, + } + : null, + })) + + return { + success: true, + output: { + members, + count: members.length, + pagination: data.pagination + ? { + hasNext: data.pagination.hasNext ?? false, + count: data.pagination.count ?? 0, + } + : null, + }, + } + }, + + outputs: { + members: { + type: 'array', + description: 'List of team members', + items: { + type: 'object', + properties: { + uid: { type: 'string', description: 'Member user ID' }, + email: { type: 'string', description: 'Member email' }, + username: { type: 'string', description: 'Member username' }, + name: { type: 'string', description: 'Member full name' }, + avatar: { type: 'string', description: 'Avatar file ID' }, + role: { type: 'string', description: 'Member role' }, + confirmed: { type: 'boolean', description: 'Whether membership is confirmed' }, + createdAt: { type: 'number', description: 'Join timestamp in milliseconds' }, + joinedFrom: { + type: 'object', + description: 'Origin of how the member joined', + properties: { + origin: { type: 'string', description: 'Join origin identifier' }, + }, + }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of members returned', + }, + pagination: { + type: 'object', + description: 'Pagination information', + properties: { + hasNext: { type: 'boolean', description: 'Whether there are more pages' }, + count: { type: 'number', description: 'Items in current page' }, + }, + }, + }, +} diff --git a/apps/sim/tools/vercel/list_teams.ts b/apps/sim/tools/vercel/list_teams.ts new file mode 100644 index 000000000..248596242 --- /dev/null +++ b/apps/sim/tools/vercel/list_teams.ts @@ -0,0 +1,132 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelListTeamsParams, VercelListTeamsResponse } from '@/tools/vercel/types' + +export const vercelListTeamsTool: ToolConfig = { + id: 'vercel_list_teams', + name: 'Vercel List Teams', + description: 'List all teams in a Vercel account', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of teams to return', + }, + since: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Timestamp in milliseconds to only include teams created since then', + }, + until: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Timestamp in milliseconds to only include teams created until then', + }, + }, + + request: { + url: (params: VercelListTeamsParams) => { + const query = new URLSearchParams() + if (params.limit) query.set('limit', String(params.limit)) + if (params.since) query.set('since', String(params.since)) + if (params.until) query.set('until', String(params.until)) + const qs = query.toString() + return `https://api.vercel.com/v2/teams${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelListTeamsParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const teams = (data.teams ?? []).map((t: any) => ({ + id: t.id ?? null, + slug: t.slug ?? null, + name: t.name ?? null, + avatar: t.avatar ?? null, + createdAt: t.createdAt ?? null, + updatedAt: t.updatedAt ?? null, + creatorId: t.creatorId ?? null, + membership: t.membership + ? { + role: t.membership.role ?? null, + confirmed: t.membership.confirmed ?? false, + created: t.membership.created ?? null, + uid: t.membership.uid ?? null, + teamId: t.membership.teamId ?? null, + } + : null, + })) + + return { + success: true, + output: { + teams, + count: teams.length, + pagination: data.pagination + ? { + count: data.pagination.count ?? 0, + next: data.pagination.next ?? null, + prev: data.pagination.prev ?? null, + } + : null, + }, + } + }, + + outputs: { + teams: { + type: 'array', + description: 'List of teams', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Team ID' }, + slug: { type: 'string', description: 'Team slug' }, + name: { type: 'string', description: 'Team name' }, + avatar: { type: 'string', description: 'Avatar file ID' }, + createdAt: { type: 'number', description: 'Creation timestamp in milliseconds' }, + updatedAt: { type: 'number', description: 'Last update timestamp in milliseconds' }, + creatorId: { type: 'string', description: 'User ID of team creator' }, + membership: { + type: 'object', + description: 'Current user membership details', + properties: { + role: { type: 'string', description: 'Membership role' }, + confirmed: { type: 'boolean', description: 'Whether membership is confirmed' }, + created: { type: 'number', description: 'Membership creation timestamp' }, + uid: { type: 'string', description: 'User ID of the member' }, + teamId: { type: 'string', description: 'Team ID' }, + }, + }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of teams returned', + }, + pagination: { + type: 'object', + description: 'Pagination information', + properties: { + count: { type: 'number', description: 'Items in current page' }, + next: { type: 'number', description: 'Timestamp for next page request' }, + prev: { type: 'number', description: 'Timestamp for previous page request' }, + }, + }, + }, +} diff --git a/apps/sim/tools/vercel/list_webhooks.ts b/apps/sim/tools/vercel/list_webhooks.ts new file mode 100644 index 000000000..df22c5743 --- /dev/null +++ b/apps/sim/tools/vercel/list_webhooks.ts @@ -0,0 +1,100 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelListWebhooksParams, VercelListWebhooksResponse } from '@/tools/vercel/types' + +export const vercelListWebhooksTool: ToolConfig< + VercelListWebhooksParams, + VercelListWebhooksResponse +> = { + id: 'vercel_list_webhooks', + name: 'Vercel List Webhooks', + description: 'List webhooks for a Vercel project or team', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter webhooks by project ID', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelListWebhooksParams) => { + const query = new URLSearchParams() + if (params.projectId) query.set('projectId', params.projectId.trim()) + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/webhooks${qs ? `?${qs}` : ''}` + }, + method: 'GET', + headers: (params: VercelListWebhooksParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + const webhooks = (Array.isArray(data) ? data : []).map((w: any) => ({ + id: w.id ?? null, + url: w.url ?? null, + events: w.events ?? [], + ownerId: w.ownerId ?? null, + projectIds: w.projectIds ?? [], + createdAt: w.createdAt ?? null, + updatedAt: w.updatedAt ?? null, + })) + + return { + success: true, + output: { + webhooks, + count: webhooks.length, + }, + } + }, + + outputs: { + webhooks: { + type: 'array', + description: 'List of webhooks', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Webhook ID' }, + url: { type: 'string', description: 'Webhook URL' }, + events: { + type: 'array', + description: 'Events the webhook listens to', + items: { type: 'string', description: 'Event name' }, + }, + ownerId: { type: 'string', description: 'Owner ID' }, + projectIds: { + type: 'array', + description: 'Associated project IDs', + items: { type: 'string', description: 'Project ID' }, + }, + createdAt: { type: 'number', description: 'Creation timestamp' }, + updatedAt: { type: 'number', description: 'Last updated timestamp' }, + }, + }, + }, + count: { + type: 'number', + description: 'Number of webhooks returned', + }, + }, +} diff --git a/apps/sim/tools/vercel/pause_project.ts b/apps/sim/tools/vercel/pause_project.ts new file mode 100644 index 000000000..97d5ac9c8 --- /dev/null +++ b/apps/sim/tools/vercel/pause_project.ts @@ -0,0 +1,65 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelPauseProjectParams, VercelPauseProjectResponse } from '@/tools/vercel/types' + +export const vercelPauseProjectTool: ToolConfig< + VercelPauseProjectParams, + VercelPauseProjectResponse +> = { + id: 'vercel_pause_project', + name: 'Vercel Pause Project', + description: 'Pause a Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or name', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelPauseProjectParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/projects/${params.projectId.trim()}/pause${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelPauseProjectParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id, + name: data.name, + paused: data.paused ?? true, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Project ID' }, + name: { type: 'string', description: 'Project name' }, + paused: { type: 'boolean', description: 'Whether the project is paused' }, + }, +} diff --git a/apps/sim/tools/vercel/remove_project_domain.ts b/apps/sim/tools/vercel/remove_project_domain.ts new file mode 100644 index 000000000..e9b15caeb --- /dev/null +++ b/apps/sim/tools/vercel/remove_project_domain.ts @@ -0,0 +1,69 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelRemoveProjectDomainParams, + VercelRemoveProjectDomainResponse, +} from '@/tools/vercel/types' + +export const vercelRemoveProjectDomainTool: ToolConfig< + VercelRemoveProjectDomainParams, + VercelRemoveProjectDomainResponse +> = { + id: 'vercel_remove_project_domain', + name: 'Vercel Remove Project Domain', + description: 'Remove a domain from a Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or name', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Domain name to remove', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelRemoveProjectDomainParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v9/projects/${params.projectId.trim()}/domains/${params.domain.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'DELETE', + headers: (params: VercelRemoveProjectDomainParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async () => { + return { + success: true, + output: { + deleted: true, + }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the domain was successfully removed' }, + }, +} diff --git a/apps/sim/tools/vercel/rerequest_check.ts b/apps/sim/tools/vercel/rerequest_check.ts new file mode 100644 index 000000000..f0da658b5 --- /dev/null +++ b/apps/sim/tools/vercel/rerequest_check.ts @@ -0,0 +1,65 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelRerequestCheckParams, VercelRerequestCheckResponse } from '@/tools/vercel/types' + +export const vercelRerequestCheckTool: ToolConfig< + VercelRerequestCheckParams, + VercelRerequestCheckResponse +> = { + id: 'vercel_rerequest_check', + name: 'Vercel Rerequest Check', + description: 'Rerequest a deployment check', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + deploymentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Deployment ID the check belongs to', + }, + checkId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Check ID to rerequest', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelRerequestCheckParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/deployments/${params.deploymentId.trim()}/checks/${params.checkId.trim()}/rerequest${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelRerequestCheckParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async () => { + return { + success: true, + output: { + rerequested: true, + }, + } + }, + + outputs: { + rerequested: { type: 'boolean', description: 'Whether the check was successfully rerequested' }, + }, +} diff --git a/apps/sim/tools/vercel/types.ts b/apps/sim/tools/vercel/types.ts new file mode 100644 index 000000000..eb265e60e --- /dev/null +++ b/apps/sim/tools/vercel/types.ts @@ -0,0 +1,1025 @@ +import type { ToolResponse } from '@/tools/types' + +export interface VercelListDeploymentsParams { + apiKey: string + projectId?: string + target?: string + state?: string + app?: string + since?: number + until?: number + limit?: number + teamId?: string +} + +export interface VercelGetDeploymentParams { + apiKey: string + deploymentId: string + withGitRepoInfo?: string + teamId?: string +} + +export interface VercelListProjectsParams { + apiKey: string + search?: string + limit?: number + teamId?: string +} + +export interface VercelGetProjectParams { + apiKey: string + projectId: string + teamId?: string +} + +export interface VercelCreateDeploymentParams { + apiKey: string + name: string + project?: string + deploymentId?: string + target?: string + gitSource?: string + forceNew?: string + teamId?: string +} + +export interface VercelListDomainsParams { + apiKey: string + limit?: number + teamId?: string +} + +export interface VercelGetEnvVarsParams { + apiKey: string + projectId: string + teamId?: string +} + +export interface VercelListDeploymentsResponse extends ToolResponse { + output: { + deployments: Array<{ + uid: string + name: string + url: string | null + state: string + target: string | null + created: number + projectId: string + source: string + inspectorUrl: string + creator: { + uid: string + email: string + username: string + } + meta: Record + }> + count: number + hasMore: boolean + } +} + +export interface VercelGetDeploymentResponse extends ToolResponse { + output: { + id: string + name: string + url: string + readyState: string + status: string + target: string | null + createdAt: number + buildingAt: number | null + ready: number | null + source: string + alias: string[] + regions: string[] + inspectorUrl: string + projectId: string + creator: { + uid: string + username: string + } + project: { + id: string + name: string + framework: string | null + } | null + meta: Record + gitSource: Record | null + } +} + +export interface VercelListProjectsResponse extends ToolResponse { + output: { + projects: Array<{ + id: string + name: string + framework: string | null + createdAt: number + updatedAt: number + domains: string[] + }> + count: number + hasMore: boolean + } +} + +export interface VercelGetProjectResponse extends ToolResponse { + output: { + id: string + name: string + framework: string | null + createdAt: number + updatedAt: number + domains: string[] + link: { + type: string + repo: string + org: string + } | null + } +} + +export interface VercelCreateDeploymentResponse extends ToolResponse { + output: { + id: string + name: string + url: string + readyState: string + projectId: string + createdAt: number + alias: string[] + target: string | null + inspectorUrl: string + } +} + +export interface VercelListDomainsResponse extends ToolResponse { + output: { + domains: Array<{ + id: string + name: string + verified: boolean + createdAt: number + expiresAt: number | null + serviceType: string + nameservers: string[] + intendedNameservers: string[] + renew: boolean + boughtAt: number | null + }> + count: number + hasMore: boolean + } +} + +export interface VercelGetEnvVarsResponse extends ToolResponse { + output: { + envs: Array<{ + id: string + key: string + value: string + type: string + target: string[] + gitBranch: string | null + comment: string | null + }> + count: number + } +} + +export interface VercelCancelDeploymentParams { + apiKey: string + deploymentId: string + teamId?: string +} + +export interface VercelCancelDeploymentResponse extends ToolResponse { + output: { + id: string + name: string | null + state: string + url: string | null + } +} + +export interface VercelDeleteDeploymentParams { + apiKey: string + deploymentId: string + teamId?: string +} + +export interface VercelDeleteDeploymentResponse extends ToolResponse { + output: { + uid: string | null + state: string + } +} + +export interface VercelGetDeploymentEventsParams { + apiKey: string + deploymentId: string + direction?: string + follow?: number + limit?: number + since?: number + until?: number + teamId?: string +} + +export interface VercelGetDeploymentEventsResponse extends ToolResponse { + output: { + events: Array<{ + type: string | null + created: number | null + date: number | null + text: string | null + serial: string | null + deploymentId: string | null + id: string | null + level: string | null + }> + count: number + } +} + +export interface VercelCreateEnvVarParams { + apiKey: string + projectId: string + key: string + value: string + target: string + type?: string + gitBranch?: string + comment?: string + teamId?: string +} + +export interface VercelCreateEnvVarResponse extends ToolResponse { + output: { + id: string + key: string + value: string + type: string + target: string[] + gitBranch: string | null + comment: string | null + } +} + +export interface VercelUpdateEnvVarParams { + apiKey: string + projectId: string + envId: string + key?: string + value?: string + target?: string + type?: string + gitBranch?: string + comment?: string + teamId?: string +} + +export interface VercelUpdateEnvVarResponse extends ToolResponse { + output: { + id: string + key: string + value: string + type: string + target: string[] + gitBranch: string | null + comment: string | null + } +} + +export interface VercelDeleteEnvVarParams { + apiKey: string + projectId: string + envId: string + teamId?: string +} + +export interface VercelDeleteEnvVarResponse extends ToolResponse { + output: { + deleted: boolean + } +} + +export interface VercelListDeploymentFilesParams { + apiKey: string + deploymentId: string + teamId?: string +} + +export interface VercelListDeploymentFilesResponse extends ToolResponse { + output: { + files: Array<{ + name: string | null + type: string | null + uid: string | null + mode: number | null + contentType: string | null + children: unknown[] + }> + count: number + } +} + +export interface VercelCreateProjectParams { + apiKey: string + name: string + framework?: string + gitRepository?: { type: string; repo: string } + buildCommand?: string + outputDirectory?: string + installCommand?: string + teamId?: string +} + +export interface VercelCreateProjectResponse extends ToolResponse { + output: { + id: string + name: string + framework: string | null + createdAt: number + updatedAt: number + } +} + +export interface VercelUpdateProjectParams { + apiKey: string + projectId: string + name?: string + framework?: string + buildCommand?: string + outputDirectory?: string + installCommand?: string + teamId?: string +} + +export interface VercelUpdateProjectResponse extends ToolResponse { + output: { + id: string + name: string + framework: string | null + updatedAt: number + } +} + +export interface VercelDeleteProjectParams { + apiKey: string + projectId: string + teamId?: string +} + +export interface VercelDeleteProjectResponse extends ToolResponse { + output: { + deleted: boolean + } +} + +export interface VercelPauseProjectParams { + apiKey: string + projectId: string + teamId?: string +} + +export interface VercelPauseProjectResponse extends ToolResponse { + output: { + id: string + name: string + paused: boolean + } +} + +export interface VercelUnpauseProjectParams { + apiKey: string + projectId: string + teamId?: string +} + +export interface VercelUnpauseProjectResponse extends ToolResponse { + output: { + id: string + name: string + paused: boolean + } +} + +export interface VercelListProjectDomainsParams { + apiKey: string + projectId: string + teamId?: string + limit?: number +} + +export interface VercelListProjectDomainsResponse extends ToolResponse { + output: { + domains: Array<{ + name: string + apexName: string + redirect: string | null + redirectStatusCode: number | null + verified: boolean + gitBranch: string | null + createdAt: number + updatedAt: number + }> + count: number + hasMore: boolean + } +} + +export interface VercelAddProjectDomainParams { + apiKey: string + projectId: string + domain: string + redirect?: string + redirectStatusCode?: number + gitBranch?: string + teamId?: string +} + +export interface VercelAddProjectDomainResponse extends ToolResponse { + output: { + name: string + apexName: string + verified: boolean + gitBranch: string | null + redirect: string | null + redirectStatusCode: number | null + createdAt: number + updatedAt: number + } +} + +export interface VercelRemoveProjectDomainParams { + apiKey: string + projectId: string + domain: string + teamId?: string +} + +export interface VercelRemoveProjectDomainResponse extends ToolResponse { + output: { + deleted: boolean + } +} + +export interface VercelGetDomainParams { + apiKey: string + domain: string + teamId?: string +} + +export interface VercelGetDomainResponse extends ToolResponse { + output: { + id: string | null + name: string | null + verified: boolean + createdAt: number | null + expiresAt: number | null + serviceType: string | null + nameservers: string[] + intendedNameservers: string[] + customNameservers: string[] + renew: boolean + boughtAt: number | null + transferredAt: number | null + } +} + +export interface VercelAddDomainParams { + apiKey: string + name: string + teamId?: string +} + +export interface VercelAddDomainResponse extends ToolResponse { + output: { + id: string | null + name: string | null + verified: boolean + createdAt: number | null + serviceType: string | null + nameservers: string[] + intendedNameservers: string[] + } +} + +export interface VercelDeleteDomainParams { + apiKey: string + domain: string + teamId?: string +} + +export interface VercelDeleteDomainResponse extends ToolResponse { + output: { + uid: string | null + deleted: boolean + } +} + +export interface VercelGetDomainConfigParams { + apiKey: string + domain: string + teamId?: string +} + +export interface VercelGetDomainConfigResponse extends ToolResponse { + output: { + configuredBy: string | null + acceptedChallenges: string[] + misconfigured: boolean + recommendedIPv4: Array<{ rank: number; value: string[] }> + recommendedCNAME: Array<{ rank: number; value: string }> + } +} + +export interface VercelCreateDnsRecordParams { + apiKey: string + domain: string + recordName: string + recordType: string + value: string + ttl?: number + mxPriority?: number + teamId?: string +} + +export interface VercelCreateDnsRecordResponse extends ToolResponse { + output: { + uid: string | null + updated: number | null + } +} + +export interface VercelListDnsRecordsParams { + apiKey: string + domain: string + limit?: number + teamId?: string +} + +export interface VercelListDnsRecordsResponse extends ToolResponse { + output: { + records: Array<{ + id: string | null + slug: string | null + name: string | null + type: string | null + value: string | null + ttl: number | null + mxPriority: number | null + priority: number | null + creator: string | null + createdAt: number | null + updatedAt: number | null + comment: string | null + }> + count: number + hasMore: boolean + } +} + +export interface VercelDeleteDnsRecordParams { + apiKey: string + domain: string + recordId: string + teamId?: string +} + +export interface VercelDeleteDnsRecordResponse extends ToolResponse { + output: { + deleted: boolean + } +} + +export interface VercelListTeamsParams { + apiKey: string + limit?: number + since?: number + until?: number +} + +export interface VercelListTeamsResponse extends ToolResponse { + output: { + teams: Array<{ + id: string | null + slug: string | null + name: string | null + avatar: string | null + createdAt: number | null + updatedAt: number | null + creatorId: string | null + membership: { + role: string | null + confirmed: boolean + created: number | null + uid: string | null + teamId: string | null + } | null + }> + count: number + pagination: { + count: number + next: number | null + prev: number | null + } | null + } +} + +export interface VercelGetTeamParams { + apiKey: string + teamId: string +} + +export interface VercelGetTeamResponse extends ToolResponse { + output: { + id: string | null + slug: string | null + name: string | null + avatar: string | null + description: string | null + createdAt: number | null + updatedAt: number | null + creatorId: string | null + membership: { + uid: string | null + teamId: string | null + role: string | null + confirmed: boolean + created: number | null + createdAt: number | null + accessRequestedAt: number | null + teamRoles: string[] + teamPermissions: string[] + } | null + } +} + +export interface VercelListTeamMembersParams { + apiKey: string + teamId: string + limit?: number + role?: string + since?: number + until?: number + search?: string +} + +export interface VercelListTeamMembersResponse extends ToolResponse { + output: { + members: Array<{ + uid: string | null + email: string | null + username: string | null + name: string | null + avatar: string | null + role: string | null + confirmed: boolean + createdAt: number | null + joinedFrom: { + origin: string | null + } | null + }> + count: number + pagination: { + hasNext: boolean + count: number + } | null + } +} + +export interface VercelGetUserParams { + apiKey: string +} + +export interface VercelGetUserResponse extends ToolResponse { + output: { + id: string | null + email: string | null + username: string | null + name: string | null + avatar: string | null + defaultTeamId: string | null + createdAt: number | null + stagingPrefix: string | null + softBlock: { + blockedAt: number | null + reason: string | null + } | null + hasTrialAvailable: boolean | null + } +} + +export interface VercelListAliasesParams { + apiKey: string + projectId?: string + domain?: string + limit?: number + teamId?: string +} + +export interface VercelListAliasesResponse extends ToolResponse { + output: { + aliases: Array<{ + uid: string | null + alias: string | null + deploymentId: string | null + projectId: string | null + createdAt: number | null + updatedAt: number | null + }> + count: number + hasMore: boolean + } +} + +export interface VercelGetAliasParams { + apiKey: string + aliasId: string + teamId?: string +} + +export interface VercelGetAliasResponse extends ToolResponse { + output: { + uid: string | null + alias: string | null + deploymentId: string | null + projectId: string | null + createdAt: number | null + updatedAt: number | null + redirect: string | null + redirectStatusCode: number | null + } +} + +export interface VercelCreateAliasParams { + apiKey: string + deploymentId: string + alias: string + teamId?: string +} + +export interface VercelCreateAliasResponse extends ToolResponse { + output: { + uid: string | null + alias: string | null + created: string | null + oldDeploymentId: string | null + } +} + +export interface VercelDeleteAliasParams { + apiKey: string + aliasId: string + teamId?: string +} + +export interface VercelDeleteAliasResponse extends ToolResponse { + output: { + status: string + } +} + +export interface VercelListEdgeConfigsParams { + apiKey: string + teamId?: string +} + +export interface VercelListEdgeConfigsResponse extends ToolResponse { + output: { + edgeConfigs: Array<{ + id: string | null + slug: string | null + ownerId: string | null + digest: string | null + createdAt: number | null + updatedAt: number | null + itemCount: number + sizeInBytes: number + }> + count: number + } +} + +export interface VercelGetEdgeConfigParams { + apiKey: string + edgeConfigId: string + teamId?: string +} + +export interface VercelGetEdgeConfigResponse extends ToolResponse { + output: { + id: string | null + slug: string | null + ownerId: string | null + digest: string | null + createdAt: number | null + updatedAt: number | null + itemCount: number + sizeInBytes: number + } +} + +export interface VercelCreateEdgeConfigParams { + apiKey: string + slug: string + teamId?: string +} + +export interface VercelCreateEdgeConfigResponse extends ToolResponse { + output: { + id: string | null + slug: string | null + ownerId: string | null + digest: string | null + createdAt: number | null + updatedAt: number | null + itemCount: number + sizeInBytes: number + } +} + +export interface VercelGetEdgeConfigItemsParams { + apiKey: string + edgeConfigId: string + teamId?: string +} + +export interface VercelGetEdgeConfigItemsResponse extends ToolResponse { + output: { + items: Array<{ + key: string | null + value: any + description: string | null + edgeConfigId: string | null + createdAt: number | null + updatedAt: number | null + }> + count: number + } +} + +export interface VercelUpdateEdgeConfigItemsParams { + apiKey: string + edgeConfigId: string + items: string | Array<{ operation: string; key: string; value?: any; description?: string }> + teamId?: string +} + +export interface VercelUpdateEdgeConfigItemsResponse extends ToolResponse { + output: { + status: string + } +} + +export interface VercelListWebhooksParams { + apiKey: string + projectId?: string + teamId?: string +} + +export interface VercelListWebhooksResponse extends ToolResponse { + output: { + webhooks: Array<{ + id: string + url: string + events: string[] + ownerId: string + projectIds: string[] + createdAt: number + updatedAt: number + }> + count: number + } +} + +export interface VercelCreateWebhookParams { + apiKey: string + url: string + events: string + projectIds?: string + teamId: string +} + +export interface VercelCreateWebhookResponse extends ToolResponse { + output: { + id: string + url: string + secret: string + events: string[] + ownerId: string + projectIds: string[] + createdAt: number + updatedAt: number + } +} + +export interface VercelDeleteWebhookParams { + apiKey: string + webhookId: string + teamId?: string +} + +export interface VercelDeleteWebhookResponse extends ToolResponse { + output: { + deleted: boolean + } +} + +export interface VercelCreateCheckParams { + apiKey: string + deploymentId: string + name: string + blocking: boolean + path?: string + detailsUrl?: string + externalId?: string + rerequestable?: boolean + teamId?: string +} + +export interface VercelCheckResponse extends ToolResponse { + output: { + id: string + name: string + status: string + conclusion: string | null + blocking: boolean + deploymentId: string + integrationId: string | null + externalId: string | null + detailsUrl: string | null + path: string | null + rerequestable: boolean + createdAt: number + updatedAt: number + startedAt: number | null + completedAt: number | null + } +} + +export interface VercelGetCheckParams { + apiKey: string + deploymentId: string + checkId: string + teamId?: string +} + +export interface VercelListChecksParams { + apiKey: string + deploymentId: string + teamId?: string +} + +export interface VercelListChecksResponse extends ToolResponse { + output: { + checks: Array<{ + id: string + name: string + status: string + conclusion: string | null + blocking: boolean + deploymentId: string + integrationId: string | null + externalId: string | null + detailsUrl: string | null + path: string | null + rerequestable: boolean + createdAt: number + updatedAt: number + startedAt: number | null + completedAt: number | null + }> + count: number + } +} + +export interface VercelUpdateCheckParams { + apiKey: string + deploymentId: string + checkId: string + name?: string + status?: string + conclusion?: string + detailsUrl?: string + externalId?: string + path?: string + output?: string + teamId?: string +} + +export interface VercelRerequestCheckParams { + apiKey: string + deploymentId: string + checkId: string + teamId?: string +} + +export interface VercelRerequestCheckResponse extends ToolResponse { + output: { + rerequested: boolean + } +} diff --git a/apps/sim/tools/vercel/unpause_project.ts b/apps/sim/tools/vercel/unpause_project.ts new file mode 100644 index 000000000..d24b20200 --- /dev/null +++ b/apps/sim/tools/vercel/unpause_project.ts @@ -0,0 +1,65 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelUnpauseProjectParams, VercelUnpauseProjectResponse } from '@/tools/vercel/types' + +export const vercelUnpauseProjectTool: ToolConfig< + VercelUnpauseProjectParams, + VercelUnpauseProjectResponse +> = { + id: 'vercel_unpause_project', + name: 'Vercel Unpause Project', + description: 'Unpause a Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or name', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelUnpauseProjectParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/projects/${params.projectId.trim()}/unpause${qs ? `?${qs}` : ''}` + }, + method: 'POST', + headers: (params: VercelUnpauseProjectParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id, + name: data.name, + paused: data.paused ?? false, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Project ID' }, + name: { type: 'string', description: 'Project name' }, + paused: { type: 'boolean', description: 'Whether the project is paused' }, + }, +} diff --git a/apps/sim/tools/vercel/update_check.ts b/apps/sim/tools/vercel/update_check.ts new file mode 100644 index 000000000..efa047dc3 --- /dev/null +++ b/apps/sim/tools/vercel/update_check.ts @@ -0,0 +1,159 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelCheckResponse, VercelUpdateCheckParams } from '@/tools/vercel/types' + +export const vercelUpdateCheckTool: ToolConfig = { + id: 'vercel_update_check', + name: 'Vercel Update Check', + description: 'Update an existing deployment check', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + deploymentId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Deployment ID the check belongs to', + }, + checkId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Check ID to update', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated name of the check', + }, + status: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Updated status: running or completed', + }, + conclusion: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Check conclusion: canceled, failed, neutral, succeeded, or skipped', + }, + detailsUrl: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'URL with details about the check', + }, + externalId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'External identifier for the check', + }, + path: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page path being checked', + }, + output: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'JSON string with check output metrics', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelUpdateCheckParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/deployments/${params.deploymentId.trim()}/checks/${params.checkId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'PATCH', + headers: (params: VercelUpdateCheckParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelUpdateCheckParams) => { + const body: Record = {} + if (params.name) body.name = params.name.trim() + if (params.status) body.status = params.status + if (params.conclusion) body.conclusion = params.conclusion + if (params.detailsUrl) body.detailsUrl = params.detailsUrl + if (params.externalId) body.externalId = params.externalId + if (params.path) body.path = params.path + if (params.output) { + try { + body.output = JSON.parse(params.output) + } catch { + body.output = params.output + } + } + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id, + name: data.name, + status: data.status ?? 'registered', + conclusion: data.conclusion ?? null, + blocking: data.blocking ?? false, + deploymentId: data.deploymentId, + integrationId: data.integrationId ?? null, + externalId: data.externalId ?? null, + detailsUrl: data.detailsUrl ?? null, + path: data.path ?? null, + rerequestable: data.rerequestable ?? false, + createdAt: data.createdAt, + updatedAt: data.updatedAt, + startedAt: data.startedAt ?? null, + completedAt: data.completedAt ?? null, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Check ID' }, + name: { type: 'string', description: 'Check name' }, + status: { type: 'string', description: 'Check status: registered, running, or completed' }, + conclusion: { + type: 'string', + description: 'Check conclusion: canceled, failed, neutral, succeeded, skipped, or stale', + optional: true, + }, + blocking: { type: 'boolean', description: 'Whether the check blocks the deployment' }, + deploymentId: { type: 'string', description: 'Associated deployment ID' }, + integrationId: { type: 'string', description: 'Associated integration ID', optional: true }, + externalId: { type: 'string', description: 'External identifier', optional: true }, + detailsUrl: { type: 'string', description: 'URL with details about the check', optional: true }, + path: { type: 'string', description: 'Page path being checked', optional: true }, + rerequestable: { type: 'boolean', description: 'Whether the check can be rerequested' }, + createdAt: { type: 'number', description: 'Creation timestamp in milliseconds' }, + updatedAt: { type: 'number', description: 'Last update timestamp in milliseconds' }, + startedAt: { type: 'number', description: 'Start timestamp in milliseconds', optional: true }, + completedAt: { + type: 'number', + description: 'Completion timestamp in milliseconds', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/vercel/update_edge_config_items.ts b/apps/sim/tools/vercel/update_edge_config_items.ts new file mode 100644 index 000000000..998a7cfbd --- /dev/null +++ b/apps/sim/tools/vercel/update_edge_config_items.ts @@ -0,0 +1,77 @@ +import type { ToolConfig } from '@/tools/types' +import type { + VercelUpdateEdgeConfigItemsParams, + VercelUpdateEdgeConfigItemsResponse, +} from '@/tools/vercel/types' + +export const vercelUpdateEdgeConfigItemsTool: ToolConfig< + VercelUpdateEdgeConfigItemsParams, + VercelUpdateEdgeConfigItemsResponse +> = { + id: 'vercel_update_edge_config_items', + name: 'Vercel Update Edge Config Items', + description: 'Create, update, upsert, or delete items in an Edge Config store', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + edgeConfigId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Edge Config ID to update items in', + }, + items: { + type: 'json', + required: true, + visibility: 'user-or-llm', + description: + 'Array of operations: [{operation: "create"|"update"|"upsert"|"delete", key: string, value?: any}]', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelUpdateEdgeConfigItemsParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v1/edge-config/${params.edgeConfigId.trim()}/items${qs ? `?${qs}` : ''}` + }, + method: 'PATCH', + headers: (params: VercelUpdateEdgeConfigItemsParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelUpdateEdgeConfigItemsParams) => { + const parsedItems = typeof params.items === 'string' ? JSON.parse(params.items) : params.items + return { items: parsedItems } + }, + }, + + transformResponse: async () => { + return { + success: true, + output: { + status: 'ok', + }, + } + }, + + outputs: { + status: { + type: 'string', + description: 'Operation status', + }, + }, +} diff --git a/apps/sim/tools/vercel/update_env_var.ts b/apps/sim/tools/vercel/update_env_var.ts new file mode 100644 index 000000000..fecc82c1c --- /dev/null +++ b/apps/sim/tools/vercel/update_env_var.ts @@ -0,0 +1,149 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelUpdateEnvVarParams, VercelUpdateEnvVarResponse } from '@/tools/vercel/types' + +export const vercelUpdateEnvVarTool: ToolConfig< + VercelUpdateEnvVarParams, + VercelUpdateEnvVarResponse +> = { + id: 'vercel_update_env_var', + name: 'Vercel Update Environment Variable', + description: 'Update an environment variable for a Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or name', + }, + envId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Environment variable ID to update', + }, + key: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New variable name', + }, + value: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New variable value', + }, + target: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of target environments (production, preview, development)', + }, + type: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Variable type: system, secret, encrypted, plain, or sensitive', + }, + gitBranch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Git branch to associate with the variable (requires target to include preview)', + }, + comment: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comment to add context to the variable (max 500 characters)', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelUpdateEnvVarParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v9/projects/${params.projectId.trim()}/env/${params.envId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'PATCH', + headers: (params: VercelUpdateEnvVarParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelUpdateEnvVarParams) => { + const body: Record = {} + if (params.key) body.key = params.key + if (params.value) body.value = params.value + if (params.target) body.target = params.target.split(',').map((t) => t.trim()) + if (params.type) body.type = params.type + if (params.gitBranch) body.gitBranch = params.gitBranch + if (params.comment) body.comment = params.comment + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id, + key: data.key, + value: data.value ?? '', + type: data.type ?? 'plain', + target: data.target ?? [], + gitBranch: data.gitBranch ?? null, + comment: data.comment ?? null, + }, + } + }, + + outputs: { + id: { + type: 'string', + description: 'Environment variable ID', + }, + key: { + type: 'string', + description: 'Variable name', + }, + value: { + type: 'string', + description: 'Variable value', + }, + type: { + type: 'string', + description: 'Variable type (secret, system, encrypted, plain, sensitive)', + }, + target: { + type: 'array', + description: 'Target environments', + items: { type: 'string', description: 'Environment name' }, + }, + gitBranch: { + type: 'string', + description: 'Git branch filter', + optional: true, + }, + comment: { + type: 'string', + description: 'Comment providing context for the variable', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/vercel/update_project.ts b/apps/sim/tools/vercel/update_project.ts new file mode 100644 index 000000000..ee76b1c48 --- /dev/null +++ b/apps/sim/tools/vercel/update_project.ts @@ -0,0 +1,106 @@ +import type { ToolConfig } from '@/tools/types' +import type { VercelUpdateProjectParams, VercelUpdateProjectResponse } from '@/tools/vercel/types' + +export const vercelUpdateProjectTool: ToolConfig< + VercelUpdateProjectParams, + VercelUpdateProjectResponse +> = { + id: 'vercel_update_project', + name: 'Vercel Update Project', + description: 'Update an existing Vercel project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Vercel Access Token', + }, + projectId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Project ID or name', + }, + name: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New project name', + }, + framework: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Project framework (e.g. nextjs, remix, vite)', + }, + buildCommand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Custom build command', + }, + outputDirectory: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Custom output directory', + }, + installCommand: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Custom install command', + }, + teamId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Team ID to scope the request', + }, + }, + + request: { + url: (params: VercelUpdateProjectParams) => { + const query = new URLSearchParams() + if (params.teamId) query.set('teamId', params.teamId.trim()) + const qs = query.toString() + return `https://api.vercel.com/v9/projects/${params.projectId.trim()}${qs ? `?${qs}` : ''}` + }, + method: 'PATCH', + headers: (params: VercelUpdateProjectParams) => ({ + Authorization: `Bearer ${params.apiKey}`, + 'Content-Type': 'application/json', + }), + body: (params: VercelUpdateProjectParams) => { + const body: Record = {} + if (params.name) body.name = params.name.trim() + if (params.framework) body.framework = params.framework.trim() + if (params.buildCommand) body.buildCommand = params.buildCommand.trim() + if (params.outputDirectory) body.outputDirectory = params.outputDirectory.trim() + if (params.installCommand) body.installCommand = params.installCommand.trim() + return body + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + return { + success: true, + output: { + id: data.id, + name: data.name, + framework: data.framework ?? null, + updatedAt: data.updatedAt, + }, + } + }, + + outputs: { + id: { type: 'string', description: 'Project ID' }, + name: { type: 'string', description: 'Project name' }, + framework: { type: 'string', description: 'Project framework', optional: true }, + updatedAt: { type: 'number', description: 'Last updated timestamp' }, + }, +} From ab48787422178e3b89eec1c2a0df74c0103c51c3 Mon Sep 17 00:00:00 2001 From: Waleed Date: Wed, 18 Feb 2026 16:25:28 -0800 Subject: [PATCH 4/4] chore(deps): upgrade next.js from 16.1.0-canary.21 to 16.1.6 (#3254) --- apps/docs/package.json | 2 +- apps/sim/package.json | 6 +++--- bun.lock | 30 +++++++++++++++--------------- package.json | 6 +++--- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/apps/docs/package.json b/apps/docs/package.json index 38f1be77f..756026153 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -21,7 +21,7 @@ "fumadocs-mdx": "14.1.0", "fumadocs-ui": "16.2.3", "lucide-react": "^0.511.0", - "next": "16.1.0-canary.21", + "next": "16.1.6", "next-themes": "^0.4.6", "postgres": "^3.4.5", "react": "19.2.1", diff --git a/apps/sim/package.json b/apps/sim/package.json index 73a0b0b06..9068e49c3 100644 --- a/apps/sim/package.json +++ b/apps/sim/package.json @@ -125,7 +125,7 @@ "mysql2": "3.14.3", "nanoid": "^3.3.7", "neo4j-driver": "6.0.1", - "next": "16.1.0-canary.21", + "next": "16.1.6", "next-mdx-remote": "^5.0.0", "next-runtime-env": "3.3.0", "next-themes": "^0.4.6", @@ -208,8 +208,8 @@ "sharp" ], "overrides": { - "next": "16.1.0-canary.21", - "@next/env": "16.1.0-canary.21", + "next": "16.1.6", + "@next/env": "16.1.6", "drizzle-orm": "^0.44.5", "postgres": "^3.4.5" } diff --git a/bun.lock b/bun.lock index bae82ffca..d0b2fce86 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "simstudio", "devDependencies": { "@biomejs/biome": "2.0.0-beta.5", - "@next/env": "16.1.0-canary.21", + "@next/env": "16.1.6", "@octokit/rest": "^21.0.0", "@tailwindcss/typography": "0.5.19", "drizzle-kit": "^0.31.4", @@ -30,7 +30,7 @@ "fumadocs-mdx": "14.1.0", "fumadocs-ui": "16.2.3", "lucide-react": "^0.511.0", - "next": "16.1.0-canary.21", + "next": "16.1.6", "next-themes": "^0.4.6", "postgres": "^3.4.5", "react": "19.2.1", @@ -156,7 +156,7 @@ "mysql2": "3.14.3", "nanoid": "^3.3.7", "neo4j-driver": "6.0.1", - "next": "16.1.0-canary.21", + "next": "16.1.6", "next-mdx-remote": "^5.0.0", "next-runtime-env": "3.3.0", "next-themes": "^0.4.6", @@ -319,9 +319,9 @@ "sharp", ], "overrides": { - "@next/env": "16.1.0-canary.21", + "@next/env": "16.1.6", "drizzle-orm": "^0.44.5", - "next": "16.1.0-canary.21", + "next": "16.1.6", "postgres": "^3.4.5", "react": "19.2.1", "react-dom": "19.2.1", @@ -829,23 +829,23 @@ "@napi-rs/canvas-win32-x64-msvc": ["@napi-rs/canvas-win32-x64-msvc@0.1.91", "", { "os": "win32", "cpu": "x64" }, "sha512-++gtW9EV/neKI8TshD8WFxzBYALSPag2kFRahIJV+LYsyt5kBn21b1dBhEUDHf7O+wiZmuFCeUa7QKGHnYRZBA=="], - "@next/env": ["@next/env@16.1.0-canary.21", "", {}, "sha512-J5inWwxC8EpAr/a2GApmQK1KkftG7K2nM6SuzNvciNaPt9Z0AHFeazvFuQxbvXn024p+akBHRlo8P7ZJRoU7kA=="], + "@next/env": ["@next/env@16.1.6", "", {}, "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.0-canary.21", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4kXAH8QQ01Mx0kCZQIcWdur048egbaK1KUj6HwGPfZg/H992jLTA0kWNxTgnnCNTw2ktea6N5QVhCsHeg538LQ=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.0-canary.21", "", { "os": "darwin", "cpu": "x64" }, "sha512-4FO08KUjRohb+rgLmiCJGBy2jxJp4tG7JCC1AuABBtPOZNwhbenVgJCK1PI8wT2yswbjC8BQn+JePnRBEkJWKg=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.0-canary.21", "", { "os": "linux", "cpu": "arm64" }, "sha512-C7xkG5HFufr+DN7KNpQUrZmjDXfuYA4ejFqx9VQAJ2w1ruvcy1z4V0ysCIZjLUcXnGJeQa07qt/SnMoKyBkujw=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.0-canary.21", "", { "os": "linux", "cpu": "arm64" }, "sha512-k6ZRbyISC20J72uKom8qLnoBKhFb7aVsZPmD6bxVGtx4CzGGY5K5ytiT79Gok+qzH/9pZscjYuN212BVhCLaPQ=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.0-canary.21", "", { "os": "linux", "cpu": "x64" }, "sha512-OEM12KOZ025SObzrtpXSbOw5aS+D2V+lVE/Al4wxChvyO4/SvaXx5gs/ckAddDB6tQj3bpIKS7ehvfTSto2HFg=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.0-canary.21", "", { "os": "linux", "cpu": "x64" }, "sha512-Cu5dzIKzopbUxiTuaaKlWcyarpdBTlgVSqoGDMgmQjJPw2k1z1Elo/UjPeOJlGlzZgvZjdYCjY3Pvvvepa98Rg=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.0-canary.21", "", { "os": "win32", "cpu": "arm64" }, "sha512-l+p3OOlGqY4hR0h4mHcMzRPnThvYDWQUJ4s9pGM5qF/ft6JLr7lWeI32sUnmS8AWHQGmLjeAylb9ismQlMHSvg=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.1.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.0-canary.21", "", { "os": "win32", "cpu": "x64" }, "sha512-JfRb34d8Q6i9Jy38ak2VXe1nl3O43kFyF+J2zsRu9P3HVtBVbUZF/4/PIaBChMeMZb+vYBDSqDTyJoVRK4/wzA=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A=="], "@noble/ciphers": ["@noble/ciphers@2.1.1", "", {}, "sha512-bysYuiVfhxNJuldNXlFEitTVdNnYUc+XNJZd7Qm2a5j1vZHgY+fazadNFWFaMK/2vye0JVlxV3gHmC0WDfAOQw=="], @@ -2791,7 +2791,7 @@ "netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="], - "next": ["next@16.1.0-canary.21", "", { "dependencies": { "@next/env": "16.1.0-canary.21", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.0-canary.21", "@next/swc-darwin-x64": "16.1.0-canary.21", "@next/swc-linux-arm64-gnu": "16.1.0-canary.21", "@next/swc-linux-arm64-musl": "16.1.0-canary.21", "@next/swc-linux-x64-gnu": "16.1.0-canary.21", "@next/swc-linux-x64-musl": "16.1.0-canary.21", "@next/swc-win32-arm64-msvc": "16.1.0-canary.21", "@next/swc-win32-x64-msvc": "16.1.0-canary.21", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-5DV7GwHGNgnTuC1SFIkDZ1WV2jEwMHE6t5LqIUmIl5OguQ1SMlanLuYqdgU8hVGk9IR6hcB8L4MJY7veUE6nww=="], + "next": ["next@16.1.6", "", { "dependencies": { "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.1.6", "@next/swc-darwin-x64": "16.1.6", "@next/swc-linux-arm64-gnu": "16.1.6", "@next/swc-linux-arm64-musl": "16.1.6", "@next/swc-linux-x64-gnu": "16.1.6", "@next/swc-linux-x64-musl": "16.1.6", "@next/swc-win32-arm64-msvc": "16.1.6", "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw=="], "next-mdx-remote": ["next-mdx-remote@5.0.0", "", { "dependencies": { "@babel/code-frame": "^7.23.5", "@mdx-js/mdx": "^3.0.1", "@mdx-js/react": "^3.0.1", "unist-util-remove": "^3.1.0", "vfile": "^6.0.1", "vfile-matter": "^5.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-RNNbqRpK9/dcIFZs/esQhuLA8jANqlH694yqoDBK8hkVdJUndzzGmnPHa2nyi90N4Z9VmzuSWNRpr5ItT3M7xQ=="], diff --git a/package.json b/package.json index 0d4b6d78a..55e9fe8fd 100644 --- a/package.json +++ b/package.json @@ -28,14 +28,14 @@ "overrides": { "react": "19.2.1", "react-dom": "19.2.1", - "next": "16.1.0-canary.21", - "@next/env": "16.1.0-canary.21", + "next": "16.1.6", + "@next/env": "16.1.6", "drizzle-orm": "^0.44.5", "postgres": "^3.4.5" }, "devDependencies": { "@biomejs/biome": "2.0.0-beta.5", - "@next/env": "16.1.0-canary.21", + "@next/env": "16.1.6", "@octokit/rest": "^21.0.0", "@tailwindcss/typography": "0.5.19", "drizzle-kit": "^0.31.4",