From ad17502b5cbc3b20eb4ce4b102d1cdd7d22d3567 Mon Sep 17 00:00:00 2001 From: Vasyl Abramovych Date: Thu, 26 Feb 2026 18:07:02 -0800 Subject: [PATCH 1/5] feat(tools): add Short.io tools and registry Add 6 Short.io tool implementations (create link, list domains, list links, delete link, get QR code, get analytics) with shared types and barrel export. Register all tools in the tools registry. --- apps/sim/tools/registry.ts | 14 +++ apps/sim/tools/short_io/create_link.ts | 81 +++++++++++++++++ apps/sim/tools/short_io/delete_link.ts | 41 +++++++++ apps/sim/tools/short_io/get_analytics.ts | 110 +++++++++++++++++++++++ apps/sim/tools/short_io/get_qr_code.ts | 94 +++++++++++++++++++ apps/sim/tools/short_io/index.ts | 6 ++ apps/sim/tools/short_io/list_domains.ts | 38 ++++++++ apps/sim/tools/short_io/list_links.ts | 84 +++++++++++++++++ apps/sim/tools/short_io/types.ts | 40 +++++++++ 9 files changed, 508 insertions(+) create mode 100644 apps/sim/tools/short_io/create_link.ts create mode 100644 apps/sim/tools/short_io/delete_link.ts create mode 100644 apps/sim/tools/short_io/get_analytics.ts create mode 100644 apps/sim/tools/short_io/get_qr_code.ts create mode 100644 apps/sim/tools/short_io/index.ts create mode 100644 apps/sim/tools/short_io/list_domains.ts create mode 100644 apps/sim/tools/short_io/list_links.ts create mode 100644 apps/sim/tools/short_io/types.ts diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 5a2f5787c7..2f7744d74f 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1609,6 +1609,14 @@ import { shopifyUpdateOrderTool, shopifyUpdateProductTool, } from '@/tools/shopify' +import { + shortIoCreateLinkTool, + shortIoListDomainsTool, + shortIoListLinksTool, + shortIoDeleteLinkTool, + shortIoGetQrCodeTool, + shortIoGetAnalyticsTool, +} from '@/tools/short_io' import { similarwebBounceRateTool, similarwebPagesPerVisitTool, @@ -2353,6 +2361,12 @@ export const tools: Record = { tavily_extract: tavilyExtractTool, tavily_crawl: tavilyCrawlTool, tavily_map: tavilyMapTool, + short_io_create_link: shortIoCreateLinkTool, + short_io_list_domains: shortIoListDomainsTool, + short_io_list_links: shortIoListLinksTool, + short_io_delete_link: shortIoDeleteLinkTool, + short_io_get_qr_code: shortIoGetQrCodeTool, + short_io_get_analytics: shortIoGetAnalyticsTool, supabase_query: supabaseQueryTool, supabase_insert: supabaseInsertTool, supabase_get_row: supabaseGetRowTool, diff --git a/apps/sim/tools/short_io/create_link.ts b/apps/sim/tools/short_io/create_link.ts new file mode 100644 index 0000000000..c622508c04 --- /dev/null +++ b/apps/sim/tools/short_io/create_link.ts @@ -0,0 +1,81 @@ +import type { ShortIoCreateLinkParams } from '@/tools/short_io/types' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +export const shortIoCreateLinkTool: ToolConfig = { + id: 'short_io_create_link', + name: 'Short.io Create Link', + description: 'Create a short link using your Short.io custom domain.', + version: '1.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'Short.io Secret API Key', + }, + domain: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Your registered Short.io custom domain', + }, + originalURL: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The long URL to shorten', + }, + path: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Optional custom path for the short link', + }, + }, + request: { + url: 'https://api.short.io/links', + method: 'POST', + headers: (params) => ({ + Authorization: params.apiKey, + 'Content-Type': 'application/json', + }), + body: (params) => { + const bodyData: Record = { + domain: params.domain, + originalURL: params.originalURL, + } + if (params.path) { + bodyData.path = params.path + } + return bodyData + }, + }, + transformResponse: async (response: Response) => { + if (!response.ok) { + const errorText = await response.text().catch(() => response.statusText) + return { + success: false, + output: { + success: false, + error: `Failed to create short link: ${errorText}`, + }, + } + } + + const data = await response.json().catch(() => ({})) + return { + success: true, + output: { + success: true, + shortURL: data.shortURL, + idString: data.idString, + }, + } + }, + outputs: { + success: { type: 'boolean', description: 'Whether the link was created successfully' }, + shortURL: { type: 'string', description: 'The generated short link URL' }, + idString: { type: 'string', description: 'The unique Short.io link ID string' }, + error: { type: 'string', description: 'Error message if failed' }, + }, +} diff --git a/apps/sim/tools/short_io/delete_link.ts b/apps/sim/tools/short_io/delete_link.ts new file mode 100644 index 0000000000..745d70b719 --- /dev/null +++ b/apps/sim/tools/short_io/delete_link.ts @@ -0,0 +1,41 @@ +import type { ShortIoDeleteLinkParams } from '@/tools/short_io/types' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +export const shortIoDeleteLinkTool: ToolConfig = { + id: 'short_io_delete_link', + name: 'Short.io Delete Link', + description: 'Delete a short link by ID (e.g. lnk_abc123_abcdef). Rate limit 20/s.', + version: '1.0', + params: { + apiKey: { type: 'string', required: true, visibility: 'hidden', description: 'Short.io Secret API Key' }, + linkId: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Link ID to delete' }, + }, + request: { + url: (params) => `https://api.short.io/links/${encodeURIComponent(params.linkId)}`, + method: 'DELETE', + headers: (params) => ({ + Authorization: params.apiKey, + }), + }, + transformResponse: async (response: Response) => { + if (!response.ok) { + const err = await response.text().catch(() => response.statusText) + return { success: false, output: { success: false, error: err } } + } + const data = await response.json().catch(() => ({})) + return { + success: true, + output: { + success: true, + deleted: data.success === true, + idString: data.idString ?? undefined, + }, + } + }, + outputs: { + success: { type: 'boolean', description: 'Success status' }, + deleted: { type: 'boolean', description: 'Whether the link was deleted' }, + idString: { type: 'string', description: 'Deleted link ID' }, + error: { type: 'string', description: 'Error message' }, + }, +} diff --git a/apps/sim/tools/short_io/get_analytics.ts b/apps/sim/tools/short_io/get_analytics.ts new file mode 100644 index 0000000000..7ad8c6c356 --- /dev/null +++ b/apps/sim/tools/short_io/get_analytics.ts @@ -0,0 +1,110 @@ +import type { ShortIoGetAnalyticsParams } from '@/tools/short_io/types' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +const STATS_PERIOD_MAP: Record = { + today: 'today', + yesterday: 'yesterday', + last_7_days: 'last7', + last_30_days: 'last30', + all_time: 'total', + week: 'week', + month: 'month', + lastmonth: 'lastmonth', +} + +export const shortIoGetAnalyticsTool: ToolConfig = { + id: 'short_io_get_analytics', + name: 'Short.io Get Link Statistics', + description: + 'Fetch click statistics for a Short.io link (Statistics API: totalClicks, humanClicks, referer, country, etc.).', + version: '1.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'Short.io Secret API Key', + }, + linkId: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Link ID' }, + period: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Period: today, yesterday, last7, last30, total, week, month, lastmonth', + }, + tz: { type: 'string', required: false, visibility: 'hidden', description: 'Timezone (default UTC)' }, + }, + request: { + url: (params) => { + const base = 'https://statistics.short.io/statistics/link/' + encodeURIComponent(params.linkId) + const period = STATS_PERIOD_MAP[params.period] ?? params.period ?? 'last30' + const q = new URLSearchParams({ period }) + if (params.tz) q.set('tz', params.tz) + return `${base}?${q.toString()}` + }, + method: 'GET', + headers: (params) => ({ + Authorization: params.apiKey, + Accept: 'application/json', + }), + }, + transformResponse: async (response: Response) => { + if (!response.ok) { + const err = await response.text().catch(() => response.statusText) + return { success: false, output: { success: false, error: err } } + } + const data = await response.json().catch(() => ({})) + const totalClicks = data.totalClicks ?? data.clicks ?? 0 + const humanClicks = data.humanClicks ?? totalClicks + return { + success: true, + output: { + success: true, + clicks: totalClicks, + totalClicks, + humanClicks, + totalClicksChange: data.totalClicksChange, + humanClicksChange: data.humanClicksChange, + referer: data.referer ?? [], + country: data.country ?? [], + browser: data.browser ?? [], + os: data.os ?? [], + city: data.city ?? [], + device: data.device ?? [], + social: data.social ?? [], + utmMedium: data.utm_medium ?? [], + utmSource: data.utm_source ?? [], + utmCampaign: data.utm_campaign ?? [], + clickStatistics: data.clickStatistics, + interval: data.interval, + }, + } + }, + outputs: { + success: { type: 'boolean', description: 'Success status' }, + clicks: { type: 'number', description: 'Total clicks in period' }, + totalClicks: { type: 'number', description: 'Total clicks' }, + humanClicks: { type: 'number', description: 'Human clicks' }, + totalClicksChange: { type: 'string', description: 'Change vs previous period' }, + humanClicksChange: { type: 'string', description: 'Human clicks change' }, + referer: { type: 'array', description: 'Referrer breakdown (referer, score)' }, + country: { type: 'array', description: 'Country breakdown (countryName, country, score)' }, + browser: { type: 'array', description: 'Browser breakdown (browser, score)' }, + os: { type: 'array', description: 'OS breakdown (os, score)' }, + city: { type: 'array', description: 'City breakdown (city, name, countryCode, score)' }, + device: { type: 'array', description: 'Device breakdown' }, + social: { type: 'array', description: 'Social source breakdown (social, score)' }, + utmMedium: { type: 'array', description: 'UTM medium breakdown' }, + utmSource: { type: 'array', description: 'UTM source breakdown' }, + utmCampaign: { type: 'array', description: 'UTM campaign breakdown' }, + clickStatistics: { + type: 'object', + description: 'Time-series click data (datasets with x/y points per interval)', + }, + interval: { + type: 'object', + description: 'Date range (startDate, endDate, prevStartDate, prevEndDate, tz)', + }, + error: { type: 'string', description: 'Error message' }, + }, +} diff --git a/apps/sim/tools/short_io/get_qr_code.ts b/apps/sim/tools/short_io/get_qr_code.ts new file mode 100644 index 0000000000..b67cd223d4 --- /dev/null +++ b/apps/sim/tools/short_io/get_qr_code.ts @@ -0,0 +1,94 @@ +import type { ShortIoGetQrParams } from '@/tools/short_io/types' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +export const shortIoGetQrCodeTool: ToolConfig = { + id: 'short_io_get_qr_code', + name: 'Short.io Generate QR Code', + description: 'Generate a QR code for a Short.io link (POST /links/qr/{linkIdString}).', + version: '1.0', + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'Short.io Secret API Key', + }, + linkId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Link ID (e.g. lnk_abc123_abcdef)', + }, + color: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'QR color hex (e.g. 000000)', + }, + backgroundColor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Background color hex (e.g. FFFFFF)', + }, + size: { type: 'number', required: false, visibility: 'user-or-llm', description: 'QR size 1–99' }, + type: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Output format: png or svg', + }, + useDomainSettings: { + type: 'boolean', + required: false, + visibility: 'hidden', + description: 'Use domain settings (default true)', + }, + }, + request: { + url: (params) => `https://api.short.io/links/qr/${params.linkId}`, + method: 'POST', + headers: (params) => ({ + Authorization: params.apiKey, + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: Record = { + useDomainSettings: params.useDomainSettings ?? true, + } + if (params.color != null && params.color !== '') body.color = params.color + if (params.backgroundColor != null && params.backgroundColor !== '') body.backgroundColor = params.backgroundColor + if (params.size != null && params.size >= 1 && params.size <= 99) body.size = params.size + if (params.type === 'svg' || params.type === 'png') body.type = params.type + return body + }, + }, + transformResponse: async (response: Response) => { + if (!response.ok) { + const err = await response.text().catch(() => response.statusText) + return { success: false, output: { success: false, error: err } } + } + + const contentType = response.headers.get('Content-Type') ?? '' + const blob = await response.blob() + const arrayBuffer = await blob.arrayBuffer() + const bytes = new Uint8Array(arrayBuffer) + let binary = '' + for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!) + const base64 = typeof btoa !== 'undefined' ? btoa(binary) : '' + const mediaType = contentType.split(';')[0]?.trim() || 'image/png' + const dataUrl = base64 ? `data:${mediaType};base64,${base64}` : '' + return { + success: true, + output: { success: true, qrCodeURL: dataUrl }, + } + }, + outputs: { + success: { type: 'boolean', description: 'Success status' }, + qrCodeURL: { + type: 'string', + description: 'Base64 data URL of the QR code image (e.g. data:image/png;base64,...)', + }, + error: { type: 'string', description: 'Error message' }, + }, +} diff --git a/apps/sim/tools/short_io/index.ts b/apps/sim/tools/short_io/index.ts new file mode 100644 index 0000000000..6ac95fe227 --- /dev/null +++ b/apps/sim/tools/short_io/index.ts @@ -0,0 +1,6 @@ +export { shortIoCreateLinkTool } from '@/tools/short_io/create_link' +export { shortIoDeleteLinkTool } from '@/tools/short_io/delete_link' +export { shortIoGetAnalyticsTool } from '@/tools/short_io/get_analytics' +export { shortIoGetQrCodeTool } from '@/tools/short_io/get_qr_code' +export { shortIoListDomainsTool } from '@/tools/short_io/list_domains' +export { shortIoListLinksTool } from '@/tools/short_io/list_links' diff --git a/apps/sim/tools/short_io/list_domains.ts b/apps/sim/tools/short_io/list_domains.ts new file mode 100644 index 0000000000..2e6389a34d --- /dev/null +++ b/apps/sim/tools/short_io/list_domains.ts @@ -0,0 +1,38 @@ +import type { ShortIoListDomainsParams } from '@/tools/short_io/types' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +export const shortIoListDomainsTool: ToolConfig = { + id: 'short_io_list_domains', + name: 'Short.io List Domains', + description: 'List Short.io domains. Returns domain IDs and details for use in List Links.', + version: '1.0', + params: { + apiKey: { type: 'string', required: true, visibility: 'hidden', description: 'Short.io Secret API Key' }, + }, + request: { + url: 'https://api.short.io/api/domains', + method: 'GET', + headers: (params) => ({ + Authorization: params.apiKey, + Accept: 'application/json', + }), + }, + transformResponse: async (response: Response) => { + if (!response.ok) { + const err = await response.text().catch(() => response.statusText) + return { success: false, output: { success: false, error: err } } + } + const data = await response.json().catch(() => ({})) + const list = Array.isArray(data) ? data : data.domains ?? data.list ?? [] + return { + success: true, + output: { success: true, domains: list, count: list.length }, + } + }, + outputs: { + success: { type: 'boolean', description: 'Success status' }, + domains: { type: 'array', description: 'List of domain objects (id, hostname, etc.)' }, + count: { type: 'number', description: 'Number of domains' }, + error: { type: 'string', description: 'Error message' }, + }, +} diff --git a/apps/sim/tools/short_io/list_links.ts b/apps/sim/tools/short_io/list_links.ts new file mode 100644 index 0000000000..b5ced301d6 --- /dev/null +++ b/apps/sim/tools/short_io/list_links.ts @@ -0,0 +1,84 @@ +import type { ShortIoListLinksParams } from '@/tools/short_io/types' +import type { ToolConfig, ToolResponse } from '@/tools/types' + +export const shortIoListLinksTool: ToolConfig = { + id: 'short_io_list_links', + name: 'Short.io List Links', + description: + 'List short links for a domain. Requires domain_id (from List Domains or dashboard). Max 150 per request.', + version: '1.0', + params: { + apiKey: { type: 'string', required: true, visibility: 'hidden', description: 'Short.io Secret API Key' }, + domainId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Domain ID (from List Domains)', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Max links to return (1–150)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Pagination token from previous response', + }, + dateSortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort by date: asc or desc', + }, + }, + request: { + url: (params) => { + const u = new URL('https://api.short.io/api/links') + u.searchParams.set('domain_id', String(params.domainId)) + if (params.limit != null && params.limit >= 1 && params.limit <= 150) { + u.searchParams.set('limit', String(params.limit)) + } + if (params.pageToken) u.searchParams.set('pageToken', params.pageToken) + if (params.dateSortOrder === 'asc' || params.dateSortOrder === 'desc') { + u.searchParams.set('dateSortOrder', params.dateSortOrder) + } + return u.toString() + }, + method: 'GET', + headers: (params) => ({ + Authorization: params.apiKey, + Accept: 'application/json', + }), + }, + transformResponse: async (response: Response) => { + if (!response.ok) { + const err = await response.text().catch(() => response.statusText) + return { success: false, output: { success: false, error: err } } + } + const data = await response.json().catch(() => ({})) + const links = data.links ?? [] + const count = data.count ?? links.length + return { + success: true, + output: { + success: true, + links, + count, + nextPageToken: data.nextPageToken ?? undefined, + }, + } + }, + outputs: { + success: { type: 'boolean', description: 'Success status' }, + links: { + type: 'array', + description: 'List of link objects (idString, shortURL, originalURL, path, etc.)', + }, + count: { type: 'number', description: 'Number of links returned' }, + nextPageToken: { type: 'string', description: 'Token for next page' }, + error: { type: 'string', description: 'Error message' }, + }, +} diff --git a/apps/sim/tools/short_io/types.ts b/apps/sim/tools/short_io/types.ts new file mode 100644 index 0000000000..ee4e9c050d --- /dev/null +++ b/apps/sim/tools/short_io/types.ts @@ -0,0 +1,40 @@ +export interface ShortIoCreateLinkParams { + apiKey: string + domain: string + originalURL: string + path?: string +} + +export interface ShortIoListDomainsParams { + apiKey: string +} + +export interface ShortIoListLinksParams { + apiKey: string + domainId: number + limit?: number + pageToken?: string + dateSortOrder?: 'asc' | 'desc' +} + +export interface ShortIoDeleteLinkParams { + apiKey: string + linkId: string +} + +export interface ShortIoGetQrParams { + apiKey: string + linkId: string + color?: string + backgroundColor?: string + size?: number + type?: 'png' | 'svg' + useDomainSettings?: boolean +} + +export interface ShortIoGetAnalyticsParams { + apiKey: string + linkId: string + period: string + tz?: string +} From 278c0becdc24dcdbbf0793c85539c71dbcdfa12c Mon Sep 17 00:00:00 2001 From: Vasyl Abramovych Date: Thu, 26 Feb 2026 18:07:16 -0800 Subject: [PATCH 2/5] feat(blocks): add Short.io block and icon Add Short.io block config with 6 operations (create link, list domains, list links, delete link, get QR code, get analytics). Add ShortIoIcon and register the block in the blocks registry. --- apps/sim/blocks/blocks/short_io.ts | 245 +++++++++++++++++++++++++++++ apps/sim/blocks/registry.ts | 2 + apps/sim/components/icons.tsx | 23 ++- 3 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 apps/sim/blocks/blocks/short_io.ts diff --git a/apps/sim/blocks/blocks/short_io.ts b/apps/sim/blocks/blocks/short_io.ts new file mode 100644 index 0000000000..8041737fd5 --- /dev/null +++ b/apps/sim/blocks/blocks/short_io.ts @@ -0,0 +1,245 @@ +import { ShortIoIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode } from '@/blocks/types' +import type { ToolResponse } from '@/tools/types' + +export const ShortIoBlock: BlockConfig = { + type: 'short_io', + name: 'Short.io', + description: 'Create and manage short links, domains, and analytics.', + authMode: AuthMode.ApiKey, + longDescription: + 'Integrate Short.io to generate branded short links, list domains and links, delete links, generate QR codes, and view link statistics. Requires your Short.io Secret API Key.', + docsLink: 'https://docs.sim.ai/tools/short_io', + category: 'tools', + bgColor: '#FFFFFF', + icon: ShortIoIcon, + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Create Link', id: 'create_link' }, + { label: 'List Domains', id: 'list_domains' }, + { label: 'List Links', id: 'list_links' }, + { label: 'Delete Link', id: 'delete_link' }, + { label: 'Get QR Code', id: 'get_qr_code' }, + { label: 'Get Link Statistics', id: 'get_analytics' }, + ], + value: () => 'create_link', + }, + { + id: 'apiKey', + title: 'Secret API Key', + type: 'short-input', + mode: 'basic', + required: true, + password: true, + placeholder: 'sk_...', + }, + { + id: 'domain', + title: 'Custom Domain', + type: 'short-input', + placeholder: 'link.yourbrand.com', + condition: { field: 'operation', value: 'create_link' }, + required: true, + }, + { + id: 'originalURL', + title: 'Original URL', + type: 'long-input', + placeholder: 'https://www.example.com/very/long/path/to/page', + condition: { field: 'operation', value: 'create_link' }, + required: true, + }, + { + id: 'path', + title: 'Custom Path (Optional)', + type: 'short-input', + placeholder: 'my-custom-path', + condition: { field: 'operation', value: 'create_link' }, + required: false, + }, + { + id: 'domainId', + title: 'Domain ID', + type: 'short-input', + placeholder: '12345', + condition: { field: 'operation', value: 'list_links' }, + required: true, + }, + { + id: 'limit', + title: 'Limit (1–150)', + type: 'short-input', + placeholder: '50', + condition: { field: 'operation', value: 'list_links' }, + required: false, + }, + { + id: 'pageToken', + title: 'Page Token', + type: 'short-input', + placeholder: 'Next page token', + condition: { field: 'operation', value: 'list_links' }, + required: false, + }, + { + id: 'linkId', + title: 'Short.io Link ID', + type: 'short-input', + placeholder: 'lnk_abc123_abcdef', + condition: { + field: 'operation', + value: ['get_qr_code', 'get_analytics', 'delete_link'], + }, + required: true, + }, + { + id: 'type', + title: 'QR Format', + type: 'dropdown', + options: [ + { label: 'PNG', id: 'png' }, + { label: 'SVG', id: 'svg' }, + ], + condition: { field: 'operation', value: 'get_qr_code' }, + required: false, + value: () => 'png', + }, + { + id: 'size', + title: 'QR Size (1–99)', + type: 'short-input', + placeholder: '10', + condition: { field: 'operation', value: 'get_qr_code' }, + required: false, + }, + { + id: 'color', + title: 'QR Color (hex)', + type: 'short-input', + placeholder: '000000', + condition: { field: 'operation', value: 'get_qr_code' }, + required: false, + }, + { + id: 'backgroundColor', + title: 'Background Color (hex)', + type: 'short-input', + placeholder: 'FFFFFF', + condition: { field: 'operation', value: 'get_qr_code' }, + required: false, + }, + { + id: 'period', + title: 'Statistics Period', + type: 'dropdown', + options: [ + { label: 'Today', id: 'today' }, + { label: 'Yesterday', id: 'yesterday' }, + { label: 'Last 7 Days', id: 'last_7_days' }, + { label: 'Last 30 Days', id: 'last_30_days' }, + { label: 'All Time', id: 'all_time' }, + ], + condition: { field: 'operation', value: 'get_analytics' }, + required: true, + value: () => 'last_30_days', + }, + ], + tools: { + access: [ + 'short_io_create_link', + 'short_io_list_domains', + 'short_io_list_links', + 'short_io_delete_link', + 'short_io_get_qr_code', + 'short_io_get_analytics', + ], + config: { + tool: (params) => { + switch (params.operation) { + case 'create_link': + return 'short_io_create_link' + case 'list_domains': + return 'short_io_list_domains' + case 'list_links': + return 'short_io_list_links' + case 'delete_link': + return 'short_io_delete_link' + case 'get_qr_code': + return 'short_io_get_qr_code' + case 'get_analytics': + return 'short_io_get_analytics' + default: + throw new Error(`Invalid Short.io operation: ${params.operation}`) + } + }, + params: (params) => { + const { apiKey, operation, size, domainId, limit, ...rest } = params + const out: Record = { ...rest, apiKey } + if (size !== undefined && size !== '') { + const n = Number(size) + if (typeof n === 'number' && !isNaN(n) && n >= 1 && n <= 99) out.size = n + } + if (operation === 'list_links' && domainId !== undefined && domainId !== '') { + const d = Number(domainId) + if (typeof d === 'number' && !isNaN(d)) out.domainId = d + } + if (operation === 'list_links' && limit !== undefined && limit !== '') { + const l = Number(limit) + if (typeof l === 'number' && !isNaN(l) && l >= 1 && l <= 150) out.limit = l + } + return out + }, + }, + }, + inputs: { + apiKey: { type: 'string', description: 'Secret API Key' }, + operation: { type: 'string', description: 'Short.io operation to perform' }, + domain: { type: 'string', description: 'Your registered Short.io custom domain' }, + originalURL: { type: 'string', description: 'The original long URL to shorten' }, + path: { type: 'string', description: 'Optional custom path for the short link' }, + domainId: { type: 'number', description: 'Domain ID (from List Domains)' }, + limit: { type: 'number', description: 'Max links to return (1–150)' }, + pageToken: { type: 'string', description: 'Pagination token for List Links' }, + linkId: { type: 'string', description: 'The Short.io internal link ID string' }, + type: { type: 'string', description: 'QR output format: png or svg' }, + size: { type: 'number', description: 'QR size 1–99' }, + color: { type: 'string', description: 'QR color hex' }, + backgroundColor: { type: 'string', description: 'QR background color hex' }, + period: { type: 'string', description: 'Statistics period (e.g. today, last_30_days, all_time)' }, + tz: { type: 'string', description: 'Timezone for statistics (e.g. UTC)' }, + }, + outputs: { + success: { type: 'boolean', description: 'Operation success status' }, + shortURL: { type: 'string', description: 'The generated short link' }, + idString: { type: 'string', description: 'The Short.io link ID' }, + domains: { type: 'array', description: 'List of domains (from List Domains)' }, + count: { type: 'number', description: 'Number of domains or links returned' }, + links: { type: 'array', description: 'List of links (from List Links)' }, + nextPageToken: { type: 'string', description: 'Pagination token for next page' }, + deleted: { type: 'boolean', description: 'Whether the link was deleted' }, + qrCodeURL: { type: 'string', description: 'Base64 data URL of the generated QR code image' }, + clicks: { type: 'number', description: 'Total clicks in period' }, + totalClicks: { type: 'number', description: 'Total clicks' }, + humanClicks: { type: 'number', description: 'Human clicks' }, + totalClicksChange: { type: 'string', description: 'Change in total clicks vs previous period' }, + humanClicksChange: { type: 'string', description: 'Change in human clicks vs previous period' }, + referer: { type: 'array', description: 'Referrer breakdown (referer, score)' }, + country: { type: 'array', description: 'Country breakdown (countryName, country, score)' }, + browser: { type: 'array', description: 'Browser breakdown (browser, score)' }, + os: { type: 'array', description: 'OS breakdown (os, score)' }, + city: { type: 'array', description: 'City breakdown (city, name, countryCode, score)' }, + device: { type: 'array', description: 'Device breakdown' }, + social: { type: 'array', description: 'Social source breakdown (social, score)' }, + utmMedium: { type: 'array', description: 'UTM medium breakdown' }, + utmSource: { type: 'array', description: 'UTM source breakdown' }, + utmCampaign: { type: 'array', description: 'UTM campaign breakdown' }, + clickStatistics: { type: 'object', description: 'Time-series click data (datasets with x/y per interval)' }, + interval: { type: 'object', description: 'Date range (startDate, endDate, prevStartDate, prevEndDate, tz)' }, + error: { type: 'string', description: 'Error message if operation failed' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 03b9827a77..4ea4629950 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -60,6 +60,7 @@ import { GuardrailsBlock } from '@/blocks/blocks/guardrails' import { HexBlock } from '@/blocks/blocks/hex' import { HubSpotBlock } from '@/blocks/blocks/hubspot' import { HuggingFaceBlock } from '@/blocks/blocks/huggingface' +import { ShortIoBlock } from '@/blocks/blocks/short_io' import { HumanInTheLoopBlock } from '@/blocks/blocks/human_in_the_loop' import { HunterBlock } from '@/blocks/blocks/hunter' import { ImageGeneratorBlock } from '@/blocks/blocks/image_generator' @@ -323,6 +324,7 @@ export const registry: Record = { sftp: SftpBlock, sharepoint: SharepointBlock, shopify: ShopifyBlock, + short_io: ShortIoBlock, similarweb: SimilarwebBlock, slack: SlackBlock, smtp: SmtpBlock, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index dcd5741f2b..4588d793cb 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -76,7 +76,6 @@ export function ApiIcon(props: SVGProps) { ) } - export function ConditionalIcon(props: SVGProps) { return ( ) { ) } + +export function ShortIoIcon(props: SVGProps) { + return ( + + + + + + + ) +} \ No newline at end of file From 9dd9066ef6e7cabb390d74bf221118b3ce7380ad Mon Sep 17 00:00:00 2001 From: Vasyl Abramovych Date: Thu, 26 Feb 2026 18:07:24 -0800 Subject: [PATCH 3/5] docs(short-io): add Short.io tool documentation Add documentation page covering all 6 Short.io tools with input/output parameter tables and usage instructions. --- apps/docs/content/docs/en/tools/short_io.mdx | 177 +++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 apps/docs/content/docs/en/tools/short_io.mdx diff --git a/apps/docs/content/docs/en/tools/short_io.mdx b/apps/docs/content/docs/en/tools/short_io.mdx new file mode 100644 index 0000000000..5a09110ae0 --- /dev/null +++ b/apps/docs/content/docs/en/tools/short_io.mdx @@ -0,0 +1,177 @@ +--- +title: Short.io +description: Create and manage short links, domains, QR codes, and analytics +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[Short.io](https://short.io/) is a white-label URL shortener that lets you create branded short links on your own domain, track clicks, and manage links at scale. Short.io is designed for businesses that want professional short URLs, QR codes, and link analytics without relying on generic shorteners. + +With Short.io in Sim, you can: + +- **Create short links**: Generate branded short URLs from long URLs using your custom domain, with optional custom paths +- **List domains**: Retrieve all Short.io domains on your account to get domain IDs for listing links +- **List links**: List short links for a domain with pagination and optional date sort order +- **Delete links**: Remove a short link by its ID (e.g. lnk_abc123_abcdef) +- **Generate QR codes**: Create QR codes for any Short.io link with optional size, color, background color, and format (PNG or SVG); returns a base64 data URL +- **Get link statistics**: Fetch click analytics for a link including total clicks, human clicks, referrer/country/browser/OS/city breakdowns, UTM dimensions, time-series data, and date interval + +These capabilities allow your Sim agents to automate link shortening, QR code generation, and analytics reporting directly in your workflows — from campaign tracking to link management and performance dashboards. +{/* MANUAL-CONTENT-END */} + +## Usage Instructions + +Integrate Short.io into your workflow for link shortening, QR codes, and analytics. Authenticate with your Short.io Secret API Key. Create links, list domains and links, delete links, generate QR codes, and fetch per-link statistics. + +## Tools + +### `short_io_create_link` + +Create a short link using your Short.io custom domain. + +#### Input + +| Parameter | Type | Required | Description | +| ------------- | ------ | -------- | ---------------------------------------------------------- | +| `domain` | string | Yes | Your registered Short.io custom domain | +| `originalURL` | string | Yes | The long URL to shorten | +| `path` | string | No | Optional custom path for the short link | +| `apiKey` | string | Yes | Short.io Secret API Key | + +#### Output + +| Parameter | Type | Description | +| ---------- | ------- | ---------------------------------- | +| `success` | boolean | Whether the link was created | +| `shortURL` | string | The generated short link URL | +| `idString` | string | The unique Short.io link ID | +| `error` | string | Error message if failed | + +### `short_io_list_domains` + +List Short.io domains. Returns domain IDs and details for use in List Links. + +#### Input + +| Parameter | Type | Required | Description | +| ------------- | ------ | -------- | ------------------------------- | +| `apiKey` | string | Yes | Short.io Secret API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ------- | ------------------------------------ | +| `success` | boolean | Success status | +| `domains` | array | List of domain objects (id, hostname) | +| `count` | number | Number of domains | +| `error` | string | Error message | + +### `short_io_list_links` + +List short links for a domain. Requires domain_id from List Domains. Max 150 per request. + +#### Input + +| Parameter | Type | Required | Description | +| --------------- | ------ | -------- | ------------------------------------ | +| `domainId` | number | Yes | Domain ID (from List Domains) | +| `limit` | number | No | Max links to return (1–150) | +| `pageToken` | string | No | Pagination token from previous call | +| `dateSortOrder`| string | No | Sort by date: asc or desc | +| `apiKey` | string | Yes | Short.io Secret API Key | + +#### Output + +| Parameter | Type | Description | +| --------------- | ------- | ---------------------------------------- | +| `success` | boolean | Success status | +| `links` | array | List of link objects (idString, shortURL, originalURL, path, etc.) | +| `count` | number | Number of links returned | +| `nextPageToken` | string | Token for next page | +| `error` | string | Error message | + +### `short_io_delete_link` + +Delete a short link by ID. Rate limit 20/s. + +#### Input + +| Parameter | Type | Required | Description | +| ------------- | ------ | -------- | ------------------------------- | +| `linkId` | string | Yes | Link ID to delete (e.g. lnk_abc123_abcdef) | +| `apiKey` | string | Yes | Short.io Secret API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ------- | -------------------------- | +| `success` | boolean | Success status | +| `deleted` | boolean | Whether the link was deleted | +| `idString`| string | Deleted link ID | +| `error` | string | Error message | + +### `short_io_get_qr_code` + +Generate a QR code for a Short.io link. Returns a base64 data URL (e.g. data:image/png;base64,...). + +#### Input + +| Parameter | Type | Required | Description | +| --------------- | ------ | -------- | ------------------------------------ | +| `linkId` | string | Yes | Link ID (e.g. lnk_abc123_abcdef) | +| `color` | string | No | QR color hex (e.g. 000000) | +| `backgroundColor` | string | No | Background color hex (e.g. FFFFFF) | +| `size` | number | No | QR size 1–99 | +| `type` | string | No | Output format: png or svg | +| `apiKey` | string | Yes | Short.io Secret API Key | + +#### Output + +| Parameter | Type | Description | +| ---------- | ------- | ------------------------------------------------ | +| `success` | boolean | Success status | +| `qrCodeURL`| string | Base64 data URL of the QR code image | +| `error` | string | Error message | + +### `short_io_get_analytics` + +Fetch click statistics for a Short.io link. + +#### Input + +| Parameter | Type | Required | Description | +| ------------- | ------ | -------- | -------------------------------------------------------- | +| `linkId` | string | Yes | Link ID | +| `period` | string | Yes | Period: today, yesterday, last7, last30, total, week, month, lastmonth | +| `tz` | string | No | Timezone (default UTC) | +| `apiKey` | string | Yes | Short.io Secret API Key | + +#### Output + +| Parameter | Type | Description | +| ------------------- | ------- | ------------------------------------------------------ | +| `success` | boolean | Success status | +| `clicks` | number | Total clicks in period | +| `totalClicks` | number | Total clicks | +| `humanClicks` | number | Human clicks | +| `totalClicksChange` | string | Change vs previous period | +| `humanClicksChange` | string | Human clicks change | +| `referer` | array | Referrer breakdown (referer, score) | +| `country` | array | Country breakdown (countryName, country, score) | +| `browser` | array | Browser breakdown (browser, score) | +| `os` | array | OS breakdown (os, score) | +| `city` | array | City breakdown (city, name, countryCode, score) | +| `device` | array | Device breakdown | +| `social` | array | Social source breakdown (social, score) | +| `utmMedium` | array | UTM medium breakdown | +| `utmSource` | array | UTM source breakdown | +| `utmCampaign` | array | UTM campaign breakdown | +| `clickStatistics` | object | Time-series click data (datasets with x/y per interval) | +| `interval` | object | Date range (startDate, endDate, prevStartDate, prevEndDate, tz) | +| `error` | string | Error message | From 5e5d132ee85397ed93aacdffbb4c3a1edea41c74 Mon Sep 17 00:00:00 2001 From: Vasyl Abramovych Date: Thu, 26 Feb 2026 20:07:54 -0800 Subject: [PATCH 4/5] fix(short-io): address PR review feedback - Change apiKey visibility from 'hidden' to 'user-only' in all 6 tools - Simplify block tool selector to string interpolation - Move QR code generation to server-side API route, return as file object (name, mimeType, data, size) matching standard file pattern - Update block outputs and docs to reflect file type for QR code --- apps/docs/content/docs/en/tools/short_io.mdx | 4 +- apps/sim/app/api/tools/short_io/qr/route.ts | 98 ++++++++++++++++++++ apps/sim/blocks/blocks/short_io.ts | 21 +---- apps/sim/tools/short_io/create_link.ts | 2 +- apps/sim/tools/short_io/delete_link.ts | 2 +- apps/sim/tools/short_io/get_analytics.ts | 2 +- apps/sim/tools/short_io/get_qr_code.ts | 38 +++----- apps/sim/tools/short_io/list_domains.ts | 2 +- apps/sim/tools/short_io/list_links.ts | 2 +- 9 files changed, 121 insertions(+), 50 deletions(-) create mode 100644 apps/sim/app/api/tools/short_io/qr/route.ts diff --git a/apps/docs/content/docs/en/tools/short_io.mdx b/apps/docs/content/docs/en/tools/short_io.mdx index 5a09110ae0..05822ee6ab 100644 --- a/apps/docs/content/docs/en/tools/short_io.mdx +++ b/apps/docs/content/docs/en/tools/short_io.mdx @@ -135,9 +135,7 @@ Generate a QR code for a Short.io link. Returns a base64 data URL (e.g. data:ima | Parameter | Type | Description | | ---------- | ------- | ------------------------------------------------ | -| `success` | boolean | Success status | -| `qrCodeURL`| string | Base64 data URL of the QR code image | -| `error` | string | Error message | +| `file` | file | Generated QR code image file | ### `short_io_get_analytics` diff --git a/apps/sim/app/api/tools/short_io/qr/route.ts b/apps/sim/app/api/tools/short_io/qr/route.ts new file mode 100644 index 0000000000..2017351067 --- /dev/null +++ b/apps/sim/app/api/tools/short_io/qr/route.ts @@ -0,0 +1,98 @@ +import { createLogger } from '@sim/logger' +import { type NextRequest, NextResponse } from 'next/server' +import { z } from 'zod' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('ShortIoQrAPI') + +const ShortIoQrSchema = z.object({ + apiKey: z.string().min(1, 'API key is required'), + linkId: z.string().min(1, 'Link ID is required'), + color: z.string().optional(), + backgroundColor: z.string().optional(), + size: z.number().min(1).max(99).optional(), + type: z.enum(['png', 'svg']).optional(), + useDomainSettings: z.boolean().optional(), +}) + +export async function POST(request: NextRequest) { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + + if (!authResult.success) { + logger.warn(`[${requestId}] Unauthorized Short.io QR request: ${authResult.error}`) + return NextResponse.json( + { success: false, error: authResult.error || 'Authentication required' }, + { status: 401 } + ) + } + + const body = await request.json() + const validated = ShortIoQrSchema.parse(body) + + const qrBody: Record = { + useDomainSettings: validated.useDomainSettings ?? true, + } + if (validated.color) qrBody.color = validated.color + if (validated.backgroundColor) qrBody.backgroundColor = validated.backgroundColor + if (validated.size) qrBody.size = validated.size + if (validated.type) qrBody.type = validated.type + + const response = await fetch(`https://api.short.io/links/qr/${validated.linkId}`, { + method: 'POST', + headers: { + Authorization: validated.apiKey, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(qrBody), + }) + + if (!response.ok) { + const errorText = await response.text().catch(() => response.statusText) + logger.error(`[${requestId}] Short.io QR API error: ${errorText}`) + return NextResponse.json( + { success: false, error: `Short.io API error: ${errorText}` }, + { status: response.status } + ) + } + + const contentType = response.headers.get('Content-Type') ?? 'image/png' + const fileBuffer = Buffer.from(await response.arrayBuffer()) + const mimeType = contentType.split(';')[0]?.trim() || 'image/png' + const ext = validated.type === 'svg' ? 'svg' : 'png' + const fileName = `qr-${validated.linkId}.${ext}` + + logger.info(`[${requestId}] QR code generated`, { + linkId: validated.linkId, + size: fileBuffer.length, + mimeType, + }) + + return NextResponse.json({ + success: true, + output: { + file: { + name: fileName, + mimeType, + data: fileBuffer.toString('base64'), + size: fileBuffer.length, + }, + }, + }) + } catch (error: unknown) { + if (error instanceof z.ZodError) { + return NextResponse.json( + { success: false, error: `Validation error: ${error.errors.map((e) => e.message).join(', ')}` }, + { status: 400 } + ) + } + const message = error instanceof Error ? error.message : 'Unknown error' + logger.error(`[${requestId}] Short.io QR error: ${message}`) + return NextResponse.json({ success: false, error: message }, { status: 500 }) + } +} diff --git a/apps/sim/blocks/blocks/short_io.ts b/apps/sim/blocks/blocks/short_io.ts index 8041737fd5..a0d9263a7c 100644 --- a/apps/sim/blocks/blocks/short_io.ts +++ b/apps/sim/blocks/blocks/short_io.ts @@ -159,24 +159,7 @@ export const ShortIoBlock: BlockConfig = { 'short_io_get_analytics', ], config: { - tool: (params) => { - switch (params.operation) { - case 'create_link': - return 'short_io_create_link' - case 'list_domains': - return 'short_io_list_domains' - case 'list_links': - return 'short_io_list_links' - case 'delete_link': - return 'short_io_delete_link' - case 'get_qr_code': - return 'short_io_get_qr_code' - case 'get_analytics': - return 'short_io_get_analytics' - default: - throw new Error(`Invalid Short.io operation: ${params.operation}`) - } - }, + tool: (params) => `short_io_${params.operation}`, params: (params) => { const { apiKey, operation, size, domainId, limit, ...rest } = params const out: Record = { ...rest, apiKey } @@ -222,7 +205,7 @@ export const ShortIoBlock: BlockConfig = { links: { type: 'array', description: 'List of links (from List Links)' }, nextPageToken: { type: 'string', description: 'Pagination token for next page' }, deleted: { type: 'boolean', description: 'Whether the link was deleted' }, - qrCodeURL: { type: 'string', description: 'Base64 data URL of the generated QR code image' }, + file: { type: 'file', description: 'Generated QR code image file' }, clicks: { type: 'number', description: 'Total clicks in period' }, totalClicks: { type: 'number', description: 'Total clicks' }, humanClicks: { type: 'number', description: 'Human clicks' }, diff --git a/apps/sim/tools/short_io/create_link.ts b/apps/sim/tools/short_io/create_link.ts index c622508c04..b4531d8e40 100644 --- a/apps/sim/tools/short_io/create_link.ts +++ b/apps/sim/tools/short_io/create_link.ts @@ -10,7 +10,7 @@ export const shortIoCreateLinkTool: ToolConfig apiKey: { type: 'string', required: true, - visibility: 'hidden', + visibility: 'user-only', description: 'Short.io Secret API Key', }, linkId: { @@ -46,14 +46,15 @@ export const shortIoGetQrCodeTool: ToolConfig }, }, request: { - url: (params) => `https://api.short.io/links/qr/${params.linkId}`, + url: '/api/tools/short_io/qr', method: 'POST', - headers: (params) => ({ - Authorization: params.apiKey, + headers: () => ({ 'Content-Type': 'application/json', }), body: (params) => { const body: Record = { + apiKey: params.apiKey, + linkId: params.linkId, useDomainSettings: params.useDomainSettings ?? true, } if (params.color != null && params.color !== '') body.color = params.color @@ -64,31 +65,22 @@ export const shortIoGetQrCodeTool: ToolConfig }, }, transformResponse: async (response: Response) => { - if (!response.ok) { - const err = await response.text().catch(() => response.statusText) - return { success: false, output: { success: false, error: err } } + const data = await response.json().catch(() => ({})) + if (!response.ok || !data.success) { + return { + success: false, + output: { success: false, error: data.error || response.statusText }, + } } - - const contentType = response.headers.get('Content-Type') ?? '' - const blob = await response.blob() - const arrayBuffer = await blob.arrayBuffer() - const bytes = new Uint8Array(arrayBuffer) - let binary = '' - for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!) - const base64 = typeof btoa !== 'undefined' ? btoa(binary) : '' - const mediaType = contentType.split(';')[0]?.trim() || 'image/png' - const dataUrl = base64 ? `data:${mediaType};base64,${base64}` : '' return { success: true, - output: { success: true, qrCodeURL: dataUrl }, + output: data.output, } }, outputs: { - success: { type: 'boolean', description: 'Success status' }, - qrCodeURL: { - type: 'string', - description: 'Base64 data URL of the QR code image (e.g. data:image/png;base64,...)', + file: { + type: 'file', + description: 'Generated QR code image file', }, - error: { type: 'string', description: 'Error message' }, }, } diff --git a/apps/sim/tools/short_io/list_domains.ts b/apps/sim/tools/short_io/list_domains.ts index 2e6389a34d..0149b64e73 100644 --- a/apps/sim/tools/short_io/list_domains.ts +++ b/apps/sim/tools/short_io/list_domains.ts @@ -7,7 +7,7 @@ export const shortIoListDomainsTool: ToolConfig Date: Fri, 27 Feb 2026 09:44:10 -0800 Subject: [PATCH 5/5] fix(lint): satisfy biome for short.io Made-with: Cursor --- apps/sim/app/api/tools/short_io/qr/route.ts | 5 ++++- apps/sim/blocks/blocks/short_io.ts | 6 +++--- apps/sim/blocks/registry.ts | 2 +- apps/sim/components/icons.tsx | 7 +------ apps/sim/tools/registry.ts | 6 +++--- apps/sim/tools/short_io/delete_link.ts | 14 ++++++++++++-- apps/sim/tools/short_io/get_analytics.ts | 9 +++++++-- apps/sim/tools/short_io/get_qr_code.ts | 10 ++++++++-- apps/sim/tools/short_io/list_domains.ts | 9 +++++++-- apps/sim/tools/short_io/list_links.ts | 7 ++++++- 10 files changed, 52 insertions(+), 23 deletions(-) diff --git a/apps/sim/app/api/tools/short_io/qr/route.ts b/apps/sim/app/api/tools/short_io/qr/route.ts index 2017351067..39b64c2802 100644 --- a/apps/sim/app/api/tools/short_io/qr/route.ts +++ b/apps/sim/app/api/tools/short_io/qr/route.ts @@ -87,7 +87,10 @@ export async function POST(request: NextRequest) { } catch (error: unknown) { if (error instanceof z.ZodError) { return NextResponse.json( - { success: false, error: `Validation error: ${error.errors.map((e) => e.message).join(', ')}` }, + { + success: false, + error: `Validation error: ${error.errors.map((e) => e.message).join(', ')}`, + }, { status: 400 } ) } diff --git a/apps/sim/blocks/blocks/short_io.ts b/apps/sim/blocks/blocks/short_io.ts index a0d9263a7c..e82802434c 100644 --- a/apps/sim/blocks/blocks/short_io.ts +++ b/apps/sim/blocks/blocks/short_io.ts @@ -165,15 +165,15 @@ export const ShortIoBlock: BlockConfig = { const out: Record = { ...rest, apiKey } if (size !== undefined && size !== '') { const n = Number(size) - if (typeof n === 'number' && !isNaN(n) && n >= 1 && n <= 99) out.size = n + if (typeof n === 'number' && !Number.isNaN(n) && n >= 1 && n <= 99) out.size = n } if (operation === 'list_links' && domainId !== undefined && domainId !== '') { const d = Number(domainId) - if (typeof d === 'number' && !isNaN(d)) out.domainId = d + if (typeof d === 'number' && !Number.isNaN(d)) out.domainId = d } if (operation === 'list_links' && limit !== undefined && limit !== '') { const l = Number(limit) - if (typeof l === 'number' && !isNaN(l) && l >= 1 && l <= 150) out.limit = l + if (typeof l === 'number' && !Number.isNaN(l) && l >= 1 && l <= 150) out.limit = l } return out }, diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 4ea4629950..cfbcfb7042 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -60,7 +60,6 @@ import { GuardrailsBlock } from '@/blocks/blocks/guardrails' import { HexBlock } from '@/blocks/blocks/hex' import { HubSpotBlock } from '@/blocks/blocks/hubspot' import { HuggingFaceBlock } from '@/blocks/blocks/huggingface' -import { ShortIoBlock } from '@/blocks/blocks/short_io' import { HumanInTheLoopBlock } from '@/blocks/blocks/human_in_the_loop' import { HunterBlock } from '@/blocks/blocks/hunter' import { ImageGeneratorBlock } from '@/blocks/blocks/image_generator' @@ -131,6 +130,7 @@ import { ServiceNowBlock } from '@/blocks/blocks/servicenow' import { SftpBlock } from '@/blocks/blocks/sftp' import { SharepointBlock } from '@/blocks/blocks/sharepoint' import { ShopifyBlock } from '@/blocks/blocks/shopify' +import { ShortIoBlock } from '@/blocks/blocks/short_io' import { SimilarwebBlock } from '@/blocks/blocks/similarweb' import { SlackBlock } from '@/blocks/blocks/slack' import { SmtpBlock } from '@/blocks/blocks/smtp' diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 4588d793cb..f79fa028c3 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -5853,12 +5853,7 @@ export function HexIcon(props: SVGProps) { export function ShortIoIcon(props: SVGProps) { return ( - + `https://api.short.io/links/${encodeURIComponent(params.linkId)}`, diff --git a/apps/sim/tools/short_io/get_analytics.ts b/apps/sim/tools/short_io/get_analytics.ts index 35b060e3fe..ea7b0992cd 100644 --- a/apps/sim/tools/short_io/get_analytics.ts +++ b/apps/sim/tools/short_io/get_analytics.ts @@ -32,11 +32,16 @@ export const shortIoGetAnalyticsTool: ToolConfig { - const base = 'https://statistics.short.io/statistics/link/' + encodeURIComponent(params.linkId) + const base = `https://statistics.short.io/statistics/link/${encodeURIComponent(params.linkId)}` const period = STATS_PERIOD_MAP[params.period] ?? params.period ?? 'last30' const q = new URLSearchParams({ period }) if (params.tz) q.set('tz', params.tz) diff --git a/apps/sim/tools/short_io/get_qr_code.ts b/apps/sim/tools/short_io/get_qr_code.ts index 09a9b196d6..84bd4adad0 100644 --- a/apps/sim/tools/short_io/get_qr_code.ts +++ b/apps/sim/tools/short_io/get_qr_code.ts @@ -31,7 +31,12 @@ export const shortIoGetQrCodeTool: ToolConfig visibility: 'user-or-llm', description: 'Background color hex (e.g. FFFFFF)', }, - size: { type: 'number', required: false, visibility: 'user-or-llm', description: 'QR size 1–99' }, + size: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'QR size 1–99', + }, type: { type: 'string', required: false, @@ -58,7 +63,8 @@ export const shortIoGetQrCodeTool: ToolConfig useDomainSettings: params.useDomainSettings ?? true, } if (params.color != null && params.color !== '') body.color = params.color - if (params.backgroundColor != null && params.backgroundColor !== '') body.backgroundColor = params.backgroundColor + if (params.backgroundColor != null && params.backgroundColor !== '') + body.backgroundColor = params.backgroundColor if (params.size != null && params.size >= 1 && params.size <= 99) body.size = params.size if (params.type === 'svg' || params.type === 'png') body.type = params.type return body diff --git a/apps/sim/tools/short_io/list_domains.ts b/apps/sim/tools/short_io/list_domains.ts index 0149b64e73..313656daa0 100644 --- a/apps/sim/tools/short_io/list_domains.ts +++ b/apps/sim/tools/short_io/list_domains.ts @@ -7,7 +7,12 @@ export const shortIoListDomainsTool: ToolConfig ({})) - const list = Array.isArray(data) ? data : data.domains ?? data.list ?? [] + const list = Array.isArray(data) ? data : (data.domains ?? data.list ?? []) return { success: true, output: { success: true, domains: list, count: list.length }, diff --git a/apps/sim/tools/short_io/list_links.ts b/apps/sim/tools/short_io/list_links.ts index 88b1b796c5..b32affefa4 100644 --- a/apps/sim/tools/short_io/list_links.ts +++ b/apps/sim/tools/short_io/list_links.ts @@ -8,7 +8,12 @@ export const shortIoListLinksTool: ToolConfig