Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
309 changes: 306 additions & 3 deletions apps/sim/blocks/blocks/dub.ts

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions apps/sim/tools/dub/create_link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ export const createLinkTool: ToolConfig<DubCreateLinkParams, DubCreateLinkRespon
visibility: 'user-or-llm',
description: 'External ID for the link in your database',
},
tenantId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Tenant ID for grouping links created on behalf of a customer/tenant',
},
folderId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Folder ID to organize the link into',
},
trackConversion: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to track conversions (leads/sales) for the short link',
},
tagIds: {
type: 'string',
required: false,
Expand Down Expand Up @@ -131,6 +149,9 @@ export const createLinkTool: ToolConfig<DubCreateLinkParams, DubCreateLinkRespon
if (params.domain) body.domain = params.domain
if (params.key) body.key = params.key
if (params.externalId) body.externalId = params.externalId
if (params.tenantId) body.tenantId = params.tenantId
if (params.folderId) body.folderId = params.folderId
if (params.trackConversion !== undefined) body.trackConversion = params.trackConversion
if (params.tagIds) body.tagIds = params.tagIds.split(',').map((id) => id.trim())
if (params.comments) body.comments = params.comments
if (params.expiresAt) body.expiresAt = params.expiresAt
Expand Down Expand Up @@ -169,8 +190,12 @@ export const createLinkTool: ToolConfig<DubCreateLinkParams, DubCreateLinkRespon
title: data.title ?? null,
description: data.description ?? null,
tags: data.tags ?? [],
folderId: data.folderId ?? null,
tenantId: data.tenantId ?? null,
trackConversion: data.trackConversion ?? false,
clicks: data.clicks ?? 0,
leads: data.leads ?? 0,
conversions: data.conversions ?? 0,
sales: data.sales ?? 0,
saleAmount: data.saleAmount ?? 0,
lastClicked: data.lastClicked ?? null,
Expand All @@ -197,8 +222,12 @@ export const createLinkTool: ToolConfig<DubCreateLinkParams, DubCreateLinkRespon
title: { type: 'string', description: 'OG title', optional: true },
description: { type: 'string', description: 'OG description', optional: true },
tags: { type: 'json', description: 'Tags assigned to the link (id, name, color)' },
folderId: { type: 'string', description: 'Folder the link is organized into', optional: true },
tenantId: { type: 'string', description: 'Tenant ID associated with the link', optional: true },
trackConversion: { type: 'boolean', description: 'Whether conversion tracking is enabled' },
clicks: { type: 'number', description: 'Number of clicks' },
leads: { type: 'number', description: 'Number of leads' },
conversions: { type: 'number', description: 'Number of conversions' },
sales: { type: 'number', description: 'Number of sales' },
saleAmount: { type: 'number', description: 'Total sale amount in cents' },
lastClicked: { type: 'string', description: 'Last clicked timestamp', optional: true },
Expand Down
68 changes: 68 additions & 0 deletions apps/sim/tools/dub/create_tag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { DubCreateTagParams, DubCreateTagResponse } from '@/tools/dub/types'
import type { ToolConfig } from '@/tools/types'

export const createTagTool: ToolConfig<DubCreateTagParams, DubCreateTagResponse> = {
id: 'dub_create_tag',
name: 'Dub Create Tag',
description: 'Create a new tag in the workspace for organizing and filtering short links.',
version: '1.0.0',

params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Dub API key',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The name of the tag to create (1-50 characters)',
},
color: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Tag color: red, yellow, green, blue, purple, brown, gray, or pink (random if omitted)',
},
},

request: {
url: 'https://api.dub.co/tags',
method: 'POST',
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Bearer ${params.apiKey}`,
}),
body: (params) => {
const body: Record<string, unknown> = { name: params.name }
if (params.color) body.color = params.color
return body
},
},

transformResponse: async (response: Response) => {
const data = await response.json()

if (!response.ok) {
throw new Error(data.error?.message || data.error || 'Failed to create tag')
}

return {
success: true,
output: {
id: data.id ?? '',
name: data.name ?? '',
color: data.color ?? '',
},
}
},

outputs: {
id: { type: 'string', description: 'Unique ID of the created tag' },
name: { type: 'string', description: 'Name of the tag' },
color: { type: 'string', description: 'Color assigned to the tag' },
},
}
8 changes: 8 additions & 0 deletions apps/sim/tools/dub/get_link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,12 @@ export const getLinkTool: ToolConfig<DubGetLinkParams, DubGetLinkResponse> = {
title: data.title ?? null,
description: data.description ?? null,
tags: data.tags ?? [],
folderId: data.folderId ?? null,
tenantId: data.tenantId ?? null,
trackConversion: data.trackConversion ?? false,
clicks: data.clicks ?? 0,
leads: data.leads ?? 0,
conversions: data.conversions ?? 0,
sales: data.sales ?? 0,
saleAmount: data.saleAmount ?? 0,
lastClicked: data.lastClicked ?? null,
Expand All @@ -106,8 +110,12 @@ export const getLinkTool: ToolConfig<DubGetLinkParams, DubGetLinkResponse> = {
title: { type: 'string', description: 'OG title', optional: true },
description: { type: 'string', description: 'OG description', optional: true },
tags: { type: 'json', description: 'Tags assigned to the link (id, name, color)' },
folderId: { type: 'string', description: 'Folder the link is organized into', optional: true },
tenantId: { type: 'string', description: 'Tenant ID associated with the link', optional: true },
trackConversion: { type: 'boolean', description: 'Whether conversion tracking is enabled' },
clicks: { type: 'number', description: 'Number of clicks' },
leads: { type: 'number', description: 'Number of leads' },
conversions: { type: 'number', description: 'Number of conversions' },
sales: { type: 'number', description: 'Number of sales' },
saleAmount: { type: 'number', description: 'Total sale amount in cents' },
lastClicked: { type: 'string', description: 'Last clicked timestamp', optional: true },
Expand Down
7 changes: 7 additions & 0 deletions apps/sim/tools/dub/get_qr_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export const getQrCodeTool: ToolConfig<DubGetQrCodeParams, DubGetQrCodeResponse>
visibility: 'user-or-llm',
description: 'The short link URL to encode in the QR code',
},
logo: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'URL of a custom logo to embed in the QR code (requires a paid Dub plan)',
},
size: {
type: 'number',
required: false,
Expand Down Expand Up @@ -63,6 +69,7 @@ export const getQrCodeTool: ToolConfig<DubGetQrCodeParams, DubGetQrCodeResponse>
url: (params) => {
const url = new URL('https://api.dub.co/qr')
url.searchParams.set('url', params.url.trim())
if (params.logo) url.searchParams.set('logo', params.logo)
if (params.size !== undefined) url.searchParams.set('size', String(params.size))
if (params.level) url.searchParams.set('level', params.level)
if (params.fgColor) url.searchParams.set('fgColor', params.fgColor)
Expand Down
8 changes: 8 additions & 0 deletions apps/sim/tools/dub/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ import { bulkCreateLinksTool } from '@/tools/dub/bulk_create_links'
import { bulkDeleteLinksTool } from '@/tools/dub/bulk_delete_links'
import { bulkUpdateLinksTool } from '@/tools/dub/bulk_update_links'
import { createLinkTool } from '@/tools/dub/create_link'
import { createTagTool } from '@/tools/dub/create_tag'
import { deleteLinkTool } from '@/tools/dub/delete_link'
import { getAnalyticsTool } from '@/tools/dub/get_analytics'
import { getEventsTool } from '@/tools/dub/get_events'
import { getLinkTool } from '@/tools/dub/get_link'
import { getLinksCountTool } from '@/tools/dub/get_links_count'
import { getQrCodeTool } from '@/tools/dub/get_qr_code'
import { listDomainsTool } from '@/tools/dub/list_domains'
import { listFoldersTool } from '@/tools/dub/list_folders'
import { listLinksTool } from '@/tools/dub/list_links'
import { listTagsTool } from '@/tools/dub/list_tags'
import { updateLinkTool } from '@/tools/dub/update_link'
import { upsertLinkTool } from '@/tools/dub/upsert_link'

Expand All @@ -25,3 +29,7 @@ export const dubBulkCreateLinksTool = bulkCreateLinksTool
export const dubBulkUpdateLinksTool = bulkUpdateLinksTool
export const dubBulkDeleteLinksTool = bulkDeleteLinksTool
export const dubGetQrCodeTool = getQrCodeTool
export const dubListDomainsTool = listDomainsTool
export const dubListTagsTool = listTagsTool
export const dubCreateTagTool = createTagTool
export const dubListFoldersTool = listFoldersTool
85 changes: 85 additions & 0 deletions apps/sim/tools/dub/list_domains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { DubListDomainsParams, DubListDomainsResponse } from '@/tools/dub/types'
import type { ToolConfig } from '@/tools/types'

export const listDomainsTool: ToolConfig<DubListDomainsParams, DubListDomainsResponse> = {
id: 'dub_list_domains',
name: 'Dub List Domains',
description:
'Retrieve the custom domains registered in the workspace, so links can be created against the right domain.',
version: '1.0.0',

params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Dub API key',
},
archived: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether to include archived domains (defaults to false)',
},
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search by domain name',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number (default: 1)',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of domains per page (default: 50, max: 50)',
},
},

request: {
url: (params) => {
const url = new URL('https://api.dub.co/domains')
if (params.archived !== undefined) url.searchParams.set('archived', String(params.archived))
if (params.search) url.searchParams.set('search', params.search)
if (params.page) url.searchParams.set('page', String(params.page))
if (params.pageSize) url.searchParams.set('pageSize', String(params.pageSize))
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/json',
Authorization: `Bearer ${params.apiKey}`,
}),
},

transformResponse: async (response: Response) => {
const data = await response.json()

if (!response.ok) {
throw new Error(data.error?.message || data.error || 'Failed to list domains')
}

const domains = Array.isArray(data) ? (data as Record<string, unknown>[]) : []

return {
success: true,
output: {
domains,
count: domains.length,
},
}
},

outputs: {
domains: {
type: 'json',
description: 'Array of domain objects (slug, verified, primary, archived)',
},
count: { type: 'number', description: 'Number of domains returned' },
},
}
78 changes: 78 additions & 0 deletions apps/sim/tools/dub/list_folders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import type { DubListFoldersParams, DubListFoldersResponse } from '@/tools/dub/types'
import type { ToolConfig } from '@/tools/types'

export const listFoldersTool: ToolConfig<DubListFoldersParams, DubListFoldersResponse> = {
id: 'dub_list_folders',
name: 'Dub List Folders',
description:
'Retrieve the folders defined in the workspace, so the right folder ID can be used to organize links.',
version: '1.0.0',

params: {
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Dub API key',
},
search: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Search by folder name',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number (default: 1)',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of folders per page (default: 50, max: 50)',
},
},

request: {
url: (params) => {
const url = new URL('https://api.dub.co/folders')
if (params.search) url.searchParams.set('search', params.search)
if (params.page) url.searchParams.set('page', String(params.page))
if (params.pageSize) url.searchParams.set('pageSize', String(params.pageSize))
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/json',
Authorization: `Bearer ${params.apiKey}`,
}),
},

transformResponse: async (response: Response) => {
const data = await response.json()

if (!response.ok) {
throw new Error(data.error?.message || data.error || 'Failed to list folders')
}

const folders = Array.isArray(data) ? (data as Record<string, unknown>[]) : []

return {
success: true,
output: {
folders,
count: folders.length,
},
}
},

outputs: {
folders: {
type: 'json',
description: 'Array of folder objects (id, name, accessLevel)',
},
count: { type: 'number', description: 'Number of folders returned' },
},
}
Loading
Loading