mirror of
https://github.com/simstudioai/sim.git
synced 2026-02-19 02:34:37 -05:00
v0.5.94: vercel integration, folder insertion, migrated tracking redirects to rewrites
This commit is contained in:
@@ -5532,3 +5532,18 @@ export function OnePasswordIcon(props: SVGProps<SVGSVGElement>) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function VercelIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox='0 0 256 222'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
preserveAspectRatio='xMidYMid'
|
||||
>
|
||||
<g transform='translate(19.2 16.63) scale(0.85)'>
|
||||
<polygon fill='#fafafa' points='128 0 256 221.705007 0 221.705007' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -125,6 +125,7 @@ import {
|
||||
TTSIcon,
|
||||
TwilioIcon,
|
||||
TypeformIcon,
|
||||
VercelIcon,
|
||||
VideoIcon,
|
||||
WealthboxIcon,
|
||||
WebflowIcon,
|
||||
@@ -262,6 +263,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
|
||||
twilio_sms: TwilioIcon,
|
||||
twilio_voice: TwilioIcon,
|
||||
typeform: TypeformIcon,
|
||||
vercel: VercelIcon,
|
||||
video_generator_v2: VideoIcon,
|
||||
vision_v2: EyeIcon,
|
||||
wealthbox: WealthboxIcon,
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
"twilio_sms",
|
||||
"twilio_voice",
|
||||
"typeform",
|
||||
"vercel",
|
||||
"video_generator",
|
||||
"vision",
|
||||
"wealthbox",
|
||||
|
||||
1391
apps/docs/content/docs/en/tools/vercel.mdx
Normal file
1391
apps/docs/content/docs/en/tools/vercel.mdx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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<string, string>([[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<string, string>
|
||||
): Promise<void> {
|
||||
// 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({
|
||||
|
||||
@@ -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<Array<{ [key: string]: unknown }>>
|
||||
insertResult?: Array<{ id: string; [key: string]: unknown }>
|
||||
onInsertValues?: (values: CapturedFolderValues) => void
|
||||
}) {
|
||||
const { selectData = [], insertResult = [] } = mockData
|
||||
return vi.fn().mockImplementation(async (callback: (tx: unknown) => Promise<unknown>) => {
|
||||
const { selectResults = [[], []], insertResult = [], onInsertValues } = mockData
|
||||
return async (callback: (tx: unknown) => Promise<unknown>) => {
|
||||
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',
|
||||
|
||||
@@ -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
|
||||
|
||||
137
apps/sim/app/api/workflows/route.test.ts
Normal file
137
apps/sim/app/api/workflows/route.test.ts
Normal file
@@ -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<Array<{ minOrder: number }>> = [
|
||||
[{ minOrder: 5 }],
|
||||
[{ minOrder: 2 }],
|
||||
]
|
||||
|
||||
mockDbSelect.mockImplementation(() => ({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockImplementation(() => Promise.resolve(minResultsQueue.shift() ?? [])),
|
||||
}),
|
||||
}))
|
||||
|
||||
let insertedValues: Record<string, unknown> | null = null
|
||||
mockDbInsert.mockReturnValue({
|
||||
values: vi.fn().mockImplementation((values: Record<string, unknown>) => {
|
||||
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<Array<{ minOrder: number }>> = [[], []]
|
||||
|
||||
mockDbSelect.mockImplementation(() => ({
|
||||
from: vi.fn().mockReturnValue({
|
||||
where: vi.fn().mockImplementation(() => Promise.resolve(minResultsQueue.shift() ?? [])),
|
||||
}),
|
||||
}))
|
||||
|
||||
let insertedValues: Record<string, unknown> | null = null
|
||||
mockDbInsert.mockReturnValue({
|
||||
values: vi.fn().mockImplementation((values: Record<string, unknown>) => {
|
||||
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)
|
||||
})
|
||||
})
|
||||
@@ -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({
|
||||
|
||||
1007
apps/sim/blocks/blocks/vercel.ts
Normal file
1007
apps/sim/blocks/blocks/vercel.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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<string, BlockConfig> = {
|
||||
twilio_sms: TwilioSMSBlock,
|
||||
twilio_voice: TwilioVoiceBlock,
|
||||
typeform: TypeformBlock,
|
||||
vercel: VercelBlock,
|
||||
variables: VariablesBlock,
|
||||
video_generator: VideoGeneratorBlock,
|
||||
video_generator_v2: VideoGeneratorV2Block,
|
||||
|
||||
@@ -5532,3 +5532,18 @@ export function OnePasswordIcon(props: SVGProps<SVGSVGElement>) {
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function VercelIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
{...props}
|
||||
viewBox='0 0 256 222'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
preserveAspectRatio='xMidYMid'
|
||||
>
|
||||
<g transform='translate(19.2 16.63) scale(0.85)'>
|
||||
<polygon fill='#fafafa' points='128 0 256 221.705007 0 221.705007' />
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
177
apps/sim/hooks/queries/folders.test.ts
Normal file
177
apps/sim/hooks/queries/folders.test.ts
Normal file
@@ -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<string, any>
|
||||
}
|
||||
|
||||
let workflowRegistryState: {
|
||||
workflows: Record<string, any>
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
@@ -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<TVariables extends { workspaceId: string }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the next sort order for a folder in a given parent
|
||||
*/
|
||||
function getNextSortOrder(
|
||||
folders: Record<string, WorkflowFolder>,
|
||||
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<CreateFolderVariables>(
|
||||
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(),
|
||||
}
|
||||
|
||||
44
apps/sim/hooks/queries/utils/top-insertion-sort-order.ts
Normal file
44
apps/sim/hooks/queries/utils/top-insertion-sort-order.ts
Normal file
@@ -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<string, SortableWorkflow>,
|
||||
folders: Record<string, SortableFolder>,
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
197
apps/sim/lib/workflows/persistence/duplicate.test.ts
Normal file
197
apps/sim/lib/workflows/persistence/duplicate.test.ts
Normal file
@@ -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<string, unknown>) => 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<string, unknown>) => {
|
||||
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<string, unknown> | 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<unknown>) =>
|
||||
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<string, unknown> | 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<unknown>) =>
|
||||
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)
|
||||
})
|
||||
})
|
||||
@@ -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<string, string>()
|
||||
|
||||
@@ -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
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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<string, ToolConfig> = {
|
||||
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,
|
||||
|
||||
84
apps/sim/tools/vercel/add_domain.ts
Normal file
84
apps/sim/tools/vercel/add_domain.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelAddDomainParams, VercelAddDomainResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelAddDomainTool: ToolConfig<VercelAddDomainParams, VercelAddDomainResponse> = {
|
||||
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' },
|
||||
},
|
||||
},
|
||||
}
|
||||
113
apps/sim/tools/vercel/add_project_domain.ts
Normal file
113
apps/sim/tools/vercel/add_project_domain.ts
Normal file
@@ -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<string, unknown> = { 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' },
|
||||
},
|
||||
}
|
||||
83
apps/sim/tools/vercel/cancel_deployment.ts
Normal file
83
apps/sim/tools/vercel/cancel_deployment.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
87
apps/sim/tools/vercel/create_alias.ts
Normal file
87
apps/sim/tools/vercel/create_alias.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelCreateAliasParams, VercelCreateAliasResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelCreateAliasTool: ToolConfig<VercelCreateAliasParams, VercelCreateAliasResponse> =
|
||||
{
|
||||
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',
|
||||
},
|
||||
},
|
||||
}
|
||||
141
apps/sim/tools/vercel/create_check.ts
Normal file
141
apps/sim/tools/vercel/create_check.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelCheckResponse, VercelCreateCheckParams } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelCreateCheckTool: ToolConfig<VercelCreateCheckParams, VercelCheckResponse> = {
|
||||
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<string, unknown> = {
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
136
apps/sim/tools/vercel/create_deployment.ts
Normal file
136
apps/sim/tools/vercel/create_deployment.ts
Normal file
@@ -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<string, any> = {
|
||||
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' },
|
||||
},
|
||||
}
|
||||
107
apps/sim/tools/vercel/create_dns_record.ts
Normal file
107
apps/sim/tools/vercel/create_dns_record.ts
Normal file
@@ -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<string, unknown> = {
|
||||
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' },
|
||||
},
|
||||
}
|
||||
106
apps/sim/tools/vercel/create_edge_config.ts
Normal file
106
apps/sim/tools/vercel/create_edge_config.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
145
apps/sim/tools/vercel/create_env_var.ts
Normal file
145
apps/sim/tools/vercel/create_env_var.ts
Normal file
@@ -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<string, unknown> = {
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
108
apps/sim/tools/vercel/create_project.ts
Normal file
108
apps/sim/tools/vercel/create_project.ts
Normal file
@@ -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<string, unknown> = { 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' },
|
||||
},
|
||||
}
|
||||
105
apps/sim/tools/vercel/create_webhook.ts
Normal file
105
apps/sim/tools/vercel/create_webhook.ts
Normal file
@@ -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<string, any> = {
|
||||
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' },
|
||||
},
|
||||
}
|
||||
62
apps/sim/tools/vercel/delete_alias.ts
Normal file
62
apps/sim/tools/vercel/delete_alias.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelDeleteAliasParams, VercelDeleteAliasResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelDeleteAliasTool: ToolConfig<VercelDeleteAliasParams, VercelDeleteAliasResponse> =
|
||||
{
|
||||
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)',
|
||||
},
|
||||
},
|
||||
}
|
||||
77
apps/sim/tools/vercel/delete_deployment.ts
Normal file
77
apps/sim/tools/vercel/delete_deployment.ts
Normal file
@@ -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)',
|
||||
},
|
||||
},
|
||||
}
|
||||
69
apps/sim/tools/vercel/delete_dns_record.ts
Normal file
69
apps/sim/tools/vercel/delete_dns_record.ts
Normal file
@@ -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' },
|
||||
},
|
||||
}
|
||||
64
apps/sim/tools/vercel/delete_domain.ts
Normal file
64
apps/sim/tools/vercel/delete_domain.ts
Normal file
@@ -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' },
|
||||
},
|
||||
}
|
||||
69
apps/sim/tools/vercel/delete_env_var.ts
Normal file
69
apps/sim/tools/vercel/delete_env_var.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
60
apps/sim/tools/vercel/delete_project.ts
Normal file
60
apps/sim/tools/vercel/delete_project.ts
Normal file
@@ -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' },
|
||||
},
|
||||
}
|
||||
63
apps/sim/tools/vercel/delete_webhook.ts
Normal file
63
apps/sim/tools/vercel/delete_webhook.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
97
apps/sim/tools/vercel/get_alias.ts
Normal file
97
apps/sim/tools/vercel/get_alias.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelGetAliasParams, VercelGetAliasResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelGetAliasTool: ToolConfig<VercelGetAliasParams, VercelGetAliasResponse> = {
|
||||
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)',
|
||||
},
|
||||
},
|
||||
}
|
||||
99
apps/sim/tools/vercel/get_check.ts
Normal file
99
apps/sim/tools/vercel/get_check.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelCheckResponse, VercelGetCheckParams } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelGetCheckTool: ToolConfig<VercelGetCheckParams, VercelCheckResponse> = {
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
176
apps/sim/tools/vercel/get_deployment.ts
Normal file
176
apps/sim/tools/vercel/get_deployment.ts
Normal file
@@ -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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
135
apps/sim/tools/vercel/get_deployment_events.ts
Normal file
135
apps/sim/tools/vercel/get_deployment_events.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
94
apps/sim/tools/vercel/get_domain.ts
Normal file
94
apps/sim/tools/vercel/get_domain.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelGetDomainParams, VercelGetDomainResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelGetDomainTool: ToolConfig<VercelGetDomainParams, VercelGetDomainResponse> = {
|
||||
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' },
|
||||
},
|
||||
}
|
||||
107
apps/sim/tools/vercel/get_domain_config.ts
Normal file
107
apps/sim/tools/vercel/get_domain_config.ts
Normal file
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
100
apps/sim/tools/vercel/get_edge_config.ts
Normal file
100
apps/sim/tools/vercel/get_edge_config.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
93
apps/sim/tools/vercel/get_edge_config_items.ts
Normal file
93
apps/sim/tools/vercel/get_edge_config_items.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
99
apps/sim/tools/vercel/get_env_vars.ts
Normal file
99
apps/sim/tools/vercel/get_env_vars.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelGetEnvVarsParams, VercelGetEnvVarsResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelGetEnvVarsTool: ToolConfig<VercelGetEnvVarsParams, VercelGetEnvVarsResponse> = {
|
||||
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',
|
||||
},
|
||||
},
|
||||
}
|
||||
89
apps/sim/tools/vercel/get_project.ts
Normal file
89
apps/sim/tools/vercel/get_project.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelGetProjectParams, VercelGetProjectResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelGetProjectTool: ToolConfig<VercelGetProjectParams, VercelGetProjectResponse> = {
|
||||
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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
98
apps/sim/tools/vercel/get_team.ts
Normal file
98
apps/sim/tools/vercel/get_team.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelGetTeamParams, VercelGetTeamResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelGetTeamTool: ToolConfig<VercelGetTeamParams, VercelGetTeamResponse> = {
|
||||
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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
73
apps/sim/tools/vercel/get_user.ts
Normal file
73
apps/sim/tools/vercel/get_user.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelGetUserParams, VercelGetUserResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelGetUserTool: ToolConfig<VercelGetUserParams, VercelGetUserResponse> = {
|
||||
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' },
|
||||
},
|
||||
}
|
||||
126
apps/sim/tools/vercel/index.ts
Normal file
126
apps/sim/tools/vercel/index.ts
Normal file
@@ -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'
|
||||
107
apps/sim/tools/vercel/list_aliases.ts
Normal file
107
apps/sim/tools/vercel/list_aliases.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelListAliasesParams, VercelListAliasesResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelListAliasesTool: ToolConfig<VercelListAliasesParams, VercelListAliasesResponse> =
|
||||
{
|
||||
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',
|
||||
},
|
||||
},
|
||||
}
|
||||
99
apps/sim/tools/vercel/list_checks.ts
Normal file
99
apps/sim/tools/vercel/list_checks.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelListChecksParams, VercelListChecksResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelListChecksTool: ToolConfig<VercelListChecksParams, VercelListChecksResponse> = {
|
||||
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<string, unknown>) => ({
|
||||
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' },
|
||||
},
|
||||
}
|
||||
114
apps/sim/tools/vercel/list_deployment_files.ts
Normal file
114
apps/sim/tools/vercel/list_deployment_files.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
170
apps/sim/tools/vercel/list_deployments.ts
Normal file
170
apps/sim/tools/vercel/list_deployments.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
116
apps/sim/tools/vercel/list_dns_records.ts
Normal file
116
apps/sim/tools/vercel/list_dns_records.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
109
apps/sim/tools/vercel/list_domains.ts
Normal file
109
apps/sim/tools/vercel/list_domains.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelListDomainsParams, VercelListDomainsResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelListDomainsTool: ToolConfig<VercelListDomainsParams, VercelListDomainsResponse> =
|
||||
{
|
||||
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',
|
||||
},
|
||||
},
|
||||
}
|
||||
91
apps/sim/tools/vercel/list_edge_configs.ts
Normal file
91
apps/sim/tools/vercel/list_edge_configs.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
116
apps/sim/tools/vercel/list_project_domains.ts
Normal file
116
apps/sim/tools/vercel/list_project_domains.ts
Normal file
@@ -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' },
|
||||
},
|
||||
}
|
||||
106
apps/sim/tools/vercel/list_projects.ts
Normal file
106
apps/sim/tools/vercel/list_projects.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
151
apps/sim/tools/vercel/list_team_members.ts
Normal file
151
apps/sim/tools/vercel/list_team_members.ts
Normal file
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
132
apps/sim/tools/vercel/list_teams.ts
Normal file
132
apps/sim/tools/vercel/list_teams.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelListTeamsParams, VercelListTeamsResponse } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelListTeamsTool: ToolConfig<VercelListTeamsParams, VercelListTeamsResponse> = {
|
||||
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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
100
apps/sim/tools/vercel/list_webhooks.ts
Normal file
100
apps/sim/tools/vercel/list_webhooks.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
65
apps/sim/tools/vercel/pause_project.ts
Normal file
65
apps/sim/tools/vercel/pause_project.ts
Normal file
@@ -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' },
|
||||
},
|
||||
}
|
||||
69
apps/sim/tools/vercel/remove_project_domain.ts
Normal file
69
apps/sim/tools/vercel/remove_project_domain.ts
Normal file
@@ -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' },
|
||||
},
|
||||
}
|
||||
65
apps/sim/tools/vercel/rerequest_check.ts
Normal file
65
apps/sim/tools/vercel/rerequest_check.ts
Normal file
@@ -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' },
|
||||
},
|
||||
}
|
||||
1025
apps/sim/tools/vercel/types.ts
Normal file
1025
apps/sim/tools/vercel/types.ts
Normal file
File diff suppressed because it is too large
Load Diff
65
apps/sim/tools/vercel/unpause_project.ts
Normal file
65
apps/sim/tools/vercel/unpause_project.ts
Normal file
@@ -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' },
|
||||
},
|
||||
}
|
||||
159
apps/sim/tools/vercel/update_check.ts
Normal file
159
apps/sim/tools/vercel/update_check.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
import type { VercelCheckResponse, VercelUpdateCheckParams } from '@/tools/vercel/types'
|
||||
|
||||
export const vercelUpdateCheckTool: ToolConfig<VercelUpdateCheckParams, VercelCheckResponse> = {
|
||||
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<string, unknown> = {}
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
77
apps/sim/tools/vercel/update_edge_config_items.ts
Normal file
77
apps/sim/tools/vercel/update_edge_config_items.ts
Normal file
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
149
apps/sim/tools/vercel/update_env_var.ts
Normal file
149
apps/sim/tools/vercel/update_env_var.ts
Normal file
@@ -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<string, unknown> = {}
|
||||
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,
|
||||
},
|
||||
},
|
||||
}
|
||||
106
apps/sim/tools/vercel/update_project.ts
Normal file
106
apps/sim/tools/vercel/update_project.ts
Normal file
@@ -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<string, unknown> = {}
|
||||
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' },
|
||||
},
|
||||
}
|
||||
46
bun.lock
46
bun.lock
@@ -6,14 +6,14 @@
|
||||
"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",
|
||||
"glob": "13.0.0",
|
||||
"husky": "9.1.7",
|
||||
"lint-staged": "16.0.0",
|
||||
"turbo": "2.8.9",
|
||||
"turbo": "2.8.10",
|
||||
},
|
||||
},
|
||||
"apps/docs": {
|
||||
@@ -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=="],
|
||||
|
||||
@@ -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=="],
|
||||
|
||||
|
||||
@@ -28,21 +28,21 @@
|
||||
"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",
|
||||
"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}": [
|
||||
|
||||
Reference in New Issue
Block a user