Compare commits

...

1 Commits

Author SHA1 Message Date
waleed
c3b35676a0 feat(tools): added figma tools & block 2025-12-15 11:46:00 -08:00
17 changed files with 1248 additions and 1 deletions

View File

@@ -225,7 +225,6 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'activities:full': 'Full access to manage your Pipedrive activities',
'mail:read': 'Read your Pipedrive emails',
'mail:full': 'Full access to manage your Pipedrive emails',
'projects:read': 'Read your Pipedrive projects',
'projects:full': 'Full access to manage your Pipedrive projects',
'webhooks:read': 'Read your Pipedrive webhooks',
'webhooks:full': 'Full access to manage your Pipedrive webhooks',
@@ -280,6 +279,13 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'user-follow-modify': 'Follow and unfollow artists and users',
'user-read-playback-position': 'View your playback position in podcasts',
'ugc-image-upload': 'Upload images to your Spotify playlists',
// Figma scopes
'current_user:read': 'Read your name, email, and profile image',
'file_content:read': 'Read file contents and design data',
'file_metadata:read': 'Read file metadata like name and last modified',
'file_comments:read': 'Read comments on design files',
'file_comments:write': 'Add and manage comments on design files',
'library_content:read': 'Read published components and styles',
}
function getScopeDescription(scope: string): string {

View File

@@ -0,0 +1,200 @@
import { FigmaIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode } from '@/blocks/types'
import type { FigmaResponse } from '@/tools/figma/types'
export const FigmaBlock: BlockConfig<FigmaResponse> = {
type: 'figma',
name: 'Figma',
description: 'Access Figma designs and assets',
authMode: AuthMode.OAuth,
longDescription:
'Integrates Figma into the workflow. Get design files, export images, list and add comments, and access components and styles from your Figma workspace.',
docsLink: 'https://docs.sim.ai/tools/figma',
category: 'tools',
bgColor: '#1E1E1E',
icon: FigmaIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Get File', id: 'get_file' },
{ label: 'Get Nodes', id: 'get_nodes' },
{ label: 'Export Images', id: 'export_images' },
{ label: 'List Comments', id: 'list_comments' },
{ label: 'Add Comment', id: 'add_comment' },
{ label: 'Get Components', id: 'get_components' },
{ label: 'Get Styles', id: 'get_styles' },
],
value: () => 'get_file',
},
{
id: 'credential',
title: 'Figma Account',
type: 'oauth-input',
serviceId: 'figma',
requiredScopes: [
'current_user:read',
'file_content:read',
'file_metadata:read',
'file_comments:read',
'file_comments:write',
'library_content:read',
],
placeholder: 'Select Figma account',
required: true,
},
{
id: 'fileKey',
title: 'File Key',
type: 'short-input',
placeholder: 'File key from URL (figma.com/file/{key}/...)',
required: true,
},
{
id: 'nodeIds',
title: 'Node IDs',
type: 'short-input',
placeholder: 'Comma-separated node IDs',
condition: { field: 'operation', value: ['get_nodes', 'export_images'] },
required: true,
},
{
id: 'depth',
title: 'Depth',
type: 'short-input',
placeholder: 'Document tree depth (optional)',
condition: { field: 'operation', value: ['get_file', 'get_nodes'] },
},
{
id: 'format',
title: 'Format',
type: 'dropdown',
options: [
{ label: 'PNG', id: 'png' },
{ label: 'SVG', id: 'svg' },
{ label: 'PDF', id: 'pdf' },
{ label: 'JPG', id: 'jpg' },
],
value: () => 'png',
condition: { field: 'operation', value: 'export_images' },
},
{
id: 'scale',
title: 'Scale',
type: 'short-input',
placeholder: 'Scale factor 0.01-4 (default: 1)',
condition: { field: 'operation', value: 'export_images' },
},
{
id: 'message',
title: 'Comment Message',
type: 'long-input',
placeholder: 'Enter your comment message',
condition: { field: 'operation', value: 'add_comment' },
required: true,
},
{
id: 'nodeId',
title: 'Node ID (Optional)',
type: 'short-input',
placeholder: 'Attach comment to specific node',
condition: { field: 'operation', value: 'add_comment' },
},
],
tools: {
access: [
'figma_get_file',
'figma_get_nodes',
'figma_export_images',
'figma_list_comments',
'figma_add_comment',
'figma_get_components',
'figma_get_styles',
],
config: {
tool: (params) => {
switch (params.operation) {
case 'get_file':
return 'figma_get_file'
case 'get_nodes':
return 'figma_get_nodes'
case 'export_images':
return 'figma_export_images'
case 'list_comments':
return 'figma_list_comments'
case 'add_comment':
return 'figma_add_comment'
case 'get_components':
return 'figma_get_components'
case 'get_styles':
return 'figma_get_styles'
default:
throw new Error(`Invalid Figma operation: ${params.operation}`)
}
},
params: (params) => {
const { credential, operation, depth, scale, ...rest } = params
const baseParams: Record<string, unknown> = {
credential,
fileKey: rest.fileKey,
}
if (depth && (operation === 'get_file' || operation === 'get_nodes')) {
baseParams.depth = Number(depth)
}
switch (operation) {
case 'get_nodes':
return {
...baseParams,
nodeIds: rest.nodeIds,
}
case 'export_images':
return {
...baseParams,
nodeIds: rest.nodeIds,
format: rest.format || 'png',
scale: scale ? Number(scale) : undefined,
}
case 'add_comment':
return {
...baseParams,
message: rest.message,
nodeId: rest.nodeId || undefined,
}
default:
return baseParams
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'Figma OAuth credential' },
fileKey: { type: 'string', description: 'Figma file key from URL' },
nodeIds: { type: 'string', description: 'Comma-separated node IDs' },
depth: { type: 'number', description: 'Document tree depth' },
format: { type: 'string', description: 'Image export format' },
scale: { type: 'number', description: 'Image export scale' },
message: { type: 'string', description: 'Comment message' },
nodeId: { type: 'string', description: 'Node ID to attach comment to' },
},
outputs: {
name: { type: 'string', description: 'File name' },
lastModified: { type: 'string', description: 'Last modified timestamp' },
thumbnailUrl: { type: 'string', description: 'File thumbnail URL' },
version: { type: 'string', description: 'File version' },
document: { type: 'json', description: 'Document tree structure' },
components: { type: 'json', description: 'Components in the file' },
styles: { type: 'json', description: 'Styles in the file' },
nodes: { type: 'json', description: 'Requested nodes' },
files: { type: 'json', description: 'Exported image files' },
comments: { type: 'json', description: 'Comments on the file' },
comment: { type: 'json', description: 'Created comment' },
metadata: { type: 'json', description: 'Operation metadata' },
},
}

View File

@@ -24,6 +24,7 @@ import { ElasticsearchBlock } from '@/blocks/blocks/elasticsearch'
import { ElevenLabsBlock } from '@/blocks/blocks/elevenlabs'
import { EvaluatorBlock } from '@/blocks/blocks/evaluator'
import { ExaBlock } from '@/blocks/blocks/exa'
import { FigmaBlock } from '@/blocks/blocks/figma'
import { FileBlock } from '@/blocks/blocks/file'
import { FirecrawlBlock } from '@/blocks/blocks/firecrawl'
import { FunctionBlock } from '@/blocks/blocks/function'
@@ -166,6 +167,7 @@ export const registry: Record<string, BlockConfig> = {
evaluator: EvaluatorBlock,
exa: ExaBlock,
file: FileBlock,
figma: FigmaBlock,
firecrawl: FirecrawlBlock,
function: FunctionBlock,
generic_webhook: GenericWebhookBlock,

View File

@@ -4223,3 +4223,41 @@ export function SpotifyIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function FigmaIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
xmlns='http://www.w3.org/2000/svg'
shapeRendering='geometricPrecision'
textRendering='geometricPrecision'
imageRendering='optimizeQuality'
fillRule='evenodd'
clipRule='evenodd'
viewBox='0 0 346 512.36'
>
<g fillRule='nonzero'>
<path
fill='#00B6FF'
d='M172.53 246.9c0-42.04 34.09-76.11 76.12-76.11h11.01c.3.01.63-.01.94-.01 47.16 0 85.4 38.25 85.4 85.4 0 47.15-38.24 85.39-85.4 85.39-.31 0-.64-.01-.95-.01l-11 .01c-42.03 0-76.12-34.09-76.12-76.12V246.9z'
/>
<path
fill='#24CB71'
d='M0 426.98c0-47.16 38.24-85.41 85.4-85.41l87.13.01v84.52c0 47.65-39.06 86.26-86.71 86.26C38.67 512.36 0 474.13 0 426.98z'
/>
<path
fill='#FF7237'
d='M172.53.01v170.78h87.13c.3-.01.63.01.94.01 47.16 0 85.4-38.25 85.4-85.4C346 38.24 307.76 0 260.6 0c-.31 0-.64.01-.95.01h-87.12z'
/>
<path
fill='#FF3737'
d='M0 85.39c0 47.16 38.24 85.4 85.4 85.4h87.13V.01H85.39C38.24.01 0 38.24 0 85.39z'
/>
<path
fill='#874FFF'
d='M0 256.18c0 47.16 38.24 85.4 85.4 85.4h87.13V170.8H85.39C38.24 170.8 0 209.03 0 256.18z'
/>
</g>
</svg>
)
}

View File

@@ -229,6 +229,7 @@ export const auth = betterAuth({
'hubspot',
'linkedin',
'spotify',
'figma',
// Common SSO provider patterns
...SSO_TRUSTED_PROVIDERS,
@@ -1958,6 +1959,53 @@ export const auth = betterAuth({
}
},
},
// Figma provider
{
providerId: 'figma',
clientId: env.FIGMA_CLIENT_ID as string,
clientSecret: env.FIGMA_CLIENT_SECRET as string,
authorizationUrl: 'https://www.figma.com/oauth',
tokenUrl: 'https://api.figma.com/v1/oauth/token',
userInfoUrl: 'https://api.figma.com/v1/me',
scopes: [
'current_user:read',
'file_content:read',
'file_metadata:read',
'file_comments:read',
'file_comments:write',
'library_content:read',
],
responseType: 'code',
pkce: false,
accessType: 'offline',
prompt: 'consent',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/figma`,
getUserInfo: async (tokens) => {
const response = await fetch('https://api.figma.com/v1/me', {
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
},
})
if (!response.ok) {
logger.error('Failed to fetch Figma user info', {
status: response.status,
})
return null
}
const profile = await response.json()
return {
id: profile.id,
name: profile.handle || profile.email || 'Figma User',
email: profile.email,
emailVerified: !!profile.email,
image: profile.img_url,
}
},
},
],
}),
// Include SSO plugin when enabled

View File

@@ -233,6 +233,8 @@ export const env = createEnv({
WORDPRESS_CLIENT_SECRET: z.string().optional(), // WordPress.com OAuth client secret
SPOTIFY_CLIENT_ID: z.string().optional(), // Spotify OAuth client ID
SPOTIFY_CLIENT_SECRET: z.string().optional(), // Spotify OAuth client secret
FIGMA_CLIENT_ID: z.string().optional(), // Figma OAuth client ID
FIGMA_CLIENT_SECRET: z.string().optional(), // Figma OAuth client secret
// E2B Remote Code Execution
E2B_ENABLED: z.string().optional(), // Enable E2B remote code execution

View File

@@ -5,6 +5,7 @@ import {
ConfluenceIcon,
// DiscordIcon,
DropboxIcon,
FigmaIcon,
GithubIcon,
GmailIcon,
GoogleCalendarIcon,
@@ -72,6 +73,7 @@ export type OAuthProvider =
| 'zoom'
| 'wordpress'
| 'spotify'
| 'figma'
| string
export type OAuthService =
@@ -114,6 +116,7 @@ export type OAuthService =
| 'zoom'
| 'wordpress'
| 'spotify'
| 'figma'
export interface OAuthProviderConfig {
id: OAuthProvider
@@ -930,6 +933,30 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
},
defaultService: 'spotify',
},
figma: {
id: 'figma',
name: 'Figma',
icon: (props) => FigmaIcon(props),
services: {
figma: {
id: 'figma',
name: 'Figma',
description: 'Access Figma designs, components, styles, and assets.',
providerId: 'figma',
icon: (props) => FigmaIcon(props),
baseProviderIcon: (props) => FigmaIcon(props),
scopes: [
'current_user:read',
'file_content:read',
'file_metadata:read',
'file_comments:read',
'file_comments:write',
'library_content:read',
],
},
},
defaultService: 'figma',
},
}
/**
@@ -1522,6 +1549,19 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig {
supportsRefreshTokenRotation: false,
}
}
case 'figma': {
const { clientId, clientSecret } = getCredentials(
env.FIGMA_CLIENT_ID,
env.FIGMA_CLIENT_SECRET
)
return {
tokenEndpoint: 'https://api.figma.com/v1/oauth/token',
clientId,
clientSecret,
useBasicAuth: false,
supportsRefreshTokenRotation: false,
}
}
default:
throw new Error(`Unsupported provider: ${provider}`)
}

View File

@@ -0,0 +1,80 @@
import type { FigmaAddCommentParams, FigmaAddCommentResponse } from '@/tools/figma/types'
import type { ToolConfig } from '@/tools/types'
export const figmaAddCommentTool: ToolConfig<FigmaAddCommentParams, FigmaAddCommentResponse> = {
id: 'figma_add_comment',
name: 'Figma - Add Comment',
description: 'Add a comment to a Figma file, optionally on a specific node',
version: '1.0.0',
oauth: {
required: true,
provider: 'figma',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The key of the Figma file (from the URL: figma.com/file/{fileKey}/...)',
},
message: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The comment message text',
},
nodeId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional node ID to attach the comment to a specific element',
},
},
request: {
url: (params) => `https://api.figma.com/v1/files/${params.fileKey}/comments`,
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const body: Record<string, unknown> = {
message: params.message,
}
if (params.nodeId) {
body.client_meta = {
node_id: params.nodeId,
}
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
return {
success: true,
output: {
comment: data,
},
}
},
outputs: {
comment: {
type: 'json',
description: 'The created comment object',
},
},
}

View File

@@ -0,0 +1,158 @@
import { createLogger } from '@/lib/logs/console/logger'
import type {
FigmaExportedFile,
FigmaExportImagesParams,
FigmaExportImagesResponse,
} from '@/tools/figma/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('FigmaExportImagesTool')
const FORMAT_MIME_TYPES: Record<string, string> = {
png: 'image/png',
svg: 'image/svg+xml',
pdf: 'application/pdf',
jpg: 'image/jpeg',
}
export const figmaExportImagesTool: ToolConfig<FigmaExportImagesParams, FigmaExportImagesResponse> =
{
id: 'figma_export_images',
name: 'Figma - Export Images',
description: 'Export images from specific nodes in a Figma file as PNG, SVG, PDF, or JPG',
version: '1.0.0',
oauth: {
required: true,
provider: 'figma',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The key of the Figma file (from the URL: figma.com/file/{fileKey}/...)',
},
nodeIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated list of node IDs to export as images',
},
format: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Image format: png, svg, pdf, or jpg',
},
scale: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Scale factor for the image (0.01 to 4, default: 1)',
},
},
request: {
url: (params) => {
const baseUrl = `https://api.figma.com/v1/images/${params.fileKey}`
const queryParams = new URLSearchParams()
queryParams.append('ids', params.nodeIds)
queryParams.append('format', params.format)
if (params.scale) {
queryParams.append('scale', params.scale.toString())
}
return `${baseUrl}?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response, params) => {
const data = await response.json()
const images: Record<string, string> = data.images || {}
const format = params?.format || 'png'
const mimeType = FORMAT_MIME_TYPES[format] || 'image/png'
const files: FigmaExportedFile[] = []
for (const [nodeId, imageUrl] of Object.entries(images)) {
if (!imageUrl) {
logger.warn('No image URL for node', { nodeId })
continue
}
try {
logger.info('Downloading image', { nodeId, format })
const imageResponse = await fetch(imageUrl)
if (!imageResponse.ok) {
logger.error('Failed to download image', {
nodeId,
status: imageResponse.status,
statusText: imageResponse.statusText,
})
continue
}
const arrayBuffer = await imageResponse.arrayBuffer()
const buffer = Buffer.from(arrayBuffer)
const sanitizedNodeId = nodeId.replace(/[^a-zA-Z0-9-_]/g, '_')
const fileName = `figma_${sanitizedNodeId}.${format}`
files.push({
name: fileName,
mimeType,
data: buffer,
size: buffer.length,
nodeId,
})
logger.info('Image downloaded successfully', {
nodeId,
fileName,
size: buffer.length,
})
} catch (error) {
logger.error('Error downloading image', { nodeId, error })
}
}
return {
success: true,
output: {
files,
metadata: {
format,
scale: params?.scale || 1,
nodeCount: files.length,
},
},
}
},
outputs: {
files: {
type: 'file[]',
description: 'Exported image files stored in execution files',
},
metadata: {
type: 'json',
description: 'Export metadata including format, scale, and node count',
},
},
}

View File

@@ -0,0 +1,66 @@
import type { FigmaGetComponentsParams, FigmaGetComponentsResponse } from '@/tools/figma/types'
import type { ToolConfig } from '@/tools/types'
export const figmaGetComponentsTool: ToolConfig<
FigmaGetComponentsParams,
FigmaGetComponentsResponse
> = {
id: 'figma_get_components',
name: 'Figma - Get Components',
description: 'Get all published components from a Figma file',
version: '1.0.0',
oauth: {
required: true,
provider: 'figma',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The key of the Figma file (from the URL: figma.com/file/{fileKey}/...)',
},
},
request: {
url: (params) => `https://api.figma.com/v1/files/${params.fileKey}/components`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response) => {
const data = await response.json()
const components = data.meta?.components || []
return {
success: true,
output: {
components,
metadata: {
componentCount: components.length,
},
},
}
},
outputs: {
components: {
type: 'json',
description: 'Array of components in the file',
},
metadata: {
type: 'json',
description: 'Metadata including component count',
},
},
}

View File

@@ -0,0 +1,101 @@
import type { FigmaGetFileParams, FigmaGetFileResponse } from '@/tools/figma/types'
import type { ToolConfig } from '@/tools/types'
export const figmaGetFileTool: ToolConfig<FigmaGetFileParams, FigmaGetFileResponse> = {
id: 'figma_get_file',
name: 'Figma - Get File',
description:
'Get the full document structure of a Figma file including all nodes, components, and styles',
version: '1.0.0',
oauth: {
required: true,
provider: 'figma',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The key of the Figma file (from the URL: figma.com/file/{fileKey}/...)',
},
depth: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Depth of the document tree to return (optional)',
},
},
request: {
url: (params) => {
const baseUrl = `https://api.figma.com/v1/files/${params.fileKey}`
const queryParams = new URLSearchParams()
if (params.depth) {
queryParams.append('depth', params.depth.toString())
}
const query = queryParams.toString()
return query ? `${baseUrl}?${query}` : baseUrl
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response) => {
const data = await response.json()
return {
success: true,
output: {
name: data.name,
lastModified: data.lastModified,
thumbnailUrl: data.thumbnailUrl,
version: data.version,
document: data.document,
components: data.components || {},
styles: data.styles || {},
},
}
},
outputs: {
name: {
type: 'string',
description: 'Name of the Figma file',
},
lastModified: {
type: 'string',
description: 'Timestamp when the file was last modified',
},
thumbnailUrl: {
type: 'string',
description: 'URL to the file thumbnail',
},
version: {
type: 'string',
description: 'Current version of the file',
},
document: {
type: 'json',
description: 'Full document tree structure',
},
components: {
type: 'json',
description: 'Components defined in the file',
},
styles: {
type: 'json',
description: 'Styles defined in the file',
},
},
}

View File

@@ -0,0 +1,87 @@
import type { FigmaGetNodesParams, FigmaGetNodesResponse } from '@/tools/figma/types'
import type { ToolConfig } from '@/tools/types'
export const figmaGetNodesTool: ToolConfig<FigmaGetNodesParams, FigmaGetNodesResponse> = {
id: 'figma_get_nodes',
name: 'Figma - Get Nodes',
description: 'Get specific nodes from a Figma file by their IDs',
version: '1.0.0',
oauth: {
required: true,
provider: 'figma',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The key of the Figma file (from the URL: figma.com/file/{fileKey}/...)',
},
nodeIds: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated list of node IDs to retrieve',
},
depth: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Depth of the node subtree to return (optional)',
},
},
request: {
url: (params) => {
const baseUrl = `https://api.figma.com/v1/files/${params.fileKey}/nodes`
const queryParams = new URLSearchParams()
queryParams.append('ids', params.nodeIds)
if (params.depth) {
queryParams.append('depth', params.depth.toString())
}
return `${baseUrl}?${queryParams.toString()}`
},
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response) => {
const data = await response.json()
return {
success: true,
output: {
name: data.name,
lastModified: data.lastModified,
nodes: data.nodes || {},
},
}
},
outputs: {
name: {
type: 'string',
description: 'Name of the Figma file',
},
lastModified: {
type: 'string',
description: 'Timestamp when the file was last modified',
},
nodes: {
type: 'json',
description: 'Map of node IDs to their document subtrees',
},
},
}

View File

@@ -0,0 +1,63 @@
import type { FigmaGetStylesParams, FigmaGetStylesResponse } from '@/tools/figma/types'
import type { ToolConfig } from '@/tools/types'
export const figmaGetStylesTool: ToolConfig<FigmaGetStylesParams, FigmaGetStylesResponse> = {
id: 'figma_get_styles',
name: 'Figma - Get Styles',
description: 'Get all published styles (colors, typography, effects, grids) from a Figma file',
version: '1.0.0',
oauth: {
required: true,
provider: 'figma',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The key of the Figma file (from the URL: figma.com/file/{fileKey}/...)',
},
},
request: {
url: (params) => `https://api.figma.com/v1/files/${params.fileKey}/styles`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response) => {
const data = await response.json()
const styles = data.meta?.styles || []
return {
success: true,
output: {
styles,
metadata: {
styleCount: styles.length,
},
},
}
},
outputs: {
styles: {
type: 'json',
description: 'Array of styles in the file (colors, typography, effects, grids)',
},
metadata: {
type: 'json',
description: 'Metadata including style count',
},
},
}

View File

@@ -0,0 +1,17 @@
import { figmaAddCommentTool } from '@/tools/figma/add_comment'
import { figmaExportImagesTool } from '@/tools/figma/export_images'
import { figmaGetComponentsTool } from '@/tools/figma/get_components'
import { figmaGetFileTool } from '@/tools/figma/get_file'
import { figmaGetNodesTool } from '@/tools/figma/get_nodes'
import { figmaGetStylesTool } from '@/tools/figma/get_styles'
import { figmaListCommentsTool } from '@/tools/figma/list_comments'
export {
figmaAddCommentTool,
figmaExportImagesTool,
figmaGetComponentsTool,
figmaGetFileTool,
figmaGetNodesTool,
figmaGetStylesTool,
figmaListCommentsTool,
}

View File

@@ -0,0 +1,64 @@
import type { FigmaListCommentsParams, FigmaListCommentsResponse } from '@/tools/figma/types'
import type { ToolConfig } from '@/tools/types'
export const figmaListCommentsTool: ToolConfig<FigmaListCommentsParams, FigmaListCommentsResponse> =
{
id: 'figma_list_comments',
name: 'Figma - List Comments',
description: 'Get all comments on a Figma file',
version: '1.0.0',
oauth: {
required: true,
provider: 'figma',
},
params: {
accessToken: {
type: 'string',
required: true,
visibility: 'hidden',
description: 'OAuth access token',
},
fileKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The key of the Figma file (from the URL: figma.com/file/{fileKey}/...)',
},
},
request: {
url: (params) => `https://api.figma.com/v1/files/${params.fileKey}/comments`,
method: 'GET',
headers: (params) => ({
Authorization: `Bearer ${params.accessToken}`,
}),
},
transformResponse: async (response) => {
const data = await response.json()
const comments = data.comments || []
return {
success: true,
output: {
comments,
metadata: {
commentCount: comments.length,
},
},
}
},
outputs: {
comments: {
type: 'json',
description: 'Array of comments on the file',
},
metadata: {
type: 'json',
description: 'Metadata including comment count',
},
},
}

View File

@@ -0,0 +1,259 @@
/**
* Base parameters for all Figma API requests
*/
export interface FigmaBaseParams {
accessToken: string
fileKey: string
}
/**
* Parameters for getting a Figma file
*/
export interface FigmaGetFileParams extends FigmaBaseParams {
depth?: number
}
/**
* Output for getting a Figma file
*/
export interface FigmaGetFileOutput {
name: string
lastModified: string
thumbnailUrl: string
version: string
document: Record<string, unknown>
components: Record<string, unknown>
styles: Record<string, unknown>
}
/**
* Response for getting a Figma file
*/
export interface FigmaGetFileResponse {
success: boolean
output: FigmaGetFileOutput
}
/**
* Parameters for getting specific nodes from a Figma file
*/
export interface FigmaGetNodesParams extends FigmaBaseParams {
nodeIds: string
depth?: number
}
/**
* Output for getting specific nodes
*/
export interface FigmaGetNodesOutput {
name: string
lastModified: string
nodes: Record<string, unknown>
}
/**
* Response for getting specific nodes
*/
export interface FigmaGetNodesResponse {
success: boolean
output: FigmaGetNodesOutput
}
/**
* Parameters for exporting images from a Figma file
*/
export interface FigmaExportImagesParams extends FigmaBaseParams {
nodeIds: string
format: 'png' | 'svg' | 'pdf' | 'jpg'
scale?: number
}
/**
* Exported image file
*/
export interface FigmaExportedFile {
name: string
mimeType: string
data: Buffer
size: number
nodeId: string
}
/**
* Output for exporting images
*/
export interface FigmaExportImagesOutput {
files: FigmaExportedFile[]
metadata: {
format: string
scale: number
nodeCount: number
}
}
/**
* Response for exporting images
*/
export interface FigmaExportImagesResponse {
success: boolean
output: FigmaExportImagesOutput
}
/**
* Parameters for listing comments on a Figma file
*/
export interface FigmaListCommentsParams extends FigmaBaseParams {}
/**
* Comment structure from Figma API
*/
export interface FigmaComment {
id: string
message: string
file_key: string
parent_id?: string
user: {
id: string
handle: string
img_url: string
}
created_at: string
resolved_at?: string
order_id?: string
client_meta?: {
node_id?: string
node_offset?: { x: number; y: number }
}
}
/**
* Output for listing comments
*/
export interface FigmaListCommentsOutput {
comments: FigmaComment[]
metadata: {
commentCount: number
}
}
/**
* Response for listing comments
*/
export interface FigmaListCommentsResponse {
success: boolean
output: FigmaListCommentsOutput
}
/**
* Parameters for adding a comment to a Figma file
*/
export interface FigmaAddCommentParams extends FigmaBaseParams {
message: string
nodeId?: string
}
/**
* Output for adding a comment
*/
export interface FigmaAddCommentOutput {
comment: FigmaComment
}
/**
* Response for adding a comment
*/
export interface FigmaAddCommentResponse {
success: boolean
output: FigmaAddCommentOutput
}
/**
* Parameters for getting components from a Figma file
*/
export interface FigmaGetComponentsParams extends FigmaBaseParams {}
/**
* Component structure from Figma API
*/
export interface FigmaComponent {
key: string
name: string
description: string
node_id: string
thumbnail_url: string
created_at: string
updated_at: string
containing_frame?: {
name: string
nodeId: string
pageName: string
pageId: string
}
}
/**
* Output for getting components
*/
export interface FigmaGetComponentsOutput {
components: FigmaComponent[]
metadata: {
componentCount: number
}
}
/**
* Response for getting components
*/
export interface FigmaGetComponentsResponse {
success: boolean
output: FigmaGetComponentsOutput
}
/**
* Parameters for getting styles from a Figma file
*/
export interface FigmaGetStylesParams extends FigmaBaseParams {}
/**
* Style structure from Figma API
*/
export interface FigmaStyle {
key: string
name: string
description: string
style_type: 'FILL' | 'TEXT' | 'EFFECT' | 'GRID'
node_id: string
thumbnail_url: string
created_at: string
updated_at: string
}
/**
* Output for getting styles
*/
export interface FigmaGetStylesOutput {
styles: FigmaStyle[]
metadata: {
styleCount: number
}
}
/**
* Response for getting styles
*/
export interface FigmaGetStylesResponse {
success: boolean
output: FigmaGetStylesOutput
}
/**
* Union type for all Figma responses
*/
export type FigmaResponse =
| FigmaGetFileResponse
| FigmaGetNodesResponse
| FigmaExportImagesResponse
| FigmaListCommentsResponse
| FigmaAddCommentResponse
| FigmaGetComponentsResponse
| FigmaGetStylesResponse

View File

@@ -185,6 +185,15 @@ import {
exaResearchTool,
exaSearchTool,
} from '@/tools/exa'
import {
figmaAddCommentTool,
figmaExportImagesTool,
figmaGetComponentsTool,
figmaGetFileTool,
figmaGetNodesTool,
figmaGetStylesTool,
figmaListCommentsTool,
} from '@/tools/figma'
import { fileParseTool } from '@/tools/file'
import {
firecrawlCrawlTool,
@@ -1379,6 +1388,13 @@ export const tools: Record<string, ToolConfig> = {
firecrawl_crawl: firecrawlCrawlTool,
firecrawl_map: firecrawlMapTool,
firecrawl_extract: firecrawlExtractTool,
figma_get_file: figmaGetFileTool,
figma_get_nodes: figmaGetNodesTool,
figma_export_images: figmaExportImagesTool,
figma_list_comments: figmaListCommentsTool,
figma_add_comment: figmaAddCommentTool,
figma_get_components: figmaGetComponentsTool,
figma_get_styles: figmaGetStylesTool,
grafana_get_dashboard: grafanaGetDashboardTool,
grafana_list_dashboards: grafanaListDashboardsTool,
grafana_create_dashboard: grafanaCreateDashboardTool,