diff --git a/apps/sim/blocks/blocks/clockify.ts b/apps/sim/blocks/blocks/clockify.ts new file mode 100644 index 00000000000..fe70b5e84bb --- /dev/null +++ b/apps/sim/blocks/blocks/clockify.ts @@ -0,0 +1,358 @@ +import { ClockifyIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode } from '@/blocks/types' + +export const ClockifyBlock: BlockConfig = { + type: 'clockify', + name: 'Clockify', + description: 'Access Clockify workspaces, users, projects, and member profiles', + authMode: AuthMode.ApiKey, + longDescription: + 'Integrate Clockify into your workflow. Retrieve workspace details, team members, projects, and member profiles for time tracking management.', + category: 'tools', + icon: ClockifyIcon, + bgColor: '#03A9F4', + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Get Current User', id: 'clockify_get_current_user' }, + { label: 'Get Workspaces', id: 'clockify_get_workspaces' }, + { label: 'Get Workspace Users', id: 'clockify_get_users' }, + { label: 'Get Member Profile', id: 'clockify_get_member_profile' }, + { label: 'Get Projects', id: 'clockify_get_projects' }, + ], + value: () => 'clockify_get_current_user', + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + placeholder: 'Enter your Clockify API key', + password: true, + required: true, + }, + { + id: 'workspaceId', + title: 'Workspace ID', + type: 'short-input', + placeholder: 'Enter workspace ID', + required: true, + condition: { + field: 'operation', + value: ['clockify_get_current_user', 'clockify_get_workspaces'], + not: true, + }, + }, + { + id: 'userId', + title: 'User ID', + type: 'short-input', + placeholder: 'Enter user ID', + required: true, + condition: { + field: 'operation', + value: 'clockify_get_member_profile', + }, + }, + ], + tools: { + access: [ + 'clockify_get_current_user', + 'clockify_get_workspaces', + 'clockify_get_users', + 'clockify_get_member_profile', + 'clockify_get_projects', + ], + config: { + tool: (params) => params.operation || 'clockify_get_current_user', + params: (params) => { + const baseParams: Record = { + apiKey: params.apiKey, + } + + switch (params.operation) { + case 'clockify_get_current_user': + case 'clockify_get_workspaces': + return baseParams + + case 'clockify_get_users': + case 'clockify_get_projects': + return { + ...baseParams, + workspaceId: params.workspaceId, + } + + case 'clockify_get_member_profile': + return { + ...baseParams, + workspaceId: params.workspaceId, + userId: params.userId, + } + + default: + return baseParams + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'Clockify API key' }, + workspaceId: { type: 'string', description: 'Workspace ID' }, + userId: { type: 'string', description: 'User ID' }, + }, + outputs: { + response: { type: 'json', description: 'API response data' }, + }, +} + +export const ClockifyReportsBlock: BlockConfig = { + type: 'clockify_reports', + name: 'Clockify Reports', + description: 'Generate Clockify time tracking reports', + authMode: AuthMode.ApiKey, + longDescription: + 'Generate summary, detailed, weekly, and attendance reports from Clockify. Filter by date range, users, and projects for comprehensive time tracking analysis.', + category: 'tools', + icon: ClockifyIcon, + bgColor: '#03A9F4', + subBlocks: [ + { + id: 'operation', + title: 'Report Type', + type: 'dropdown', + options: [ + { label: 'Summary Report', id: 'clockify_report_summary' }, + { label: 'Detailed Report', id: 'clockify_report_detailed' }, + { label: 'Weekly Report', id: 'clockify_report_weekly' }, + { label: 'Attendance Report', id: 'clockify_report_attendance' }, + ], + value: () => 'clockify_report_summary', + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + placeholder: 'Enter your Clockify API key', + password: true, + required: true, + }, + { + id: 'workspaceId', + title: 'Workspace ID', + type: 'short-input', + placeholder: 'Enter workspace ID', + required: true, + }, + { + id: 'dateRangeStart', + title: 'Start Date', + type: 'short-input', + placeholder: 'ISO8601 (e.g., 2024-01-01T00:00:00Z)', + required: true, + }, + { + id: 'dateRangeEnd', + title: 'End Date', + type: 'short-input', + placeholder: 'ISO8601 (e.g., 2024-01-31T23:59:59Z)', + required: true, + }, + { + id: 'userIds', + title: 'User IDs', + type: 'short-input', + placeholder: 'Comma-separated user IDs (optional)', + }, + { + id: 'projectIds', + title: 'Project IDs', + type: 'short-input', + placeholder: 'Comma-separated project IDs (optional)', + }, + ], + tools: { + access: [ + 'clockify_report_summary', + 'clockify_report_detailed', + 'clockify_report_weekly', + 'clockify_report_attendance', + ], + config: { + tool: (params) => params.operation || 'clockify_report_summary', + params: (params) => ({ + apiKey: params.apiKey, + workspaceId: params.workspaceId, + dateRangeStart: params.dateRangeStart, + dateRangeEnd: params.dateRangeEnd, + userIds: params.userIds || undefined, + projectIds: params.projectIds || undefined, + }), + }, + }, + inputs: { + operation: { type: 'string', description: 'Report type to generate' }, + apiKey: { type: 'string', description: 'Clockify API key' }, + workspaceId: { type: 'string', description: 'Workspace ID' }, + dateRangeStart: { type: 'string', description: 'Start date in ISO8601 format' }, + dateRangeEnd: { type: 'string', description: 'End date in ISO8601 format' }, + userIds: { type: 'string', description: 'Comma-separated user IDs to filter' }, + projectIds: { type: 'string', description: 'Comma-separated project IDs to filter' }, + }, + outputs: { + response: { type: 'json', description: 'Report data' }, + }, +} + +export const ClockifyTimeBlock: BlockConfig = { + type: 'clockify_time', + name: 'Clockify Time', + description: 'Access Clockify time entries, timers, time off, and holidays', + authMode: AuthMode.ApiKey, + longDescription: + 'Retrieve time entries, in-progress timers, time off requests, and holidays from Clockify for detailed time tracking and absence management.', + category: 'tools', + icon: ClockifyIcon, + bgColor: '#03A9F4', + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Get Time Entries', id: 'clockify_get_time_entries' }, + { label: 'Get Time Entry', id: 'clockify_get_time_entry' }, + { label: 'In-Progress Timers', id: 'clockify_get_in_progress' }, + { label: 'Time Off Requests', id: 'clockify_get_time_off' }, + { label: 'Holidays', id: 'clockify_get_holidays' }, + ], + value: () => 'clockify_get_time_entries', + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + placeholder: 'Enter your Clockify API key', + password: true, + required: true, + }, + { + id: 'workspaceId', + title: 'Workspace ID', + type: 'short-input', + placeholder: 'Enter workspace ID', + required: true, + }, + { + id: 'userId', + title: 'User ID', + type: 'short-input', + placeholder: 'Enter user ID', + required: true, + condition: { + field: 'operation', + value: 'clockify_get_time_entries', + }, + }, + { + id: 'timeEntryId', + title: 'Time Entry ID', + type: 'short-input', + placeholder: 'Enter time entry ID', + required: true, + condition: { + field: 'operation', + value: 'clockify_get_time_entry', + }, + }, + { + id: 'start', + title: 'Start Date', + type: 'short-input', + placeholder: 'ISO8601 (e.g., 2024-01-01T00:00:00Z)', + condition: { + field: 'operation', + value: ['clockify_get_time_entries', 'clockify_get_time_off', 'clockify_get_holidays'], + }, + }, + { + id: 'end', + title: 'End Date', + type: 'short-input', + placeholder: 'ISO8601 (e.g., 2024-01-31T23:59:59Z)', + condition: { + field: 'operation', + value: ['clockify_get_time_entries', 'clockify_get_time_off', 'clockify_get_holidays'], + }, + }, + ], + tools: { + access: [ + 'clockify_get_time_entries', + 'clockify_get_time_entry', + 'clockify_get_in_progress', + 'clockify_get_time_off', + 'clockify_get_holidays', + ], + config: { + tool: (params) => params.operation || 'clockify_get_time_entries', + params: (params) => { + const baseParams: Record = { + apiKey: params.apiKey, + workspaceId: params.workspaceId, + } + + switch (params.operation) { + case 'clockify_get_time_entries': + return { + ...baseParams, + userId: params.userId, + start: params.start || undefined, + end: params.end || undefined, + } + + case 'clockify_get_time_entry': + return { + ...baseParams, + timeEntryId: params.timeEntryId, + } + + case 'clockify_get_in_progress': + return baseParams + + case 'clockify_get_time_off': + return { + ...baseParams, + start: params.start || undefined, + end: params.end || undefined, + } + + case 'clockify_get_holidays': + return { + ...baseParams, + start: params.start || undefined, + end: params.end || undefined, + } + + default: + return baseParams + } + }, + }, + }, + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'Clockify API key' }, + workspaceId: { type: 'string', description: 'Workspace ID' }, + userId: { type: 'string', description: 'User ID' }, + timeEntryId: { type: 'string', description: 'Time entry ID' }, + start: { type: 'string', description: 'Start date in ISO8601 format' }, + end: { type: 'string', description: 'End date in ISO8601 format' }, + }, + outputs: { + response: { type: 'json', description: 'API response data' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index ee75b893544..fc29ef414ac 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -21,6 +21,7 @@ import { ChatTriggerBlock } from '@/blocks/blocks/chat_trigger' import { CirclebackBlock } from '@/blocks/blocks/circleback' import { ClayBlock } from '@/blocks/blocks/clay' import { ClerkBlock } from '@/blocks/blocks/clerk' +import { ClockifyBlock, ClockifyReportsBlock, ClockifyTimeBlock } from '@/blocks/blocks/clockify' import { CloudflareBlock } from '@/blocks/blocks/cloudflare' import { ConditionBlock } from '@/blocks/blocks/condition' import { ConfluenceBlock, ConfluenceV2Block } from '@/blocks/blocks/confluence' @@ -223,6 +224,9 @@ export const registry: Record = { cloudflare: CloudflareBlock, clay: ClayBlock, clerk: ClerkBlock, + clockify: ClockifyBlock, + clockify_reports: ClockifyReportsBlock, + clockify_time: ClockifyTimeBlock, condition: ConditionBlock, confluence: ConfluenceBlock, confluence_v2: ConfluenceV2Block, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index a1c6beb37fd..59f68f1f12c 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -2316,6 +2316,17 @@ export function ClayIcon(props: SVGProps) { ) } +export function ClockifyIcon(props: SVGProps) { + return ( + + + + + + + ) +} + export function ClerkIcon(props: SVGProps) { return ( diff --git a/apps/sim/lib/auth/anonymous.ts b/apps/sim/lib/auth/anonymous.ts index 97a06b9565a..1fa4926c4da 100644 --- a/apps/sim/lib/auth/anonymous.ts +++ b/apps/sim/lib/auth/anonymous.ts @@ -103,6 +103,6 @@ export function createAnonymousSession(): AnonymousSession { } } -export function createAnonymousGetSessionResponse(): { data: AnonymousSession } { - return { data: createAnonymousSession() } +export function createAnonymousGetSessionResponse(): AnonymousSession { + return createAnonymousSession() } diff --git a/apps/sim/tools/clockify/get_current_user.ts b/apps/sim/tools/clockify/get_current_user.ts new file mode 100644 index 00000000000..0d6d2fa49a2 --- /dev/null +++ b/apps/sim/tools/clockify/get_current_user.ts @@ -0,0 +1,73 @@ +import type { ClockifyGetCurrentUserParams, ClockifyGetCurrentUserResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyGetCurrentUserTool: ToolConfig< + ClockifyGetCurrentUserParams, + ClockifyGetCurrentUserResponse +> = { + id: 'clockify_get_current_user', + name: 'Clockify Get Current User', + description: 'Get the currently authenticated Clockify user profile', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + }, + + request: { + url: 'https://api.clockify.me/api/v1/user', + method: 'GET', + headers: (params) => ({ + 'X-Api-Key': params.apiKey, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get current user') + } + + return { + success: true, + output: data, + } + }, + + outputs: { + id: { + type: 'string', + description: 'User ID', + }, + name: { + type: 'string', + description: 'User name', + }, + email: { + type: 'string', + description: 'User email address', + }, + status: { + type: 'string', + description: 'User status', + }, + profilePicture: { + type: 'string', + description: 'URL to the user profile picture', + }, + activeWorkspace: { + type: 'string', + description: 'ID of the active workspace', + }, + defaultWorkspace: { + type: 'string', + description: 'ID of the default workspace', + }, + }, +} diff --git a/apps/sim/tools/clockify/get_holidays.ts b/apps/sim/tools/clockify/get_holidays.ts new file mode 100644 index 00000000000..8e4c1d423b1 --- /dev/null +++ b/apps/sim/tools/clockify/get_holidays.ts @@ -0,0 +1,75 @@ +import type { ClockifyGetHolidaysParams, ClockifyGetHolidaysResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyGetHolidaysTool: ToolConfig< + ClockifyGetHolidaysParams, + ClockifyGetHolidaysResponse +> = { + id: 'clockify_get_holidays', + name: 'Clockify Holidays', + description: 'Get holidays configured in a Clockify workspace', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Workspace ID to get holidays from', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter start date in ISO8601 format (e.g., "2024-01-01T00:00:00Z")', + }, + end: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter end date in ISO8601 format (e.g., "2024-12-31T23:59:59Z")', + }, + }, + + request: { + url: (params) => { + const base = `https://api.clockify.me/api/v1/workspaces/${params.workspaceId}/holidays` + const query: string[] = [] + if (params.start) query.push(`start=${encodeURIComponent(params.start)}`) + if (params.end) query.push(`end=${encodeURIComponent(params.end)}`) + return query.length > 0 ? `${base}?${query.join('&')}` : base + }, + method: 'GET', + headers: (params) => ({ + 'X-Api-Key': params.apiKey, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get holidays') + } + + return { + success: true, + output: { + holidays: data, + }, + } + }, + + outputs: { + holidays: { + type: 'json', + description: 'Array of holiday objects', + }, + }, +} diff --git a/apps/sim/tools/clockify/get_in_progress.ts b/apps/sim/tools/clockify/get_in_progress.ts new file mode 100644 index 00000000000..51082d85ad7 --- /dev/null +++ b/apps/sim/tools/clockify/get_in_progress.ts @@ -0,0 +1,68 @@ +import type { ClockifyGetInProgressParams, ClockifyGetInProgressResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyGetInProgressTool: ToolConfig< + ClockifyGetInProgressParams, + ClockifyGetInProgressResponse +> = { + id: 'clockify_get_in_progress', + name: 'Clockify In-Progress Timers', + description: 'Get currently running time entries in a Clockify workspace', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Workspace ID to check for in-progress timers', + }, + }, + + request: { + url: (params) => + `https://api.clockify.me/api/v1/workspaces/${params.workspaceId}/time-entries/in-progress`, + method: 'GET', + headers: (params) => ({ + 'X-Api-Key': params.apiKey, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get in-progress timers') + } + + return { + success: true, + output: { + timeEntries: Array.isArray(data) ? data : [data].filter(Boolean), + }, + } + }, + + outputs: { + timeEntries: { + type: 'array', + description: 'Array of in-progress time entry objects', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Time entry ID' }, + description: { type: 'string', description: 'Time entry description' }, + timeInterval: { type: 'object', description: 'Start time and duration of the entry' }, + projectId: { type: 'string', description: 'Associated project ID' }, + userId: { type: 'string', description: 'User ID who created the entry' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/clockify/get_member_profile.ts b/apps/sim/tools/clockify/get_member_profile.ts new file mode 100644 index 00000000000..488b8ce28f6 --- /dev/null +++ b/apps/sim/tools/clockify/get_member_profile.ts @@ -0,0 +1,73 @@ +import type { + ClockifyGetMemberProfileParams, + ClockifyGetMemberProfileResponse, +} from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyGetMemberProfileTool: ToolConfig< + ClockifyGetMemberProfileParams, + ClockifyGetMemberProfileResponse +> = { + id: 'clockify_get_member_profile', + name: 'Clockify Get Member Profile', + description: 'Get a member profile from a Clockify workspace', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Workspace ID', + }, + userId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'User ID to get the member profile for', + }, + }, + + request: { + url: (params) => + `https://api.clockify.me/api/v1/workspaces/${params.workspaceId}/member-profile/${params.userId}`, + method: 'GET', + headers: (params) => ({ + 'X-Api-Key': params.apiKey, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get member profile') + } + + return { + success: true, + output: data, + } + }, + + outputs: { + workCapacity: { + type: 'string', + description: 'Work capacity of the member', + }, + costRate: { + type: 'json', + description: 'Cost rate information for the member', + }, + weeklyWorkingDays: { + type: 'json', + description: 'Weekly working days configuration', + }, + }, +} diff --git a/apps/sim/tools/clockify/get_projects.ts b/apps/sim/tools/clockify/get_projects.ts new file mode 100644 index 00000000000..6be12a549e9 --- /dev/null +++ b/apps/sim/tools/clockify/get_projects.ts @@ -0,0 +1,68 @@ +import type { ClockifyGetProjectsParams, ClockifyGetProjectsResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyGetProjectsTool: ToolConfig< + ClockifyGetProjectsParams, + ClockifyGetProjectsResponse +> = { + id: 'clockify_get_projects', + name: 'Clockify Get Projects', + description: 'Get all projects in a Clockify workspace', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Workspace ID to get projects from', + }, + }, + + request: { + url: (params) => `https://api.clockify.me/api/v1/workspaces/${params.workspaceId}/projects`, + method: 'GET', + headers: (params) => ({ + 'X-Api-Key': params.apiKey, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get projects') + } + + return { + success: true, + output: { + projects: data, + }, + } + }, + + outputs: { + projects: { + type: 'array', + description: 'Array of project objects', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Project ID' }, + name: { type: 'string', description: 'Project name' }, + clientId: { type: 'string', description: 'Client ID associated with the project' }, + color: { type: 'string', description: 'Project color' }, + archived: { type: 'boolean', description: 'Whether the project is archived' }, + billable: { type: 'boolean', description: 'Whether the project is billable' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/clockify/get_time_entries.ts b/apps/sim/tools/clockify/get_time_entries.ts new file mode 100644 index 00000000000..f095d9f1b49 --- /dev/null +++ b/apps/sim/tools/clockify/get_time_entries.ts @@ -0,0 +1,92 @@ +import type { ClockifyGetTimeEntriesParams, ClockifyGetTimeEntriesResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyGetTimeEntriesTool: ToolConfig< + ClockifyGetTimeEntriesParams, + ClockifyGetTimeEntriesResponse +> = { + id: 'clockify_get_time_entries', + name: 'Clockify Get Time Entries', + description: 'Get time entries for a user in a Clockify workspace with optional date filtering', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Workspace ID to get time entries from', + }, + userId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'User ID to get time entries for', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter start date in ISO8601 format (e.g., "2024-01-01T00:00:00Z")', + }, + end: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter end date in ISO8601 format (e.g., "2024-01-31T23:59:59Z")', + }, + }, + + request: { + url: (params) => { + const base = `https://api.clockify.me/api/v1/workspaces/${params.workspaceId}/user/${params.userId}/time-entries` + const query: string[] = [] + if (params.start) query.push(`start=${encodeURIComponent(params.start)}`) + if (params.end) query.push(`end=${encodeURIComponent(params.end)}`) + return query.length > 0 ? `${base}?${query.join('&')}` : base + }, + method: 'GET', + headers: (params) => ({ + 'X-Api-Key': params.apiKey, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get time entries') + } + + return { + success: true, + output: { + timeEntries: data, + }, + } + }, + + outputs: { + timeEntries: { + type: 'array', + description: 'Array of time entry objects', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Time entry ID' }, + description: { type: 'string', description: 'Time entry description' }, + timeInterval: { type: 'object', description: 'Start, end, and duration of the entry' }, + projectId: { type: 'string', description: 'Associated project ID' }, + billable: { type: 'boolean', description: 'Whether the entry is billable' }, + userId: { type: 'string', description: 'User ID who created the entry' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/clockify/get_time_entry.ts b/apps/sim/tools/clockify/get_time_entry.ts new file mode 100644 index 00000000000..45895a4156b --- /dev/null +++ b/apps/sim/tools/clockify/get_time_entry.ts @@ -0,0 +1,82 @@ +import type { ClockifyGetTimeEntryParams, ClockifyGetTimeEntryResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyGetTimeEntryTool: ToolConfig< + ClockifyGetTimeEntryParams, + ClockifyGetTimeEntryResponse +> = { + id: 'clockify_get_time_entry', + name: 'Clockify Get Time Entry', + description: 'Get details of a single time entry by ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Workspace ID the time entry belongs to', + }, + timeEntryId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Time entry ID to retrieve', + }, + }, + + request: { + url: (params) => + `https://api.clockify.me/api/v1/workspaces/${params.workspaceId}/time-entries/${params.timeEntryId}`, + method: 'GET', + headers: (params) => ({ + 'X-Api-Key': params.apiKey, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get time entry') + } + + return { + success: true, + output: data, + } + }, + + outputs: { + id: { + type: 'string', + description: 'Time entry ID', + }, + description: { + type: 'string', + description: 'Time entry description', + }, + timeInterval: { + type: 'json', + description: 'Start, end, and duration of the entry', + }, + projectId: { + type: 'string', + description: 'Associated project ID', + }, + billable: { + type: 'boolean', + description: 'Whether the entry is billable', + }, + userId: { + type: 'string', + description: 'User ID who created the entry', + }, + }, +} diff --git a/apps/sim/tools/clockify/get_time_off.ts b/apps/sim/tools/clockify/get_time_off.ts new file mode 100644 index 00000000000..b04a5f3f450 --- /dev/null +++ b/apps/sim/tools/clockify/get_time_off.ts @@ -0,0 +1,77 @@ +import type { ClockifyGetTimeOffParams, ClockifyGetTimeOffResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyGetTimeOffTool: ToolConfig< + ClockifyGetTimeOffParams, + ClockifyGetTimeOffResponse +> = { + id: 'clockify_get_time_off', + name: 'Clockify Time Off Requests', + description: 'Get time-off requests for a Clockify workspace with optional date filtering', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Workspace ID to get time-off requests from', + }, + start: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter start date in ISO8601 format (e.g., "2024-01-01T00:00:00Z")', + }, + end: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter end date in ISO8601 format (e.g., "2024-01-31T23:59:59Z")', + }, + }, + + request: { + url: (params) => + `https://api.clockify.me/api/v1/workspaces/${params.workspaceId}/time-off/requests`, + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + 'X-Api-Key': params.apiKey, + }), + body: (params) => { + const body: Record = {} + if (params.start) body.start = params.start + if (params.end) body.end = params.end + return body + }, + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get time-off requests') + } + + return { + success: true, + output: { + requests: data, + }, + } + }, + + outputs: { + requests: { + type: 'json', + description: 'Array of time-off request objects', + }, + }, +} diff --git a/apps/sim/tools/clockify/get_users.ts b/apps/sim/tools/clockify/get_users.ts new file mode 100644 index 00000000000..f7b8c1203bf --- /dev/null +++ b/apps/sim/tools/clockify/get_users.ts @@ -0,0 +1,66 @@ +import type { ClockifyGetUsersParams, ClockifyGetUsersResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyGetUsersTool: ToolConfig< + ClockifyGetUsersParams, + ClockifyGetUsersResponse +> = { + id: 'clockify_get_users', + name: 'Clockify Get Workspace Users', + description: 'Get all users in a Clockify workspace', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Workspace ID to get users from', + }, + }, + + request: { + url: (params) => `https://api.clockify.me/api/v1/workspaces/${params.workspaceId}/users`, + method: 'GET', + headers: (params) => ({ + 'X-Api-Key': params.apiKey, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get workspace users') + } + + return { + success: true, + output: { + users: data, + }, + } + }, + + outputs: { + users: { + type: 'array', + description: 'Array of user objects', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'User ID' }, + name: { type: 'string', description: 'User name' }, + email: { type: 'string', description: 'User email address' }, + status: { type: 'string', description: 'User status' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/clockify/get_workspaces.ts b/apps/sim/tools/clockify/get_workspaces.ts new file mode 100644 index 00000000000..2bd0a22b54b --- /dev/null +++ b/apps/sim/tools/clockify/get_workspaces.ts @@ -0,0 +1,58 @@ +import type { ClockifyGetWorkspacesParams, ClockifyGetWorkspacesResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyGetWorkspacesTool: ToolConfig< + ClockifyGetWorkspacesParams, + ClockifyGetWorkspacesResponse +> = { + id: 'clockify_get_workspaces', + name: 'Clockify Get Workspaces', + description: 'Get all workspaces the authenticated user belongs to', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + }, + + request: { + url: 'https://api.clockify.me/api/v1/workspaces', + method: 'GET', + headers: (params) => ({ + 'X-Api-Key': params.apiKey, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to get workspaces') + } + + return { + success: true, + output: { + workspaces: data, + }, + } + }, + + outputs: { + workspaces: { + type: 'array', + description: 'Array of workspace objects', + items: { + type: 'object', + properties: { + id: { type: 'string', description: 'Workspace ID' }, + name: { type: 'string', description: 'Workspace name' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/clockify/index.ts b/apps/sim/tools/clockify/index.ts new file mode 100644 index 00000000000..06db9bf4824 --- /dev/null +++ b/apps/sim/tools/clockify/index.ts @@ -0,0 +1,14 @@ +export { clockifyGetCurrentUserTool } from './get_current_user' +export { clockifyGetHolidaysTool } from './get_holidays' +export { clockifyGetInProgressTool } from './get_in_progress' +export { clockifyGetMemberProfileTool } from './get_member_profile' +export { clockifyGetProjectsTool } from './get_projects' +export { clockifyGetTimeEntriesTool } from './get_time_entries' +export { clockifyGetTimeEntryTool } from './get_time_entry' +export { clockifyGetTimeOffTool } from './get_time_off' +export { clockifyGetUsersTool } from './get_users' +export { clockifyGetWorkspacesTool } from './get_workspaces' +export { clockifyReportAttendanceTool } from './report_attendance' +export { clockifyReportDetailedTool } from './report_detailed' +export { clockifyReportSummaryTool } from './report_summary' +export { clockifyReportWeeklyTool } from './report_weekly' diff --git a/apps/sim/tools/clockify/report_attendance.ts b/apps/sim/tools/clockify/report_attendance.ts new file mode 100644 index 00000000000..e563b879d06 --- /dev/null +++ b/apps/sim/tools/clockify/report_attendance.ts @@ -0,0 +1,108 @@ +import type { ClockifyReportAttendanceParams, ClockifyReportAttendanceResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyReportAttendanceTool: ToolConfig< + ClockifyReportAttendanceParams, + ClockifyReportAttendanceResponse +> = { + id: 'clockify_report_attendance', + name: 'Clockify Attendance Report', + description: 'Generate an attendance report for a workspace with optional user and project filters', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Workspace ID to generate the report for', + }, + dateRangeStart: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Start date in ISO8601 format (e.g., "2024-01-01T00:00:00Z")', + }, + dateRangeEnd: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'End date in ISO8601 format (e.g., "2024-01-31T23:59:59Z")', + }, + userIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of user IDs to filter by', + }, + projectIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of project IDs to filter by', + }, + }, + + request: { + url: (params) => + `https://reports.api.clockify.me/v1/workspaces/${params.workspaceId}/reports/attendance`, + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + 'X-Api-Key': params.apiKey, + }), + body: (params) => { + const body: Record = { + dateRangeStart: params.dateRangeStart, + dateRangeEnd: params.dateRangeEnd, + } + if (params.userIds) { + const ids = params.userIds + .split(',') + .map((id: string) => id.trim()) + .filter(Boolean) + if (ids.length > 0) { + body.users = { ids, contains: 'CONTAINS', status: 'ALL' } + } + } + if (params.projectIds) { + const ids = params.projectIds + .split(',') + .map((id: string) => id.trim()) + .filter(Boolean) + if (ids.length > 0) { + body.projects = { ids, contains: 'CONTAINS', status: 'ALL' } + } + } + return body + }, + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to generate report') + } + + return { + success: true, + output: { + attendance: data.groupOne || [], + }, + } + }, + + outputs: { + attendance: { + type: 'json', + description: 'Attendance report records', + }, + }, +} diff --git a/apps/sim/tools/clockify/report_detailed.ts b/apps/sim/tools/clockify/report_detailed.ts new file mode 100644 index 00000000000..2a8794362dd --- /dev/null +++ b/apps/sim/tools/clockify/report_detailed.ts @@ -0,0 +1,113 @@ +import type { ClockifyReportDetailedParams, ClockifyReportDetailedResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyReportDetailedTool: ToolConfig< + ClockifyReportDetailedParams, + ClockifyReportDetailedResponse +> = { + id: 'clockify_report_detailed', + name: 'Clockify Detailed Report', + description: 'Generate a detailed time entry report for a workspace with optional user and project filters', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Workspace ID to generate the report for', + }, + dateRangeStart: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Start date in ISO8601 format (e.g., "2024-01-01T00:00:00Z")', + }, + dateRangeEnd: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'End date in ISO8601 format (e.g., "2024-01-31T23:59:59Z")', + }, + userIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of user IDs to filter by', + }, + projectIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of project IDs to filter by', + }, + }, + + request: { + url: (params) => + `https://reports.api.clockify.me/v1/workspaces/${params.workspaceId}/reports/detailed`, + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + 'X-Api-Key': params.apiKey, + }), + body: (params) => { + const body: Record = { + dateRangeStart: params.dateRangeStart, + dateRangeEnd: params.dateRangeEnd, + } + if (params.userIds) { + const ids = params.userIds + .split(',') + .map((id: string) => id.trim()) + .filter(Boolean) + if (ids.length > 0) { + body.users = { ids, contains: 'CONTAINS', status: 'ALL' } + } + } + if (params.projectIds) { + const ids = params.projectIds + .split(',') + .map((id: string) => id.trim()) + .filter(Boolean) + if (ids.length > 0) { + body.projects = { ids, contains: 'CONTAINS', status: 'ALL' } + } + } + return body + }, + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to generate report') + } + + return { + success: true, + output: { + timeentries: data.timeentries || [], + totals: data.totals?.[0] || {}, + }, + } + }, + + outputs: { + timeentries: { + type: 'json', + description: 'Detailed time entry records', + }, + totals: { + type: 'json', + description: 'Detailed report totals', + }, + }, +} diff --git a/apps/sim/tools/clockify/report_summary.ts b/apps/sim/tools/clockify/report_summary.ts new file mode 100644 index 00000000000..47efb351bb5 --- /dev/null +++ b/apps/sim/tools/clockify/report_summary.ts @@ -0,0 +1,113 @@ +import type { ClockifyReportSummaryParams, ClockifyReportSummaryResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyReportSummaryTool: ToolConfig< + ClockifyReportSummaryParams, + ClockifyReportSummaryResponse +> = { + id: 'clockify_report_summary', + name: 'Clockify Summary Report', + description: 'Generate a summary report for a workspace with optional user and project filters', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Workspace ID to generate the report for', + }, + dateRangeStart: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Start date in ISO8601 format (e.g., "2024-01-01T00:00:00Z")', + }, + dateRangeEnd: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'End date in ISO8601 format (e.g., "2024-01-31T23:59:59Z")', + }, + userIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of user IDs to filter by', + }, + projectIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of project IDs to filter by', + }, + }, + + request: { + url: (params) => + `https://reports.api.clockify.me/v1/workspaces/${params.workspaceId}/reports/summary`, + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + 'X-Api-Key': params.apiKey, + }), + body: (params) => { + const body: Record = { + dateRangeStart: params.dateRangeStart, + dateRangeEnd: params.dateRangeEnd, + } + if (params.userIds) { + const ids = params.userIds + .split(',') + .map((id: string) => id.trim()) + .filter(Boolean) + if (ids.length > 0) { + body.users = { ids, contains: 'CONTAINS', status: 'ALL' } + } + } + if (params.projectIds) { + const ids = params.projectIds + .split(',') + .map((id: string) => id.trim()) + .filter(Boolean) + if (ids.length > 0) { + body.projects = { ids, contains: 'CONTAINS', status: 'ALL' } + } + } + return body + }, + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to generate report') + } + + return { + success: true, + output: { + groups: data.groupOne || [], + totals: data.totals?.[0] || {}, + }, + } + }, + + outputs: { + groups: { + type: 'json', + description: 'Summary report groups', + }, + totals: { + type: 'json', + description: 'Summary report totals', + }, + }, +} diff --git a/apps/sim/tools/clockify/report_weekly.ts b/apps/sim/tools/clockify/report_weekly.ts new file mode 100644 index 00000000000..ea5adf1eed4 --- /dev/null +++ b/apps/sim/tools/clockify/report_weekly.ts @@ -0,0 +1,113 @@ +import type { ClockifyReportWeeklyParams, ClockifyReportWeeklyResponse } from '@/tools/clockify/types' +import type { ToolConfig } from '@/tools/types' + +export const clockifyReportWeeklyTool: ToolConfig< + ClockifyReportWeeklyParams, + ClockifyReportWeeklyResponse +> = { + id: 'clockify_report_weekly', + name: 'Clockify Weekly Report', + description: 'Generate a weekly time report for a workspace with optional user and project filters', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Clockify API key', + }, + workspaceId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Workspace ID to generate the report for', + }, + dateRangeStart: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Start date in ISO8601 format (e.g., "2024-01-01T00:00:00Z")', + }, + dateRangeEnd: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'End date in ISO8601 format (e.g., "2024-01-31T23:59:59Z")', + }, + userIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of user IDs to filter by', + }, + projectIds: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of project IDs to filter by', + }, + }, + + request: { + url: (params) => + `https://reports.api.clockify.me/v1/workspaces/${params.workspaceId}/reports/weekly`, + method: 'POST', + headers: (params) => ({ + 'Content-Type': 'application/json', + 'X-Api-Key': params.apiKey, + }), + body: (params) => { + const body: Record = { + dateRangeStart: params.dateRangeStart, + dateRangeEnd: params.dateRangeEnd, + } + if (params.userIds) { + const ids = params.userIds + .split(',') + .map((id: string) => id.trim()) + .filter(Boolean) + if (ids.length > 0) { + body.users = { ids, contains: 'CONTAINS', status: 'ALL' } + } + } + if (params.projectIds) { + const ids = params.projectIds + .split(',') + .map((id: string) => id.trim()) + .filter(Boolean) + if (ids.length > 0) { + body.projects = { ids, contains: 'CONTAINS', status: 'ALL' } + } + } + return body + }, + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.message || data.error || 'Failed to generate report') + } + + return { + success: true, + output: { + weeks: data.groupOne || [], + totals: data.totals?.[0] || {}, + }, + } + }, + + outputs: { + weeks: { + type: 'json', + description: 'Weekly report groups', + }, + totals: { + type: 'json', + description: 'Weekly report totals', + }, + }, +} diff --git a/apps/sim/tools/clockify/types.ts b/apps/sim/tools/clockify/types.ts new file mode 100644 index 00000000000..0b787b235a7 --- /dev/null +++ b/apps/sim/tools/clockify/types.ts @@ -0,0 +1,377 @@ +/** + * Clockify API Types + * Base URL: https://api.clockify.me/api/v1 + * Reports URL: https://reports.api.clockify.me/v1 + */ + +import type { ToolResponse } from '@/tools/types' + +// --------------------------------------------------------------------------- +// Shared entity types +// --------------------------------------------------------------------------- + +/** Represents a Clockify workspace */ +export interface ClockifyWorkspace { + id: string + name: string + imageUrl?: string + memberships?: string[] +} + +/** Represents a Clockify user */ +export interface ClockifyUser { + id: string + name: string + email: string + status: string + profilePicture: string + activeWorkspace: string + defaultWorkspace: string + memberships: string[] +} + +/** Represents a member's profile within a workspace */ +export interface ClockifyMemberProfile { + workCapacity: string + costRate: string + weeklyWorkingDays: string[] +} + +/** Represents a Clockify project */ +export interface ClockifyProject { + id: string + name: string + clientId: string + clientName: string + color: string + archived: boolean + billable: boolean + public: boolean + note: string + duration: string + memberships: string[] +} + +/** Represents a time interval with start, end, and duration */ +export interface ClockifyTimeInterval { + start: string + end: string + duration: string +} + +/** Represents a Clockify time entry */ +export interface ClockifyTimeEntry { + id: string + description: string + timeInterval: ClockifyTimeInterval + projectId: string + taskId: string + billable: boolean + tags: string[] + userId: string + workspaceId: string + isLocked: boolean +} + +/** Represents a time-off period with optional half-day flag */ +export interface ClockifyTimeOffPeriod { + period: { + start: string + end: string + } + halfDay?: boolean +} + +/** Represents a time-off request */ +export interface ClockifyTimeOffRequest { + id: string + userId: string + policyId: string + status: 'PENDING' | 'APPROVED' | 'REJECTED' + timeOffPeriod: ClockifyTimeOffPeriod + note?: string + balanceDiff?: number +} + +/** Represents a holiday entry */ +export interface ClockifyHoliday { + id: string + name: string + date: string + recurring: boolean +} + +// --------------------------------------------------------------------------- +// Report types +// --------------------------------------------------------------------------- + +/** Filter criteria for Clockify reports */ +export interface ClockifyReportFilter { + dateRangeStart: string + dateRangeEnd: string + users?: { + ids: string[] + contains: 'CONTAINS' | 'DOES_NOT_CONTAIN' + } + projects?: { + ids: string[] + contains: 'CONTAINS' | 'DOES_NOT_CONTAIN' + } +} + +/** A group entry in a summary report, which may contain nested children */ +export interface ClockifySummaryReportGroup { + _id: string + name: string + duration: number + amount: number + children?: ClockifySummaryReportGroup[] +} + +/** A single entry in a detailed report */ +export interface ClockifyDetailedReportEntry { + _id: string + description: string + timeInterval: { + start: string + end: string + duration: number + } + projectName: string + userName: string + tags: string[] +} + +/** A single entry in a weekly report */ +export interface ClockifyWeeklyReportEntry { + _id: string + userName: string + totalTime: number + days: Record +} + +/** A single entry in an attendance report */ +export interface ClockifyAttendanceEntry { + userId: string + userName: string + date: string + firstEntry: string + lastEntry: string + totalTime: number +} + +// --------------------------------------------------------------------------- +// Tool params and responses +// --------------------------------------------------------------------------- + +/** Params for retrieving the current authenticated user */ +export interface ClockifyGetCurrentUserParams { + apiKey: string +} + +/** Response for retrieving the current authenticated user */ +export interface ClockifyGetCurrentUserResponse extends ToolResponse { + output: ClockifyUser +} + +/** Params for listing all workspaces */ +export interface ClockifyGetWorkspacesParams { + apiKey: string +} + +/** Response for listing all workspaces */ +export interface ClockifyGetWorkspacesResponse extends ToolResponse { + output: { + workspaces: ClockifyWorkspace[] + } +} + +/** Params for listing users in a workspace */ +export interface ClockifyGetUsersParams { + apiKey: string + workspaceId: string +} + +/** Response for listing users in a workspace */ +export interface ClockifyGetUsersResponse extends ToolResponse { + output: { + users: ClockifyUser[] + } +} + +/** Params for retrieving a member profile */ +export interface ClockifyGetMemberProfileParams { + apiKey: string + workspaceId: string + userId: string +} + +/** Response for retrieving a member profile */ +export interface ClockifyGetMemberProfileResponse extends ToolResponse { + output: ClockifyMemberProfile +} + +/** Params for listing projects in a workspace */ +export interface ClockifyGetProjectsParams { + apiKey: string + workspaceId: string +} + +/** Response for listing projects in a workspace */ +export interface ClockifyGetProjectsResponse extends ToolResponse { + output: { + projects: ClockifyProject[] + } +} + +/** Params for generating a summary report */ +export interface ClockifyReportSummaryParams { + apiKey: string + workspaceId: string + dateRangeStart: string + dateRangeEnd: string + userIds?: string + projectIds?: string +} + +/** Response for a summary report */ +export interface ClockifyReportSummaryResponse extends ToolResponse { + output: { + groups: ClockifySummaryReportGroup[] + totals: { + totalTime: number + totalBillableTime: number + entriesCount: number + } + } +} + +/** Params for generating a detailed report */ +export interface ClockifyReportDetailedParams { + apiKey: string + workspaceId: string + dateRangeStart: string + dateRangeEnd: string + userIds?: string + projectIds?: string +} + +/** Response for a detailed report */ +export interface ClockifyReportDetailedResponse extends ToolResponse { + output: { + timeentries: ClockifyDetailedReportEntry[] + totals: { + totalTime: number + totalBillableTime: number + entriesCount: number + } + } +} + +/** Params for generating a weekly report */ +export interface ClockifyReportWeeklyParams { + apiKey: string + workspaceId: string + dateRangeStart: string + dateRangeEnd: string + userIds?: string + projectIds?: string +} + +/** Response for a weekly report */ +export interface ClockifyReportWeeklyResponse extends ToolResponse { + output: { + weeks: ClockifyWeeklyReportEntry[] + totals: { + totalTime: number + } + } +} + +/** Params for generating an attendance report */ +export interface ClockifyReportAttendanceParams { + apiKey: string + workspaceId: string + dateRangeStart: string + dateRangeEnd: string + userIds?: string + projectIds?: string +} + +/** Response for an attendance report */ +export interface ClockifyReportAttendanceResponse extends ToolResponse { + output: { + attendance: ClockifyAttendanceEntry[] + } +} + +/** Params for listing time entries for a user */ +export interface ClockifyGetTimeEntriesParams { + apiKey: string + workspaceId: string + userId: string + start?: string + end?: string +} + +/** Response for listing time entries */ +export interface ClockifyGetTimeEntriesResponse extends ToolResponse { + output: { + timeEntries: ClockifyTimeEntry[] + } +} + +/** Params for retrieving a single time entry */ +export interface ClockifyGetTimeEntryParams { + apiKey: string + workspaceId: string + timeEntryId: string +} + +/** Response for retrieving a single time entry */ +export interface ClockifyGetTimeEntryResponse extends ToolResponse { + output: ClockifyTimeEntry +} + +/** Params for listing in-progress time entries across a workspace */ +export interface ClockifyGetInProgressParams { + apiKey: string + workspaceId: string +} + +/** Response for listing in-progress time entries */ +export interface ClockifyGetInProgressResponse extends ToolResponse { + output: { + timeEntries: ClockifyTimeEntry[] + } +} + +/** Params for listing time-off requests */ +export interface ClockifyGetTimeOffParams { + apiKey: string + workspaceId: string + start?: string + end?: string +} + +/** Response for listing time-off requests */ +export interface ClockifyGetTimeOffResponse extends ToolResponse { + output: { + requests: ClockifyTimeOffRequest[] + } +} + +/** Params for listing holidays in a workspace */ +export interface ClockifyGetHolidaysParams { + apiKey: string + workspaceId: string + start?: string + end?: string +} + +/** Response for listing holidays */ +export interface ClockifyGetHolidaysResponse extends ToolResponse { + output: { + holidays: ClockifyHoliday[] + } +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index f14ff024ebf..8d08c647d5a 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -189,6 +189,22 @@ import { calendlyListWebhooksTool, } from '@/tools/calendly' import { clayPopulateTool } from '@/tools/clay' +import { + clockifyGetCurrentUserTool, + clockifyGetHolidaysTool, + clockifyGetInProgressTool, + clockifyGetMemberProfileTool, + clockifyGetProjectsTool, + clockifyGetTimeEntriesTool, + clockifyGetTimeEntryTool, + clockifyGetTimeOffTool, + clockifyGetUsersTool, + clockifyGetWorkspacesTool, + clockifyReportAttendanceTool, + clockifyReportDetailedTool, + clockifyReportSummaryTool, + clockifyReportWeeklyTool, +} from '@/tools/clockify' import { clerkCreateOrganizationTool, clerkCreateUserTool, @@ -3745,6 +3761,20 @@ export const tools: Record = { telegram_send_video: telegramSendVideoTool, telegram_send_document: telegramSendDocumentTool, clay_populate: clayPopulateTool, + clockify_get_current_user: clockifyGetCurrentUserTool, + clockify_get_workspaces: clockifyGetWorkspacesTool, + clockify_get_users: clockifyGetUsersTool, + clockify_get_member_profile: clockifyGetMemberProfileTool, + clockify_get_projects: clockifyGetProjectsTool, + clockify_report_summary: clockifyReportSummaryTool, + clockify_report_detailed: clockifyReportDetailedTool, + clockify_report_weekly: clockifyReportWeeklyTool, + clockify_report_attendance: clockifyReportAttendanceTool, + clockify_get_time_entries: clockifyGetTimeEntriesTool, + clockify_get_time_entry: clockifyGetTimeEntryTool, + clockify_get_in_progress: clockifyGetInProgressTool, + clockify_get_time_off: clockifyGetTimeOffTool, + clockify_get_holidays: clockifyGetHolidaysTool, clerk_list_users: clerkListUsersTool, clerk_get_user: clerkGetUserTool, clerk_create_user: clerkCreateUserTool, diff --git a/docker-compose.local.yml b/docker-compose.local.yml index f47643ad00f..076537ba659 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -32,7 +32,7 @@ services: realtime: condition: service_healthy healthcheck: - test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3000'] + test: [ 'CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3000' ] interval: 90s timeout: 5s retries: 3 @@ -61,7 +61,7 @@ services: limits: memory: 1G healthcheck: - test: ['CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002/health'] + test: [ 'CMD', 'wget', '--spider', '--quiet', 'http://127.0.0.1:3002/health' ] interval: 90s timeout: 5s retries: 3 @@ -77,7 +77,7 @@ services: depends_on: db: condition: service_healthy - command: ['bun', 'run', 'db:migrate'] + command: [ 'bun', 'run', 'db:migrate' ] restart: 'no' db: @@ -92,7 +92,7 @@ services: volumes: - postgres_data:/var/lib/postgresql/data healthcheck: - test: ['CMD-SHELL', 'pg_isready -U postgres'] + test: [ 'CMD-SHELL', 'pg_isready -U postgres' ] interval: 5s timeout: 5s retries: 5