From 2faa233b406eeadf64b44262a8e1bf3a2372b453 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 16 Jul 2026 02:44:49 -0700 Subject: [PATCH 1/4] =?UTF-8?q?feat(rocketlane):=20Rocketlane=20integratio?= =?UTF-8?q?n=20=E2=80=94=2064=20tools=20across=20projects,=20tasks,=20phas?= =?UTF-8?q?es,=20fields,=20time=20tracking,=20spaces,=20and=20invoices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/docs/components/icons.tsx | 21 + apps/docs/components/ui/icon-mapping.ts | 2 + .../content/docs/en/integrations/meta.json | 1 + .../docs/en/integrations/rocketlane.mdx | 4246 ++++++++++++++++ apps/sim/blocks/blocks/rocketlane.ts | 3010 ++++++++++++ apps/sim/blocks/registry-maps.ts | 3 + apps/sim/components/icons.tsx | 21 + apps/sim/lib/integrations/icon-mapping.ts | 2 + apps/sim/lib/integrations/integrations.json | 277 +- apps/sim/tools/registry.ts | 130 + apps/sim/tools/rocketlane/add_field_option.ts | 78 + .../tools/rocketlane/add_project_members.ts | 103 + .../tools/rocketlane/add_task_assignees.ts | 79 + .../tools/rocketlane/add_task_dependencies.ts | 71 + .../tools/rocketlane/add_task_followers.ts | 79 + apps/sim/tools/rocketlane/archive_project.ts | 60 + .../tools/rocketlane/assign_placeholders.ts | 99 + apps/sim/tools/rocketlane/create_field.ts | 146 + apps/sim/tools/rocketlane/create_phase.ts | 118 + apps/sim/tools/rocketlane/create_project.ts | 363 ++ apps/sim/tools/rocketlane/create_space.ts | 80 + .../tools/rocketlane/create_space_document.ts | 94 + apps/sim/tools/rocketlane/create_task.ts | 222 + .../sim/tools/rocketlane/create_time_entry.ts | 165 + apps/sim/tools/rocketlane/create_time_off.ts | 166 + apps/sim/tools/rocketlane/delete_field.ts | 54 + apps/sim/tools/rocketlane/delete_phase.ts | 54 + apps/sim/tools/rocketlane/delete_project.ts | 60 + apps/sim/tools/rocketlane/delete_space.ts | 54 + .../tools/rocketlane/delete_space_document.ts | 59 + apps/sim/tools/rocketlane/delete_task.ts | 54 + .../sim/tools/rocketlane/delete_time_entry.ts | 59 + apps/sim/tools/rocketlane/delete_time_off.ts | 58 + apps/sim/tools/rocketlane/get_field.ts | 80 + apps/sim/tools/rocketlane/get_invoice.ts | 82 + .../rocketlane/get_invoice_line_items.ts | 90 + .../tools/rocketlane/get_invoice_payments.ts | 90 + apps/sim/tools/rocketlane/get_phase.ts | 80 + apps/sim/tools/rocketlane/get_project.ts | 81 + apps/sim/tools/rocketlane/get_space.ts | 58 + .../tools/rocketlane/get_space_document.ts | 61 + apps/sim/tools/rocketlane/get_task.ts | 82 + apps/sim/tools/rocketlane/get_time_entry.ts | 82 + apps/sim/tools/rocketlane/get_time_off.ts | 84 + apps/sim/tools/rocketlane/get_user.ts | 76 + apps/sim/tools/rocketlane/import_template.ts | 88 + apps/sim/tools/rocketlane/index.ts | 131 + apps/sim/tools/rocketlane/list_fields.ts | 150 + apps/sim/tools/rocketlane/list_invoices.ts | 481 ++ apps/sim/tools/rocketlane/list_phases.ts | 136 + .../sim/tools/rocketlane/list_placeholders.ts | 71 + apps/sim/tools/rocketlane/list_projects.ts | 224 + .../rocketlane/list_resource_allocations.ts | 209 + .../tools/rocketlane/list_space_documents.ts | 222 + apps/sim/tools/rocketlane/list_spaces.ts | 212 + apps/sim/tools/rocketlane/list_tasks.ts | 209 + .../sim/tools/rocketlane/list_time_entries.ts | 315 ++ .../rocketlane/list_time_entry_categories.ts | 89 + apps/sim/tools/rocketlane/list_time_offs.ts | 259 + apps/sim/tools/rocketlane/list_users.ts | 389 ++ .../tools/rocketlane/move_task_to_phase.ts | 70 + .../rocketlane/remove_project_members.ts | 84 + .../tools/rocketlane/remove_task_assignees.ts | 79 + .../rocketlane/remove_task_dependencies.ts | 71 + .../tools/rocketlane/remove_task_followers.ts | 79 + .../tools/rocketlane/search_time_entries.ts | 227 + apps/sim/tools/rocketlane/types.ts | 4372 +++++++++++++++++ .../tools/rocketlane/unassign_placeholders.ts | 80 + apps/sim/tools/rocketlane/update_field.ts | 113 + .../tools/rocketlane/update_field_option.ts | 86 + apps/sim/tools/rocketlane/update_phase.ts | 119 + apps/sim/tools/rocketlane/update_project.ts | 194 + apps/sim/tools/rocketlane/update_space.ts | 71 + .../tools/rocketlane/update_space_document.ts | 79 + apps/sim/tools/rocketlane/update_task.ts | 175 + .../sim/tools/rocketlane/update_time_entry.ts | 131 + 76 files changed, 20248 insertions(+), 1 deletion(-) create mode 100644 apps/docs/content/docs/en/integrations/rocketlane.mdx create mode 100644 apps/sim/blocks/blocks/rocketlane.ts create mode 100644 apps/sim/tools/rocketlane/add_field_option.ts create mode 100644 apps/sim/tools/rocketlane/add_project_members.ts create mode 100644 apps/sim/tools/rocketlane/add_task_assignees.ts create mode 100644 apps/sim/tools/rocketlane/add_task_dependencies.ts create mode 100644 apps/sim/tools/rocketlane/add_task_followers.ts create mode 100644 apps/sim/tools/rocketlane/archive_project.ts create mode 100644 apps/sim/tools/rocketlane/assign_placeholders.ts create mode 100644 apps/sim/tools/rocketlane/create_field.ts create mode 100644 apps/sim/tools/rocketlane/create_phase.ts create mode 100644 apps/sim/tools/rocketlane/create_project.ts create mode 100644 apps/sim/tools/rocketlane/create_space.ts create mode 100644 apps/sim/tools/rocketlane/create_space_document.ts create mode 100644 apps/sim/tools/rocketlane/create_task.ts create mode 100644 apps/sim/tools/rocketlane/create_time_entry.ts create mode 100644 apps/sim/tools/rocketlane/create_time_off.ts create mode 100644 apps/sim/tools/rocketlane/delete_field.ts create mode 100644 apps/sim/tools/rocketlane/delete_phase.ts create mode 100644 apps/sim/tools/rocketlane/delete_project.ts create mode 100644 apps/sim/tools/rocketlane/delete_space.ts create mode 100644 apps/sim/tools/rocketlane/delete_space_document.ts create mode 100644 apps/sim/tools/rocketlane/delete_task.ts create mode 100644 apps/sim/tools/rocketlane/delete_time_entry.ts create mode 100644 apps/sim/tools/rocketlane/delete_time_off.ts create mode 100644 apps/sim/tools/rocketlane/get_field.ts create mode 100644 apps/sim/tools/rocketlane/get_invoice.ts create mode 100644 apps/sim/tools/rocketlane/get_invoice_line_items.ts create mode 100644 apps/sim/tools/rocketlane/get_invoice_payments.ts create mode 100644 apps/sim/tools/rocketlane/get_phase.ts create mode 100644 apps/sim/tools/rocketlane/get_project.ts create mode 100644 apps/sim/tools/rocketlane/get_space.ts create mode 100644 apps/sim/tools/rocketlane/get_space_document.ts create mode 100644 apps/sim/tools/rocketlane/get_task.ts create mode 100644 apps/sim/tools/rocketlane/get_time_entry.ts create mode 100644 apps/sim/tools/rocketlane/get_time_off.ts create mode 100644 apps/sim/tools/rocketlane/get_user.ts create mode 100644 apps/sim/tools/rocketlane/import_template.ts create mode 100644 apps/sim/tools/rocketlane/index.ts create mode 100644 apps/sim/tools/rocketlane/list_fields.ts create mode 100644 apps/sim/tools/rocketlane/list_invoices.ts create mode 100644 apps/sim/tools/rocketlane/list_phases.ts create mode 100644 apps/sim/tools/rocketlane/list_placeholders.ts create mode 100644 apps/sim/tools/rocketlane/list_projects.ts create mode 100644 apps/sim/tools/rocketlane/list_resource_allocations.ts create mode 100644 apps/sim/tools/rocketlane/list_space_documents.ts create mode 100644 apps/sim/tools/rocketlane/list_spaces.ts create mode 100644 apps/sim/tools/rocketlane/list_tasks.ts create mode 100644 apps/sim/tools/rocketlane/list_time_entries.ts create mode 100644 apps/sim/tools/rocketlane/list_time_entry_categories.ts create mode 100644 apps/sim/tools/rocketlane/list_time_offs.ts create mode 100644 apps/sim/tools/rocketlane/list_users.ts create mode 100644 apps/sim/tools/rocketlane/move_task_to_phase.ts create mode 100644 apps/sim/tools/rocketlane/remove_project_members.ts create mode 100644 apps/sim/tools/rocketlane/remove_task_assignees.ts create mode 100644 apps/sim/tools/rocketlane/remove_task_dependencies.ts create mode 100644 apps/sim/tools/rocketlane/remove_task_followers.ts create mode 100644 apps/sim/tools/rocketlane/search_time_entries.ts create mode 100644 apps/sim/tools/rocketlane/types.ts create mode 100644 apps/sim/tools/rocketlane/unassign_placeholders.ts create mode 100644 apps/sim/tools/rocketlane/update_field.ts create mode 100644 apps/sim/tools/rocketlane/update_field_option.ts create mode 100644 apps/sim/tools/rocketlane/update_phase.ts create mode 100644 apps/sim/tools/rocketlane/update_project.ts create mode 100644 apps/sim/tools/rocketlane/update_space.ts create mode 100644 apps/sim/tools/rocketlane/update_space_document.ts create mode 100644 apps/sim/tools/rocketlane/update_task.ts create mode 100644 apps/sim/tools/rocketlane/update_time_entry.ts diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index bae77686e5f..1ebe7b408bc 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -8710,3 +8710,24 @@ export function JupyterIcon(props: SVGProps) { ) } + +export function RocketlaneIcon(props: SVGProps) { + return ( + + + + + + + ) +} diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 65a7684ef0a..cdab0cd4c76 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -182,6 +182,7 @@ import { ResendIcon, RevenueCatIcon, RipplingIcon, + RocketlaneIcon, RootlyIcon, S3Icon, SalesforceIcon, @@ -444,6 +445,7 @@ export const blockTypeToIconMap: Record = { resend: ResendIcon, revenuecat: RevenueCatIcon, rippling: RipplingIcon, + rocketlane: RocketlaneIcon, rootly: RootlyIcon, s3: S3Icon, salesforce: SalesforceIcon, diff --git a/apps/docs/content/docs/en/integrations/meta.json b/apps/docs/content/docs/en/integrations/meta.json index 878bd13fa6a..551b8d25890 100644 --- a/apps/docs/content/docs/en/integrations/meta.json +++ b/apps/docs/content/docs/en/integrations/meta.json @@ -203,6 +203,7 @@ "resend", "revenuecat", "rippling", + "rocketlane", "rootly", "s3", "salesforce", diff --git a/apps/docs/content/docs/en/integrations/rocketlane.mdx b/apps/docs/content/docs/en/integrations/rocketlane.mdx new file mode 100644 index 00000000000..b011a0eced1 --- /dev/null +++ b/apps/docs/content/docs/en/integrations/rocketlane.mdx @@ -0,0 +1,4246 @@ +--- +title: Rocketlane +description: Manage client onboarding projects, tasks, time tracking, and invoices +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +## Usage Instructions + +Integrate Rocketlane into your workflow. Rocketlane is a professional-services automation platform for client onboarding and project delivery. Create and manage projects, tasks, phases, custom fields, time entries, time-offs, spaces, documents, resource allocations, and invoices. + + + +## Actions + +### `rocketlane_create_project` + +Create a new Rocketlane project with a customer, owner, dates, team members, templates, financials, and custom fields + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectName` | string | Yes | Name of the project | +| `customerCompanyName` | string | Yes | Name of the customer company \(case-sensitive exact match; cannot be changed after creation\) | +| `ownerUserId` | number | No | User ID of the project owner \(either ownerUserId or ownerEmailId must be provided\) | +| `ownerEmailId` | string | No | Email of the project owner \(either ownerUserId or ownerEmailId must be provided\) | +| `startDate` | string | No | Date on which the project begins \(YYYY-MM-DD\); required when sources are provided | +| `dueDate` | string | No | Date on which the project is planned to complete \(YYYY-MM-DD, on or after startDate\) | +| `visibility` | string | No | Who can see the project: EVERYONE or MEMBERS | +| `statusValue` | number | No | Value \(identifier\) of the project status | +| `memberUserIds` | array | No | User IDs of team members from your organization to add to the project | +| `customerUserIds` | array | No | User IDs of customer stakeholders to add to the project | +| `customerChampionUserId` | number | No | User ID of the customer champion | +| `fields` | array | No | Custom field assignments, each with fieldId and fieldValue \(string or number matching the field type\) | +| `sources` | array | No | Project templates to import at creation, each with templateId, startDate \(YYYY-MM-DD\), and optional prefix | +| `placeholders` | array | No | Placeholder-to-user mappings, each with placeholderId and user \(\{ userId \} or \{ emailId \}\); ignored unless the project is built from sources | +| `assignProjectOwner` | boolean | No | Automatically assign unassigned tasks to the project owner \(skipped when no sources are used\) | +| `annualizedRecurringRevenue` | number | No | Recurring revenue of the customer subscriptions for a single calendar year | +| `projectFee` | number | No | Total fee charged for the project | +| `autoAllocation` | boolean | No | Whether auto allocation is enabled for the project | +| `autoCreateCompany` | boolean | No | Create the customer company if it does not already exist | +| `budgetedHours` | number | No | Total hours allocated for project execution \(decimal, up to two places\) | +| `contractType` | string | No | Contract type for the project financials: FIXED_FEE, TIME_AND_MATERIAL, NON_BILLABLE, or SUBSCRIPTION | +| `fixedFee` | number | No | Project fee for FIXED_FEE contract type projects | +| `projectBudget` | number | No | Project budget for TIME_AND_MATERIAL contract type projects | +| `rateCardId` | number | No | Rate card ID for TIME_AND_MATERIAL contract type projects | +| `subscriptionFrequency` | string | No | Subscription renewal interval for SUBSCRIPTION contracts: MONTHLY, QUARTERLY, HALF_YEARLY, or YEARLY | +| `subscriptionStartDate` | string | No | Date when the subscription interval begins \(YYYY-MM-DD\) | +| `periodMinutes` | number | No | Budgeted minutes for each subscription period | +| `periodBudget` | number | No | Fixed budget of every subscription period | +| `noOfPeriods` | number | No | Number of periods in the subscription | +| `currency` | string | No | Currency for the project financials \(ISO code, e.g. USD\); cannot be changed once set | +| `externalReferenceId` | string | No | Identifier linking the project to an external system | +| `includeFields` | string | No | Comma-separated extra fields to return in the response \(e.g. budgetedHours,progressPercentage\) | +| `includeAllFields` | boolean | No | Return all fields in the response body | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `project` | object | The created project | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Date on which the project execution begins \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date on which the project execution is planned to complete \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time when the project was created \(epoch millis\) | +| ↳ `updatedAt` | number | Time when the project was last updated \(epoch millis\) | +| ↳ `owner` | object | Project owner | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `teamMembers` | object | Project members, customers, and customer champion | +| ↳ `members` | array | Team members working on the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customers` | array | Customer stakeholders involved in the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customerChampion` | object | Customer champion of the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Project status value and label | +| ↳ `value` | number | Unique identifier of the status | +| ↳ `label` | string | Name of the status | +| ↳ `fields` | array | Custom project field values | +| ↳ `fieldId` | number | Unique identifier of the custom field | +| ↳ `fieldLabel` | string | Name of the custom project field | +| ↳ `fieldValue` | string | Value assigned to the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `customer` | object | Customer company of the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `partnerCompanies` | array | Partner companies on the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `archived` | boolean | Whether the project is archived | +| ↳ `visibility` | string | Project visibility \(EVERYONE, MEMBERS, or GROUP\) | +| ↳ `createdBy` | object | Team member who created the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `currency` | string | Currency for the project financials \(ISO code\) | +| ↳ `financials` | object | Project financials \(contract type and per-contract-type fields\) | +| ↳ `contractType` | string | Contract type for the project financials \(FIXED_FEE, TIME_AND_MATERIAL, SUBSCRIPTION, or NON_BILLABLE\) | +| ↳ `revenueRecognitionType` | string | Method used for revenue recognition | +| ↳ `fixedFee` | number | Project fee for Fixed fee contract type projects | +| ↳ `projectBudget` | number | Budget allocated for Time & Material contract type projects | +| ↳ `rateCardId` | number | Unique identifier of the rate card | +| ↳ `rateCardName` | string | Name of the rate card | +| ↳ `subscriptionFrequency` | string | Interval at which the subscription renews \(MONTHLY, QUARTERLY, HALF_YEARLY, YEARLY\) | +| ↳ `subscriptionStartDate` | string | Date when the subscription interval begins \(YYYY-MM-DD\) | +| ↳ `periodMinutes` | number | Budgeted minutes for each subscription period | +| ↳ `periodBudget` | number | Fixed budget of every subscription period | +| ↳ `noOfPeriods` | number | Number of periods in the subscription | +| ↳ `startDateActual` | string | Date on which the project status changed to in progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date on which the project status changed to completed \(YYYY-MM-DD\) | +| ↳ `annualizedRecurringRevenue` | number | Recurring revenue of the customer subscriptions for a single calendar year | +| ↳ `projectFee` | number | Total fee charged for the project | +| ↳ `budgetedHours` | number | Total hours allocated for project execution | +| ↳ `percentageBudgetedHoursConsumed` | number | Budgeted hours consumed percentage | +| ↳ `percentageBudgetConsumed` | number | Budget consumed percentage | +| ↳ `trackedHours` | number | Hours tracked as part of submitted time entries | +| ↳ `trackedMinutes` | number | Minutes tracked as part of submitted time entries | +| ↳ `allocatedHours` | number | Allocated hours against users or placeholders | +| ↳ `allocatedMinutes` | number | Allocated minutes against users or placeholders | +| ↳ `billableHours` | number | Hours of time entries tracked as billable | +| ↳ `billableMinutes` | number | Minutes of time entries tracked as billable | +| ↳ `nonBillableHours` | number | Hours of time entries tracked as non-billable | +| ↳ `nonBillableMinutes` | number | Minutes of time entries tracked as non-billable | +| ↳ `remainingHours` | number | Hours left to complete the project based on tracked and budgeted hours | +| ↳ `remainingMinutes` | number | Minutes left to complete the project \(complements remainingHours\) | +| ↳ `progressPercentage` | number | Progress based on completed tasks vs total tasks | +| ↳ `currentPhases` | array | Phases currently marked as in progress | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `autoAllocation` | boolean | Whether auto allocation is enabled for the project | +| ↳ `sources` | array | Project templates imported into the project | +| ↳ `prefix` | string | Prefix distinguishing which phase or task corresponds to which template | +| ↳ `startDate` | string | Date on which the template goes into effect \(YYYY-MM-DD\) | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `plannedDurationInDays` | number | Difference between startDate and dueDate in days | +| ↳ `inferredProgress` | string | Inferred progress \(ON_TRACK, AHEAD_OF_TIME, RUNNING_LATE, or NONE\) | +| ↳ `projectAgeInDays` | number | Age of the project in days based on actual dates | +| ↳ `customersInvited` | number | Number of customers invited to the project | +| ↳ `customersJoined` | number | Number of customers who joined the project | +| ↳ `externalReferenceId` | string | Identifier linking the project to an external system | + +### `rocketlane_get_project` + +Retrieve a Rocketlane project by its unique identifier + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | Unique identifier of the project | +| `includeFields` | string | No | Comma-separated extra fields to return in the response \(e.g. budgetedHours,progressPercentage\) | +| `includeAllFields` | boolean | No | Return all fields in the response body | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `project` | object | The requested project | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Date on which the project execution begins \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date on which the project execution is planned to complete \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time when the project was created \(epoch millis\) | +| ↳ `updatedAt` | number | Time when the project was last updated \(epoch millis\) | +| ↳ `owner` | object | Project owner | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `teamMembers` | object | Project members, customers, and customer champion | +| ↳ `members` | array | Team members working on the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customers` | array | Customer stakeholders involved in the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customerChampion` | object | Customer champion of the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Project status value and label | +| ↳ `value` | number | Unique identifier of the status | +| ↳ `label` | string | Name of the status | +| ↳ `fields` | array | Custom project field values | +| ↳ `fieldId` | number | Unique identifier of the custom field | +| ↳ `fieldLabel` | string | Name of the custom project field | +| ↳ `fieldValue` | string | Value assigned to the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `customer` | object | Customer company of the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `partnerCompanies` | array | Partner companies on the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `archived` | boolean | Whether the project is archived | +| ↳ `visibility` | string | Project visibility \(EVERYONE, MEMBERS, or GROUP\) | +| ↳ `createdBy` | object | Team member who created the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `currency` | string | Currency for the project financials \(ISO code\) | +| ↳ `financials` | object | Project financials \(contract type and per-contract-type fields\) | +| ↳ `contractType` | string | Contract type for the project financials \(FIXED_FEE, TIME_AND_MATERIAL, SUBSCRIPTION, or NON_BILLABLE\) | +| ↳ `revenueRecognitionType` | string | Method used for revenue recognition | +| ↳ `fixedFee` | number | Project fee for Fixed fee contract type projects | +| ↳ `projectBudget` | number | Budget allocated for Time & Material contract type projects | +| ↳ `rateCardId` | number | Unique identifier of the rate card | +| ↳ `rateCardName` | string | Name of the rate card | +| ↳ `subscriptionFrequency` | string | Interval at which the subscription renews \(MONTHLY, QUARTERLY, HALF_YEARLY, YEARLY\) | +| ↳ `subscriptionStartDate` | string | Date when the subscription interval begins \(YYYY-MM-DD\) | +| ↳ `periodMinutes` | number | Budgeted minutes for each subscription period | +| ↳ `periodBudget` | number | Fixed budget of every subscription period | +| ↳ `noOfPeriods` | number | Number of periods in the subscription | +| ↳ `startDateActual` | string | Date on which the project status changed to in progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date on which the project status changed to completed \(YYYY-MM-DD\) | +| ↳ `annualizedRecurringRevenue` | number | Recurring revenue of the customer subscriptions for a single calendar year | +| ↳ `projectFee` | number | Total fee charged for the project | +| ↳ `budgetedHours` | number | Total hours allocated for project execution | +| ↳ `percentageBudgetedHoursConsumed` | number | Budgeted hours consumed percentage | +| ↳ `percentageBudgetConsumed` | number | Budget consumed percentage | +| ↳ `trackedHours` | number | Hours tracked as part of submitted time entries | +| ↳ `trackedMinutes` | number | Minutes tracked as part of submitted time entries | +| ↳ `allocatedHours` | number | Allocated hours against users or placeholders | +| ↳ `allocatedMinutes` | number | Allocated minutes against users or placeholders | +| ↳ `billableHours` | number | Hours of time entries tracked as billable | +| ↳ `billableMinutes` | number | Minutes of time entries tracked as billable | +| ↳ `nonBillableHours` | number | Hours of time entries tracked as non-billable | +| ↳ `nonBillableMinutes` | number | Minutes of time entries tracked as non-billable | +| ↳ `remainingHours` | number | Hours left to complete the project based on tracked and budgeted hours | +| ↳ `remainingMinutes` | number | Minutes left to complete the project \(complements remainingHours\) | +| ↳ `progressPercentage` | number | Progress based on completed tasks vs total tasks | +| ↳ `currentPhases` | array | Phases currently marked as in progress | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `autoAllocation` | boolean | Whether auto allocation is enabled for the project | +| ↳ `sources` | array | Project templates imported into the project | +| ↳ `prefix` | string | Prefix distinguishing which phase or task corresponds to which template | +| ↳ `startDate` | string | Date on which the template goes into effect \(YYYY-MM-DD\) | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `plannedDurationInDays` | number | Difference between startDate and dueDate in days | +| ↳ `inferredProgress` | string | Inferred progress \(ON_TRACK, AHEAD_OF_TIME, RUNNING_LATE, or NONE\) | +| ↳ `projectAgeInDays` | number | Age of the project in days based on actual dates | +| ↳ `customersInvited` | number | Number of customers invited to the project | +| ↳ `customersJoined` | number | Number of customers who joined the project | +| ↳ `externalReferenceId` | string | Identifier linking the project to an external system | + +### `rocketlane_list_projects` + +List Rocketlane projects with optional filters, sorting, and token-based pagination + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `pageSize` | number | No | Maximum number of projects per page \(defaults to 100\) | +| `pageToken` | string | No | Page token from a previous response \(valid for 15 minutes\) | +| `includeFields` | string | No | Comma-separated extra fields to return in the response \(e.g. budgetedHours,progressPercentage\) | +| `includeAllFields` | boolean | No | Return all fields in the response body | +| `sortBy` | string | No | Field to sort by: projectName, startDate, dueDate, startDateActual, dueDateActual, annualizedRecurringRevenue, or projectFee | +| `sortOrder` | string | No | Sort order: ASC or DESC \(defaults to DESC\) | +| `match` | string | No | Combine filters with AND \(all\) or OR \(any\); defaults to all | +| `projectNameContains` | string | No | Only return projects whose name contains this value | +| `projectNameEquals` | string | No | Only return projects whose name exactly matches this value | +| `statusEquals` | string | No | Only return projects with this status value | +| `statusOneOf` | string | No | Comma-separated status values; returns projects matching any of them | +| `customerIdEquals` | string | No | Only return projects for this customer company ID | +| `customerIdOneOf` | string | No | Comma-separated customer company IDs; returns projects matching any of them | +| `teamMemberIdEquals` | string | No | Only return projects that include this team member ID | +| `contractTypeEquals` | string | No | Only return projects with this contract type: FIXED_FEE, TIME_AND_MATERIAL, SUBSCRIPTION, or NON_BILLABLE | +| `includeArchived` | boolean | No | Whether to include archived projects in the results | +| `externalReferenceIdEquals` | string | No | Only return projects with this external reference ID | +| `startDateAfter` | string | No | Only return projects whose start date is after this date \(YYYY-MM-DD\) | +| `startDateBefore` | string | No | Only return projects whose start date is before this date \(YYYY-MM-DD\) | +| `dueDateAfter` | string | No | Only return projects whose due date is after this date \(YYYY-MM-DD\) | +| `dueDateBefore` | string | No | Only return projects whose due date is before this date \(YYYY-MM-DD\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `projects` | array | List of projects | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Date on which the project execution begins \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date on which the project execution is planned to complete \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time when the project was created \(epoch millis\) | +| ↳ `updatedAt` | number | Time when the project was last updated \(epoch millis\) | +| ↳ `owner` | object | Project owner | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `teamMembers` | object | Project members, customers, and customer champion | +| ↳ `members` | array | Team members working on the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customers` | array | Customer stakeholders involved in the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customerChampion` | object | Customer champion of the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Project status value and label | +| ↳ `value` | number | Unique identifier of the status | +| ↳ `label` | string | Name of the status | +| ↳ `fields` | array | Custom project field values | +| ↳ `fieldId` | number | Unique identifier of the custom field | +| ↳ `fieldLabel` | string | Name of the custom project field | +| ↳ `fieldValue` | string | Value assigned to the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `customer` | object | Customer company of the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `partnerCompanies` | array | Partner companies on the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `archived` | boolean | Whether the project is archived | +| ↳ `visibility` | string | Project visibility \(EVERYONE, MEMBERS, or GROUP\) | +| ↳ `createdBy` | object | Team member who created the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `currency` | string | Currency for the project financials \(ISO code\) | +| ↳ `financials` | object | Project financials \(contract type and per-contract-type fields\) | +| ↳ `contractType` | string | Contract type for the project financials \(FIXED_FEE, TIME_AND_MATERIAL, SUBSCRIPTION, or NON_BILLABLE\) | +| ↳ `revenueRecognitionType` | string | Method used for revenue recognition | +| ↳ `fixedFee` | number | Project fee for Fixed fee contract type projects | +| ↳ `projectBudget` | number | Budget allocated for Time & Material contract type projects | +| ↳ `rateCardId` | number | Unique identifier of the rate card | +| ↳ `rateCardName` | string | Name of the rate card | +| ↳ `subscriptionFrequency` | string | Interval at which the subscription renews \(MONTHLY, QUARTERLY, HALF_YEARLY, YEARLY\) | +| ↳ `subscriptionStartDate` | string | Date when the subscription interval begins \(YYYY-MM-DD\) | +| ↳ `periodMinutes` | number | Budgeted minutes for each subscription period | +| ↳ `periodBudget` | number | Fixed budget of every subscription period | +| ↳ `noOfPeriods` | number | Number of periods in the subscription | +| ↳ `startDateActual` | string | Date on which the project status changed to in progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date on which the project status changed to completed \(YYYY-MM-DD\) | +| ↳ `annualizedRecurringRevenue` | number | Recurring revenue of the customer subscriptions for a single calendar year | +| ↳ `projectFee` | number | Total fee charged for the project | +| ↳ `budgetedHours` | number | Total hours allocated for project execution | +| ↳ `percentageBudgetedHoursConsumed` | number | Budgeted hours consumed percentage | +| ↳ `percentageBudgetConsumed` | number | Budget consumed percentage | +| ↳ `trackedHours` | number | Hours tracked as part of submitted time entries | +| ↳ `trackedMinutes` | number | Minutes tracked as part of submitted time entries | +| ↳ `allocatedHours` | number | Allocated hours against users or placeholders | +| ↳ `allocatedMinutes` | number | Allocated minutes against users or placeholders | +| ↳ `billableHours` | number | Hours of time entries tracked as billable | +| ↳ `billableMinutes` | number | Minutes of time entries tracked as billable | +| ↳ `nonBillableHours` | number | Hours of time entries tracked as non-billable | +| ↳ `nonBillableMinutes` | number | Minutes of time entries tracked as non-billable | +| ↳ `remainingHours` | number | Hours left to complete the project based on tracked and budgeted hours | +| ↳ `remainingMinutes` | number | Minutes left to complete the project \(complements remainingHours\) | +| ↳ `progressPercentage` | number | Progress based on completed tasks vs total tasks | +| ↳ `currentPhases` | array | Phases currently marked as in progress | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `autoAllocation` | boolean | Whether auto allocation is enabled for the project | +| ↳ `sources` | array | Project templates imported into the project | +| ↳ `prefix` | string | Prefix distinguishing which phase or task corresponds to which template | +| ↳ `startDate` | string | Date on which the template goes into effect \(YYYY-MM-DD\) | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `plannedDurationInDays` | number | Difference between startDate and dueDate in days | +| ↳ `inferredProgress` | string | Inferred progress \(ON_TRACK, AHEAD_OF_TIME, RUNNING_LATE, or NONE\) | +| ↳ `projectAgeInDays` | number | Age of the project in days based on actual dates | +| ↳ `customersInvited` | number | Number of customers invited to the project | +| ↳ `customersJoined` | number | Number of customers who joined the project | +| ↳ `externalReferenceId` | string | Identifier linking the project to an external system | +| `pagination` | object | Pagination details for fetching further pages | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_update_project` + +Update a Rocketlane project by ID, including name, dates, visibility, owner, status, and custom fields + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | Unique identifier of the project to update | +| `projectName` | string | No | New name of the project | +| `startDate` | string | No | Date on which the project begins \(YYYY-MM-DD\) | +| `dueDate` | string | No | Date on which the project is planned to complete \(YYYY-MM-DD, on or after startDate\) | +| `visibility` | string | No | Who can see the project: EVERYONE or MEMBERS | +| `ownerUserId` | number | No | User ID of the new project owner \(transfers ownership and revokes access for the previous owner\) | +| `ownerEmailId` | string | No | Email of the new project owner | +| `statusValue` | number | No | Value \(identifier\) of the project status | +| `fields` | array | No | Custom field assignments, each with fieldId and fieldValue \(string or number matching the field type\) | +| `annualizedRecurringRevenue` | number | No | Recurring revenue of the customer subscriptions for a single calendar year | +| `projectFee` | number | No | Total fee charged for the project | +| `autoAllocation` | boolean | No | Whether auto allocation is enabled for the project | +| `budgetedHours` | number | No | Total hours allocated for project execution \(decimal, up to two places\) | +| `externalReferenceId` | string | No | Identifier linking the project to an external system | +| `includeFields` | string | No | Comma-separated extra fields to return in the response \(e.g. budgetedHours,progressPercentage\) | +| `includeAllFields` | boolean | No | Return all fields in the response body | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `project` | object | The updated project | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Date on which the project execution begins \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date on which the project execution is planned to complete \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time when the project was created \(epoch millis\) | +| ↳ `updatedAt` | number | Time when the project was last updated \(epoch millis\) | +| ↳ `owner` | object | Project owner | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `teamMembers` | object | Project members, customers, and customer champion | +| ↳ `members` | array | Team members working on the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customers` | array | Customer stakeholders involved in the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customerChampion` | object | Customer champion of the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Project status value and label | +| ↳ `value` | number | Unique identifier of the status | +| ↳ `label` | string | Name of the status | +| ↳ `fields` | array | Custom project field values | +| ↳ `fieldId` | number | Unique identifier of the custom field | +| ↳ `fieldLabel` | string | Name of the custom project field | +| ↳ `fieldValue` | string | Value assigned to the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `customer` | object | Customer company of the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `partnerCompanies` | array | Partner companies on the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `archived` | boolean | Whether the project is archived | +| ↳ `visibility` | string | Project visibility \(EVERYONE, MEMBERS, or GROUP\) | +| ↳ `createdBy` | object | Team member who created the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `currency` | string | Currency for the project financials \(ISO code\) | +| ↳ `financials` | object | Project financials \(contract type and per-contract-type fields\) | +| ↳ `contractType` | string | Contract type for the project financials \(FIXED_FEE, TIME_AND_MATERIAL, SUBSCRIPTION, or NON_BILLABLE\) | +| ↳ `revenueRecognitionType` | string | Method used for revenue recognition | +| ↳ `fixedFee` | number | Project fee for Fixed fee contract type projects | +| ↳ `projectBudget` | number | Budget allocated for Time & Material contract type projects | +| ↳ `rateCardId` | number | Unique identifier of the rate card | +| ↳ `rateCardName` | string | Name of the rate card | +| ↳ `subscriptionFrequency` | string | Interval at which the subscription renews \(MONTHLY, QUARTERLY, HALF_YEARLY, YEARLY\) | +| ↳ `subscriptionStartDate` | string | Date when the subscription interval begins \(YYYY-MM-DD\) | +| ↳ `periodMinutes` | number | Budgeted minutes for each subscription period | +| ↳ `periodBudget` | number | Fixed budget of every subscription period | +| ↳ `noOfPeriods` | number | Number of periods in the subscription | +| ↳ `startDateActual` | string | Date on which the project status changed to in progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date on which the project status changed to completed \(YYYY-MM-DD\) | +| ↳ `annualizedRecurringRevenue` | number | Recurring revenue of the customer subscriptions for a single calendar year | +| ↳ `projectFee` | number | Total fee charged for the project | +| ↳ `budgetedHours` | number | Total hours allocated for project execution | +| ↳ `percentageBudgetedHoursConsumed` | number | Budgeted hours consumed percentage | +| ↳ `percentageBudgetConsumed` | number | Budget consumed percentage | +| ↳ `trackedHours` | number | Hours tracked as part of submitted time entries | +| ↳ `trackedMinutes` | number | Minutes tracked as part of submitted time entries | +| ↳ `allocatedHours` | number | Allocated hours against users or placeholders | +| ↳ `allocatedMinutes` | number | Allocated minutes against users or placeholders | +| ↳ `billableHours` | number | Hours of time entries tracked as billable | +| ↳ `billableMinutes` | number | Minutes of time entries tracked as billable | +| ↳ `nonBillableHours` | number | Hours of time entries tracked as non-billable | +| ↳ `nonBillableMinutes` | number | Minutes of time entries tracked as non-billable | +| ↳ `remainingHours` | number | Hours left to complete the project based on tracked and budgeted hours | +| ↳ `remainingMinutes` | number | Minutes left to complete the project \(complements remainingHours\) | +| ↳ `progressPercentage` | number | Progress based on completed tasks vs total tasks | +| ↳ `currentPhases` | array | Phases currently marked as in progress | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `autoAllocation` | boolean | Whether auto allocation is enabled for the project | +| ↳ `sources` | array | Project templates imported into the project | +| ↳ `prefix` | string | Prefix distinguishing which phase or task corresponds to which template | +| ↳ `startDate` | string | Date on which the template goes into effect \(YYYY-MM-DD\) | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `plannedDurationInDays` | number | Difference between startDate and dueDate in days | +| ↳ `inferredProgress` | string | Inferred progress \(ON_TRACK, AHEAD_OF_TIME, RUNNING_LATE, or NONE\) | +| ↳ `projectAgeInDays` | number | Age of the project in days based on actual dates | +| ↳ `customersInvited` | number | Number of customers invited to the project | +| ↳ `customersJoined` | number | Number of customers who joined the project | +| ↳ `externalReferenceId` | string | Identifier linking the project to an external system | + +### `rocketlane_archive_project` + +Archive a Rocketlane project by ID, making it dormant while preserving its details and history + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | Unique identifier of the project to archive | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `archived` | boolean | Whether the project was archived | +| `projectId` | number | Unique identifier of the archived project | + +### `rocketlane_delete_project` + +Permanently delete a Rocketlane project by ID (irreversible; only Admins, Super Users, and Project Owners can delete) + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | Unique identifier of the project to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the project was deleted | +| `projectId` | number | Unique identifier of the deleted project | + +### `rocketlane_add_project_members` + +Add team members and customer stakeholders to a Rocketlane project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | Unique identifier of the project | +| `memberUserIds` | array | No | User IDs of team members from your organization to add | +| `memberEmailIds` | array | No | Emails of team members from your organization to add | +| `customerUserIds` | array | No | User IDs of customer stakeholders to add | +| `customerEmailIds` | array | No | Emails of customer stakeholders to add | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `project` | object | The project with its updated team members | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Date on which the project execution begins \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date on which the project execution is planned to complete \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time when the project was created \(epoch millis\) | +| ↳ `updatedAt` | number | Time when the project was last updated \(epoch millis\) | +| ↳ `owner` | object | Project owner | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `teamMembers` | object | Project members, customers, and customer champion | +| ↳ `members` | array | Team members working on the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customers` | array | Customer stakeholders involved in the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customerChampion` | object | Customer champion of the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Project status value and label | +| ↳ `value` | number | Unique identifier of the status | +| ↳ `label` | string | Name of the status | +| ↳ `fields` | array | Custom project field values | +| ↳ `fieldId` | number | Unique identifier of the custom field | +| ↳ `fieldLabel` | string | Name of the custom project field | +| ↳ `fieldValue` | string | Value assigned to the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `customer` | object | Customer company of the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `partnerCompanies` | array | Partner companies on the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `archived` | boolean | Whether the project is archived | +| ↳ `visibility` | string | Project visibility \(EVERYONE, MEMBERS, or GROUP\) | +| ↳ `createdBy` | object | Team member who created the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `currency` | string | Currency for the project financials \(ISO code\) | +| ↳ `financials` | object | Project financials \(contract type and per-contract-type fields\) | +| ↳ `contractType` | string | Contract type for the project financials \(FIXED_FEE, TIME_AND_MATERIAL, SUBSCRIPTION, or NON_BILLABLE\) | +| ↳ `revenueRecognitionType` | string | Method used for revenue recognition | +| ↳ `fixedFee` | number | Project fee for Fixed fee contract type projects | +| ↳ `projectBudget` | number | Budget allocated for Time & Material contract type projects | +| ↳ `rateCardId` | number | Unique identifier of the rate card | +| ↳ `rateCardName` | string | Name of the rate card | +| ↳ `subscriptionFrequency` | string | Interval at which the subscription renews \(MONTHLY, QUARTERLY, HALF_YEARLY, YEARLY\) | +| ↳ `subscriptionStartDate` | string | Date when the subscription interval begins \(YYYY-MM-DD\) | +| ↳ `periodMinutes` | number | Budgeted minutes for each subscription period | +| ↳ `periodBudget` | number | Fixed budget of every subscription period | +| ↳ `noOfPeriods` | number | Number of periods in the subscription | +| ↳ `startDateActual` | string | Date on which the project status changed to in progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date on which the project status changed to completed \(YYYY-MM-DD\) | +| ↳ `annualizedRecurringRevenue` | number | Recurring revenue of the customer subscriptions for a single calendar year | +| ↳ `projectFee` | number | Total fee charged for the project | +| ↳ `budgetedHours` | number | Total hours allocated for project execution | +| ↳ `percentageBudgetedHoursConsumed` | number | Budgeted hours consumed percentage | +| ↳ `percentageBudgetConsumed` | number | Budget consumed percentage | +| ↳ `trackedHours` | number | Hours tracked as part of submitted time entries | +| ↳ `trackedMinutes` | number | Minutes tracked as part of submitted time entries | +| ↳ `allocatedHours` | number | Allocated hours against users or placeholders | +| ↳ `allocatedMinutes` | number | Allocated minutes against users or placeholders | +| ↳ `billableHours` | number | Hours of time entries tracked as billable | +| ↳ `billableMinutes` | number | Minutes of time entries tracked as billable | +| ↳ `nonBillableHours` | number | Hours of time entries tracked as non-billable | +| ↳ `nonBillableMinutes` | number | Minutes of time entries tracked as non-billable | +| ↳ `remainingHours` | number | Hours left to complete the project based on tracked and budgeted hours | +| ↳ `remainingMinutes` | number | Minutes left to complete the project \(complements remainingHours\) | +| ↳ `progressPercentage` | number | Progress based on completed tasks vs total tasks | +| ↳ `currentPhases` | array | Phases currently marked as in progress | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `autoAllocation` | boolean | Whether auto allocation is enabled for the project | +| ↳ `sources` | array | Project templates imported into the project | +| ↳ `prefix` | string | Prefix distinguishing which phase or task corresponds to which template | +| ↳ `startDate` | string | Date on which the template goes into effect \(YYYY-MM-DD\) | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `plannedDurationInDays` | number | Difference between startDate and dueDate in days | +| ↳ `inferredProgress` | string | Inferred progress \(ON_TRACK, AHEAD_OF_TIME, RUNNING_LATE, or NONE\) | +| ↳ `projectAgeInDays` | number | Age of the project in days based on actual dates | +| ↳ `customersInvited` | number | Number of customers invited to the project | +| ↳ `customersJoined` | number | Number of customers who joined the project | +| ↳ `externalReferenceId` | string | Identifier linking the project to an external system | + +### `rocketlane_remove_project_members` + +Remove team members from a Rocketlane project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | Unique identifier of the project | +| `memberUserIds` | array | No | User IDs of team members to remove \(at least one of memberUserIds or memberEmailIds is required\) | +| `memberEmailIds` | array | No | Emails of team members to remove \(at least one of memberUserIds or memberEmailIds is required\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `project` | object | The project with its updated team members | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Date on which the project execution begins \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date on which the project execution is planned to complete \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time when the project was created \(epoch millis\) | +| ↳ `updatedAt` | number | Time when the project was last updated \(epoch millis\) | +| ↳ `owner` | object | Project owner | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `teamMembers` | object | Project members, customers, and customer champion | +| ↳ `members` | array | Team members working on the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customers` | array | Customer stakeholders involved in the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customerChampion` | object | Customer champion of the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Project status value and label | +| ↳ `value` | number | Unique identifier of the status | +| ↳ `label` | string | Name of the status | +| ↳ `fields` | array | Custom project field values | +| ↳ `fieldId` | number | Unique identifier of the custom field | +| ↳ `fieldLabel` | string | Name of the custom project field | +| ↳ `fieldValue` | string | Value assigned to the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `customer` | object | Customer company of the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `partnerCompanies` | array | Partner companies on the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `archived` | boolean | Whether the project is archived | +| ↳ `visibility` | string | Project visibility \(EVERYONE, MEMBERS, or GROUP\) | +| ↳ `createdBy` | object | Team member who created the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `currency` | string | Currency for the project financials \(ISO code\) | +| ↳ `financials` | object | Project financials \(contract type and per-contract-type fields\) | +| ↳ `contractType` | string | Contract type for the project financials \(FIXED_FEE, TIME_AND_MATERIAL, SUBSCRIPTION, or NON_BILLABLE\) | +| ↳ `revenueRecognitionType` | string | Method used for revenue recognition | +| ↳ `fixedFee` | number | Project fee for Fixed fee contract type projects | +| ↳ `projectBudget` | number | Budget allocated for Time & Material contract type projects | +| ↳ `rateCardId` | number | Unique identifier of the rate card | +| ↳ `rateCardName` | string | Name of the rate card | +| ↳ `subscriptionFrequency` | string | Interval at which the subscription renews \(MONTHLY, QUARTERLY, HALF_YEARLY, YEARLY\) | +| ↳ `subscriptionStartDate` | string | Date when the subscription interval begins \(YYYY-MM-DD\) | +| ↳ `periodMinutes` | number | Budgeted minutes for each subscription period | +| ↳ `periodBudget` | number | Fixed budget of every subscription period | +| ↳ `noOfPeriods` | number | Number of periods in the subscription | +| ↳ `startDateActual` | string | Date on which the project status changed to in progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date on which the project status changed to completed \(YYYY-MM-DD\) | +| ↳ `annualizedRecurringRevenue` | number | Recurring revenue of the customer subscriptions for a single calendar year | +| ↳ `projectFee` | number | Total fee charged for the project | +| ↳ `budgetedHours` | number | Total hours allocated for project execution | +| ↳ `percentageBudgetedHoursConsumed` | number | Budgeted hours consumed percentage | +| ↳ `percentageBudgetConsumed` | number | Budget consumed percentage | +| ↳ `trackedHours` | number | Hours tracked as part of submitted time entries | +| ↳ `trackedMinutes` | number | Minutes tracked as part of submitted time entries | +| ↳ `allocatedHours` | number | Allocated hours against users or placeholders | +| ↳ `allocatedMinutes` | number | Allocated minutes against users or placeholders | +| ↳ `billableHours` | number | Hours of time entries tracked as billable | +| ↳ `billableMinutes` | number | Minutes of time entries tracked as billable | +| ↳ `nonBillableHours` | number | Hours of time entries tracked as non-billable | +| ↳ `nonBillableMinutes` | number | Minutes of time entries tracked as non-billable | +| ↳ `remainingHours` | number | Hours left to complete the project based on tracked and budgeted hours | +| ↳ `remainingMinutes` | number | Minutes left to complete the project \(complements remainingHours\) | +| ↳ `progressPercentage` | number | Progress based on completed tasks vs total tasks | +| ↳ `currentPhases` | array | Phases currently marked as in progress | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `autoAllocation` | boolean | Whether auto allocation is enabled for the project | +| ↳ `sources` | array | Project templates imported into the project | +| ↳ `prefix` | string | Prefix distinguishing which phase or task corresponds to which template | +| ↳ `startDate` | string | Date on which the template goes into effect \(YYYY-MM-DD\) | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `plannedDurationInDays` | number | Difference between startDate and dueDate in days | +| ↳ `inferredProgress` | string | Inferred progress \(ON_TRACK, AHEAD_OF_TIME, RUNNING_LATE, or NONE\) | +| ↳ `projectAgeInDays` | number | Age of the project in days based on actual dates | +| ↳ `customersInvited` | number | Number of customers invited to the project | +| ↳ `customersJoined` | number | Number of customers who joined the project | +| ↳ `externalReferenceId` | string | Identifier linking the project to an external system | + +### `rocketlane_import_template` + +Import a project template into an existing Rocketlane project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | Unique identifier of the project to import the template into | +| `templateId` | number | Yes | Unique identifier of the template to import | +| `startDate` | string | Yes | Date on which the template goes into effect for the project \(YYYY-MM-DD\) | +| `prefix` | string | No | Prefix distinguishing which phase or task corresponds to this template when importing multiple templates | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `project` | object | The project after the template import \(including its sources\) | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Date on which the project execution begins \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date on which the project execution is planned to complete \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time when the project was created \(epoch millis\) | +| ↳ `updatedAt` | number | Time when the project was last updated \(epoch millis\) | +| ↳ `owner` | object | Project owner | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `teamMembers` | object | Project members, customers, and customer champion | +| ↳ `members` | array | Team members working on the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customers` | array | Customer stakeholders involved in the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customerChampion` | object | Customer champion of the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Project status value and label | +| ↳ `value` | number | Unique identifier of the status | +| ↳ `label` | string | Name of the status | +| ↳ `fields` | array | Custom project field values | +| ↳ `fieldId` | number | Unique identifier of the custom field | +| ↳ `fieldLabel` | string | Name of the custom project field | +| ↳ `fieldValue` | string | Value assigned to the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `customer` | object | Customer company of the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `partnerCompanies` | array | Partner companies on the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `archived` | boolean | Whether the project is archived | +| ↳ `visibility` | string | Project visibility \(EVERYONE, MEMBERS, or GROUP\) | +| ↳ `createdBy` | object | Team member who created the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `currency` | string | Currency for the project financials \(ISO code\) | +| ↳ `financials` | object | Project financials \(contract type and per-contract-type fields\) | +| ↳ `contractType` | string | Contract type for the project financials \(FIXED_FEE, TIME_AND_MATERIAL, SUBSCRIPTION, or NON_BILLABLE\) | +| ↳ `revenueRecognitionType` | string | Method used for revenue recognition | +| ↳ `fixedFee` | number | Project fee for Fixed fee contract type projects | +| ↳ `projectBudget` | number | Budget allocated for Time & Material contract type projects | +| ↳ `rateCardId` | number | Unique identifier of the rate card | +| ↳ `rateCardName` | string | Name of the rate card | +| ↳ `subscriptionFrequency` | string | Interval at which the subscription renews \(MONTHLY, QUARTERLY, HALF_YEARLY, YEARLY\) | +| ↳ `subscriptionStartDate` | string | Date when the subscription interval begins \(YYYY-MM-DD\) | +| ↳ `periodMinutes` | number | Budgeted minutes for each subscription period | +| ↳ `periodBudget` | number | Fixed budget of every subscription period | +| ↳ `noOfPeriods` | number | Number of periods in the subscription | +| ↳ `startDateActual` | string | Date on which the project status changed to in progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date on which the project status changed to completed \(YYYY-MM-DD\) | +| ↳ `annualizedRecurringRevenue` | number | Recurring revenue of the customer subscriptions for a single calendar year | +| ↳ `projectFee` | number | Total fee charged for the project | +| ↳ `budgetedHours` | number | Total hours allocated for project execution | +| ↳ `percentageBudgetedHoursConsumed` | number | Budgeted hours consumed percentage | +| ↳ `percentageBudgetConsumed` | number | Budget consumed percentage | +| ↳ `trackedHours` | number | Hours tracked as part of submitted time entries | +| ↳ `trackedMinutes` | number | Minutes tracked as part of submitted time entries | +| ↳ `allocatedHours` | number | Allocated hours against users or placeholders | +| ↳ `allocatedMinutes` | number | Allocated minutes against users or placeholders | +| ↳ `billableHours` | number | Hours of time entries tracked as billable | +| ↳ `billableMinutes` | number | Minutes of time entries tracked as billable | +| ↳ `nonBillableHours` | number | Hours of time entries tracked as non-billable | +| ↳ `nonBillableMinutes` | number | Minutes of time entries tracked as non-billable | +| ↳ `remainingHours` | number | Hours left to complete the project based on tracked and budgeted hours | +| ↳ `remainingMinutes` | number | Minutes left to complete the project \(complements remainingHours\) | +| ↳ `progressPercentage` | number | Progress based on completed tasks vs total tasks | +| ↳ `currentPhases` | array | Phases currently marked as in progress | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `autoAllocation` | boolean | Whether auto allocation is enabled for the project | +| ↳ `sources` | array | Project templates imported into the project | +| ↳ `prefix` | string | Prefix distinguishing which phase or task corresponds to which template | +| ↳ `startDate` | string | Date on which the template goes into effect \(YYYY-MM-DD\) | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `plannedDurationInDays` | number | Difference between startDate and dueDate in days | +| ↳ `inferredProgress` | string | Inferred progress \(ON_TRACK, AHEAD_OF_TIME, RUNNING_LATE, or NONE\) | +| ↳ `projectAgeInDays` | number | Age of the project in days based on actual dates | +| ↳ `customersInvited` | number | Number of customers invited to the project | +| ↳ `customersJoined` | number | Number of customers who joined the project | +| ↳ `externalReferenceId` | string | Identifier linking the project to an external system | + +### `rocketlane_list_placeholders` + +List the placeholders of a Rocketlane project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | Unique identifier of the project | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `placeholders` | array | Placeholders of the project | +| ↳ `placeholderId` | number | Unique identifier of the placeholder | +| ↳ `placeholderName` | string | Name of the placeholder | +| ↳ `project` | object | Project of the placeholder | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `role` | object | Role of the placeholder | +| ↳ `roleId` | number | Unique identifier of the role | +| ↳ `roleName` | string | Name of the role | +| ↳ `placeholderType` | string | Type of the placeholder \(NATIVE or EXTERNAL\) | +| ↳ `createdAt` | number | Time when the placeholder was created \(epoch millis\) | +| ↳ `updatedAt` | number | Time when the placeholder was last updated \(epoch millis\) | +| `pagination` | object | Pagination details for fetching further pages | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_assign_placeholders` + +Assign a placeholder in a Rocketlane project to a user + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | Unique identifier of the project | +| `placeholderId` | number | Yes | Unique identifier of the placeholder to assign | +| `userId` | number | No | User ID of the project member to assign \(either userId or userEmailId must be provided; must be a customer user for CUSTOMER placeholders\) | +| `userEmailId` | string | No | Email of the project member to assign \(either userId or userEmailId must be provided\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `project` | object | The project after the placeholder assignment | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Date on which the project execution begins \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date on which the project execution is planned to complete \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time when the project was created \(epoch millis\) | +| ↳ `updatedAt` | number | Time when the project was last updated \(epoch millis\) | +| ↳ `owner` | object | Project owner | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `teamMembers` | object | Project members, customers, and customer champion | +| ↳ `members` | array | Team members working on the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customers` | array | Customer stakeholders involved in the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customerChampion` | object | Customer champion of the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Project status value and label | +| ↳ `value` | number | Unique identifier of the status | +| ↳ `label` | string | Name of the status | +| ↳ `fields` | array | Custom project field values | +| ↳ `fieldId` | number | Unique identifier of the custom field | +| ↳ `fieldLabel` | string | Name of the custom project field | +| ↳ `fieldValue` | string | Value assigned to the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `customer` | object | Customer company of the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `partnerCompanies` | array | Partner companies on the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `archived` | boolean | Whether the project is archived | +| ↳ `visibility` | string | Project visibility \(EVERYONE, MEMBERS, or GROUP\) | +| ↳ `createdBy` | object | Team member who created the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `currency` | string | Currency for the project financials \(ISO code\) | +| ↳ `financials` | object | Project financials \(contract type and per-contract-type fields\) | +| ↳ `contractType` | string | Contract type for the project financials \(FIXED_FEE, TIME_AND_MATERIAL, SUBSCRIPTION, or NON_BILLABLE\) | +| ↳ `revenueRecognitionType` | string | Method used for revenue recognition | +| ↳ `fixedFee` | number | Project fee for Fixed fee contract type projects | +| ↳ `projectBudget` | number | Budget allocated for Time & Material contract type projects | +| ↳ `rateCardId` | number | Unique identifier of the rate card | +| ↳ `rateCardName` | string | Name of the rate card | +| ↳ `subscriptionFrequency` | string | Interval at which the subscription renews \(MONTHLY, QUARTERLY, HALF_YEARLY, YEARLY\) | +| ↳ `subscriptionStartDate` | string | Date when the subscription interval begins \(YYYY-MM-DD\) | +| ↳ `periodMinutes` | number | Budgeted minutes for each subscription period | +| ↳ `periodBudget` | number | Fixed budget of every subscription period | +| ↳ `noOfPeriods` | number | Number of periods in the subscription | +| ↳ `startDateActual` | string | Date on which the project status changed to in progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date on which the project status changed to completed \(YYYY-MM-DD\) | +| ↳ `annualizedRecurringRevenue` | number | Recurring revenue of the customer subscriptions for a single calendar year | +| ↳ `projectFee` | number | Total fee charged for the project | +| ↳ `budgetedHours` | number | Total hours allocated for project execution | +| ↳ `percentageBudgetedHoursConsumed` | number | Budgeted hours consumed percentage | +| ↳ `percentageBudgetConsumed` | number | Budget consumed percentage | +| ↳ `trackedHours` | number | Hours tracked as part of submitted time entries | +| ↳ `trackedMinutes` | number | Minutes tracked as part of submitted time entries | +| ↳ `allocatedHours` | number | Allocated hours against users or placeholders | +| ↳ `allocatedMinutes` | number | Allocated minutes against users or placeholders | +| ↳ `billableHours` | number | Hours of time entries tracked as billable | +| ↳ `billableMinutes` | number | Minutes of time entries tracked as billable | +| ↳ `nonBillableHours` | number | Hours of time entries tracked as non-billable | +| ↳ `nonBillableMinutes` | number | Minutes of time entries tracked as non-billable | +| ↳ `remainingHours` | number | Hours left to complete the project based on tracked and budgeted hours | +| ↳ `remainingMinutes` | number | Minutes left to complete the project \(complements remainingHours\) | +| ↳ `progressPercentage` | number | Progress based on completed tasks vs total tasks | +| ↳ `currentPhases` | array | Phases currently marked as in progress | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `autoAllocation` | boolean | Whether auto allocation is enabled for the project | +| ↳ `sources` | array | Project templates imported into the project | +| ↳ `prefix` | string | Prefix distinguishing which phase or task corresponds to which template | +| ↳ `startDate` | string | Date on which the template goes into effect \(YYYY-MM-DD\) | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `plannedDurationInDays` | number | Difference between startDate and dueDate in days | +| ↳ `inferredProgress` | string | Inferred progress \(ON_TRACK, AHEAD_OF_TIME, RUNNING_LATE, or NONE\) | +| ↳ `projectAgeInDays` | number | Age of the project in days based on actual dates | +| ↳ `customersInvited` | number | Number of customers invited to the project | +| ↳ `customersJoined` | number | Number of customers who joined the project | +| ↳ `externalReferenceId` | string | Identifier linking the project to an external system | +| `placeholders` | array | Placeholder-to-user mappings on the project | +| ↳ `placeholder` | object | Placeholder being mapped | +| ↳ `placeholderId` | number | Unique identifier of the placeholder | +| ↳ `placeholderName` | string | Name of the placeholder | +| ↳ `placeholderStatus` | string | Status of the placeholder \(ASSIGNED or UNASSIGNED\) | +| ↳ `user` | object | User assigned to the placeholder | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `role` | string | Role name of the assigned user | +| ↳ `hourlyCostRate` | number | Latest hourly cost rate for the placeholder | +| ↳ `costRateCurrency` | string | Currency for the cost rate | + +### `rocketlane_unassign_placeholders` + +Unassign a placeholder from its user in a Rocketlane project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | Unique identifier of the project | +| `placeholderId` | number | Yes | Unique identifier of the placeholder to unassign | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `project` | object | The project after the placeholder was unassigned | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Date on which the project execution begins \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date on which the project execution is planned to complete \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time when the project was created \(epoch millis\) | +| ↳ `updatedAt` | number | Time when the project was last updated \(epoch millis\) | +| ↳ `owner` | object | Project owner | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `teamMembers` | object | Project members, customers, and customer champion | +| ↳ `members` | array | Team members working on the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customers` | array | Customer stakeholders involved in the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `customerChampion` | object | Customer champion of the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Project status value and label | +| ↳ `value` | number | Unique identifier of the status | +| ↳ `label` | string | Name of the status | +| ↳ `fields` | array | Custom project field values | +| ↳ `fieldId` | number | Unique identifier of the custom field | +| ↳ `fieldLabel` | string | Name of the custom project field | +| ↳ `fieldValue` | string | Value assigned to the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `customer` | object | Customer company of the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `partnerCompanies` | array | Partner companies on the project | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `companyUrl` | string | Website URL of the company | +| ↳ `archived` | boolean | Whether the project is archived | +| ↳ `visibility` | string | Project visibility \(EVERYONE, MEMBERS, or GROUP\) | +| ↳ `createdBy` | object | Team member who created the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the project | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `currency` | string | Currency for the project financials \(ISO code\) | +| ↳ `financials` | object | Project financials \(contract type and per-contract-type fields\) | +| ↳ `contractType` | string | Contract type for the project financials \(FIXED_FEE, TIME_AND_MATERIAL, SUBSCRIPTION, or NON_BILLABLE\) | +| ↳ `revenueRecognitionType` | string | Method used for revenue recognition | +| ↳ `fixedFee` | number | Project fee for Fixed fee contract type projects | +| ↳ `projectBudget` | number | Budget allocated for Time & Material contract type projects | +| ↳ `rateCardId` | number | Unique identifier of the rate card | +| ↳ `rateCardName` | string | Name of the rate card | +| ↳ `subscriptionFrequency` | string | Interval at which the subscription renews \(MONTHLY, QUARTERLY, HALF_YEARLY, YEARLY\) | +| ↳ `subscriptionStartDate` | string | Date when the subscription interval begins \(YYYY-MM-DD\) | +| ↳ `periodMinutes` | number | Budgeted minutes for each subscription period | +| ↳ `periodBudget` | number | Fixed budget of every subscription period | +| ↳ `noOfPeriods` | number | Number of periods in the subscription | +| ↳ `startDateActual` | string | Date on which the project status changed to in progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date on which the project status changed to completed \(YYYY-MM-DD\) | +| ↳ `annualizedRecurringRevenue` | number | Recurring revenue of the customer subscriptions for a single calendar year | +| ↳ `projectFee` | number | Total fee charged for the project | +| ↳ `budgetedHours` | number | Total hours allocated for project execution | +| ↳ `percentageBudgetedHoursConsumed` | number | Budgeted hours consumed percentage | +| ↳ `percentageBudgetConsumed` | number | Budget consumed percentage | +| ↳ `trackedHours` | number | Hours tracked as part of submitted time entries | +| ↳ `trackedMinutes` | number | Minutes tracked as part of submitted time entries | +| ↳ `allocatedHours` | number | Allocated hours against users or placeholders | +| ↳ `allocatedMinutes` | number | Allocated minutes against users or placeholders | +| ↳ `billableHours` | number | Hours of time entries tracked as billable | +| ↳ `billableMinutes` | number | Minutes of time entries tracked as billable | +| ↳ `nonBillableHours` | number | Hours of time entries tracked as non-billable | +| ↳ `nonBillableMinutes` | number | Minutes of time entries tracked as non-billable | +| ↳ `remainingHours` | number | Hours left to complete the project based on tracked and budgeted hours | +| ↳ `remainingMinutes` | number | Minutes left to complete the project \(complements remainingHours\) | +| ↳ `progressPercentage` | number | Progress based on completed tasks vs total tasks | +| ↳ `currentPhases` | array | Phases currently marked as in progress | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `autoAllocation` | boolean | Whether auto allocation is enabled for the project | +| ↳ `sources` | array | Project templates imported into the project | +| ↳ `prefix` | string | Prefix distinguishing which phase or task corresponds to which template | +| ↳ `startDate` | string | Date on which the template goes into effect \(YYYY-MM-DD\) | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `plannedDurationInDays` | number | Difference between startDate and dueDate in days | +| ↳ `inferredProgress` | string | Inferred progress \(ON_TRACK, AHEAD_OF_TIME, RUNNING_LATE, or NONE\) | +| ↳ `projectAgeInDays` | number | Age of the project in days based on actual dates | +| ↳ `customersInvited` | number | Number of customers invited to the project | +| ↳ `customersJoined` | number | Number of customers who joined the project | +| ↳ `externalReferenceId` | string | Identifier linking the project to an external system | +| `placeholders` | array | Placeholder-to-user mappings on the project | +| ↳ `placeholder` | object | Placeholder being mapped | +| ↳ `placeholderId` | number | Unique identifier of the placeholder | +| ↳ `placeholderName` | string | Name of the placeholder | +| ↳ `placeholderStatus` | string | Status of the placeholder \(ASSIGNED or UNASSIGNED\) | +| ↳ `user` | object | User assigned to the placeholder | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `role` | string | Role name of the assigned user | +| ↳ `hourlyCostRate` | number | Latest hourly cost rate for the placeholder | +| ↳ `costRateCurrency` | string | Currency for the cost rate | + +### `rocketlane_create_task` + +Create a new task in a Rocketlane project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `taskName` | string | Yes | Name of the task | +| `projectId` | number | Yes | ID of the project the task belongs to | +| `taskDescription` | string | No | Description of the task in HTML format | +| `taskPrivateNote` | string | No | Private note visible only to team members, in HTML format | +| `startDate` | string | No | Date when the task starts \(YYYY-MM-DD\) | +| `dueDate` | string | No | Date when the task is due, on or after the start date \(YYYY-MM-DD\) | +| `effortInMinutes` | number | No | Expected effort to complete the task, in minutes | +| `progress` | number | No | Progress of the task \(0-100\) | +| `atRisk` | boolean | No | Whether the task is marked as At Risk | +| `type` | string | No | Type of the task: TASK or MILESTONE \(defaults to TASK\) | +| `phaseId` | number | No | ID of the phase to associate the task with \(must belong to the project\) | +| `statusValue` | number | No | Status value to set on the task | +| `assigneeUserIds` | array | No | User IDs of members to assign to the task | +| `assigneeEmailIds` | array | No | Email addresses of members to assign to the task | +| `followerUserIds` | array | No | User IDs of members to add as followers of the task | +| `followerEmailIds` | array | No | Email addresses of members to add as followers of the task | +| `parentTaskId` | number | No | ID of the parent task | +| `externalReferenceId` | string | No | External reference identifier linking the task to an external system | +| `private` | boolean | No | Whether the task is private | +| `includeFields` | array | No | Extra fields to include in the response \(startDateActual, dueDateActual, type, phase, assignees, followers, dependencies, billable, csatEnabled, priority, timeEntryCategory, financialsBudget, taskPrivateNote, parent, externalReferenceId\) | +| `includeAllFields` | boolean | No | Whether all fields should be returned in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The created task | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `taskDescription` | string | Description of the task in HTML format | +| ↳ `taskPrivateNote` | string | Private note visible only to team members, in HTML format | +| ↳ `startDate` | string | Date when the task starts \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date when the task is due \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Date the task status changed to In Progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date the task status changed to Completed \(YYYY-MM-DD\) | +| ↳ `archived` | boolean | Whether the task is archived | +| ↳ `effortInMinutes` | number | Expected effort to complete the task, in minutes | +| ↳ `progress` | number | Progress of the task \(0-100\) | +| ↳ `atRisk` | boolean | Whether the task is marked as At Risk | +| ↳ `type` | string | Type of the task: TASK or MILESTONE | +| ↳ `createdAt` | number | Time the task was created, in epoch millis | +| ↳ `updatedAt` | number | Time the task was last updated, in epoch millis | +| ↳ `createdBy` | object | User who created the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `project` | object | Project associated with the task | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `phase` | object | Phase associated with the task | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `status` | object | Status of the task \(value and label\) | +| ↳ `priority` | object | Priority of the task \(value and label\) | +| ↳ `fields` | array | Custom field values set on the task | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `assignees` | object | Assignees of the task \(members and placeholders\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `followers` | object | Followers of the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `dependencies` | array | Tasks this task depends on \(finish-to-start dependencies\) | +| ↳ `parent` | object | Parent task of the task | +| ↳ `externalReferenceId` | string | External reference identifier linking the task to an external system | +| ↳ `billable` | boolean | Whether the task is billable | +| ↳ `timeEntryCategory` | object | Category in which the task time entries are added | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `financialsBudgets` | array | Financials budgets in which the task time entries are added | +| ↳ `budgetId` | number | Unique identifier of the budget | +| ↳ `budgetName` | string | Name of the budget | +| ↳ `csatEnabled` | boolean | Whether a CSAT survey is sent on completion \(milestone tasks\) | +| ↳ `private` | boolean | Whether the task is private | + +### `rocketlane_get_task` + +Retrieve detailed information about a Rocketlane task by its unique identifier + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `taskId` | number | Yes | Unique identifier of the task to retrieve | +| `includeFields` | array | No | Extra fields to include in the response \(startDateActual, dueDateActual, type, phase, assignees, followers, dependencies, billable, csatEnabled, priority, timeEntryCategory, financialsBudget, taskPrivateNote, parent, externalReferenceId\) | +| `includeAllFields` | boolean | No | Whether all fields should be returned in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The requested task | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `taskDescription` | string | Description of the task in HTML format | +| ↳ `taskPrivateNote` | string | Private note visible only to team members, in HTML format | +| ↳ `startDate` | string | Date when the task starts \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date when the task is due \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Date the task status changed to In Progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date the task status changed to Completed \(YYYY-MM-DD\) | +| ↳ `archived` | boolean | Whether the task is archived | +| ↳ `effortInMinutes` | number | Expected effort to complete the task, in minutes | +| ↳ `progress` | number | Progress of the task \(0-100\) | +| ↳ `atRisk` | boolean | Whether the task is marked as At Risk | +| ↳ `type` | string | Type of the task: TASK or MILESTONE | +| ↳ `createdAt` | number | Time the task was created, in epoch millis | +| ↳ `updatedAt` | number | Time the task was last updated, in epoch millis | +| ↳ `createdBy` | object | User who created the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `project` | object | Project associated with the task | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `phase` | object | Phase associated with the task | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `status` | object | Status of the task \(value and label\) | +| ↳ `priority` | object | Priority of the task \(value and label\) | +| ↳ `fields` | array | Custom field values set on the task | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `assignees` | object | Assignees of the task \(members and placeholders\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `followers` | object | Followers of the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `dependencies` | array | Tasks this task depends on \(finish-to-start dependencies\) | +| ↳ `parent` | object | Parent task of the task | +| ↳ `externalReferenceId` | string | External reference identifier linking the task to an external system | +| ↳ `billable` | boolean | Whether the task is billable | +| ↳ `timeEntryCategory` | object | Category in which the task time entries are added | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `financialsBudgets` | array | Financials budgets in which the task time entries are added | +| ↳ `budgetId` | number | Unique identifier of the budget | +| ↳ `budgetName` | string | Name of the budget | +| ↳ `csatEnabled` | boolean | Whether a CSAT survey is sent on completion \(milestone tasks\) | +| ↳ `private` | boolean | Whether the task is private | + +### `rocketlane_list_tasks` + +Retrieve all Rocketlane tasks with optional filters, sorting, and pagination + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `pageSize` | number | No | Number of tasks per page \(defaults to 100\) | +| `pageToken` | string | No | Token pointing to the next page of results, from a previous response | +| `includeFields` | array | No | Extra fields to include in the response \(startDateActual, dueDateActual, type, phase, assignees, followers, dependencies, billable, csatEnabled, priority, timeEntryCategory, financialsBudget, taskPrivateNote, parent, externalReferenceId\) | +| `includeAllFields` | boolean | No | Whether all fields should be returned in the response | +| `sortBy` | string | No | Field to sort by: taskName, startDate, dueDate, startDateActual, or dueDateActual | +| `sortOrder` | string | No | Sort order: ASC or DESC | +| `match` | string | No | How multiple filters combine: all \(AND\) or any \(OR\) | +| `projectId` | number | No | Filter tasks by project ID | +| `phaseId` | number | No | Filter tasks by phase ID | +| `taskName` | string | No | Filter tasks whose name exactly matches this value | +| `taskNameContains` | string | No | Filter tasks whose name contains this value | +| `taskStatus` | string | No | Filter tasks by task status value | +| `startDateFrom` | string | No | Filter tasks with a start date on or after this date \(YYYY-MM-DD\) | +| `startDateTo` | string | No | Filter tasks with a start date on or before this date \(YYYY-MM-DD\) | +| `dueDateFrom` | string | No | Filter tasks with a due date on or after this date \(YYYY-MM-DD\) | +| `dueDateTo` | string | No | Filter tasks with a due date on or before this date \(YYYY-MM-DD\) | +| `includeArchive` | boolean | No | Whether archived tasks should be included in the results | +| `externalReferenceId` | string | No | Filter tasks by external reference identifier | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `tasks` | array | List of tasks matching the filters | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `taskDescription` | string | Description of the task in HTML format | +| ↳ `taskPrivateNote` | string | Private note visible only to team members, in HTML format | +| ↳ `startDate` | string | Date when the task starts \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date when the task is due \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Date the task status changed to In Progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date the task status changed to Completed \(YYYY-MM-DD\) | +| ↳ `archived` | boolean | Whether the task is archived | +| ↳ `effortInMinutes` | number | Expected effort to complete the task, in minutes | +| ↳ `progress` | number | Progress of the task \(0-100\) | +| ↳ `atRisk` | boolean | Whether the task is marked as At Risk | +| ↳ `type` | string | Type of the task: TASK or MILESTONE | +| ↳ `createdAt` | number | Time the task was created, in epoch millis | +| ↳ `updatedAt` | number | Time the task was last updated, in epoch millis | +| ↳ `createdBy` | object | User who created the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `project` | object | Project associated with the task | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `phase` | object | Phase associated with the task | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `status` | object | Status of the task \(value and label\) | +| ↳ `priority` | object | Priority of the task \(value and label\) | +| ↳ `fields` | array | Custom field values set on the task | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `assignees` | object | Assignees of the task \(members and placeholders\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `followers` | object | Followers of the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `dependencies` | array | Tasks this task depends on \(finish-to-start dependencies\) | +| ↳ `parent` | object | Parent task of the task | +| ↳ `externalReferenceId` | string | External reference identifier linking the task to an external system | +| ↳ `billable` | boolean | Whether the task is billable | +| ↳ `timeEntryCategory` | object | Category in which the task time entries are added | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `financialsBudgets` | array | Financials budgets in which the task time entries are added | +| ↳ `budgetId` | number | Unique identifier of the budget | +| ↳ `budgetName` | string | Name of the budget | +| ↳ `csatEnabled` | boolean | Whether a CSAT survey is sent on completion \(milestone tasks\) | +| ↳ `private` | boolean | Whether the task is private | +| `pagination` | object | Pagination details for the result set | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_update_task` + +Update the properties of an existing Rocketlane task by its unique identifier + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `taskId` | number | Yes | Unique identifier of the task to update | +| `taskName` | string | No | New name of the task | +| `taskDescription` | string | No | Description of the task in HTML format | +| `taskPrivateNote` | string | No | Private note visible only to team members, in HTML format | +| `startDate` | string | No | Date when the task starts \(YYYY-MM-DD\) | +| `dueDate` | string | No | Date when the task is due, on or after the start date \(YYYY-MM-DD\) | +| `effortInMinutes` | number | No | Expected effort to complete the task, in minutes | +| `progress` | number | No | Progress of the task \(0-100\) | +| `atRisk` | boolean | No | Whether the task is marked as At Risk | +| `type` | string | No | Type of the task: TASK or MILESTONE | +| `statusValue` | number | No | Status value to set on the task | +| `externalReferenceId` | string | No | External reference identifier linking the task to an external system | +| `private` | boolean | No | Whether the task is private | +| `includeFields` | array | No | Extra fields to include in the response \(startDateActual, dueDateActual, type, phase, assignees, followers, dependencies, billable, csatEnabled, priority, timeEntryCategory, financialsBudget, taskPrivateNote, parent, externalReferenceId\) | +| `includeAllFields` | boolean | No | Whether all fields should be returned in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The updated task | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `taskDescription` | string | Description of the task in HTML format | +| ↳ `taskPrivateNote` | string | Private note visible only to team members, in HTML format | +| ↳ `startDate` | string | Date when the task starts \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date when the task is due \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Date the task status changed to In Progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date the task status changed to Completed \(YYYY-MM-DD\) | +| ↳ `archived` | boolean | Whether the task is archived | +| ↳ `effortInMinutes` | number | Expected effort to complete the task, in minutes | +| ↳ `progress` | number | Progress of the task \(0-100\) | +| ↳ `atRisk` | boolean | Whether the task is marked as At Risk | +| ↳ `type` | string | Type of the task: TASK or MILESTONE | +| ↳ `createdAt` | number | Time the task was created, in epoch millis | +| ↳ `updatedAt` | number | Time the task was last updated, in epoch millis | +| ↳ `createdBy` | object | User who created the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `project` | object | Project associated with the task | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `phase` | object | Phase associated with the task | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `status` | object | Status of the task \(value and label\) | +| ↳ `priority` | object | Priority of the task \(value and label\) | +| ↳ `fields` | array | Custom field values set on the task | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `assignees` | object | Assignees of the task \(members and placeholders\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `followers` | object | Followers of the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `dependencies` | array | Tasks this task depends on \(finish-to-start dependencies\) | +| ↳ `parent` | object | Parent task of the task | +| ↳ `externalReferenceId` | string | External reference identifier linking the task to an external system | +| ↳ `billable` | boolean | Whether the task is billable | +| ↳ `timeEntryCategory` | object | Category in which the task time entries are added | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `financialsBudgets` | array | Financials budgets in which the task time entries are added | +| ↳ `budgetId` | number | Unique identifier of the budget | +| ↳ `budgetName` | string | Name of the budget | +| ↳ `csatEnabled` | boolean | Whether a CSAT survey is sent on completion \(milestone tasks\) | +| ↳ `private` | boolean | Whether the task is private | + +### `rocketlane_delete_task` + +Permanently delete a Rocketlane task by its unique identifier + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `taskId` | number | Yes | Unique identifier of the task to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the task was deleted | +| `taskId` | number | ID of the deleted task | + +### `rocketlane_move_task_to_phase` + +Move a Rocketlane task to a given phase, associating the task with that phase + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `taskId` | number | Yes | Unique identifier of the task to move | +| `phaseId` | number | Yes | Unique identifier of the phase to move the task to | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The task with its updated phase | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `taskDescription` | string | Description of the task in HTML format | +| ↳ `taskPrivateNote` | string | Private note visible only to team members, in HTML format | +| ↳ `startDate` | string | Date when the task starts \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date when the task is due \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Date the task status changed to In Progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date the task status changed to Completed \(YYYY-MM-DD\) | +| ↳ `archived` | boolean | Whether the task is archived | +| ↳ `effortInMinutes` | number | Expected effort to complete the task, in minutes | +| ↳ `progress` | number | Progress of the task \(0-100\) | +| ↳ `atRisk` | boolean | Whether the task is marked as At Risk | +| ↳ `type` | string | Type of the task: TASK or MILESTONE | +| ↳ `createdAt` | number | Time the task was created, in epoch millis | +| ↳ `updatedAt` | number | Time the task was last updated, in epoch millis | +| ↳ `createdBy` | object | User who created the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `project` | object | Project associated with the task | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `phase` | object | Phase associated with the task | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `status` | object | Status of the task \(value and label\) | +| ↳ `priority` | object | Priority of the task \(value and label\) | +| ↳ `fields` | array | Custom field values set on the task | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `assignees` | object | Assignees of the task \(members and placeholders\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `followers` | object | Followers of the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `dependencies` | array | Tasks this task depends on \(finish-to-start dependencies\) | +| ↳ `parent` | object | Parent task of the task | +| ↳ `externalReferenceId` | string | External reference identifier linking the task to an external system | +| ↳ `billable` | boolean | Whether the task is billable | +| ↳ `timeEntryCategory` | object | Category in which the task time entries are added | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `financialsBudgets` | array | Financials budgets in which the task time entries are added | +| ↳ `budgetId` | number | Unique identifier of the budget | +| ↳ `budgetName` | string | Name of the budget | +| ↳ `csatEnabled` | boolean | Whether a CSAT survey is sent on completion \(milestone tasks\) | +| ↳ `private` | boolean | Whether the task is private | + +### `rocketlane_add_task_assignees` + +Add members as assignees to a Rocketlane task + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `taskId` | number | Yes | Unique identifier of the task to add assignees to | +| `memberUserIds` | array | No | User IDs of members to add as assignees | +| `memberEmailIds` | array | No | Email addresses of members to add as assignees | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The task with its updated assignees | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `taskDescription` | string | Description of the task in HTML format | +| ↳ `taskPrivateNote` | string | Private note visible only to team members, in HTML format | +| ↳ `startDate` | string | Date when the task starts \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date when the task is due \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Date the task status changed to In Progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date the task status changed to Completed \(YYYY-MM-DD\) | +| ↳ `archived` | boolean | Whether the task is archived | +| ↳ `effortInMinutes` | number | Expected effort to complete the task, in minutes | +| ↳ `progress` | number | Progress of the task \(0-100\) | +| ↳ `atRisk` | boolean | Whether the task is marked as At Risk | +| ↳ `type` | string | Type of the task: TASK or MILESTONE | +| ↳ `createdAt` | number | Time the task was created, in epoch millis | +| ↳ `updatedAt` | number | Time the task was last updated, in epoch millis | +| ↳ `createdBy` | object | User who created the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `project` | object | Project associated with the task | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `phase` | object | Phase associated with the task | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `status` | object | Status of the task \(value and label\) | +| ↳ `priority` | object | Priority of the task \(value and label\) | +| ↳ `fields` | array | Custom field values set on the task | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `assignees` | object | Assignees of the task \(members and placeholders\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `followers` | object | Followers of the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `dependencies` | array | Tasks this task depends on \(finish-to-start dependencies\) | +| ↳ `parent` | object | Parent task of the task | +| ↳ `externalReferenceId` | string | External reference identifier linking the task to an external system | +| ↳ `billable` | boolean | Whether the task is billable | +| ↳ `timeEntryCategory` | object | Category in which the task time entries are added | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `financialsBudgets` | array | Financials budgets in which the task time entries are added | +| ↳ `budgetId` | number | Unique identifier of the budget | +| ↳ `budgetName` | string | Name of the budget | +| ↳ `csatEnabled` | boolean | Whether a CSAT survey is sent on completion \(milestone tasks\) | +| ↳ `private` | boolean | Whether the task is private | + +### `rocketlane_remove_task_assignees` + +Remove assignees from a Rocketlane task + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `taskId` | number | Yes | Unique identifier of the task to remove assignees from | +| `memberUserIds` | array | No | User IDs of members to remove from the assignees | +| `memberEmailIds` | array | No | Email addresses of members to remove from the assignees | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The task with its updated assignees | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `taskDescription` | string | Description of the task in HTML format | +| ↳ `taskPrivateNote` | string | Private note visible only to team members, in HTML format | +| ↳ `startDate` | string | Date when the task starts \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date when the task is due \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Date the task status changed to In Progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date the task status changed to Completed \(YYYY-MM-DD\) | +| ↳ `archived` | boolean | Whether the task is archived | +| ↳ `effortInMinutes` | number | Expected effort to complete the task, in minutes | +| ↳ `progress` | number | Progress of the task \(0-100\) | +| ↳ `atRisk` | boolean | Whether the task is marked as At Risk | +| ↳ `type` | string | Type of the task: TASK or MILESTONE | +| ↳ `createdAt` | number | Time the task was created, in epoch millis | +| ↳ `updatedAt` | number | Time the task was last updated, in epoch millis | +| ↳ `createdBy` | object | User who created the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `project` | object | Project associated with the task | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `phase` | object | Phase associated with the task | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `status` | object | Status of the task \(value and label\) | +| ↳ `priority` | object | Priority of the task \(value and label\) | +| ↳ `fields` | array | Custom field values set on the task | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `assignees` | object | Assignees of the task \(members and placeholders\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `followers` | object | Followers of the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `dependencies` | array | Tasks this task depends on \(finish-to-start dependencies\) | +| ↳ `parent` | object | Parent task of the task | +| ↳ `externalReferenceId` | string | External reference identifier linking the task to an external system | +| ↳ `billable` | boolean | Whether the task is billable | +| ↳ `timeEntryCategory` | object | Category in which the task time entries are added | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `financialsBudgets` | array | Financials budgets in which the task time entries are added | +| ↳ `budgetId` | number | Unique identifier of the budget | +| ↳ `budgetName` | string | Name of the budget | +| ↳ `csatEnabled` | boolean | Whether a CSAT survey is sent on completion \(milestone tasks\) | +| ↳ `private` | boolean | Whether the task is private | + +### `rocketlane_add_task_followers` + +Add members as followers to a Rocketlane task + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `taskId` | number | Yes | Unique identifier of the task to add followers to | +| `memberUserIds` | array | No | User IDs of members to add as followers | +| `memberEmailIds` | array | No | Email addresses of members to add as followers | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The task with its updated followers | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `taskDescription` | string | Description of the task in HTML format | +| ↳ `taskPrivateNote` | string | Private note visible only to team members, in HTML format | +| ↳ `startDate` | string | Date when the task starts \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date when the task is due \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Date the task status changed to In Progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date the task status changed to Completed \(YYYY-MM-DD\) | +| ↳ `archived` | boolean | Whether the task is archived | +| ↳ `effortInMinutes` | number | Expected effort to complete the task, in minutes | +| ↳ `progress` | number | Progress of the task \(0-100\) | +| ↳ `atRisk` | boolean | Whether the task is marked as At Risk | +| ↳ `type` | string | Type of the task: TASK or MILESTONE | +| ↳ `createdAt` | number | Time the task was created, in epoch millis | +| ↳ `updatedAt` | number | Time the task was last updated, in epoch millis | +| ↳ `createdBy` | object | User who created the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `project` | object | Project associated with the task | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `phase` | object | Phase associated with the task | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `status` | object | Status of the task \(value and label\) | +| ↳ `priority` | object | Priority of the task \(value and label\) | +| ↳ `fields` | array | Custom field values set on the task | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `assignees` | object | Assignees of the task \(members and placeholders\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `followers` | object | Followers of the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `dependencies` | array | Tasks this task depends on \(finish-to-start dependencies\) | +| ↳ `parent` | object | Parent task of the task | +| ↳ `externalReferenceId` | string | External reference identifier linking the task to an external system | +| ↳ `billable` | boolean | Whether the task is billable | +| ↳ `timeEntryCategory` | object | Category in which the task time entries are added | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `financialsBudgets` | array | Financials budgets in which the task time entries are added | +| ↳ `budgetId` | number | Unique identifier of the budget | +| ↳ `budgetName` | string | Name of the budget | +| ↳ `csatEnabled` | boolean | Whether a CSAT survey is sent on completion \(milestone tasks\) | +| ↳ `private` | boolean | Whether the task is private | + +### `rocketlane_remove_task_followers` + +Remove followers from a Rocketlane task + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `taskId` | number | Yes | Unique identifier of the task to remove followers from | +| `memberUserIds` | array | No | User IDs of members to remove from the followers | +| `memberEmailIds` | array | No | Email addresses of members to remove from the followers | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The task with its updated followers | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `taskDescription` | string | Description of the task in HTML format | +| ↳ `taskPrivateNote` | string | Private note visible only to team members, in HTML format | +| ↳ `startDate` | string | Date when the task starts \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date when the task is due \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Date the task status changed to In Progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date the task status changed to Completed \(YYYY-MM-DD\) | +| ↳ `archived` | boolean | Whether the task is archived | +| ↳ `effortInMinutes` | number | Expected effort to complete the task, in minutes | +| ↳ `progress` | number | Progress of the task \(0-100\) | +| ↳ `atRisk` | boolean | Whether the task is marked as At Risk | +| ↳ `type` | string | Type of the task: TASK or MILESTONE | +| ↳ `createdAt` | number | Time the task was created, in epoch millis | +| ↳ `updatedAt` | number | Time the task was last updated, in epoch millis | +| ↳ `createdBy` | object | User who created the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `project` | object | Project associated with the task | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `phase` | object | Phase associated with the task | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `status` | object | Status of the task \(value and label\) | +| ↳ `priority` | object | Priority of the task \(value and label\) | +| ↳ `fields` | array | Custom field values set on the task | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `assignees` | object | Assignees of the task \(members and placeholders\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `followers` | object | Followers of the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `dependencies` | array | Tasks this task depends on \(finish-to-start dependencies\) | +| ↳ `parent` | object | Parent task of the task | +| ↳ `externalReferenceId` | string | External reference identifier linking the task to an external system | +| ↳ `billable` | boolean | Whether the task is billable | +| ↳ `timeEntryCategory` | object | Category in which the task time entries are added | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `financialsBudgets` | array | Financials budgets in which the task time entries are added | +| ↳ `budgetId` | number | Unique identifier of the budget | +| ↳ `budgetName` | string | Name of the budget | +| ↳ `csatEnabled` | boolean | Whether a CSAT survey is sent on completion \(milestone tasks\) | +| ↳ `private` | boolean | Whether the task is private | + +### `rocketlane_add_task_dependencies` + +Add finish-to-start dependencies between a Rocketlane task and other tasks + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `taskId` | number | Yes | Unique identifier of the task to add dependencies to | +| `dependencyTaskIds` | array | Yes | Task IDs the task should depend on | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The task with its updated dependencies | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `taskDescription` | string | Description of the task in HTML format | +| ↳ `taskPrivateNote` | string | Private note visible only to team members, in HTML format | +| ↳ `startDate` | string | Date when the task starts \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date when the task is due \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Date the task status changed to In Progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date the task status changed to Completed \(YYYY-MM-DD\) | +| ↳ `archived` | boolean | Whether the task is archived | +| ↳ `effortInMinutes` | number | Expected effort to complete the task, in minutes | +| ↳ `progress` | number | Progress of the task \(0-100\) | +| ↳ `atRisk` | boolean | Whether the task is marked as At Risk | +| ↳ `type` | string | Type of the task: TASK or MILESTONE | +| ↳ `createdAt` | number | Time the task was created, in epoch millis | +| ↳ `updatedAt` | number | Time the task was last updated, in epoch millis | +| ↳ `createdBy` | object | User who created the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `project` | object | Project associated with the task | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `phase` | object | Phase associated with the task | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `status` | object | Status of the task \(value and label\) | +| ↳ `priority` | object | Priority of the task \(value and label\) | +| ↳ `fields` | array | Custom field values set on the task | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `assignees` | object | Assignees of the task \(members and placeholders\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `followers` | object | Followers of the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `dependencies` | array | Tasks this task depends on \(finish-to-start dependencies\) | +| ↳ `parent` | object | Parent task of the task | +| ↳ `externalReferenceId` | string | External reference identifier linking the task to an external system | +| ↳ `billable` | boolean | Whether the task is billable | +| ↳ `timeEntryCategory` | object | Category in which the task time entries are added | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `financialsBudgets` | array | Financials budgets in which the task time entries are added | +| ↳ `budgetId` | number | Unique identifier of the budget | +| ↳ `budgetName` | string | Name of the budget | +| ↳ `csatEnabled` | boolean | Whether a CSAT survey is sent on completion \(milestone tasks\) | +| ↳ `private` | boolean | Whether the task is private | + +### `rocketlane_remove_task_dependencies` + +Remove dependencies from a Rocketlane task + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `taskId` | number | Yes | Unique identifier of the task to remove dependencies from | +| `dependencyTaskIds` | array | Yes | Task IDs to remove from the task dependencies | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `task` | object | The task with its updated dependencies | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `taskDescription` | string | Description of the task in HTML format | +| ↳ `taskPrivateNote` | string | Private note visible only to team members, in HTML format | +| ↳ `startDate` | string | Date when the task starts \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Date when the task is due \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Date the task status changed to In Progress \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Date the task status changed to Completed \(YYYY-MM-DD\) | +| ↳ `archived` | boolean | Whether the task is archived | +| ↳ `effortInMinutes` | number | Expected effort to complete the task, in minutes | +| ↳ `progress` | number | Progress of the task \(0-100\) | +| ↳ `atRisk` | boolean | Whether the task is marked as At Risk | +| ↳ `type` | string | Type of the task: TASK or MILESTONE | +| ↳ `createdAt` | number | Time the task was created, in epoch millis | +| ↳ `updatedAt` | number | Time the task was last updated, in epoch millis | +| ↳ `createdBy` | object | User who created the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `project` | object | Project associated with the task | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `phase` | object | Phase associated with the task | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `status` | object | Status of the task \(value and label\) | +| ↳ `priority` | object | Priority of the task \(value and label\) | +| ↳ `fields` | array | Custom field values set on the task | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `assignees` | object | Assignees of the task \(members and placeholders\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `followers` | object | Followers of the task | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `dependencies` | array | Tasks this task depends on \(finish-to-start dependencies\) | +| ↳ `parent` | object | Parent task of the task | +| ↳ `externalReferenceId` | string | External reference identifier linking the task to an external system | +| ↳ `billable` | boolean | Whether the task is billable | +| ↳ `timeEntryCategory` | object | Category in which the task time entries are added | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `financialsBudgets` | array | Financials budgets in which the task time entries are added | +| ↳ `budgetId` | number | Unique identifier of the budget | +| ↳ `budgetName` | string | Name of the budget | +| ↳ `csatEnabled` | boolean | Whether a CSAT survey is sent on completion \(milestone tasks\) | +| ↳ `private` | boolean | Whether the task is private | + +### `rocketlane_create_phase` + +Create a new phase in a Rocketlane project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `phaseName` | string | Yes | Name of the phase | +| `projectId` | number | Yes | ID of the project to create the phase in | +| `startDate` | string | Yes | Planned start date of the phase \(YYYY-MM-DD\) | +| `dueDate` | string | Yes | Planned due date of the phase \(YYYY-MM-DD\) | +| `statusValue` | number | No | Numeric status value to set on the phase | +| `private` | boolean | No | Whether the phase is private | +| `includeFields` | string | No | Comma-separated extra phase properties to include in the response \(supported: startDateActual, dueDateActual\) | +| `includeAllFields` | boolean | No | Whether to return all phase properties in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `phase` | object | The created phase | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `project` | object | Project the phase belongs to | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Planned start date of the phase \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Planned due date of the phase \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Actual start date of the phase \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Actual due date of the phase \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time the phase was created, in epoch milliseconds | +| ↳ `updatedAt` | number | Time the phase was last updated, in epoch milliseconds | +| ↳ `createdBy` | object | Team member who created the phase | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the phase | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Status of the phase | +| ↳ `value` | number | Numeric status value | +| ↳ `label` | string | Display label of the status | +| ↳ `private` | boolean | Whether the phase is private | + +### `rocketlane_get_phase` + +Retrieve a single Rocketlane phase by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `phaseId` | number | Yes | ID of the phase to retrieve | +| `includeFields` | string | No | Comma-separated extra phase properties to include in the response \(supported: startDateActual, dueDateActual\) | +| `includeAllFields` | boolean | No | Whether to return all phase properties in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `phase` | object | The requested phase | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `project` | object | Project the phase belongs to | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Planned start date of the phase \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Planned due date of the phase \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Actual start date of the phase \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Actual due date of the phase \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time the phase was created, in epoch milliseconds | +| ↳ `updatedAt` | number | Time the phase was last updated, in epoch milliseconds | +| ↳ `createdBy` | object | Team member who created the phase | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the phase | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Status of the phase | +| ↳ `value` | number | Numeric status value | +| ↳ `label` | string | Display label of the status | +| ↳ `private` | boolean | Whether the phase is private | + +### `rocketlane_list_phases` + +List phases of a Rocketlane project, with optional filters, sorting, and pagination + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | ID of the project to list phases for | +| `pageSize` | number | No | Number of phases per page | +| `pageToken` | string | No | Page token returned by a previous request \(valid for 15 minutes\) | +| `includeFields` | string | No | Comma-separated extra phase properties to include in the response \(supported: startDateActual, dueDateActual\) | +| `includeAllFields` | boolean | No | Whether to return all phase properties in the response | +| `sortBy` | string | No | Property to sort by \(phaseName, startDate, dueDate, startDateActual, dueDateActual\) | +| `sortOrder` | string | No | Sort order \(ASC or DESC\) | +| `match` | string | No | Whether results must match all filters or any filter \(all or any\) | +| `phaseName` | string | No | Filter by exact phase name | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `phases` | array | List of phases | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `project` | object | Project the phase belongs to | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Planned start date of the phase \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Planned due date of the phase \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Actual start date of the phase \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Actual due date of the phase \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time the phase was created, in epoch milliseconds | +| ↳ `updatedAt` | number | Time the phase was last updated, in epoch milliseconds | +| ↳ `createdBy` | object | Team member who created the phase | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the phase | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Status of the phase | +| ↳ `value` | number | Numeric status value | +| ↳ `label` | string | Display label of the status | +| ↳ `private` | boolean | Whether the phase is private | +| `pagination` | object | Pagination details for the result set | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_update_phase` + +Update the name, dates, status, or privacy of an existing Rocketlane phase + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `phaseId` | number | Yes | ID of the phase to update | +| `phaseName` | string | No | New name of the phase | +| `startDate` | string | No | New planned start date of the phase \(YYYY-MM-DD\) | +| `dueDate` | string | No | New planned due date of the phase \(YYYY-MM-DD\) | +| `statusValue` | number | No | New numeric status value for the phase | +| `private` | boolean | No | Whether the phase is private | +| `includeFields` | string | No | Comma-separated extra phase properties to include in the response \(supported: startDateActual, dueDateActual\) | +| `includeAllFields` | boolean | No | Whether to return all phase properties in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `phase` | object | The updated phase | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `project` | object | Project the phase belongs to | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `startDate` | string | Planned start date of the phase \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Planned due date of the phase \(YYYY-MM-DD\) | +| ↳ `startDateActual` | string | Actual start date of the phase \(YYYY-MM-DD\) | +| ↳ `dueDateActual` | string | Actual due date of the phase \(YYYY-MM-DD\) | +| ↳ `createdAt` | number | Time the phase was created, in epoch milliseconds | +| ↳ `updatedAt` | number | Time the phase was last updated, in epoch milliseconds | +| ↳ `createdBy` | object | Team member who created the phase | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the phase | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `status` | object | Status of the phase | +| ↳ `value` | number | Numeric status value | +| ↳ `label` | string | Display label of the status | +| ↳ `private` | boolean | Whether the phase is private | + +### `rocketlane_delete_phase` + +Permanently delete a Rocketlane phase by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `phaseId` | number | Yes | ID of the phase to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the phase was deleted | +| `phaseId` | number | ID of the deleted phase | + +### `rocketlane_create_field` + +Create a custom field in your Rocketlane account, with options for SINGLE_CHOICE and MULTIPLE_CHOICE field types + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `fieldLabel` | string | Yes | Name of the field | +| `fieldType` | string | Yes | Type of the field \(TEXT, MULTI_LINE_TEXT, YES_OR_NO, DATE, SINGLE_CHOICE, MULTIPLE_CHOICE, SINGLE_USER, MULTIPLE_USER, NUMBER, NOTE, RATING\) | +| `objectType` | string | Yes | Object the field is associated with \(PROJECT, TASK, or USER\) | +| `fieldDescription` | string | No | Description of the field | +| `fieldOptions` | array | No | Options for SINGLE_CHOICE and MULTIPLE_CHOICE fields; the order provided is preserved | +| `ratingScale` | string | No | Number of stars for RATING fields \(THREE, FIVE, SEVEN, TEN\) | +| `enabled` | boolean | No | Whether the field is enabled \(defaults to true\) | +| `private` | boolean | No | Whether the field is private | +| `includeFields` | string | No | Comma-separated extra field properties to include in the response \(supported: options\) | +| `includeAllFields` | boolean | No | Whether to return all field properties in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `field` | object | The created field | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Name of the field | +| ↳ `fieldDescription` | string | Description of the field | +| ↳ `fieldType` | string | Type of the field \(TEXT, MULTI_LINE_TEXT, YES_OR_NO, DATE, SINGLE_CHOICE, MULTIPLE_CHOICE, SINGLE_USER, MULTIPLE_USER, NUMBER, NOTE, RATING\) | +| ↳ `objectType` | string | Object the field is associated with \(PROJECT, TASK, or USER\) | +| ↳ `fieldOptions` | array | Options available for SINGLE_CHOICE and MULTIPLE_CHOICE fields | +| ↳ `optionValue` | number | Unique identifier of the option within the field | +| ↳ `optionLabel` | string | Display label of the option | +| ↳ `optionColor` | string | Color of the option \(RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY\) | +| ↳ `ratingScale` | string | Rating scale for RATING fields \(THREE, FIVE, SEVEN, TEN\) | +| ↳ `createdBy` | object | Team member who created the field | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the field | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `createdAt` | number | Time the field was created, in epoch milliseconds | +| ↳ `updatedAt` | number | Time the field was last updated, in epoch milliseconds | +| ↳ `enabled` | boolean | Whether the field is enabled | +| ↳ `private` | boolean | Whether the field is private | + +### `rocketlane_get_field` + +Retrieve a single Rocketlane field by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `fieldId` | number | Yes | ID of the field to retrieve | +| `includeFields` | string | No | Comma-separated extra field properties to include in the response \(supported: options\) | +| `includeAllFields` | boolean | No | Whether to return all field properties in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `field` | object | The requested field | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Name of the field | +| ↳ `fieldDescription` | string | Description of the field | +| ↳ `fieldType` | string | Type of the field \(TEXT, MULTI_LINE_TEXT, YES_OR_NO, DATE, SINGLE_CHOICE, MULTIPLE_CHOICE, SINGLE_USER, MULTIPLE_USER, NUMBER, NOTE, RATING\) | +| ↳ `objectType` | string | Object the field is associated with \(PROJECT, TASK, or USER\) | +| ↳ `fieldOptions` | array | Options available for SINGLE_CHOICE and MULTIPLE_CHOICE fields | +| ↳ `optionValue` | number | Unique identifier of the option within the field | +| ↳ `optionLabel` | string | Display label of the option | +| ↳ `optionColor` | string | Color of the option \(RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY\) | +| ↳ `ratingScale` | string | Rating scale for RATING fields \(THREE, FIVE, SEVEN, TEN\) | +| ↳ `createdBy` | object | Team member who created the field | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the field | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `createdAt` | number | Time the field was created, in epoch milliseconds | +| ↳ `updatedAt` | number | Time the field was last updated, in epoch milliseconds | +| ↳ `enabled` | boolean | Whether the field is enabled | +| ↳ `private` | boolean | Whether the field is private | + +### `rocketlane_list_fields` + +List fields in your Rocketlane account, with optional filters, sorting, and pagination + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `pageSize` | number | No | Number of fields per page | +| `pageToken` | string | No | Page token returned by a previous request \(valid for 15 minutes\) | +| `includeFields` | string | No | Comma-separated extra field properties to include in the response \(supported: options\) | +| `includeAllFields` | boolean | No | Whether to return all field properties in the response | +| `sortBy` | string | No | Property to sort by \(supported: fieldLabel\) | +| `sortOrder` | string | No | Sort order \(ASC or DESC\) | +| `match` | string | No | Whether results must match all filters or any filter \(all or any\) | +| `objectType` | string | No | Filter by associated object type \(PROJECT, TASK, or USER\) | +| `fieldType` | string | No | Filter by field type \(TEXT, MULTI_LINE_TEXT, YES_OR_NO, DATE, SINGLE_CHOICE, MULTIPLE_CHOICE, SINGLE_USER, MULTIPLE_USER, NUMBER, NOTE, RATING\) | +| `enabled` | boolean | No | Filter by enabled state | +| `private` | boolean | No | Filter by privacy setting | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `fields` | array | List of fields | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Name of the field | +| ↳ `fieldDescription` | string | Description of the field | +| ↳ `fieldType` | string | Type of the field \(TEXT, MULTI_LINE_TEXT, YES_OR_NO, DATE, SINGLE_CHOICE, MULTIPLE_CHOICE, SINGLE_USER, MULTIPLE_USER, NUMBER, NOTE, RATING\) | +| ↳ `objectType` | string | Object the field is associated with \(PROJECT, TASK, or USER\) | +| ↳ `fieldOptions` | array | Options available for SINGLE_CHOICE and MULTIPLE_CHOICE fields | +| ↳ `optionValue` | number | Unique identifier of the option within the field | +| ↳ `optionLabel` | string | Display label of the option | +| ↳ `optionColor` | string | Color of the option \(RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY\) | +| ↳ `ratingScale` | string | Rating scale for RATING fields \(THREE, FIVE, SEVEN, TEN\) | +| ↳ `createdBy` | object | Team member who created the field | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the field | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `createdAt` | number | Time the field was created, in epoch milliseconds | +| ↳ `updatedAt` | number | Time the field was last updated, in epoch milliseconds | +| ↳ `enabled` | boolean | Whether the field is enabled | +| ↳ `private` | boolean | Whether the field is private | +| `pagination` | object | Pagination details for the result set | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_update_field` + +Update the label, description, enabled state, or privacy of an existing Rocketlane field + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `fieldId` | number | Yes | ID of the field to update | +| `fieldLabel` | string | No | New name of the field | +| `fieldDescription` | string | No | New description of the field | +| `enabled` | boolean | No | Whether the field is enabled | +| `private` | boolean | No | Whether the field is private | +| `includeFields` | string | No | Comma-separated extra field properties to include in the response \(supported: options\) | +| `includeAllFields` | boolean | No | Whether to return all field properties in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `field` | object | The updated field | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Name of the field | +| ↳ `fieldDescription` | string | Description of the field | +| ↳ `fieldType` | string | Type of the field \(TEXT, MULTI_LINE_TEXT, YES_OR_NO, DATE, SINGLE_CHOICE, MULTIPLE_CHOICE, SINGLE_USER, MULTIPLE_USER, NUMBER, NOTE, RATING\) | +| ↳ `objectType` | string | Object the field is associated with \(PROJECT, TASK, or USER\) | +| ↳ `fieldOptions` | array | Options available for SINGLE_CHOICE and MULTIPLE_CHOICE fields | +| ↳ `optionValue` | number | Unique identifier of the option within the field | +| ↳ `optionLabel` | string | Display label of the option | +| ↳ `optionColor` | string | Color of the option \(RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY\) | +| ↳ `ratingScale` | string | Rating scale for RATING fields \(THREE, FIVE, SEVEN, TEN\) | +| ↳ `createdBy` | object | Team member who created the field | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | Team member who last updated the field | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `createdAt` | number | Time the field was created, in epoch milliseconds | +| ↳ `updatedAt` | number | Time the field was last updated, in epoch milliseconds | +| ↳ `enabled` | boolean | Whether the field is enabled | +| ↳ `private` | boolean | Whether the field is private | + +### `rocketlane_delete_field` + +Permanently delete a Rocketlane field by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `fieldId` | number | Yes | ID of the field to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the field was deleted | +| `fieldId` | number | ID of the deleted field | + +### `rocketlane_add_field_option` + +Add a new option to an existing SINGLE_CHOICE or MULTIPLE_CHOICE Rocketlane field + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `fieldId` | number | Yes | ID of the field to add the option to | +| `optionLabel` | string | Yes | Display label of the new option | +| `optionColor` | string | Yes | Color of the new option \(RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `option` | object | The created field option | +| ↳ `optionValue` | number | Unique identifier of the option within the field | +| ↳ `optionLabel` | string | Display label of the option | +| ↳ `optionColor` | string | Color of the option \(RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY\) | + +### `rocketlane_update_field_option` + +Update the label or color of an existing option on a SINGLE_CHOICE or MULTIPLE_CHOICE Rocketlane field + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `fieldId` | number | Yes | ID of the field the option belongs to | +| `optionValue` | number | Yes | Unique identifier of the option to update | +| `optionLabel` | string | No | New display label of the option | +| `optionColor` | string | No | New color of the option \(RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `option` | object | The updated field option | +| ↳ `optionValue` | number | Unique identifier of the option within the field | +| ↳ `optionLabel` | string | Display label of the option | +| ↳ `optionColor` | string | Color of the option \(RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY\) | + +### `rocketlane_create_time_entry` + +Create a time entry in Rocketlane against an adhoc activity, task, project phase, or project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `date` | string | Yes | Date of the time entry in YYYY-MM-DD format | +| `minutes` | number | Yes | Duration of the time entry in minutes \(1-1440\) | +| `activityName` | string | No | Name of the adhoc activity to track time against. Exactly one source is required among activityName, taskId, projectPhaseId, and projectId — providing more than one results in an error | +| `taskId` | number | No | ID of the task to track time against. Exactly one source is required among activityName, taskId, projectPhaseId, and projectId — providing more than one results in an error | +| `projectPhaseId` | number | No | ID of the project phase to track time against. Exactly one source is required among activityName, taskId, projectPhaseId, and projectId — providing more than one results in an error | +| `projectId` | number | No | ID of the project to track time against. Exactly one source is required among activityName, taskId, projectPhaseId, and projectId — providing more than one results in an error | +| `billable` | boolean | No | Whether the time entry is billable \(defaults to true\) | +| `userId` | number | No | ID of the user the time entry belongs to | +| `userEmail` | string | No | Email of the user the time entry belongs to \(alternative to userId\) | +| `notes` | string | No | Notes for the time entry | +| `categoryId` | number | No | ID of the time entry category | +| `includeFields` | string | No | Comma-separated extra fields to include in the response \(notes, sourceType, deleted, status, submittedBy, submittedAt, approvedBy, approvedAt, rejectedBy, rejectedAt, costRate, billRate\) | +| `includeAllFields` | boolean | No | Whether all fields should be returned in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `timeEntry` | object | The created time entry | +| ↳ `timeEntryId` | number | Unique identifier of the time entry | +| ↳ `date` | string | Date of the time entry \(YYYY-MM-DD\) | +| ↳ `minutes` | number | Duration of the time entry in minutes | +| ↳ `activityName` | string | Name of the adhoc activity, when the entry is tracked against an activity | +| ↳ `project` | object | Project associated with the time entry | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `task` | object | Task associated with the time entry | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `projectPhase` | object | Project phase associated with the time entry | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `billable` | boolean | Whether the time entry is billable | +| ↳ `user` | object | User the time entry belongs to | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `notes` | string | Notes for the time entry | +| ↳ `category` | object | Category associated with the time entry | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `sourceType` | string | Source of the time entry \(GOOGLE_CALENDAR, OUTLOOK_CALENDAR, TASK, PROJECT, PHASE, ADHOC, MILESTONE\) | +| ↳ `status` | string | Approval status of the time entry \(NOT_SUBMITTED, SUBMITTED, APPROVED, REJECTED\) | +| ↳ `createdAt` | number | Creation timestamp in epoch milliseconds | +| ↳ `updatedAt` | number | Last-updated timestamp in epoch milliseconds | +| ↳ `createdBy` | object | User who created the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `submittedBy` | object | User who submitted the time entry \(may be null even for approved/rejected entries\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `submittedAt` | number | Submission timestamp in epoch milliseconds | +| ↳ `approvedBy` | object | User who approved the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `approvedAt` | number | Approval timestamp in epoch milliseconds | +| ↳ `rejectedBy` | object | User who rejected the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `rejectedAt` | number | Rejection timestamp in epoch milliseconds | +| ↳ `deleted` | boolean | Whether the time entry is deleted | +| ↳ `costRate` | object | Hourly cost rate assigned to the user for this entry | +| ↳ `billRate` | object | Hourly rate billed to the customer for this entry | +| ↳ `fields` | array | Custom fields associated with the time entry | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | + +### `rocketlane_get_time_entry` + +Get a single Rocketlane time entry by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `timeEntryId` | number | Yes | ID of the time entry to retrieve | +| `includeFields` | string | No | Comma-separated extra fields to include in the response \(notes, sourceType, deleted, status, submittedBy, submittedAt, approvedBy, approvedAt, rejectedBy, rejectedAt, costRate, billRate\) | +| `includeAllFields` | boolean | No | Whether all fields should be returned in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `timeEntry` | object | The requested time entry | +| ↳ `timeEntryId` | number | Unique identifier of the time entry | +| ↳ `date` | string | Date of the time entry \(YYYY-MM-DD\) | +| ↳ `minutes` | number | Duration of the time entry in minutes | +| ↳ `activityName` | string | Name of the adhoc activity, when the entry is tracked against an activity | +| ↳ `project` | object | Project associated with the time entry | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `task` | object | Task associated with the time entry | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `projectPhase` | object | Project phase associated with the time entry | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `billable` | boolean | Whether the time entry is billable | +| ↳ `user` | object | User the time entry belongs to | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `notes` | string | Notes for the time entry | +| ↳ `category` | object | Category associated with the time entry | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `sourceType` | string | Source of the time entry \(GOOGLE_CALENDAR, OUTLOOK_CALENDAR, TASK, PROJECT, PHASE, ADHOC, MILESTONE\) | +| ↳ `status` | string | Approval status of the time entry \(NOT_SUBMITTED, SUBMITTED, APPROVED, REJECTED\) | +| ↳ `createdAt` | number | Creation timestamp in epoch milliseconds | +| ↳ `updatedAt` | number | Last-updated timestamp in epoch milliseconds | +| ↳ `createdBy` | object | User who created the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `submittedBy` | object | User who submitted the time entry \(may be null even for approved/rejected entries\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `submittedAt` | number | Submission timestamp in epoch milliseconds | +| ↳ `approvedBy` | object | User who approved the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `approvedAt` | number | Approval timestamp in epoch milliseconds | +| ↳ `rejectedBy` | object | User who rejected the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `rejectedAt` | number | Rejection timestamp in epoch milliseconds | +| ↳ `deleted` | boolean | Whether the time entry is deleted | +| ↳ `costRate` | object | Hourly cost rate assigned to the user for this entry | +| ↳ `billRate` | object | Hourly rate billed to the customer for this entry | +| ↳ `fields` | array | Custom fields associated with the time entry | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | + +### `rocketlane_list_time_entries` + +List Rocketlane time entries with optional filters, sorting, and cursor-based pagination + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `dateEq` | string | No | Only entries on this exact date \(YYYY-MM-DD\) | +| `dateGt` | string | No | Only entries after this date \(YYYY-MM-DD\) | +| `dateGe` | string | No | Only entries on or after this date \(YYYY-MM-DD\) | +| `dateLt` | string | No | Only entries before this date \(YYYY-MM-DD\) | +| `dateLe` | string | No | Only entries on or before this date \(YYYY-MM-DD\) | +| `projectIdEq` | number | No | Only entries for this project ID | +| `taskIdEq` | number | No | Only entries for this task ID | +| `projectPhaseIdEq` | number | No | Only entries for this project phase ID | +| `categoryIdEq` | number | No | Only entries with this category ID | +| `userIdEq` | number | No | Only entries belonging to this user ID | +| `emailIdEq` | string | No | Only entries belonging to the user with this exact email | +| `emailIdCn` | string | No | Only entries whose user email contains this text | +| `sourceTypeEq` | string | No | Only entries with this source type \(GOOGLE_CALENDAR, OUTLOOK_CALENDAR, TASK, PROJECT, PHASE, ADHOC, MILESTONE\) | +| `activityNameEq` | string | No | Only entries with this exact activity name | +| `activityNameCn` | string | No | Only entries whose activity name contains this text | +| `approvalStatusEq` | string | No | Only entries with this approval status \(NOT_SUBMITTED, SUBMITTED, APPROVED, REJECTED\) | +| `billableEq` | boolean | No | Only billable \(true\) or non-billable \(false\) entries | +| `includeDeletedEq` | boolean | No | Whether deleted time entries are included in the response | +| `submittedByEq` | number | No | Only entries submitted by this user ID | +| `approvedByEq` | number | No | Only entries approved by this user ID | +| `rejectedByEq` | number | No | Only entries rejected by this user ID | +| `createdAtGt` | number | No | Only entries created after this epoch-millisecond timestamp | +| `createdAtLt` | number | No | Only entries created before this epoch-millisecond timestamp | +| `updatedAtGt` | number | No | Only entries updated after this epoch-millisecond timestamp | +| `updatedAtLt` | number | No | Only entries updated before this epoch-millisecond timestamp | +| `match` | string | No | How to combine filters: all \(AND, default\) or any \(OR\) | +| `sortBy` | string | No | Field to sort by \(minutes, date, id, billable\) | +| `sortOrder` | string | No | Sort order: ASC or DESC \(default DESC\) | +| `includeFields` | string | No | Comma-separated extra fields to include in the response \(notes, sourceType, deleted, status, submittedBy, submittedAt, approvedBy, approvedAt, rejectedBy, rejectedAt, costRate, billRate\) | +| `pageSize` | number | No | Maximum number of entries per page \(defaults to 100\) | +| `pageToken` | string | No | Page token from a previous response for fetching the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `timeEntries` | array | List of time entries matching the filters | +| ↳ `timeEntryId` | number | Unique identifier of the time entry | +| ↳ `date` | string | Date of the time entry \(YYYY-MM-DD\) | +| ↳ `minutes` | number | Duration of the time entry in minutes | +| ↳ `activityName` | string | Name of the adhoc activity, when the entry is tracked against an activity | +| ↳ `project` | object | Project associated with the time entry | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `task` | object | Task associated with the time entry | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `projectPhase` | object | Project phase associated with the time entry | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `billable` | boolean | Whether the time entry is billable | +| ↳ `user` | object | User the time entry belongs to | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `notes` | string | Notes for the time entry | +| ↳ `category` | object | Category associated with the time entry | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `sourceType` | string | Source of the time entry \(GOOGLE_CALENDAR, OUTLOOK_CALENDAR, TASK, PROJECT, PHASE, ADHOC, MILESTONE\) | +| ↳ `status` | string | Approval status of the time entry \(NOT_SUBMITTED, SUBMITTED, APPROVED, REJECTED\) | +| ↳ `createdAt` | number | Creation timestamp in epoch milliseconds | +| ↳ `updatedAt` | number | Last-updated timestamp in epoch milliseconds | +| ↳ `createdBy` | object | User who created the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `submittedBy` | object | User who submitted the time entry \(may be null even for approved/rejected entries\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `submittedAt` | number | Submission timestamp in epoch milliseconds | +| ↳ `approvedBy` | object | User who approved the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `approvedAt` | number | Approval timestamp in epoch milliseconds | +| ↳ `rejectedBy` | object | User who rejected the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `rejectedAt` | number | Rejection timestamp in epoch milliseconds | +| ↳ `deleted` | boolean | Whether the time entry is deleted | +| ↳ `costRate` | object | Hourly cost rate assigned to the user for this entry | +| ↳ `billRate` | object | Hourly rate billed to the customer for this entry | +| ↳ `fields` | array | Custom fields associated with the time entry | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| `pagination` | object | Pagination details for the result set | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_search_time_entries` + +Search Rocketlane time entries with filters, sorting, and pagination (deprecated by Rocketlane in favor of listing time entries with filters) + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `dateEq` | string | No | Only entries on this exact date \(YYYY-MM-DD\) | +| `dateGt` | string | No | Only entries after this date \(YYYY-MM-DD\) | +| `dateGe` | string | No | Only entries on or after this date \(YYYY-MM-DD\) | +| `dateLt` | string | No | Only entries before this date \(YYYY-MM-DD\) | +| `dateLe` | string | No | Only entries on or before this date \(YYYY-MM-DD\) | +| `projectEq` | number | No | Only entries for this project ID | +| `taskEq` | number | No | Only entries for this task ID | +| `projectPhaseIdEq` | number | No | Only entries for this project phase ID | +| `categoryIdEq` | number | No | Only entries with this category ID | +| `userIdEq` | number | No | Only entries belonging to this user ID | +| `sourceTypeEq` | string | No | Only entries with this source type \(GOOGLE_CALENDAR, OUTLOOK_CALENDAR, TASK, PROJECT, PHASE, ADHOC, MILESTONE\) | +| `activityNameEq` | string | No | Only entries with this exact activity name | +| `activityNameCn` | string | No | Only entries whose activity name contains this text | +| `approvalStatusEq` | string | No | Only entries with this approval status \(NOT_SUBMITTED, SUBMITTED, APPROVED, REJECTED\) | +| `match` | string | No | How to combine filters: all \(AND, default\) or any \(OR\) | +| `sortBy` | string | No | Field to sort by \(minutes, date, id, billable\) | +| `sortOrder` | string | No | Sort order: ASC or DESC \(default DESC\) | +| `includeFields` | string | No | Comma-separated extra fields to include in the response \(notes, sourceType, deleted, status, submittedBy, submittedAt, approvedBy, approvedAt, rejectedBy, rejectedAt, costRate, billRate\) | +| `includeAllFields` | boolean | No | Whether all fields should be returned in the response | +| `pageSize` | number | No | Maximum number of entries per page \(defaults to 100\) | +| `pageToken` | string | No | Page token from a previous response for fetching the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `timeEntries` | array | List of time entries matching the search filters | +| ↳ `timeEntryId` | number | Unique identifier of the time entry | +| ↳ `date` | string | Date of the time entry \(YYYY-MM-DD\) | +| ↳ `minutes` | number | Duration of the time entry in minutes | +| ↳ `activityName` | string | Name of the adhoc activity, when the entry is tracked against an activity | +| ↳ `project` | object | Project associated with the time entry | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `task` | object | Task associated with the time entry | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `projectPhase` | object | Project phase associated with the time entry | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `billable` | boolean | Whether the time entry is billable | +| ↳ `user` | object | User the time entry belongs to | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `notes` | string | Notes for the time entry | +| ↳ `category` | object | Category associated with the time entry | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `sourceType` | string | Source of the time entry \(GOOGLE_CALENDAR, OUTLOOK_CALENDAR, TASK, PROJECT, PHASE, ADHOC, MILESTONE\) | +| ↳ `status` | string | Approval status of the time entry \(NOT_SUBMITTED, SUBMITTED, APPROVED, REJECTED\) | +| ↳ `createdAt` | number | Creation timestamp in epoch milliseconds | +| ↳ `updatedAt` | number | Last-updated timestamp in epoch milliseconds | +| ↳ `createdBy` | object | User who created the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `submittedBy` | object | User who submitted the time entry \(may be null even for approved/rejected entries\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `submittedAt` | number | Submission timestamp in epoch milliseconds | +| ↳ `approvedBy` | object | User who approved the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `approvedAt` | number | Approval timestamp in epoch milliseconds | +| ↳ `rejectedBy` | object | User who rejected the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `rejectedAt` | number | Rejection timestamp in epoch milliseconds | +| ↳ `deleted` | boolean | Whether the time entry is deleted | +| ↳ `costRate` | object | Hourly cost rate assigned to the user for this entry | +| ↳ `billRate` | object | Hourly rate billed to the customer for this entry | +| ↳ `fields` | array | Custom fields associated with the time entry | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| `pagination` | object | Pagination details for the result set | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_update_time_entry` + +Update a Rocketlane time entry by ID. The activityName, notes, billable, and minutes properties can be updated + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `timeEntryId` | number | Yes | ID of the time entry to update | +| `date` | string | Yes | Date of the time entry in YYYY-MM-DD format \(mandatory so the total time for the date does not exceed 24 hours\) | +| `minutes` | number | Yes | Duration of the time entry in minutes \(1-1440\) | +| `activityName` | string | No | New name for the adhoc activity | +| `notes` | string | No | New notes for the time entry | +| `billable` | boolean | No | Whether the time entry is billable | +| `categoryId` | number | No | ID of the time entry category | +| `includeFields` | string | No | Comma-separated extra fields to include in the response \(notes, sourceType, deleted, status, submittedBy, submittedAt, approvedBy, approvedAt, rejectedBy, rejectedAt, costRate, billRate\) | +| `includeAllFields` | boolean | No | Whether all fields should be returned in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `timeEntry` | object | The updated time entry | +| ↳ `timeEntryId` | number | Unique identifier of the time entry | +| ↳ `date` | string | Date of the time entry \(YYYY-MM-DD\) | +| ↳ `minutes` | number | Duration of the time entry in minutes | +| ↳ `activityName` | string | Name of the adhoc activity, when the entry is tracked against an activity | +| ↳ `project` | object | Project associated with the time entry | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `task` | object | Task associated with the time entry | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `projectPhase` | object | Project phase associated with the time entry | +| ↳ `phaseId` | number | Unique identifier of the phase | +| ↳ `phaseName` | string | Name of the phase | +| ↳ `billable` | boolean | Whether the time entry is billable | +| ↳ `user` | object | User the time entry belongs to | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `notes` | string | Notes for the time entry | +| ↳ `category` | object | Category associated with the time entry | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| ↳ `sourceType` | string | Source of the time entry \(GOOGLE_CALENDAR, OUTLOOK_CALENDAR, TASK, PROJECT, PHASE, ADHOC, MILESTONE\) | +| ↳ `status` | string | Approval status of the time entry \(NOT_SUBMITTED, SUBMITTED, APPROVED, REJECTED\) | +| ↳ `createdAt` | number | Creation timestamp in epoch milliseconds | +| ↳ `updatedAt` | number | Last-updated timestamp in epoch milliseconds | +| ↳ `createdBy` | object | User who created the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | User who last updated the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `submittedBy` | object | User who submitted the time entry \(may be null even for approved/rejected entries\) | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `submittedAt` | number | Submission timestamp in epoch milliseconds | +| ↳ `approvedBy` | object | User who approved the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `approvedAt` | number | Approval timestamp in epoch milliseconds | +| ↳ `rejectedBy` | object | User who rejected the time entry | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `rejectedAt` | number | Rejection timestamp in epoch milliseconds | +| ↳ `deleted` | boolean | Whether the time entry is deleted | +| ↳ `costRate` | object | Hourly cost rate assigned to the user for this entry | +| ↳ `billRate` | object | Hourly rate billed to the customer for this entry | +| ↳ `fields` | array | Custom fields associated with the time entry | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field | +| ↳ `fieldValueLabel` | string | String representation of the field value | + +### `rocketlane_delete_time_entry` + +Permanently delete a Rocketlane time entry by ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `timeEntryId` | number | Yes | ID of the time entry to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the time entry was deleted | +| `timeEntryId` | number | ID of the deleted time entry | + +### `rocketlane_list_time_entry_categories` + +List the time entry categories configured in Rocketlane + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `pageSize` | number | No | Maximum number of categories per page \(defaults to 100\) | +| `pageToken` | string | No | Page token from a previous response for fetching the next page | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `categories` | array | List of time entry categories | +| ↳ `categoryId` | number | Unique identifier of the category | +| ↳ `categoryName` | string | Name of the category | +| `pagination` | object | Pagination details for the result set | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_create_time_off` + +Create a time-off for a team member in Rocketlane. Holidays and weekends within the date range are automatically excluded from the duration. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `userId` | number | No | User ID of the team member taking the time-off \(provide this or the user email\) | +| `userEmail` | string | No | Email of the team member taking the time-off \(provide this or the user ID\) | +| `startDate` | string | Yes | Time-off start date in YYYY-MM-DD format | +| `endDate` | string | Yes | Time-off end date in YYYY-MM-DD format \(on or after the start date\) | +| `type` | string | Yes | Type of the time-off: FULL_DAY, HALF_DAY, or CUSTOM | +| `durationInMinutes` | number | No | Duration in minutes per day; required when type is CUSTOM | +| `note` | string | No | Note or comment about the time-off | +| `notifyProjectOwners` | boolean | No | Notify project owners of projects the user is part of | +| `notifyUserIds` | array | No | User IDs of additional users to notify about the time-off | +| `notifyUserEmails` | array | No | Emails of additional users to notify about the time-off | +| `includeFields` | array | No | Optional fields to include in the response: note, notifyUsers | +| `includeAllFields` | boolean | No | Return all fields in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `timeOff` | object | The created time-off | +| ↳ `timeOffId` | number | Unique identifier of the time-off | +| ↳ `user` | object | The team member the time-off belongs to | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `note` | string | Note or comment about the time-off | +| ↳ `startDate` | string | Time-off start date \(YYYY-MM-DD\) | +| ↳ `endDate` | string | Time-off end date \(YYYY-MM-DD\) | +| ↳ `durationInMinutes` | number | Duration in minutes per day for the time-off interval | +| ↳ `type` | string | Type of the time-off \(FULL_DAY, HALF_DAY, or CUSTOM\) | +| ↳ `notifyUsers` | object | Users notified about the time-off | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `createdAt` | number | Timestamp when the time-off was created \(epoch milliseconds\) | +| ↳ `createdBy` | object | The team member who created the time-off | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | + +### `rocketlane_get_time_off` + +Retrieve a Rocketlane time-off by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `timeOffId` | number | Yes | Unique identifier of the time-off | +| `includeFields` | array | No | Optional fields to include in the response: note, notifyUsers | +| `includeAllFields` | boolean | No | Return all fields in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `timeOff` | object | The requested time-off | +| ↳ `timeOffId` | number | Unique identifier of the time-off | +| ↳ `user` | object | The team member the time-off belongs to | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `note` | string | Note or comment about the time-off | +| ↳ `startDate` | string | Time-off start date \(YYYY-MM-DD\) | +| ↳ `endDate` | string | Time-off end date \(YYYY-MM-DD\) | +| ↳ `durationInMinutes` | number | Duration in minutes per day for the time-off interval | +| ↳ `type` | string | Type of the time-off \(FULL_DAY, HALF_DAY, or CUSTOM\) | +| ↳ `notifyUsers` | object | Users notified about the time-off | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `createdAt` | number | Timestamp when the time-off was created \(epoch milliseconds\) | +| ↳ `createdBy` | object | The team member who created the time-off | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | + +### `rocketlane_list_time_offs` + +List time-offs in Rocketlane with optional date, type, and user filters, sorting, and pagination + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `pageSize` | number | No | Maximum number of time-offs per page \(defaults to 100\) | +| `pageToken` | string | No | Page token from a previous response \(valid for 15 minutes\) | +| `includeFields` | array | No | Optional fields to include in the response: note, notifyUsers | +| `includeAllFields` | boolean | No | Return all fields in the response | +| `sortBy` | string | No | Field to sort by: startDate, endDate, or createdAt | +| `sortOrder` | string | No | Sort order: ASC or DESC \(defaults to DESC\) | +| `match` | string | No | Combine filters with AND \(all\) or OR \(any\); defaults to all | +| `startDateGt` | string | No | Return time-offs with start dates greater than this date \(YYYY-MM-DD\) | +| `startDateEq` | string | No | Return time-offs with start dates equal to this date \(YYYY-MM-DD\) | +| `startDateLt` | string | No | Return time-offs with start dates lesser than this date \(YYYY-MM-DD\) | +| `startDateGe` | string | No | Return time-offs with start dates greater than or equal to this date \(YYYY-MM-DD\) | +| `startDateLe` | string | No | Return time-offs with start dates lesser than or equal to this date \(YYYY-MM-DD\) | +| `endDateGt` | string | No | Return time-offs with end dates greater than this date \(YYYY-MM-DD\) | +| `endDateEq` | string | No | Return time-offs with end dates equal to this date \(YYYY-MM-DD\) | +| `endDateLt` | string | No | Return time-offs with end dates lesser than this date \(YYYY-MM-DD\) | +| `endDateGe` | string | No | Return time-offs with end dates greater than or equal to this date \(YYYY-MM-DD\) | +| `endDateLe` | string | No | Return time-offs with end dates lesser than or equal to this date \(YYYY-MM-DD\) | +| `typeEq` | string | No | Return time-offs matching this type: FULL_DAY, HALF_DAY, or CUSTOM | +| `typeOneOf` | string | No | Comma-separated time-off types to match any of \(FULL_DAY, HALF_DAY, CUSTOM\) | +| `typeNoneOf` | string | No | Comma-separated time-off types to match none of \(FULL_DAY, HALF_DAY, CUSTOM\) | +| `userIdEq` | string | No | Return time-offs that exactly match this user ID | +| `userIdOneOf` | string | No | Comma-separated user IDs to match any of | +| `userIdNoneOf` | string | No | Comma-separated user IDs to match none of | +| `emailIdEq` | string | No | Return time-offs that exactly match this user email | +| `emailIdOneOf` | string | No | Comma-separated user emails to match any of | +| `emailIdNoneOf` | string | No | Comma-separated user emails to match none of | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `timeOffs` | array | List of time-offs | +| ↳ `timeOffId` | number | Unique identifier of the time-off | +| ↳ `user` | object | The team member the time-off belongs to | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `note` | string | Note or comment about the time-off | +| ↳ `startDate` | string | Time-off start date \(YYYY-MM-DD\) | +| ↳ `endDate` | string | Time-off end date \(YYYY-MM-DD\) | +| ↳ `durationInMinutes` | number | Duration in minutes per day for the time-off interval | +| ↳ `type` | string | Type of the time-off \(FULL_DAY, HALF_DAY, or CUSTOM\) | +| ↳ `notifyUsers` | object | Users notified about the time-off | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `createdAt` | number | Timestamp when the time-off was created \(epoch milliseconds\) | +| ↳ `createdBy` | object | The team member who created the time-off | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| `pagination` | object | Pagination details for the result set | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_delete_time_off` + +Permanently delete a Rocketlane time-off by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `timeOffId` | number | Yes | Unique identifier of the time-off to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the time-off was deleted | +| `timeOffId` | number | ID of the deleted time-off | + +### `rocketlane_get_user` + +Retrieve a Rocketlane user by their ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `userId` | number | Yes | ID of the user to retrieve | +| `includeFields` | string | No | Comma-separated extra fields to include: role, company, permission, holidayCalendar, capacityInMinutes, profilePictureUrl | +| `includeAllFields` | boolean | No | Whether to include all fields in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `user` | object | The requested user | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `email` | string | Email address of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `type` | string | Type of the user \(TEAM_MEMBER, PARTNER, CUSTOMER, or EXTERNAL_PARTNER\) | +| ↳ `status` | string | Status of the user \(INACTIVE, INVITED, ACTIVE, or PASSIVE\) | +| ↳ `role` | object | Role of the user | +| ↳ `roleId` | number | Unique identifier of the role | +| ↳ `roleName` | string | Name of the role | +| ↳ `company` | object | Company of the user | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `permission` | object | Permission of the user | +| ↳ `permissionId` | number | Unique identifier of the permission | +| ↳ `permissionName` | string | Name of the permission | +| ↳ `fields` | array | Custom user field values | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Name of the custom user field | +| ↳ `fieldValue` | string | Value of the custom user field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `capacityInMinutes` | number | Capacity of the user in minutes | +| ↳ `holidayCalendar` | object | Holiday calendar of the user | +| ↳ `calenderId` | number | Unique identifier of the holiday calendar | +| ↳ `calenderName` | string | Name of the holiday calendar | +| ↳ `profilePictureUrl` | string | URL of the user's profile picture | +| ↳ `createdAt` | number | Timestamp when the user was created \(epoch millis\) | +| ↳ `createdBy` | object | Team member who created the user | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedAt` | number | Timestamp when the user was last updated \(epoch millis\) | +| ↳ `updatedBy` | object | Team member who last updated the user | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | + +### `rocketlane_list_users` + +List users in your Rocketlane account, with optional filters, sorting, and pagination + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `pageSize` | number | No | Maximum number of users per page \(defaults to 100\) | +| `pageToken` | string | No | Page token from a previous request \(valid for 15 minutes\) | +| `includeFields` | string | No | Comma-separated extra fields to include: role, company, permission, holidayCalendar, capacityInMinutes, profilePictureUrl | +| `includeAllFields` | boolean | No | Whether to include all fields in the response | +| `sortBy` | string | No | Field to sort by: email, firstName, lastName, type, status, or capacityInMinutes | +| `sortOrder` | string | No | Sort order: ASC or DESC \(defaults to DESC\) | +| `match` | string | No | How to combine filters: all \(AND\) or any \(OR\); defaults to all | +| `firstNameEq` | string | No | Only include users whose first name exactly matches this value | +| `firstNameCn` | string | No | Only include users whose first name contains this value | +| `firstNameNc` | string | No | Exclude users whose first name contains this value | +| `lastNameEq` | string | No | Only include users whose last name exactly matches this value | +| `lastNameCn` | string | No | Only include users whose last name contains this value | +| `lastNameNc` | string | No | Exclude users whose last name contains this value | +| `emailEq` | string | No | Only include users whose email exactly matches this value | +| `emailCn` | string | No | Only include users whose email contains this value | +| `emailNc` | string | No | Exclude users whose email contains this value | +| `statusEq` | string | No | Only include users with this status: INACTIVE, INVITED, ACTIVE, or PASSIVE | +| `statusOneOf` | string | No | Comma-separated statuses; only include users matching one of them \(INACTIVE, INVITED, ACTIVE, PASSIVE\) | +| `statusNoneOf` | string | No | Comma-separated statuses; exclude users matching any of them \(INACTIVE, INVITED, ACTIVE, PASSIVE\) | +| `typeEq` | string | No | Only include users of this type: TEAM_MEMBER, PARTNER, CUSTOMER, or EXTERNAL_PARTNER | +| `typeOneOf` | string | No | Comma-separated types; only include users matching one of them \(TEAM_MEMBER, PARTNER, CUSTOMER, EXTERNAL_PARTNER\) | +| `roleIdEq` | string | No | Only include users with this role ID | +| `roleIdOneOf` | string | No | Comma-separated role IDs; only include users matching one of them | +| `roleIdNoneOf` | string | No | Comma-separated role IDs; exclude users matching any of them | +| `permissionIdEq` | string | No | Only include users with this permission ID | +| `permissionIdOneOf` | string | No | Comma-separated permission IDs; only include users matching one of them | +| `permissionIdNoneOf` | string | No | Comma-separated permission IDs; exclude users matching any of them | +| `capacityInMinutesEq` | number | No | Only include users whose capacity in minutes equals this value | +| `capacityInMinutesGt` | number | No | Only include users whose capacity in minutes is greater than this value | +| `capacityInMinutesGe` | number | No | Only include users whose capacity in minutes is greater than or equal to this value | +| `capacityInMinutesLt` | number | No | Only include users whose capacity in minutes is less than this value | +| `capacityInMinutesLe` | number | No | Only include users whose capacity in minutes is less than or equal to this value | +| `createdAtGt` | number | No | Only include users created after this time \(epoch millis\) | +| `createdAtEq` | number | No | Only include users created at exactly this time \(epoch millis\) | +| `createdAtLt` | number | No | Only include users created before this time \(epoch millis\) | +| `createdAtGe` | number | No | Only include users created at or after this time \(epoch millis\) | +| `createdAtLe` | number | No | Only include users created at or before this time \(epoch millis\) | +| `updatedAtGt` | number | No | Only include users updated after this time \(epoch millis\) | +| `updatedAtEq` | number | No | Only include users updated at exactly this time \(epoch millis\) | +| `updatedAtLt` | number | No | Only include users updated before this time \(epoch millis\) | +| `updatedAtGe` | number | No | Only include users updated at or after this time \(epoch millis\) | +| `updatedAtLe` | number | No | Only include users updated at or before this time \(epoch millis\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `users` | array | List of users | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `email` | string | Email address of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `type` | string | Type of the user \(TEAM_MEMBER, PARTNER, CUSTOMER, or EXTERNAL_PARTNER\) | +| ↳ `status` | string | Status of the user \(INACTIVE, INVITED, ACTIVE, or PASSIVE\) | +| ↳ `role` | object | Role of the user | +| ↳ `roleId` | number | Unique identifier of the role | +| ↳ `roleName` | string | Name of the role | +| ↳ `company` | object | Company of the user | +| ↳ `companyId` | number | Unique identifier of the company | +| ↳ `companyName` | string | Name of the company | +| ↳ `permission` | object | Permission of the user | +| ↳ `permissionId` | number | Unique identifier of the permission | +| ↳ `permissionName` | string | Name of the permission | +| ↳ `fields` | array | Custom user field values | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Name of the custom user field | +| ↳ `fieldValue` | string | Value of the custom user field | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `capacityInMinutes` | number | Capacity of the user in minutes | +| ↳ `holidayCalendar` | object | Holiday calendar of the user | +| ↳ `calenderId` | number | Unique identifier of the holiday calendar | +| ↳ `calenderName` | string | Name of the holiday calendar | +| ↳ `profilePictureUrl` | string | URL of the user's profile picture | +| ↳ `createdAt` | number | Timestamp when the user was created \(epoch millis\) | +| ↳ `createdBy` | object | Team member who created the user | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedAt` | number | Timestamp when the user was last updated \(epoch millis\) | +| ↳ `updatedBy` | object | Team member who last updated the user | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| `pagination` | object | Pagination details | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_create_space` + +Create a new space in a Rocketlane project + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | ID of the project the space belongs to | +| `spaceName` | string | Yes | Name of the space | +| `private` | boolean | No | Whether the space is private or shared \(defaults to false\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `space` | object | The created space | +| ↳ `spaceId` | number | Unique identifier of the space | +| ↳ `spaceName` | string | Name of the space | +| ↳ `project` | object | Project the space belongs to | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `createdAt` | number | Timestamp when the space was created \(epoch millis\) | +| ↳ `createdBy` | object | Team member who created the space | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedAt` | number | Timestamp when the space was last updated \(epoch millis\) | +| ↳ `updatedBy` | object | Team member who last updated the space | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `private` | boolean | Whether the space is private or shared | + +### `rocketlane_get_space` + +Retrieve a Rocketlane space by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `spaceId` | number | Yes | ID of the space to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `space` | object | The requested space | +| ↳ `spaceId` | number | Unique identifier of the space | +| ↳ `spaceName` | string | Name of the space | +| ↳ `project` | object | Project the space belongs to | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `createdAt` | number | Timestamp when the space was created \(epoch millis\) | +| ↳ `createdBy` | object | Team member who created the space | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedAt` | number | Timestamp when the space was last updated \(epoch millis\) | +| ↳ `updatedBy` | object | Team member who last updated the space | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `private` | boolean | Whether the space is private or shared | + +### `rocketlane_list_spaces` + +List spaces in a Rocketlane project, with optional filters, sorting, and pagination + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | ID of the project whose spaces to list | +| `pageSize` | number | No | Maximum number of spaces per page \(defaults to 100\) | +| `pageToken` | string | No | Page token from a previous request \(valid for 15 minutes\) | +| `sortBy` | string | No | Field to sort by \(spaceName\) | +| `sortOrder` | string | No | Sort order: ASC or DESC \(defaults to DESC\) | +| `match` | string | No | How to combine filters: all \(AND\) or any \(OR\); defaults to all | +| `spaceNameEq` | string | No | Only include spaces whose name exactly matches this value | +| `spaceNameCn` | string | No | Only include spaces whose name contains this value | +| `spaceNameNc` | string | No | Exclude spaces whose name contains this value | +| `createdAtGt` | number | No | Only include spaces created after this time \(epoch millis\) | +| `createdAtEq` | number | No | Only include spaces created at exactly this time \(epoch millis\) | +| `createdAtLt` | number | No | Only include spaces created before this time \(epoch millis\) | +| `createdAtGe` | number | No | Only include spaces created at or after this time \(epoch millis\) | +| `createdAtLe` | number | No | Only include spaces created at or before this time \(epoch millis\) | +| `updatedAtGt` | number | No | Only include spaces updated after this time \(epoch millis\) | +| `updatedAtEq` | number | No | Only include spaces updated at exactly this time \(epoch millis\) | +| `updatedAtLt` | number | No | Only include spaces updated before this time \(epoch millis\) | +| `updatedAtGe` | number | No | Only include spaces updated at or after this time \(epoch millis\) | +| `updatedAtLe` | number | No | Only include spaces updated at or before this time \(epoch millis\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `spaces` | array | List of spaces | +| ↳ `spaceId` | number | Unique identifier of the space | +| ↳ `spaceName` | string | Name of the space | +| ↳ `project` | object | Project the space belongs to | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `createdAt` | number | Timestamp when the space was created \(epoch millis\) | +| ↳ `createdBy` | object | Team member who created the space | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedAt` | number | Timestamp when the space was last updated \(epoch millis\) | +| ↳ `updatedBy` | object | Team member who last updated the space | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `private` | boolean | Whether the space is private or shared | +| `pagination` | object | Pagination details | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_update_space` + +Update a Rocketlane space by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `spaceId` | number | Yes | ID of the space to update | +| `spaceName` | string | No | New name of the space | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `space` | object | The updated space | +| ↳ `spaceId` | number | Unique identifier of the space | +| ↳ `spaceName` | string | Name of the space | +| ↳ `project` | object | Project the space belongs to | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `createdAt` | number | Timestamp when the space was created \(epoch millis\) | +| ↳ `createdBy` | object | Team member who created the space | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedAt` | number | Timestamp when the space was last updated \(epoch millis\) | +| ↳ `updatedBy` | object | Team member who last updated the space | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `private` | boolean | Whether the space is private or shared | + +### `rocketlane_delete_space` + +Permanently delete a Rocketlane space by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `spaceId` | number | Yes | ID of the space to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the space was deleted | +| `spaceId` | number | ID of the deleted space | + +### `rocketlane_create_space_document` + +Create a new space document in a Rocketlane space + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `spaceId` | number | Yes | ID of the space the document belongs to | +| `spaceDocumentType` | string | Yes | Type of the space document: ROCKETLANE_DOCUMENT or EMBEDDED_DOCUMENT | +| `spaceDocumentName` | string | No | Name of the space document \(defaults to Untitled\) | +| `url` | string | No | URL to embed in the space document \(for embedded documents\) | +| `templateId` | number | No | ID of the document template to create the document from | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `spaceDocument` | object | The created space document | +| ↳ `spaceDocumentId` | number | Unique identifier of the space document | +| ↳ `spaceDocumentName` | string | Name of the space document | +| ↳ `space` | object | Space the document belongs to | +| ↳ `spaceId` | number | Unique identifier of the space | +| ↳ `spaceName` | string | Name of the space | +| ↳ `spaceDocumentType` | string | Type of the space document \(ROCKETLANE_DOCUMENT or EMBEDDED_DOCUMENT\) | +| ↳ `url` | string | URL embedded in the space document | +| ↳ `source` | object | Document template the space document was created from | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `createdAt` | number | Timestamp when the space document was created \(epoch millis\) | +| ↳ `createdBy` | object | Team member who created the space document | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedAt` | number | Timestamp when the space document was last updated \(epoch millis\) | +| ↳ `updatedBy` | object | Team member who last updated the space document | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `private` | boolean | Whether the space document is private or shared | + +### `rocketlane_get_space_document` + +Retrieve a Rocketlane space document by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `spaceDocumentId` | number | Yes | ID of the space document to retrieve | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `spaceDocument` | object | The requested space document | +| ↳ `spaceDocumentId` | number | Unique identifier of the space document | +| ↳ `spaceDocumentName` | string | Name of the space document | +| ↳ `space` | object | Space the document belongs to | +| ↳ `spaceId` | number | Unique identifier of the space | +| ↳ `spaceName` | string | Name of the space | +| ↳ `spaceDocumentType` | string | Type of the space document \(ROCKETLANE_DOCUMENT or EMBEDDED_DOCUMENT\) | +| ↳ `url` | string | URL embedded in the space document | +| ↳ `source` | object | Document template the space document was created from | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `createdAt` | number | Timestamp when the space document was created \(epoch millis\) | +| ↳ `createdBy` | object | Team member who created the space document | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedAt` | number | Timestamp when the space document was last updated \(epoch millis\) | +| ↳ `updatedBy` | object | Team member who last updated the space document | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `private` | boolean | Whether the space document is private or shared | + +### `rocketlane_list_space_documents` + +List space documents in a Rocketlane project, with optional filters, sorting, and pagination + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `projectId` | number | Yes | ID of the project whose space documents to list | +| `pageSize` | number | No | Maximum number of space documents per page \(defaults to 100\) | +| `pageToken` | string | No | Page token from a previous request \(valid for 15 minutes\) | +| `sortBy` | string | No | Field to sort by \(spaceTabName\) | +| `sortOrder` | string | No | Sort order: ASC or DESC \(defaults to DESC\) | +| `match` | string | No | How to combine filters: all \(AND\) or any \(OR\); defaults to all | +| `spaceDocumentNameEq` | string | No | Only include space documents whose name exactly matches this value | +| `spaceDocumentNameCn` | string | No | Only include space documents whose name contains this value | +| `spaceDocumentNameNc` | string | No | Exclude space documents whose name contains this value | +| `spaceIdEq` | number | No | Only include space documents in the space with this ID | +| `createdAtGt` | number | No | Only include space documents created after this time \(epoch millis\) | +| `createdAtEq` | number | No | Only include space documents created at exactly this time \(epoch millis\) | +| `createdAtLt` | number | No | Only include space documents created before this time \(epoch millis\) | +| `createdAtGe` | number | No | Only include space documents created at or after this time \(epoch millis\) | +| `createdAtLe` | number | No | Only include space documents created at or before this time \(epoch millis\) | +| `updatedAtGt` | number | No | Only include space documents updated after this time \(epoch millis\) | +| `updatedAtEq` | number | No | Only include space documents updated at exactly this time \(epoch millis\) | +| `updatedAtLt` | number | No | Only include space documents updated before this time \(epoch millis\) | +| `updatedAtGe` | number | No | Only include space documents updated at or after this time \(epoch millis\) | +| `updatedAtLe` | number | No | Only include space documents updated at or before this time \(epoch millis\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `spaceDocuments` | array | List of space documents | +| ↳ `spaceDocumentId` | number | Unique identifier of the space document | +| ↳ `spaceDocumentName` | string | Name of the space document | +| ↳ `space` | object | Space the document belongs to | +| ↳ `spaceId` | number | Unique identifier of the space | +| ↳ `spaceName` | string | Name of the space | +| ↳ `spaceDocumentType` | string | Type of the space document \(ROCKETLANE_DOCUMENT or EMBEDDED_DOCUMENT\) | +| ↳ `url` | string | URL embedded in the space document | +| ↳ `source` | object | Document template the space document was created from | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `createdAt` | number | Timestamp when the space document was created \(epoch millis\) | +| ↳ `createdBy` | object | Team member who created the space document | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedAt` | number | Timestamp when the space document was last updated \(epoch millis\) | +| ↳ `updatedBy` | object | Team member who last updated the space document | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `private` | boolean | Whether the space document is private or shared | +| `pagination` | object | Pagination details | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_update_space_document` + +Update a Rocketlane space document by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `spaceDocumentId` | number | Yes | ID of the space document to update | +| `spaceDocumentName` | string | No | New name of the space document | +| `url` | string | No | New URL to embed in the space document \(for embedded documents\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `spaceDocument` | object | The updated space document | +| ↳ `spaceDocumentId` | number | Unique identifier of the space document | +| ↳ `spaceDocumentName` | string | Name of the space document | +| ↳ `space` | object | Space the document belongs to | +| ↳ `spaceId` | number | Unique identifier of the space | +| ↳ `spaceName` | string | Name of the space | +| ↳ `spaceDocumentType` | string | Type of the space document \(ROCKETLANE_DOCUMENT or EMBEDDED_DOCUMENT\) | +| ↳ `url` | string | URL embedded in the space document | +| ↳ `source` | object | Document template the space document was created from | +| ↳ `templateId` | number | Unique identifier of the template | +| ↳ `templateName` | string | Name of the template | +| ↳ `createdAt` | number | Timestamp when the space document was created \(epoch millis\) | +| ↳ `createdBy` | object | Team member who created the space document | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedAt` | number | Timestamp when the space document was last updated \(epoch millis\) | +| ↳ `updatedBy` | object | Team member who last updated the space document | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `private` | boolean | Whether the space document is private or shared | + +### `rocketlane_delete_space_document` + +Permanently delete a Rocketlane space document by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `spaceDocumentId` | number | Yes | ID of the space document to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the space document was deleted | +| `spaceDocumentId` | number | ID of the deleted space document | + +### `rocketlane_list_resource_allocations` + +List resource allocations in Rocketlane within a date range, with optional member, project, and placeholder filters, sorting, and pagination + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `startDate` | string | Yes | Return allocations that start on or after this date \(YYYY-MM-DD\) | +| `endDate` | string | Yes | Return allocations that end on or before this date \(YYYY-MM-DD\) | +| `pageSize` | number | No | Maximum number of allocations per page \(defaults to 100\) | +| `pageToken` | string | No | Page token from a previous response \(valid for 15 minutes\) | +| `includeFields` | array | No | Optional fields to include in the response: member, task, placeholder, duration | +| `includeAllFields` | boolean | No | Return all fields in the response | +| `sortBy` | string | No | Field to sort by: startDate, endDate, allocationType, or allocationFor | +| `sortOrder` | string | No | Sort order: ASC or DESC \(defaults to DESC\) | +| `match` | string | No | Combine filters with AND \(all\) or OR \(any\); defaults to all | +| `memberIdEq` | string | No | Return allocations that exactly match this member ID | +| `memberIdOneOf` | string | No | Comma-separated member IDs to match any of | +| `memberIdNoneOf` | string | No | Comma-separated member IDs to exclude | +| `projectIdEq` | string | No | Return allocations that exactly match this project ID | +| `projectIdOneOf` | string | No | Comma-separated project IDs to match any of | +| `projectIdNoneOf` | string | No | Comma-separated project IDs to exclude | +| `placeholderIdEq` | string | No | Return allocations that exactly match this placeholder ID | +| `placeholderIdOneOf` | string | No | Comma-separated placeholder IDs to match any of | +| `placeholderIdNoneOf` | string | No | Comma-separated placeholder IDs to exclude | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `allocations` | array | List of resource allocations | +| ↳ `startDate` | string | Allocation start date \(YYYY-MM-DD\) | +| ↳ `endDate` | string | Allocation end date \(YYYY-MM-DD\) | +| ↳ `secondsPerDay` | number | Allocated seconds per day | +| ↳ `minutesPerDay` | number | Allocated minutes per day | +| ↳ `hoursPerDay` | number | Allocated hours per day | +| ↳ `duration` | object | Total allocation duration between the start and end dates | +| ↳ `daysConsider` | number | Number of week days considered for the duration computation | +| ↳ `seconds` | number | Total allocation seconds | +| ↳ `minutes` | number | Total allocation minutes | +| ↳ `hours` | number | Total allocation hours | +| ↳ `allocationType` | string | Type of allocation \(SOFT or HARD\) | +| ↳ `allocationFor` | string | Who the allocation is for \(TEAM_MEMBER or PLACEHOLDER\) | +| ↳ `project` | object | The project associated with the allocation | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `tasks` | array | Tasks associated with the allocation | +| ↳ `taskId` | number | Unique identifier of the task | +| ↳ `taskName` | string | Name of the task | +| ↳ `member` | object | The team member allocated when allocationFor is TEAM_MEMBER | +| ↳ `placeholder` | object | The placeholder allocated when allocationFor is PLACEHOLDER | +| ↳ `createdAt` | number | Timestamp when the allocation was created \(epoch milliseconds\) | +| ↳ `updatedAt` | number | Timestamp when the allocation was last updated \(epoch milliseconds\) | +| ↳ `createdBy` | object | The team member who created the allocation | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | The team member who last updated the allocation | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| `pagination` | object | Pagination details for the result set | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_get_invoice` + +Retrieve a Rocketlane invoice by its ID + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `invoiceId` | number | Yes | Unique identifier of the invoice | +| `includeFields` | array | No | Optional fields to include in the response: notes, attachments | +| `includeAllFields` | boolean | No | Return all fields in the response | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `invoice` | object | The requested invoice | +| ↳ `invoiceId` | number | Unique identifier of the invoice | +| ↳ `invoiceNumber` | string | Invoice number assigned to this invoice | +| ↳ `dateOfIssue` | string | Date when the invoice was issued \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Due date for the invoice payment \(YYYY-MM-DD\) | +| ↳ `currency` | string | Currency of the invoice amount \(e.g. USD\) | +| ↳ `status` | string | Current status of the invoice | +| ↳ `amount` | number | Total amount of the invoice including tax | +| ↳ `tax` | number | Tax amount applied to the invoice | +| ↳ `subTotal` | number | Total amount of the invoice excluding tax | +| ↳ `amountOutstanding` | number | Balance amount remaining to be paid | +| ↳ `amountPaid` | number | Total amount paid for this invoice | +| ↳ `amountWrittenOff` | number | Total amount written off for this invoice | +| ↳ `notes` | string | Notes or additional information about the invoice | +| ↳ `createdAt` | number | Timestamp when the invoice was created \(epoch milliseconds\) | +| ↳ `updatedAt` | number | Timestamp when the invoice was last updated \(epoch milliseconds\) | +| ↳ `createdBy` | object | The team member who created the invoice | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | The team member who last updated the invoice | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `company` | object | Customer company details for the invoice | +| ↳ `companyId` | number | Unique identifier of the customer company | +| ↳ `companyName` | string | Name of the customer company | +| ↳ `companyUrl` | string | URL of the customer company website | +| ↳ `projects` | array | Projects mapped to this invoice | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `fields` | array | Custom invoice fields with their values | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array depending on field type\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `attachments` | array | Attachments associated with the invoice | +| ↳ `attachmentId` | number | Unique identifier of the attachment | +| ↳ `attachmentName` | string | Name of the attachment | +| ↳ `createdAt` | number | Timestamp when the attachment was created \(epoch milliseconds\) | +| ↳ `location` | string | URL of the attachment | +| ↳ `thumbLocation` | string | Thumbnail URL of the attachment | +| ↳ `visibility` | boolean | Visibility of the attachment | + +### `rocketlane_list_invoices` + +Search invoices in Rocketlane with date, amount, company, invoice-number, and status filters, sorting, and pagination + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `pageSize` | number | No | Maximum number of invoices per page \(defaults to 100\) | +| `pageToken` | string | No | Page token from a previous response \(valid for 15 minutes\) | +| `includeFields` | array | No | Optional fields to include in the response: notes, attachments | +| `includeAllFields` | boolean | No | Return all fields in the response | +| `sortBy` | string | No | Field to sort by: createdAt or invoiceNumber | +| `sortOrder` | string | No | Sort order: ASC or DESC \(defaults to DESC\) | +| `match` | string | No | Combine filters with AND \(all\) or OR \(any\); defaults to all | +| `dateOfIssueEq` | string | No | Filter by date of issue equal to this date \(YYYY-MM-DD\) | +| `dateOfIssueGt` | string | No | Filter by date of issue greater than this date \(YYYY-MM-DD\) | +| `dateOfIssueGe` | string | No | Filter by date of issue greater than or equal to this date \(YYYY-MM-DD\) | +| `dateOfIssueLt` | string | No | Filter by date of issue less than this date \(YYYY-MM-DD\) | +| `dateOfIssueLe` | string | No | Filter by date of issue less than or equal to this date \(YYYY-MM-DD\) | +| `dueDateEq` | string | No | Filter by due date equal to this date \(YYYY-MM-DD\) | +| `dueDateGt` | string | No | Filter by due date greater than this date \(YYYY-MM-DD\) | +| `dueDateGe` | string | No | Filter by due date greater than or equal to this date \(YYYY-MM-DD\) | +| `dueDateLt` | string | No | Filter by due date less than this date \(YYYY-MM-DD\) | +| `dueDateLe` | string | No | Filter by due date less than or equal to this date \(YYYY-MM-DD\) | +| `amountEq` | number | No | Filter by total amount equal to this value | +| `amountGt` | number | No | Filter by total amount greater than this value | +| `amountGe` | number | No | Filter by total amount greater than or equal to this value | +| `amountLt` | number | No | Filter by total amount less than this value | +| `amountLe` | number | No | Filter by total amount less than or equal to this value | +| `amountOutstandingEq` | number | No | Filter by amount outstanding equal to this value | +| `amountOutstandingGt` | number | No | Filter by amount outstanding greater than this value | +| `amountOutstandingGe` | number | No | Filter by amount outstanding greater than or equal to this value | +| `amountOutstandingLt` | number | No | Filter by amount outstanding less than this value | +| `amountOutstandingLe` | number | No | Filter by amount outstanding less than or equal to this value | +| `amountPaidEq` | number | No | Filter by amount paid equal to this value | +| `amountPaidGt` | number | No | Filter by amount paid greater than this value | +| `amountPaidGe` | number | No | Filter by amount paid greater than or equal to this value | +| `amountPaidLt` | number | No | Filter by amount paid less than this value | +| `amountPaidLe` | number | No | Filter by amount paid less than or equal to this value | +| `amountWrittenOffEq` | number | No | Filter by amount written off equal to this value | +| `amountWrittenOffGt` | number | No | Filter by amount written off greater than this value | +| `amountWrittenOffGe` | number | No | Filter by amount written off greater than or equal to this value | +| `amountWrittenOffLt` | number | No | Filter by amount written off less than this value | +| `amountWrittenOffLe` | number | No | Filter by amount written off less than or equal to this value | +| `createdAtEq` | number | No | Filter by created timestamp equal to this value \(epoch milliseconds\) | +| `createdAtGt` | number | No | Filter by created timestamp greater than this value \(epoch milliseconds\) | +| `createdAtGe` | number | No | Filter by created timestamp greater than or equal to this value \(epoch milliseconds\) | +| `createdAtLt` | number | No | Filter by created timestamp less than this value \(epoch milliseconds\) | +| `createdAtLe` | number | No | Filter by created timestamp less than or equal to this value \(epoch milliseconds\) | +| `companyIdEq` | string | No | Return invoices that exactly match this customer company ID | +| `companyIdOneOf` | string | No | Comma-separated customer company IDs to match any of | +| `companyIdNoneOf` | string | No | Comma-separated customer company IDs to match none of | +| `invoiceNumberEq` | string | No | Return invoices whose invoice number equals this value | +| `invoiceNumberCn` | string | No | Return invoices whose invoice number contains this text | +| `invoiceNumberNc` | string | No | Return invoices whose invoice number does not contain this text | +| `statusEq` | string | No | Return invoices that equal this status \(e.g. DRAFT\) | +| `statusOneOf` | string | No | Comma-separated statuses to match any of \(e.g. DRAFT,PAID\) | +| `statusNoneOf` | string | No | Comma-separated statuses to match none of \(e.g. DRAFT,PAID\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `invoices` | array | List of invoices | +| ↳ `invoiceId` | number | Unique identifier of the invoice | +| ↳ `invoiceNumber` | string | Invoice number assigned to this invoice | +| ↳ `dateOfIssue` | string | Date when the invoice was issued \(YYYY-MM-DD\) | +| ↳ `dueDate` | string | Due date for the invoice payment \(YYYY-MM-DD\) | +| ↳ `currency` | string | Currency of the invoice amount \(e.g. USD\) | +| ↳ `status` | string | Current status of the invoice | +| ↳ `amount` | number | Total amount of the invoice including tax | +| ↳ `tax` | number | Tax amount applied to the invoice | +| ↳ `subTotal` | number | Total amount of the invoice excluding tax | +| ↳ `amountOutstanding` | number | Balance amount remaining to be paid | +| ↳ `amountPaid` | number | Total amount paid for this invoice | +| ↳ `amountWrittenOff` | number | Total amount written off for this invoice | +| ↳ `notes` | string | Notes or additional information about the invoice | +| ↳ `createdAt` | number | Timestamp when the invoice was created \(epoch milliseconds\) | +| ↳ `updatedAt` | number | Timestamp when the invoice was last updated \(epoch milliseconds\) | +| ↳ `createdBy` | object | The team member who created the invoice | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `updatedBy` | object | The team member who last updated the invoice | +| ↳ `userId` | number | Unique identifier of the user | +| ↳ `firstName` | string | First name of the user | +| ↳ `lastName` | string | Last name of the user | +| ↳ `emailId` | string | Email address of the user | +| ↳ `company` | object | Customer company details for the invoice | +| ↳ `companyId` | number | Unique identifier of the customer company | +| ↳ `companyName` | string | Name of the customer company | +| ↳ `companyUrl` | string | URL of the customer company website | +| ↳ `projects` | array | Projects mapped to this invoice | +| ↳ `projectId` | number | Unique identifier of the project | +| ↳ `projectName` | string | Name of the project | +| ↳ `fields` | array | Custom invoice fields with their values | +| ↳ `fieldId` | number | Unique identifier of the field | +| ↳ `fieldLabel` | string | Label of the field | +| ↳ `fieldValue` | json | Value of the field \(string, number, or array depending on field type\) | +| ↳ `fieldValueLabel` | string | String representation of the field value | +| ↳ `attachments` | array | Attachments associated with the invoice | +| ↳ `attachmentId` | number | Unique identifier of the attachment | +| ↳ `attachmentName` | string | Name of the attachment | +| ↳ `createdAt` | number | Timestamp when the attachment was created \(epoch milliseconds\) | +| ↳ `location` | string | URL of the attachment | +| ↳ `thumbLocation` | string | Thumbnail URL of the attachment | +| ↳ `visibility` | boolean | Visibility of the attachment | +| `pagination` | object | Pagination details for the result set | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_get_invoice_line_items` + +List line items of a Rocketlane invoice + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `invoiceId` | number | Yes | Unique identifier of the invoice | +| `pageSize` | number | No | Maximum number of line items per page \(defaults to 100\) | +| `pageToken` | string | No | Page token from a previous response \(valid for 15 minutes\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `lineItems` | array | List of invoice line items | +| ↳ `invoiceLineItemId` | number | Unique identifier of the invoice line item | +| ↳ `description` | string | Description of the line item or service provided | +| ↳ `quantity` | number | Quantity of the item or service | +| ↳ `unitPrice` | number | Unit price for the item or service | +| ↳ `amount` | number | Total amount for this line item \(quantity times unit price\) | +| ↳ `sourceId` | number | Unique identifier of the source entity \(e.g. project ID\) | +| ↳ `sourceType` | string | Type of source entity this line item is associated with \(e.g. PROJECT\) | +| ↳ `taxCode` | object | Tax code information for this line item | +| ↳ `taxCodeId` | number | Unique identifier of the tax code | +| ↳ `taxCodeName` | string | Name of the tax code | +| ↳ `taxCodeRate` | number | Tax rate percentage for the tax code | +| ↳ `taxCodeAmount` | number | Tax amount calculated for this tax code | +| ↳ `taxComponents` | array | Tax components that make up the tax code | +| ↳ `taxComponentId` | number | Unique identifier of the tax component | +| ↳ `taxComponentName` | string | Name of the tax component | +| ↳ `taxComponentRate` | number | Tax rate percentage for the tax component | +| ↳ `taxComponentAmount` | number | Tax amount calculated for this tax component | +| ↳ `taxComponentType` | string | Type of the tax component \(e.g. GST, VAT\) | +| `pagination` | object | Pagination details for the result set | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + +### `rocketlane_get_invoice_payments` + +List payments recorded against a Rocketlane invoice + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | Rocketlane API key | +| `invoiceId` | number | Yes | Unique identifier of the invoice | +| `pageSize` | number | No | Maximum number of payments per page \(defaults to 100\) | +| `pageToken` | string | No | Page token from a previous response \(valid for 15 minutes\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `payments` | array | List of payments recorded against the invoice | +| ↳ `paymentId` | number | Unique identifier of the payment record | +| ↳ `paymentRecordType` | string | Type of the payment record \(PAID or WRITE_OFF\) | +| ↳ `currency` | string | Currency of the payment amount \(e.g. USD\) | +| ↳ `paymentDate` | string | Date when the payment was made \(YYYY-MM-DD\) | +| ↳ `amount` | number | Amount of the payment | +| ↳ `notes` | string | Additional notes or comments regarding the payment | +| `pagination` | object | Pagination details for the result set | +| ↳ `pageSize` | number | Page size used for the current request | +| ↳ `hasMore` | boolean | Whether more results are available | +| ↳ `totalRecordCount` | number | Total number of records matching the request | +| ↳ `nextPageToken` | string | Token for fetching the next page \(valid for 15 minutes\) | + + diff --git a/apps/sim/blocks/blocks/rocketlane.ts b/apps/sim/blocks/blocks/rocketlane.ts new file mode 100644 index 00000000000..feece942f22 --- /dev/null +++ b/apps/sim/blocks/blocks/rocketlane.ts @@ -0,0 +1,3010 @@ +import { RocketlaneIcon } from '@/components/icons' +import type { BlockConfig, BlockMeta } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' + +const PROJECT_ID_REQUIRED_OPS = [ + 'get_project', + 'update_project', + 'archive_project', + 'delete_project', + 'add_project_members', + 'remove_project_members', + 'import_template', + 'list_placeholders', + 'assign_placeholders', + 'unassign_placeholders', + 'create_task', + 'create_phase', + 'create_space', + 'list_phases', + 'list_spaces', + 'list_space_documents', +] +const PROJECT_ID_OPS = [...PROJECT_ID_REQUIRED_OPS, 'list_tasks'] + +const TASK_ID_OPS = [ + 'get_task', + 'update_task', + 'delete_task', + 'move_task_to_phase', + 'add_task_assignees', + 'remove_task_assignees', + 'add_task_followers', + 'remove_task_followers', + 'add_task_dependencies', + 'remove_task_dependencies', +] + +const PHASE_ID_REQUIRED_OPS = ['get_phase', 'update_phase', 'delete_phase', 'move_task_to_phase'] +const PHASE_ID_OPS = [...PHASE_ID_REQUIRED_OPS, 'create_task', 'list_tasks'] + +const FIELD_ID_OPS = [ + 'get_field', + 'update_field', + 'delete_field', + 'add_field_option', + 'update_field_option', +] + +const SPACE_ID_OPS = ['get_space', 'update_space', 'delete_space', 'create_space_document'] +const SPACE_DOCUMENT_ID_OPS = [ + 'get_space_document', + 'update_space_document', + 'delete_space_document', +] +const TIME_ENTRY_ID_OPS = ['get_time_entry', 'update_time_entry', 'delete_time_entry'] +const TIME_OFF_ID_OPS = ['get_time_off', 'delete_time_off'] +const INVOICE_ID_OPS = ['get_invoice', 'get_invoice_line_items', 'get_invoice_payments'] + +const MEMBER_OPS = [ + 'add_project_members', + 'remove_project_members', + 'add_task_assignees', + 'remove_task_assignees', + 'add_task_followers', + 'remove_task_followers', +] + +const START_DATE_OPS = [ + 'create_project', + 'update_project', + 'create_phase', + 'update_phase', + 'create_task', + 'update_task', + 'import_template', + 'create_time_off', + 'list_resource_allocations', +] +const START_DATE_REQUIRED_OPS = [ + 'create_phase', + 'import_template', + 'create_time_off', + 'list_resource_allocations', +] + +const DUE_DATE_OPS = [ + 'create_project', + 'update_project', + 'create_phase', + 'update_phase', + 'create_task', + 'update_task', +] + +const STATUS_VALUE_OPS = [ + 'create_project', + 'update_project', + 'create_phase', + 'update_phase', + 'create_task', + 'update_task', +] + +const PRIVATE_OPS = [ + 'create_field', + 'update_field', + 'list_fields', + 'create_phase', + 'update_phase', + 'create_task', + 'update_task', + 'create_space', +] + +const EXTERNAL_REFERENCE_OPS = [ + 'create_project', + 'update_project', + 'create_task', + 'update_task', + 'list_tasks', +] + +const PAGINATED_OPS = [ + 'list_projects', + 'list_tasks', + 'list_phases', + 'list_fields', + 'list_invoices', + 'list_spaces', + 'list_space_documents', + 'list_time_entries', + 'search_time_entries', + 'list_time_entry_categories', + 'list_time_offs', + 'list_users', + 'list_resource_allocations', + 'get_invoice_line_items', + 'get_invoice_payments', +] + +const SORT_MATCH_OPS = [ + 'list_projects', + 'list_tasks', + 'list_phases', + 'list_fields', + 'list_invoices', + 'list_spaces', + 'list_space_documents', + 'list_time_entries', + 'search_time_entries', + 'list_time_offs', + 'list_users', + 'list_resource_allocations', +] + +const INCLUDE_FIELDS_OPS = [ + 'create_project', + 'get_project', + 'list_projects', + 'update_project', + 'create_task', + 'get_task', + 'list_tasks', + 'update_task', + 'create_phase', + 'get_phase', + 'list_phases', + 'update_phase', + 'create_field', + 'get_field', + 'list_fields', + 'update_field', + 'create_time_entry', + 'get_time_entry', + 'list_time_entries', + 'search_time_entries', + 'update_time_entry', + 'create_time_off', + 'get_time_off', + 'list_time_offs', + 'get_user', + 'list_users', + 'get_invoice', + 'list_invoices', + 'list_resource_allocations', +] +// list_time_entries supports includeFields but has no includeAllFields param. +const INCLUDE_ALL_FIELDS_OPS = INCLUDE_FIELDS_OPS.filter((op) => op !== 'list_time_entries') + +const TIME_ENTRY_FILTER_OPS = ['list_time_entries', 'search_time_entries'] +const CREATED_UPDATED_AT_OPS = ['list_time_entries', 'list_spaces', 'list_space_documents'] + +/** Coerces a short-input value to a number; empty and non-numeric values become undefined. */ +function toNumber(value: unknown): number | undefined { + if (value === undefined || value === null || value === '') return undefined + const parsed = Number(value) + return Number.isFinite(parsed) ? parsed : undefined +} + +/** Returns the trimmed string, or undefined for empty/non-string values. */ +function toStr(value: unknown): string | undefined { + if (typeof value !== 'string') return undefined + const trimmed = value.trim() + return trimmed.length > 0 ? trimmed : undefined +} + +/** Normalizes switch values (booleans or 'true'/'false' strings) to booleans. */ +function toBool(value: unknown): boolean | undefined { + if (typeof value === 'boolean') return value + if (value === 'true') return true + if (value === 'false') return false + return undefined +} + +/** Splits a comma-separated string (or passes an array through) into trimmed strings. */ +function toStringList(value: unknown): string[] | undefined { + if (Array.isArray(value)) { + const parts = value.map((item) => String(item).trim()).filter((item) => item.length > 0) + return parts.length > 0 ? parts : undefined + } + if (typeof value !== 'string') return undefined + const parts = value + .split(',') + .map((part) => part.trim()) + .filter((part) => part.length > 0) + return parts.length > 0 ? parts : undefined +} + +/** Splits a comma-separated string (or array) into finite numbers. */ +function toNumberList(value: unknown): number[] | undefined { + const parts = toStringList(value) + if (!parts) return undefined + const numbers = parts.map((part) => Number(part)).filter((num) => Number.isFinite(num)) + return numbers.length > 0 ? numbers : undefined +} + +/** Safely parses a JSON code input; objects/arrays pass through, invalid JSON becomes undefined. */ +function toJson(value: unknown): unknown { + if (value === undefined || value === null || value === '') return undefined + if (typeof value !== 'string') return value + try { + return JSON.parse(value) + } catch { + return undefined + } +} + +type BlockParams = Record + +/** Shared pagination params for list operations. */ +function pageParams(params: BlockParams) { + return { + pageSize: toNumber(params.pageSize), + pageToken: toStr(params.pageToken), + } +} + +/** Shared sort/match params for filterable list operations. */ +function sortParams(params: BlockParams) { + return { + sortBy: toStr(params.sortBy), + sortOrder: toStr(params.sortOrder), + match: toStr(params.match), + } +} + +/** includeFields/includeAllFields for tools that take includeFields as a CSV string. */ +function includeCsv(params: BlockParams) { + return { + includeFields: toStr(params.includeFields), + includeAllFields: toBool(params.includeAllFields), + } +} + +/** includeFields/includeAllFields for tools that take includeFields as a string array. */ +function includeList(params: BlockParams) { + return { + includeFields: toStringList(params.includeFields), + includeAllFields: toBool(params.includeAllFields), + } +} + +/** Builds the exact param set declared by the selected operation's tool. */ +function buildOperationParams(params: BlockParams): Record { + switch (params.operation) { + // Projects + case 'create_project': + return { + projectName: toStr(params.projectName), + customerCompanyName: toStr(params.customerCompanyName), + ownerUserId: toNumber(params.ownerUserId), + ownerEmailId: toStr(params.ownerEmailId), + startDate: toStr(params.startDate), + dueDate: toStr(params.dueDate), + visibility: toStr(params.visibility), + statusValue: toNumber(params.statusValue), + memberUserIds: toNumberList(params.memberUserIds), + customerUserIds: toNumberList(params.customerUserIds), + customerChampionUserId: toNumber(params.customerChampionUserId), + fields: toJson(params.fields), + sources: toJson(params.sources), + placeholders: toJson(params.placeholders), + assignProjectOwner: toBool(params.assignProjectOwner), + annualizedRecurringRevenue: toNumber(params.annualizedRecurringRevenue), + projectFee: toNumber(params.projectFee), + autoAllocation: toBool(params.autoAllocation), + autoCreateCompany: toBool(params.autoCreateCompany), + budgetedHours: toNumber(params.budgetedHours), + contractType: toStr(params.contractType), + fixedFee: toNumber(params.fixedFee), + projectBudget: toNumber(params.projectBudget), + rateCardId: toNumber(params.rateCardId), + subscriptionFrequency: toStr(params.subscriptionFrequency), + subscriptionStartDate: toStr(params.subscriptionStartDate), + periodMinutes: toNumber(params.periodMinutes), + periodBudget: toNumber(params.periodBudget), + noOfPeriods: toNumber(params.noOfPeriods), + currency: toStr(params.currency), + externalReferenceId: toStr(params.externalReferenceId), + ...includeCsv(params), + } + case 'get_project': + return { projectId: toNumber(params.projectId), ...includeCsv(params) } + case 'list_projects': + return { + ...pageParams(params), + ...sortParams(params), + ...includeCsv(params), + projectNameContains: toStr(params.projectNameContains), + projectNameEquals: toStr(params.projectNameEquals), + statusEquals: toStr(params.statusEquals), + statusOneOf: toStr(params.statusOneOf), + customerIdEquals: toStr(params.customerIdEquals), + customerIdOneOf: toStr(params.customerIdOneOf), + teamMemberIdEquals: toStr(params.teamMemberIdEquals), + contractTypeEquals: toStr(params.contractTypeEquals), + includeArchived: toBool(params.includeArchived), + externalReferenceIdEquals: toStr(params.externalReferenceIdEquals), + startDateAfter: toStr(params.startDateAfter), + startDateBefore: toStr(params.startDateBefore), + dueDateAfter: toStr(params.dueDateAfter), + dueDateBefore: toStr(params.dueDateBefore), + } + case 'update_project': + return { + projectId: toNumber(params.projectId), + projectName: toStr(params.projectName), + startDate: toStr(params.startDate), + dueDate: toStr(params.dueDate), + visibility: toStr(params.visibility), + ownerUserId: toNumber(params.ownerUserId), + ownerEmailId: toStr(params.ownerEmailId), + statusValue: toNumber(params.statusValue), + fields: toJson(params.fields), + annualizedRecurringRevenue: toNumber(params.annualizedRecurringRevenue), + projectFee: toNumber(params.projectFee), + autoAllocation: toBool(params.autoAllocation), + budgetedHours: toNumber(params.budgetedHours), + externalReferenceId: toStr(params.externalReferenceId), + ...includeCsv(params), + } + case 'archive_project': + case 'delete_project': + return { projectId: toNumber(params.projectId) } + case 'add_project_members': + return { + projectId: toNumber(params.projectId), + memberUserIds: toNumberList(params.memberUserIds), + memberEmailIds: toStringList(params.memberEmailIds), + customerUserIds: toNumberList(params.customerUserIds), + customerEmailIds: toStringList(params.customerEmailIds), + } + case 'remove_project_members': + return { + projectId: toNumber(params.projectId), + memberUserIds: toNumberList(params.memberUserIds), + memberEmailIds: toStringList(params.memberEmailIds), + } + case 'import_template': + return { + projectId: toNumber(params.projectId), + templateId: toNumber(params.templateId), + startDate: toStr(params.startDate), + prefix: toStr(params.prefix), + } + case 'list_placeholders': + return { projectId: toNumber(params.projectId) } + case 'assign_placeholders': + return { + projectId: toNumber(params.projectId), + placeholderId: toNumber(params.placeholderId), + userId: toNumber(params.userId), + userEmailId: toStr(params.userEmail), + } + case 'unassign_placeholders': + return { + projectId: toNumber(params.projectId), + placeholderId: toNumber(params.placeholderId), + } + + // Tasks + case 'create_task': + return { + taskName: toStr(params.taskName), + projectId: toNumber(params.projectId), + taskDescription: toStr(params.taskDescription), + taskPrivateNote: toStr(params.taskPrivateNote), + startDate: toStr(params.startDate), + dueDate: toStr(params.dueDate), + effortInMinutes: toNumber(params.effortInMinutes), + progress: toNumber(params.progress), + atRisk: toBool(params.atRisk), + type: toStr(params.taskType), + phaseId: toNumber(params.phaseId), + statusValue: toNumber(params.statusValue), + assigneeUserIds: toNumberList(params.assigneeUserIds), + assigneeEmailIds: toStringList(params.assigneeEmailIds), + followerUserIds: toNumberList(params.followerUserIds), + followerEmailIds: toStringList(params.followerEmailIds), + parentTaskId: toNumber(params.parentTaskId), + externalReferenceId: toStr(params.externalReferenceId), + private: toBool(params.private), + ...includeList(params), + } + case 'get_task': + return { taskId: toNumber(params.taskId), ...includeList(params) } + case 'list_tasks': + return { + ...pageParams(params), + ...sortParams(params), + ...includeList(params), + projectId: toNumber(params.projectId), + phaseId: toNumber(params.phaseId), + taskName: toStr(params.taskName), + taskNameContains: toStr(params.taskNameContains), + taskStatus: toStr(params.taskStatus), + startDateFrom: toStr(params.startDateFrom), + startDateTo: toStr(params.startDateTo), + dueDateFrom: toStr(params.dueDateFrom), + dueDateTo: toStr(params.dueDateTo), + includeArchive: toBool(params.includeArchive), + externalReferenceId: toStr(params.externalReferenceId), + } + case 'update_task': + return { + taskId: toNumber(params.taskId), + taskName: toStr(params.taskName), + taskDescription: toStr(params.taskDescription), + taskPrivateNote: toStr(params.taskPrivateNote), + startDate: toStr(params.startDate), + dueDate: toStr(params.dueDate), + effortInMinutes: toNumber(params.effortInMinutes), + progress: toNumber(params.progress), + atRisk: toBool(params.atRisk), + type: toStr(params.taskType), + statusValue: toNumber(params.statusValue), + externalReferenceId: toStr(params.externalReferenceId), + private: toBool(params.private), + ...includeList(params), + } + case 'delete_task': + return { taskId: toNumber(params.taskId) } + case 'move_task_to_phase': + return { taskId: toNumber(params.taskId), phaseId: toNumber(params.phaseId) } + case 'add_task_assignees': + case 'remove_task_assignees': + case 'add_task_followers': + case 'remove_task_followers': + return { + taskId: toNumber(params.taskId), + memberUserIds: toNumberList(params.memberUserIds), + memberEmailIds: toStringList(params.memberEmailIds), + } + case 'add_task_dependencies': + case 'remove_task_dependencies': + return { + taskId: toNumber(params.taskId), + dependencyTaskIds: toNumberList(params.dependencyTaskIds), + } + + // Phases + case 'create_phase': + return { + phaseName: toStr(params.phaseName), + projectId: toNumber(params.projectId), + startDate: toStr(params.startDate), + dueDate: toStr(params.dueDate), + statusValue: toNumber(params.statusValue), + private: toBool(params.private), + ...includeCsv(params), + } + case 'get_phase': + return { phaseId: toNumber(params.phaseId), ...includeCsv(params) } + case 'list_phases': + return { + projectId: toNumber(params.projectId), + ...pageParams(params), + ...sortParams(params), + ...includeCsv(params), + phaseName: toStr(params.phaseName), + } + case 'update_phase': + return { + phaseId: toNumber(params.phaseId), + phaseName: toStr(params.phaseName), + startDate: toStr(params.startDate), + dueDate: toStr(params.dueDate), + statusValue: toNumber(params.statusValue), + private: toBool(params.private), + ...includeCsv(params), + } + case 'delete_phase': + return { phaseId: toNumber(params.phaseId) } + + // Fields + case 'create_field': + return { + fieldLabel: toStr(params.fieldLabel), + fieldType: toStr(params.fieldType), + objectType: toStr(params.objectType), + fieldDescription: toStr(params.fieldDescription), + fieldOptions: toJson(params.fieldOptions), + ratingScale: toStr(params.ratingScale), + enabled: toBool(params.enabled), + private: toBool(params.private), + ...includeCsv(params), + } + case 'get_field': + return { fieldId: toNumber(params.fieldId), ...includeCsv(params) } + case 'list_fields': + return { + ...pageParams(params), + ...sortParams(params), + ...includeCsv(params), + objectType: toStr(params.objectType), + fieldType: toStr(params.fieldType), + enabled: toBool(params.enabled), + private: toBool(params.private), + } + case 'update_field': + return { + fieldId: toNumber(params.fieldId), + fieldLabel: toStr(params.fieldLabel), + fieldDescription: toStr(params.fieldDescription), + enabled: toBool(params.enabled), + private: toBool(params.private), + ...includeCsv(params), + } + case 'delete_field': + return { fieldId: toNumber(params.fieldId) } + case 'add_field_option': + return { + fieldId: toNumber(params.fieldId), + optionLabel: toStr(params.optionLabel), + optionColor: toStr(params.optionColor), + } + case 'update_field_option': + return { + fieldId: toNumber(params.fieldId), + optionValue: toNumber(params.optionValue), + optionLabel: toStr(params.optionLabel), + optionColor: toStr(params.optionColor), + } + + // Time entries + case 'create_time_entry': { + const source = params.timeEntrySource ?? 'task' + return { + date: toStr(params.date), + minutes: toNumber(params.minutes), + // Exactly one time source may be sent — pass only the selected one. + activityName: source === 'activity' ? toStr(params.timeEntryActivityName) : undefined, + taskId: source === 'task' ? toNumber(params.timeEntryTaskId) : undefined, + projectPhaseId: source === 'phase' ? toNumber(params.timeEntryPhaseId) : undefined, + projectId: source === 'project' ? toNumber(params.timeEntryProjectId) : undefined, + billable: toBool(params.billable), + userId: toNumber(params.userId), + userEmail: toStr(params.userEmail), + notes: toStr(params.notes), + categoryId: toNumber(params.categoryId), + ...includeCsv(params), + } + } + case 'get_time_entry': + return { timeEntryId: toNumber(params.timeEntryId), ...includeCsv(params) } + case 'list_time_entries': + return { + ...pageParams(params), + ...sortParams(params), + includeFields: toStr(params.includeFields), + dateEq: toStr(params.dateEq), + dateGe: toStr(params.dateGe), + dateLe: toStr(params.dateLe), + projectIdEq: toNumber(params.projectIdEq), + taskIdEq: toNumber(params.taskIdEq), + projectPhaseIdEq: toNumber(params.projectPhaseIdEq), + categoryIdEq: toNumber(params.categoryIdEq), + userIdEq: toNumber(params.userIdEq), + emailIdEq: toStr(params.emailIdEq), + emailIdCn: toStr(params.emailIdCn), + sourceTypeEq: toStr(params.sourceTypeEq), + activityNameEq: toStr(params.activityNameEq), + activityNameCn: toStr(params.activityNameCn), + approvalStatusEq: toStr(params.approvalStatusEq), + billableEq: toBool(params.billableEq), + includeDeletedEq: toBool(params.includeDeletedEq), + createdAtGt: toNumber(params.createdAtGt), + createdAtLt: toNumber(params.createdAtLt), + updatedAtGt: toNumber(params.updatedAtGt), + updatedAtLt: toNumber(params.updatedAtLt), + } + case 'search_time_entries': + return { + ...pageParams(params), + ...sortParams(params), + ...includeCsv(params), + dateEq: toStr(params.dateEq), + dateGe: toStr(params.dateGe), + dateLe: toStr(params.dateLe), + projectEq: toNumber(params.projectIdEq), + taskEq: toNumber(params.taskIdEq), + projectPhaseIdEq: toNumber(params.projectPhaseIdEq), + categoryIdEq: toNumber(params.categoryIdEq), + userIdEq: toNumber(params.userIdEq), + sourceTypeEq: toStr(params.sourceTypeEq), + activityNameEq: toStr(params.activityNameEq), + activityNameCn: toStr(params.activityNameCn), + approvalStatusEq: toStr(params.approvalStatusEq), + } + case 'update_time_entry': + return { + timeEntryId: toNumber(params.timeEntryId), + date: toStr(params.date), + minutes: toNumber(params.minutes), + activityName: toStr(params.activityName), + notes: toStr(params.notes), + billable: toBool(params.billable), + categoryId: toNumber(params.categoryId), + ...includeCsv(params), + } + case 'delete_time_entry': + return { timeEntryId: toNumber(params.timeEntryId) } + case 'list_time_entry_categories': + return pageParams(params) + + // Time-offs + case 'create_time_off': + return { + userId: toNumber(params.userId), + userEmail: toStr(params.userEmail), + startDate: toStr(params.startDate), + endDate: toStr(params.endDate), + type: toStr(params.timeOffType), + durationInMinutes: toNumber(params.durationInMinutes), + note: toStr(params.note), + notifyProjectOwners: toBool(params.notifyProjectOwners), + notifyUserIds: toNumberList(params.notifyUserIds), + notifyUserEmails: toStringList(params.notifyUserEmails), + ...includeList(params), + } + case 'get_time_off': + return { timeOffId: toNumber(params.timeOffId), ...includeList(params) } + case 'list_time_offs': + return { + ...pageParams(params), + ...sortParams(params), + ...includeList(params), + startDateGe: toStr(params.startDateGe), + startDateLe: toStr(params.startDateLe), + endDateGe: toStr(params.endDateGe), + endDateLe: toStr(params.endDateLe), + typeEq: toStr(params.timeOffTypeEq), + userIdEq: toStr(params.userIdEq), + emailIdEq: toStr(params.emailIdEq), + } + case 'delete_time_off': + return { timeOffId: toNumber(params.timeOffId) } + + // Users + case 'get_user': + return { userId: toNumber(params.userId), ...includeCsv(params) } + case 'list_users': + return { + ...pageParams(params), + ...sortParams(params), + ...includeCsv(params), + firstNameCn: toStr(params.firstNameCn), + lastNameCn: toStr(params.lastNameCn), + emailEq: toStr(params.emailEq), + emailCn: toStr(params.emailCn), + statusEq: toStr(params.userStatusEq), + typeEq: toStr(params.userTypeEq), + } + + // Spaces + case 'create_space': + return { + projectId: toNumber(params.projectId), + spaceName: toStr(params.spaceName), + private: toBool(params.private), + } + case 'get_space': + return { spaceId: toNumber(params.spaceId) } + case 'list_spaces': + return { + projectId: toNumber(params.projectId), + ...pageParams(params), + ...sortParams(params), + spaceNameEq: toStr(params.spaceNameEq), + spaceNameCn: toStr(params.spaceNameCn), + createdAtGt: toNumber(params.createdAtGt), + createdAtLt: toNumber(params.createdAtLt), + updatedAtGt: toNumber(params.updatedAtGt), + updatedAtLt: toNumber(params.updatedAtLt), + } + case 'update_space': + return { spaceId: toNumber(params.spaceId), spaceName: toStr(params.spaceName) } + case 'delete_space': + return { spaceId: toNumber(params.spaceId) } + + // Space documents + case 'create_space_document': + return { + spaceId: toNumber(params.spaceId), + spaceDocumentType: toStr(params.spaceDocumentType), + spaceDocumentName: toStr(params.spaceDocumentName), + url: toStr(params.url), + templateId: toNumber(params.documentTemplateId), + } + case 'get_space_document': + return { spaceDocumentId: toNumber(params.spaceDocumentId) } + case 'list_space_documents': + return { + projectId: toNumber(params.projectId), + ...pageParams(params), + ...sortParams(params), + spaceDocumentNameEq: toStr(params.spaceDocumentNameEq), + spaceDocumentNameCn: toStr(params.spaceDocumentNameCn), + spaceIdEq: toNumber(params.spaceIdEq), + createdAtGt: toNumber(params.createdAtGt), + createdAtLt: toNumber(params.createdAtLt), + updatedAtGt: toNumber(params.updatedAtGt), + updatedAtLt: toNumber(params.updatedAtLt), + } + case 'update_space_document': + return { + spaceDocumentId: toNumber(params.spaceDocumentId), + spaceDocumentName: toStr(params.spaceDocumentName), + url: toStr(params.url), + } + case 'delete_space_document': + return { spaceDocumentId: toNumber(params.spaceDocumentId) } + + // Resource allocations + case 'list_resource_allocations': + return { + startDate: toStr(params.startDate), + endDate: toStr(params.endDate), + ...pageParams(params), + ...sortParams(params), + ...includeList(params), + memberIdEq: toStr(params.memberIdEq), + projectIdEq: toStr(params.projectIdEq), + placeholderIdEq: toStr(params.placeholderIdEq), + } + + // Invoices + case 'get_invoice': + return { invoiceId: toNumber(params.invoiceId), ...includeList(params) } + case 'list_invoices': + return { + ...pageParams(params), + ...sortParams(params), + ...includeList(params), + dateOfIssueGe: toStr(params.dateOfIssueGe), + dateOfIssueLe: toStr(params.dateOfIssueLe), + dueDateGe: toStr(params.dueDateGe), + dueDateLe: toStr(params.dueDateLe), + amountGe: toNumber(params.amountGe), + amountLe: toNumber(params.amountLe), + amountOutstandingGt: toNumber(params.amountOutstandingGt), + invoiceNumberEq: toStr(params.invoiceNumberEq), + invoiceNumberCn: toStr(params.invoiceNumberCn), + statusEq: toStr(params.invoiceStatusEq), + statusOneOf: toStr(params.invoiceStatusOneOf), + companyIdEq: toStr(params.companyIdEq), + companyIdOneOf: toStr(params.companyIdOneOf), + } + case 'get_invoice_line_items': + case 'get_invoice_payments': + return { invoiceId: toNumber(params.invoiceId), ...pageParams(params) } + + default: + return {} + } +} + +export const RocketlaneBlock: BlockConfig = { + type: 'rocketlane', + name: 'Rocketlane', + description: 'Manage client onboarding projects, tasks, time tracking, and invoices', + longDescription: + 'Integrate Rocketlane into your workflow. Rocketlane is a professional-services automation platform for client onboarding and project delivery. Create and manage projects, tasks, phases, custom fields, time entries, time-offs, spaces, documents, resource allocations, and invoices.', + docsLink: 'https://docs.sim.ai/integrations/rocketlane', + category: 'tools', + integrationType: IntegrationType.Productivity, + bgColor: '#000000', + icon: RocketlaneIcon, + authMode: AuthMode.ApiKey, + + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + // Projects + { label: 'Create Project', id: 'create_project' }, + { label: 'Get Project', id: 'get_project' }, + { label: 'List Projects', id: 'list_projects' }, + { label: 'Update Project', id: 'update_project' }, + { label: 'Archive Project', id: 'archive_project' }, + { label: 'Delete Project', id: 'delete_project' }, + { label: 'Add Project Members', id: 'add_project_members' }, + { label: 'Remove Project Members', id: 'remove_project_members' }, + { label: 'Import Template', id: 'import_template' }, + { label: 'List Placeholders', id: 'list_placeholders' }, + { label: 'Assign Placeholder', id: 'assign_placeholders' }, + { label: 'Unassign Placeholder', id: 'unassign_placeholders' }, + // Tasks + { label: 'Create Task', id: 'create_task' }, + { label: 'Get Task', id: 'get_task' }, + { label: 'List Tasks', id: 'list_tasks' }, + { label: 'Update Task', id: 'update_task' }, + { label: 'Delete Task', id: 'delete_task' }, + { label: 'Move Task to Phase', id: 'move_task_to_phase' }, + { label: 'Add Task Assignees', id: 'add_task_assignees' }, + { label: 'Remove Task Assignees', id: 'remove_task_assignees' }, + { label: 'Add Task Followers', id: 'add_task_followers' }, + { label: 'Remove Task Followers', id: 'remove_task_followers' }, + { label: 'Add Task Dependencies', id: 'add_task_dependencies' }, + { label: 'Remove Task Dependencies', id: 'remove_task_dependencies' }, + // Phases + { label: 'Create Phase', id: 'create_phase' }, + { label: 'Get Phase', id: 'get_phase' }, + { label: 'List Phases', id: 'list_phases' }, + { label: 'Update Phase', id: 'update_phase' }, + { label: 'Delete Phase', id: 'delete_phase' }, + // Fields + { label: 'Create Field', id: 'create_field' }, + { label: 'Get Field', id: 'get_field' }, + { label: 'List Fields', id: 'list_fields' }, + { label: 'Update Field', id: 'update_field' }, + { label: 'Delete Field', id: 'delete_field' }, + { label: 'Add Field Option', id: 'add_field_option' }, + { label: 'Update Field Option', id: 'update_field_option' }, + // Time entries + { label: 'Create Time Entry', id: 'create_time_entry' }, + { label: 'Get Time Entry', id: 'get_time_entry' }, + { label: 'List Time Entries', id: 'list_time_entries' }, + { label: 'Search Time Entries', id: 'search_time_entries' }, + { label: 'Update Time Entry', id: 'update_time_entry' }, + { label: 'Delete Time Entry', id: 'delete_time_entry' }, + { label: 'List Time Entry Categories', id: 'list_time_entry_categories' }, + // Time-offs + { label: 'Create Time-Off', id: 'create_time_off' }, + { label: 'Get Time-Off', id: 'get_time_off' }, + { label: 'List Time-Offs', id: 'list_time_offs' }, + { label: 'Delete Time-Off', id: 'delete_time_off' }, + // Users + { label: 'Get User', id: 'get_user' }, + { label: 'List Users', id: 'list_users' }, + // Spaces + { label: 'Create Space', id: 'create_space' }, + { label: 'Get Space', id: 'get_space' }, + { label: 'List Spaces', id: 'list_spaces' }, + { label: 'Update Space', id: 'update_space' }, + { label: 'Delete Space', id: 'delete_space' }, + // Space documents + { label: 'Create Space Document', id: 'create_space_document' }, + { label: 'Get Space Document', id: 'get_space_document' }, + { label: 'List Space Documents', id: 'list_space_documents' }, + { label: 'Update Space Document', id: 'update_space_document' }, + { label: 'Delete Space Document', id: 'delete_space_document' }, + // Resource allocations + { label: 'List Resource Allocations', id: 'list_resource_allocations' }, + // Invoices + { label: 'Get Invoice', id: 'get_invoice' }, + { label: 'List Invoices', id: 'list_invoices' }, + { label: 'Get Invoice Line Items', id: 'get_invoice_line_items' }, + { label: 'Get Invoice Payments', id: 'get_invoice_payments' }, + ], + value: () => 'list_projects', + }, + + // Shared identifiers + { + id: 'projectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'e.g. 101', + condition: { field: 'operation', value: PROJECT_ID_OPS }, + required: { field: 'operation', value: PROJECT_ID_REQUIRED_OPS }, + }, + { + id: 'taskId', + title: 'Task ID', + type: 'short-input', + placeholder: 'e.g. 5001', + condition: { field: 'operation', value: TASK_ID_OPS }, + required: { field: 'operation', value: TASK_ID_OPS }, + }, + { + id: 'phaseId', + title: 'Phase ID', + type: 'short-input', + placeholder: 'e.g. 301', + condition: { field: 'operation', value: PHASE_ID_OPS }, + required: { field: 'operation', value: PHASE_ID_REQUIRED_OPS }, + }, + { + id: 'fieldId', + title: 'Field ID', + type: 'short-input', + placeholder: 'e.g. 41', + condition: { field: 'operation', value: FIELD_ID_OPS }, + required: { field: 'operation', value: FIELD_ID_OPS }, + }, + { + id: 'spaceId', + title: 'Space ID', + type: 'short-input', + placeholder: 'e.g. 21', + condition: { field: 'operation', value: SPACE_ID_OPS }, + required: { field: 'operation', value: SPACE_ID_OPS }, + }, + { + id: 'spaceDocumentId', + title: 'Space Document ID', + type: 'short-input', + placeholder: 'e.g. 11', + condition: { field: 'operation', value: SPACE_DOCUMENT_ID_OPS }, + required: { field: 'operation', value: SPACE_DOCUMENT_ID_OPS }, + }, + { + id: 'timeEntryId', + title: 'Time Entry ID', + type: 'short-input', + placeholder: 'e.g. 9001', + condition: { field: 'operation', value: TIME_ENTRY_ID_OPS }, + required: { field: 'operation', value: TIME_ENTRY_ID_OPS }, + }, + { + id: 'timeOffId', + title: 'Time-Off ID', + type: 'short-input', + placeholder: 'e.g. 71', + condition: { field: 'operation', value: TIME_OFF_ID_OPS }, + required: { field: 'operation', value: TIME_OFF_ID_OPS }, + }, + { + id: 'invoiceId', + title: 'Invoice ID', + type: 'short-input', + placeholder: 'e.g. 61', + condition: { field: 'operation', value: INVOICE_ID_OPS }, + required: { field: 'operation', value: INVOICE_ID_OPS }, + }, + { + id: 'placeholderId', + title: 'Placeholder ID', + type: 'short-input', + placeholder: 'e.g. 12', + condition: { field: 'operation', value: ['assign_placeholders', 'unassign_placeholders'] }, + required: { field: 'operation', value: ['assign_placeholders', 'unassign_placeholders'] }, + }, + { + id: 'userId', + title: 'User ID', + type: 'short-input', + placeholder: 'e.g. 1001', + condition: { + field: 'operation', + value: ['get_user', 'assign_placeholders', 'create_time_entry', 'create_time_off'], + }, + required: { field: 'operation', value: 'get_user' }, + }, + { + id: 'userEmail', + title: 'User Email', + type: 'short-input', + placeholder: 'user@company.com', + condition: { + field: 'operation', + value: ['assign_placeholders', 'create_time_entry', 'create_time_off'], + }, + }, + + // Project fields + { + id: 'projectName', + title: 'Project Name', + type: 'short-input', + placeholder: 'e.g. Acme Onboarding', + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + required: { field: 'operation', value: 'create_project' }, + }, + { + id: 'customerCompanyName', + title: 'Customer Company Name', + type: 'short-input', + placeholder: 'Exact company name (case-sensitive)', + condition: { field: 'operation', value: 'create_project' }, + required: { field: 'operation', value: 'create_project' }, + }, + { + id: 'ownerUserId', + title: 'Owner User ID', + type: 'short-input', + placeholder: 'e.g. 1001', + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + }, + { + id: 'ownerEmailId', + title: 'Owner Email', + type: 'short-input', + placeholder: 'owner@company.com', + mode: 'advanced', + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + }, + { + id: 'visibility', + title: 'Visibility', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Everyone', id: 'EVERYONE' }, + { label: 'Members only', id: 'MEMBERS' }, + ], + value: () => '', + mode: 'advanced', + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + }, + { + id: 'customerChampionUserId', + title: 'Customer Champion User ID', + type: 'short-input', + placeholder: 'e.g. 2001', + mode: 'advanced', + condition: { field: 'operation', value: 'create_project' }, + }, + { + id: 'customerUserIds', + title: 'Customer User IDs', + type: 'short-input', + placeholder: 'Comma-separated user IDs', + mode: 'advanced', + condition: { field: 'operation', value: ['create_project', 'add_project_members'] }, + }, + { + id: 'customerEmailIds', + title: 'Customer Emails', + type: 'short-input', + placeholder: 'Comma-separated emails', + mode: 'advanced', + condition: { field: 'operation', value: 'add_project_members' }, + }, + { + id: 'fields', + title: 'Custom Fields', + type: 'code', + language: 'json', + placeholder: '[{"fieldId": 41, "fieldValue": "High"}]', + mode: 'advanced', + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON array of Rocketlane custom field assignments, each object with a numeric "fieldId" and a "fieldValue" (string or number matching the field type). Return ONLY the JSON array.', + generationType: 'json-object', + }, + }, + { + id: 'sources', + title: 'Template Sources', + type: 'code', + language: 'json', + placeholder: '[{"templateId": 7, "startDate": "2026-08-01", "prefix": "KO"}]', + mode: 'advanced', + condition: { field: 'operation', value: 'create_project' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON array of Rocketlane project template sources, each object with a numeric "templateId", a "startDate" (YYYY-MM-DD), and an optional "prefix" string. Return ONLY the JSON array.', + generationType: 'json-object', + }, + }, + { + id: 'placeholders', + title: 'Placeholder Assignments', + type: 'code', + language: 'json', + placeholder: '[{"placeholderId": 12, "user": {"userId": 1001}}]', + mode: 'advanced', + condition: { field: 'operation', value: 'create_project' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON array of Rocketlane placeholder mappings, each object with a numeric "placeholderId" and a "user" object containing either "userId" (number) or "emailId" (string). Return ONLY the JSON array.', + generationType: 'json-object', + }, + }, + { + id: 'assignProjectOwner', + title: 'Assign Unassigned Tasks to Owner', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: 'create_project' }, + }, + { + id: 'autoCreateCompany', + title: 'Auto-create Customer Company', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: 'create_project' }, + }, + { + id: 'autoAllocation', + title: 'Auto Allocation', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + }, + { + id: 'annualizedRecurringRevenue', + title: 'Annualized Recurring Revenue', + type: 'short-input', + placeholder: 'e.g. 50000', + mode: 'advanced', + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + }, + { + id: 'projectFee', + title: 'Project Fee', + type: 'short-input', + placeholder: 'e.g. 25000', + mode: 'advanced', + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + }, + { + id: 'budgetedHours', + title: 'Budgeted Hours', + type: 'short-input', + placeholder: 'e.g. 120.5', + mode: 'advanced', + condition: { field: 'operation', value: ['create_project', 'update_project'] }, + }, + { + id: 'contractType', + title: 'Contract Type', + type: 'dropdown', + options: [ + { label: 'None', id: '' }, + { label: 'Fixed fee', id: 'FIXED_FEE' }, + { label: 'Time and material', id: 'TIME_AND_MATERIAL' }, + { label: 'Non-billable', id: 'NON_BILLABLE' }, + { label: 'Subscription', id: 'SUBSCRIPTION' }, + ], + value: () => '', + mode: 'advanced', + condition: { field: 'operation', value: 'create_project' }, + }, + { + id: 'fixedFee', + title: 'Fixed Fee', + type: 'short-input', + placeholder: 'e.g. 25000', + mode: 'advanced', + condition: { + field: 'operation', + value: 'create_project', + and: { field: 'contractType', value: 'FIXED_FEE' }, + }, + }, + { + id: 'projectBudget', + title: 'Project Budget', + type: 'short-input', + placeholder: 'e.g. 40000', + mode: 'advanced', + condition: { + field: 'operation', + value: 'create_project', + and: { field: 'contractType', value: 'TIME_AND_MATERIAL' }, + }, + }, + { + id: 'rateCardId', + title: 'Rate Card ID', + type: 'short-input', + placeholder: 'e.g. 3', + mode: 'advanced', + condition: { + field: 'operation', + value: 'create_project', + and: { field: 'contractType', value: 'TIME_AND_MATERIAL' }, + }, + }, + { + id: 'subscriptionFrequency', + title: 'Subscription Frequency', + type: 'dropdown', + options: [ + { label: 'None', id: '' }, + { label: 'Monthly', id: 'MONTHLY' }, + { label: 'Quarterly', id: 'QUARTERLY' }, + { label: 'Half-yearly', id: 'HALF_YEARLY' }, + { label: 'Yearly', id: 'YEARLY' }, + ], + value: () => '', + mode: 'advanced', + condition: { + field: 'operation', + value: 'create_project', + and: { field: 'contractType', value: 'SUBSCRIPTION' }, + }, + }, + { + id: 'subscriptionStartDate', + title: 'Subscription Start Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { + field: 'operation', + value: 'create_project', + and: { field: 'contractType', value: 'SUBSCRIPTION' }, + }, + }, + { + id: 'periodMinutes', + title: 'Budgeted Minutes per Period', + type: 'short-input', + placeholder: 'e.g. 2400', + mode: 'advanced', + condition: { + field: 'operation', + value: 'create_project', + and: { field: 'contractType', value: 'SUBSCRIPTION' }, + }, + }, + { + id: 'periodBudget', + title: 'Budget per Period', + type: 'short-input', + placeholder: 'e.g. 5000', + mode: 'advanced', + condition: { + field: 'operation', + value: 'create_project', + and: { field: 'contractType', value: 'SUBSCRIPTION' }, + }, + }, + { + id: 'noOfPeriods', + title: 'Number of Periods', + type: 'short-input', + placeholder: 'e.g. 12', + mode: 'advanced', + condition: { + field: 'operation', + value: 'create_project', + and: { field: 'contractType', value: 'SUBSCRIPTION' }, + }, + }, + { + id: 'currency', + title: 'Currency', + type: 'short-input', + placeholder: 'ISO code, e.g. USD', + mode: 'advanced', + condition: { field: 'operation', value: 'create_project' }, + }, + + // Members / assignees / followers / dependencies + { + id: 'memberUserIds', + title: 'Member User IDs', + type: 'short-input', + placeholder: 'Comma-separated user IDs', + condition: { field: 'operation', value: [...MEMBER_OPS, 'create_project'] }, + }, + { + id: 'memberEmailIds', + title: 'Member Emails', + type: 'short-input', + placeholder: 'Comma-separated emails', + mode: 'advanced', + condition: { field: 'operation', value: MEMBER_OPS }, + }, + { + id: 'dependencyTaskIds', + title: 'Dependency Task IDs', + type: 'short-input', + placeholder: 'Comma-separated task IDs', + condition: { + field: 'operation', + value: ['add_task_dependencies', 'remove_task_dependencies'], + }, + required: { + field: 'operation', + value: ['add_task_dependencies', 'remove_task_dependencies'], + }, + }, + + // Task fields + { + id: 'taskName', + title: 'Task Name', + type: 'short-input', + placeholder: 'e.g. Kickoff call', + condition: { field: 'operation', value: ['create_task', 'update_task', 'list_tasks'] }, + required: { field: 'operation', value: 'create_task' }, + }, + { + id: 'taskDescription', + title: 'Description', + type: 'long-input', + placeholder: 'Task description (HTML supported)', + condition: { field: 'operation', value: ['create_task', 'update_task'] }, + }, + { + id: 'taskPrivateNote', + title: 'Private Note', + type: 'long-input', + placeholder: 'Visible only to team members (HTML supported)', + mode: 'advanced', + condition: { field: 'operation', value: ['create_task', 'update_task'] }, + }, + { + id: 'taskType', + title: 'Task Type', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Task', id: 'TASK' }, + { label: 'Milestone', id: 'MILESTONE' }, + ], + value: () => '', + mode: 'advanced', + condition: { field: 'operation', value: ['create_task', 'update_task'] }, + }, + { + id: 'effortInMinutes', + title: 'Effort (minutes)', + type: 'short-input', + placeholder: 'e.g. 480', + mode: 'advanced', + condition: { field: 'operation', value: ['create_task', 'update_task'] }, + }, + { + id: 'progress', + title: 'Progress (%)', + type: 'short-input', + placeholder: '0-100', + mode: 'advanced', + condition: { field: 'operation', value: ['create_task', 'update_task'] }, + }, + { + id: 'atRisk', + title: 'At Risk', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: ['create_task', 'update_task'] }, + }, + { + id: 'assigneeUserIds', + title: 'Assignee User IDs', + type: 'short-input', + placeholder: 'Comma-separated user IDs', + condition: { field: 'operation', value: 'create_task' }, + }, + { + id: 'assigneeEmailIds', + title: 'Assignee Emails', + type: 'short-input', + placeholder: 'Comma-separated emails', + mode: 'advanced', + condition: { field: 'operation', value: 'create_task' }, + }, + { + id: 'followerUserIds', + title: 'Follower User IDs', + type: 'short-input', + placeholder: 'Comma-separated user IDs', + mode: 'advanced', + condition: { field: 'operation', value: 'create_task' }, + }, + { + id: 'followerEmailIds', + title: 'Follower Emails', + type: 'short-input', + placeholder: 'Comma-separated emails', + mode: 'advanced', + condition: { field: 'operation', value: 'create_task' }, + }, + { + id: 'parentTaskId', + title: 'Parent Task ID', + type: 'short-input', + placeholder: 'e.g. 4001', + mode: 'advanced', + condition: { field: 'operation', value: 'create_task' }, + }, + { + id: 'externalReferenceId', + title: 'External Reference ID', + type: 'short-input', + placeholder: 'ID in an external system', + mode: 'advanced', + condition: { field: 'operation', value: EXTERNAL_REFERENCE_OPS }, + }, + + // Phase fields + { + id: 'phaseName', + title: 'Phase Name', + type: 'short-input', + placeholder: 'e.g. Implementation', + condition: { field: 'operation', value: ['create_phase', 'update_phase', 'list_phases'] }, + required: { field: 'operation', value: 'create_phase' }, + }, + + // Field (custom field) configuration + { + id: 'fieldLabel', + title: 'Field Label', + type: 'short-input', + placeholder: 'e.g. Priority', + condition: { field: 'operation', value: ['create_field', 'update_field'] }, + required: { field: 'operation', value: 'create_field' }, + }, + { + id: 'fieldType', + title: 'Field Type', + type: 'dropdown', + options: [ + { label: 'Select type', id: '' }, + { label: 'Text', id: 'TEXT' }, + { label: 'Multi-line text', id: 'MULTI_LINE_TEXT' }, + { label: 'Yes or no', id: 'YES_OR_NO' }, + { label: 'Date', id: 'DATE' }, + { label: 'Single choice', id: 'SINGLE_CHOICE' }, + { label: 'Multiple choice', id: 'MULTIPLE_CHOICE' }, + { label: 'Single user', id: 'SINGLE_USER' }, + { label: 'Multiple user', id: 'MULTIPLE_USER' }, + { label: 'Number', id: 'NUMBER' }, + { label: 'Note', id: 'NOTE' }, + { label: 'Rating', id: 'RATING' }, + ], + value: () => '', + condition: { field: 'operation', value: ['create_field', 'list_fields'] }, + required: { field: 'operation', value: 'create_field' }, + }, + { + id: 'objectType', + title: 'Object Type', + type: 'dropdown', + options: [ + { label: 'Select object', id: '' }, + { label: 'Project', id: 'PROJECT' }, + { label: 'Task', id: 'TASK' }, + { label: 'User', id: 'USER' }, + ], + value: () => '', + condition: { field: 'operation', value: ['create_field', 'list_fields'] }, + required: { field: 'operation', value: 'create_field' }, + }, + { + id: 'fieldDescription', + title: 'Field Description', + type: 'short-input', + placeholder: 'What this field captures', + mode: 'advanced', + condition: { field: 'operation', value: ['create_field', 'update_field'] }, + }, + { + id: 'fieldOptions', + title: 'Field Options', + type: 'code', + language: 'json', + placeholder: '[{"optionLabel": "High", "optionColor": "RED"}]', + condition: { + field: 'operation', + value: 'create_field', + and: { field: 'fieldType', value: ['SINGLE_CHOICE', 'MULTIPLE_CHOICE'] }, + }, + wandConfig: { + enabled: true, + prompt: + 'Generate a JSON array of Rocketlane field options for a SINGLE_CHOICE or MULTIPLE_CHOICE field. Each object has "optionLabel" (string) and "optionColor" (one of RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY). Return ONLY the JSON array.', + generationType: 'json-object', + }, + }, + { + id: 'ratingScale', + title: 'Rating Scale', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: '3 stars', id: 'THREE' }, + { label: '5 stars', id: 'FIVE' }, + { label: '7 stars', id: 'SEVEN' }, + { label: '10 stars', id: 'TEN' }, + ], + value: () => '', + mode: 'advanced', + condition: { + field: 'operation', + value: 'create_field', + and: { field: 'fieldType', value: 'RATING' }, + }, + }, + { + id: 'enabled', + title: 'Enabled', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: ['create_field', 'update_field', 'list_fields'] }, + }, + { + id: 'private', + title: 'Private', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: PRIVATE_OPS }, + }, + { + id: 'optionLabel', + title: 'Option Label', + type: 'short-input', + placeholder: 'e.g. High', + condition: { field: 'operation', value: ['add_field_option', 'update_field_option'] }, + required: { field: 'operation', value: 'add_field_option' }, + }, + { + id: 'optionColor', + title: 'Option Color', + type: 'dropdown', + options: [ + { label: 'Select color', id: '' }, + { label: 'Red', id: 'RED' }, + { label: 'Yellow', id: 'YELLOW' }, + { label: 'Green', id: 'GREEN' }, + { label: 'Teal', id: 'TEAL' }, + { label: 'Cyan', id: 'CYAN' }, + { label: 'Blue', id: 'BLUE' }, + { label: 'Purple', id: 'PURPLE' }, + { label: 'Magenta', id: 'MAGENTA' }, + { label: 'Gray', id: 'GRAY' }, + { label: 'Cool gray', id: 'COOL_GRAY' }, + ], + value: () => '', + condition: { field: 'operation', value: ['add_field_option', 'update_field_option'] }, + required: { field: 'operation', value: 'add_field_option' }, + }, + { + id: 'optionValue', + title: 'Option Value', + type: 'short-input', + placeholder: 'Identifier of the option to update', + condition: { field: 'operation', value: 'update_field_option' }, + required: { field: 'operation', value: 'update_field_option' }, + }, + + // Template import + { + id: 'templateId', + title: 'Template ID', + type: 'short-input', + placeholder: 'e.g. 7', + condition: { field: 'operation', value: 'import_template' }, + required: { field: 'operation', value: 'import_template' }, + }, + { + id: 'prefix', + title: 'Prefix', + type: 'short-input', + placeholder: 'Distinguishes this template import', + mode: 'advanced', + condition: { field: 'operation', value: 'import_template' }, + }, + + // Dates + { + id: 'startDate', + title: 'Start Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: START_DATE_OPS }, + required: { field: 'operation', value: START_DATE_REQUIRED_OPS }, + }, + { + id: 'dueDate', + title: 'Due Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: DUE_DATE_OPS }, + required: { field: 'operation', value: 'create_phase' }, + }, + { + id: 'endDate', + title: 'End Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: ['create_time_off', 'list_resource_allocations'] }, + required: { field: 'operation', value: ['create_time_off', 'list_resource_allocations'] }, + }, + { + id: 'statusValue', + title: 'Status Value', + type: 'short-input', + placeholder: 'Numeric status identifier', + mode: 'advanced', + condition: { field: 'operation', value: STATUS_VALUE_OPS }, + }, + + // Time entry fields + { + id: 'date', + title: 'Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: ['create_time_entry', 'update_time_entry'] }, + required: { field: 'operation', value: ['create_time_entry', 'update_time_entry'] }, + }, + { + id: 'minutes', + title: 'Minutes', + type: 'short-input', + placeholder: '1-1440', + condition: { field: 'operation', value: ['create_time_entry', 'update_time_entry'] }, + required: { field: 'operation', value: ['create_time_entry', 'update_time_entry'] }, + }, + { + id: 'timeEntrySource', + title: 'Track Time Against', + type: 'dropdown', + options: [ + { label: 'Task', id: 'task' }, + { label: 'Project', id: 'project' }, + { label: 'Project phase', id: 'phase' }, + { label: 'Ad-hoc activity', id: 'activity' }, + ], + condition: { field: 'operation', value: 'create_time_entry' }, + value: () => 'task', + }, + { + id: 'timeEntryTaskId', + title: 'Task ID', + type: 'short-input', + placeholder: 'e.g. 5001', + condition: { + field: 'operation', + value: 'create_time_entry', + and: { field: 'timeEntrySource', value: 'task' }, + }, + required: { + field: 'operation', + value: 'create_time_entry', + and: { field: 'timeEntrySource', value: 'task' }, + }, + }, + { + id: 'timeEntryProjectId', + title: 'Project ID', + type: 'short-input', + placeholder: 'e.g. 101', + condition: { + field: 'operation', + value: 'create_time_entry', + and: { field: 'timeEntrySource', value: 'project' }, + }, + required: { + field: 'operation', + value: 'create_time_entry', + and: { field: 'timeEntrySource', value: 'project' }, + }, + }, + { + id: 'timeEntryPhaseId', + title: 'Project Phase ID', + type: 'short-input', + placeholder: 'e.g. 301', + condition: { + field: 'operation', + value: 'create_time_entry', + and: { field: 'timeEntrySource', value: 'phase' }, + }, + required: { + field: 'operation', + value: 'create_time_entry', + and: { field: 'timeEntrySource', value: 'phase' }, + }, + }, + { + id: 'timeEntryActivityName', + title: 'Activity Name', + type: 'short-input', + placeholder: 'e.g. Internal sync', + condition: { + field: 'operation', + value: 'create_time_entry', + and: { field: 'timeEntrySource', value: 'activity' }, + }, + required: { + field: 'operation', + value: 'create_time_entry', + and: { field: 'timeEntrySource', value: 'activity' }, + }, + }, + { + id: 'activityName', + title: 'Activity Name', + type: 'short-input', + placeholder: 'New name for the ad-hoc activity', + mode: 'advanced', + condition: { field: 'operation', value: 'update_time_entry' }, + }, + { + id: 'notes', + title: 'Notes', + type: 'long-input', + placeholder: 'Notes for the time entry', + condition: { field: 'operation', value: ['create_time_entry', 'update_time_entry'] }, + }, + { + id: 'billable', + title: 'Billable', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: ['create_time_entry', 'update_time_entry'] }, + }, + { + id: 'categoryId', + title: 'Category ID', + type: 'short-input', + placeholder: 'Time entry category ID', + mode: 'advanced', + condition: { field: 'operation', value: ['create_time_entry', 'update_time_entry'] }, + }, + + // Time-off fields + { + id: 'timeOffType', + title: 'Time-Off Type', + type: 'dropdown', + options: [ + { label: 'Full day', id: 'FULL_DAY' }, + { label: 'Half day', id: 'HALF_DAY' }, + { label: 'Custom', id: 'CUSTOM' }, + ], + value: () => 'FULL_DAY', + condition: { field: 'operation', value: 'create_time_off' }, + required: { field: 'operation', value: 'create_time_off' }, + }, + { + id: 'durationInMinutes', + title: 'Duration per Day (minutes)', + type: 'short-input', + placeholder: 'e.g. 240', + condition: { + field: 'operation', + value: 'create_time_off', + and: { field: 'timeOffType', value: 'CUSTOM' }, + }, + required: { + field: 'operation', + value: 'create_time_off', + and: { field: 'timeOffType', value: 'CUSTOM' }, + }, + }, + { + id: 'note', + title: 'Note', + type: 'long-input', + placeholder: 'Note about the time-off', + condition: { field: 'operation', value: 'create_time_off' }, + }, + { + id: 'notifyProjectOwners', + title: 'Notify Project Owners', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: 'create_time_off' }, + }, + { + id: 'notifyUserIds', + title: 'Notify User IDs', + type: 'short-input', + placeholder: 'Comma-separated user IDs', + mode: 'advanced', + condition: { field: 'operation', value: 'create_time_off' }, + }, + { + id: 'notifyUserEmails', + title: 'Notify User Emails', + type: 'short-input', + placeholder: 'Comma-separated emails', + mode: 'advanced', + condition: { field: 'operation', value: 'create_time_off' }, + }, + + // Space fields + { + id: 'spaceName', + title: 'Space Name', + type: 'short-input', + placeholder: 'e.g. Shared Docs', + condition: { field: 'operation', value: ['create_space', 'update_space'] }, + required: { field: 'operation', value: 'create_space' }, + }, + + // Space document fields + { + id: 'spaceDocumentType', + title: 'Document Type', + type: 'dropdown', + options: [ + { label: 'Rocketlane document', id: 'ROCKETLANE_DOCUMENT' }, + { label: 'Embedded document', id: 'EMBEDDED_DOCUMENT' }, + ], + value: () => 'ROCKETLANE_DOCUMENT', + condition: { field: 'operation', value: 'create_space_document' }, + required: { field: 'operation', value: 'create_space_document' }, + }, + { + id: 'spaceDocumentName', + title: 'Document Name', + type: 'short-input', + placeholder: 'e.g. Kickoff Notes', + condition: { field: 'operation', value: ['create_space_document', 'update_space_document'] }, + }, + { + id: 'url', + title: 'Embed URL', + type: 'short-input', + placeholder: 'https://... (embedded documents)', + condition: { field: 'operation', value: ['create_space_document', 'update_space_document'] }, + }, + { + id: 'documentTemplateId', + title: 'Document Template ID', + type: 'short-input', + placeholder: 'e.g. 5', + mode: 'advanced', + condition: { field: 'operation', value: 'create_space_document' }, + }, + + // List filters — projects + { + id: 'projectNameContains', + title: 'Project Name Contains', + type: 'short-input', + placeholder: 'e.g. Onboarding', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'projectNameEquals', + title: 'Project Name Equals', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'statusEquals', + title: 'Status Equals', + type: 'short-input', + placeholder: 'Project status value', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'statusOneOf', + title: 'Status One Of', + type: 'short-input', + placeholder: 'Comma-separated status values', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'customerIdEquals', + title: 'Customer Company ID', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'customerIdOneOf', + title: 'Customer Company ID One Of', + type: 'short-input', + placeholder: 'Comma-separated company IDs', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'teamMemberIdEquals', + title: 'Team Member ID', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'contractTypeEquals', + title: 'Contract Type', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'Fixed fee', id: 'FIXED_FEE' }, + { label: 'Time and material', id: 'TIME_AND_MATERIAL' }, + { label: 'Subscription', id: 'SUBSCRIPTION' }, + { label: 'Non-billable', id: 'NON_BILLABLE' }, + ], + value: () => '', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'includeArchived', + title: 'Include Archived', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'externalReferenceIdEquals', + title: 'External Reference ID Equals', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'startDateAfter', + title: 'Start Date After', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'startDateBefore', + title: 'Start Date Before', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'dueDateAfter', + title: 'Due Date After', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + { + id: 'dueDateBefore', + title: 'Due Date Before', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_projects' }, + }, + + // List filters — tasks + { + id: 'taskNameContains', + title: 'Task Name Contains', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_tasks' }, + }, + { + id: 'taskStatus', + title: 'Task Status', + type: 'short-input', + placeholder: 'Task status value', + mode: 'advanced', + condition: { field: 'operation', value: 'list_tasks' }, + }, + { + id: 'startDateFrom', + title: 'Start Date From', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_tasks' }, + }, + { + id: 'startDateTo', + title: 'Start Date To', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_tasks' }, + }, + { + id: 'dueDateFrom', + title: 'Due Date From', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_tasks' }, + }, + { + id: 'dueDateTo', + title: 'Due Date To', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_tasks' }, + }, + { + id: 'includeArchive', + title: 'Include Archived Tasks', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: 'list_tasks' }, + }, + + // List filters — time entries (shared with search) + { + id: 'dateEq', + title: 'Date Equals', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: TIME_ENTRY_FILTER_OPS }, + }, + { + id: 'dateGe', + title: 'Date On or After', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: TIME_ENTRY_FILTER_OPS }, + }, + { + id: 'dateLe', + title: 'Date On or Before', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: TIME_ENTRY_FILTER_OPS }, + }, + { + id: 'projectIdEq', + title: 'Project ID Filter', + type: 'short-input', + placeholder: 'e.g. 101', + mode: 'advanced', + condition: { + field: 'operation', + value: [...TIME_ENTRY_FILTER_OPS, 'list_resource_allocations'], + }, + }, + { + id: 'taskIdEq', + title: 'Task ID Filter', + type: 'short-input', + placeholder: 'e.g. 5001', + mode: 'advanced', + condition: { field: 'operation', value: TIME_ENTRY_FILTER_OPS }, + }, + { + id: 'projectPhaseIdEq', + title: 'Phase ID Filter', + type: 'short-input', + placeholder: 'e.g. 301', + mode: 'advanced', + condition: { field: 'operation', value: TIME_ENTRY_FILTER_OPS }, + }, + { + id: 'categoryIdEq', + title: 'Category ID Filter', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: TIME_ENTRY_FILTER_OPS }, + }, + { + id: 'userIdEq', + title: 'User ID Filter', + type: 'short-input', + placeholder: 'e.g. 1001', + mode: 'advanced', + condition: { field: 'operation', value: [...TIME_ENTRY_FILTER_OPS, 'list_time_offs'] }, + }, + { + id: 'emailIdEq', + title: 'User Email Filter', + type: 'short-input', + placeholder: 'user@company.com', + mode: 'advanced', + condition: { field: 'operation', value: ['list_time_entries', 'list_time_offs'] }, + }, + { + id: 'emailIdCn', + title: 'User Email Contains', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_time_entries' }, + }, + { + id: 'sourceTypeEq', + title: 'Source Type', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'Task', id: 'TASK' }, + { label: 'Project', id: 'PROJECT' }, + { label: 'Phase', id: 'PHASE' }, + { label: 'Ad-hoc', id: 'ADHOC' }, + { label: 'Milestone', id: 'MILESTONE' }, + { label: 'Google Calendar', id: 'GOOGLE_CALENDAR' }, + { label: 'Outlook Calendar', id: 'OUTLOOK_CALENDAR' }, + ], + value: () => '', + mode: 'advanced', + condition: { field: 'operation', value: TIME_ENTRY_FILTER_OPS }, + }, + { + id: 'activityNameEq', + title: 'Activity Name Equals', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: TIME_ENTRY_FILTER_OPS }, + }, + { + id: 'activityNameCn', + title: 'Activity Name Contains', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: TIME_ENTRY_FILTER_OPS }, + }, + { + id: 'approvalStatusEq', + title: 'Approval Status', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'Not submitted', id: 'NOT_SUBMITTED' }, + { label: 'Submitted', id: 'SUBMITTED' }, + { label: 'Approved', id: 'APPROVED' }, + { label: 'Rejected', id: 'REJECTED' }, + ], + value: () => '', + mode: 'advanced', + condition: { field: 'operation', value: TIME_ENTRY_FILTER_OPS }, + }, + { + id: 'billableEq', + title: 'Billable Only', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: 'list_time_entries' }, + }, + { + id: 'includeDeletedEq', + title: 'Include Deleted', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: 'list_time_entries' }, + }, + { + id: 'createdAtGt', + title: 'Created After (epoch ms)', + type: 'short-input', + placeholder: 'e.g. 1750000000000', + mode: 'advanced', + condition: { field: 'operation', value: CREATED_UPDATED_AT_OPS }, + }, + { + id: 'createdAtLt', + title: 'Created Before (epoch ms)', + type: 'short-input', + placeholder: 'e.g. 1750000000000', + mode: 'advanced', + condition: { field: 'operation', value: CREATED_UPDATED_AT_OPS }, + }, + { + id: 'updatedAtGt', + title: 'Updated After (epoch ms)', + type: 'short-input', + placeholder: 'e.g. 1750000000000', + mode: 'advanced', + condition: { field: 'operation', value: CREATED_UPDATED_AT_OPS }, + }, + { + id: 'updatedAtLt', + title: 'Updated Before (epoch ms)', + type: 'short-input', + placeholder: 'e.g. 1750000000000', + mode: 'advanced', + condition: { field: 'operation', value: CREATED_UPDATED_AT_OPS }, + }, + + // List filters — time-offs + { + id: 'startDateGe', + title: 'Start Date On or After', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_time_offs' }, + }, + { + id: 'startDateLe', + title: 'Start Date On or Before', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_time_offs' }, + }, + { + id: 'endDateGe', + title: 'End Date On or After', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_time_offs' }, + }, + { + id: 'endDateLe', + title: 'End Date On or Before', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_time_offs' }, + }, + { + id: 'timeOffTypeEq', + title: 'Time-Off Type Filter', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'Full day', id: 'FULL_DAY' }, + { label: 'Half day', id: 'HALF_DAY' }, + { label: 'Custom', id: 'CUSTOM' }, + ], + value: () => '', + mode: 'advanced', + condition: { field: 'operation', value: 'list_time_offs' }, + }, + + // List filters — users + { + id: 'firstNameCn', + title: 'First Name Contains', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_users' }, + }, + { + id: 'lastNameCn', + title: 'Last Name Contains', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_users' }, + }, + { + id: 'emailEq', + title: 'Email Equals', + type: 'short-input', + placeholder: 'user@company.com', + mode: 'advanced', + condition: { field: 'operation', value: 'list_users' }, + }, + { + id: 'emailCn', + title: 'Email Contains', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_users' }, + }, + { + id: 'userStatusEq', + title: 'User Status', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'Active', id: 'ACTIVE' }, + { label: 'Invited', id: 'INVITED' }, + { label: 'Inactive', id: 'INACTIVE' }, + { label: 'Passive', id: 'PASSIVE' }, + ], + value: () => '', + mode: 'advanced', + condition: { field: 'operation', value: 'list_users' }, + }, + { + id: 'userTypeEq', + title: 'User Type', + type: 'dropdown', + options: [ + { label: 'Any', id: '' }, + { label: 'Team member', id: 'TEAM_MEMBER' }, + { label: 'Partner', id: 'PARTNER' }, + { label: 'Customer', id: 'CUSTOMER' }, + { label: 'External partner', id: 'EXTERNAL_PARTNER' }, + ], + value: () => '', + mode: 'advanced', + condition: { field: 'operation', value: 'list_users' }, + }, + + // List filters — spaces / space documents + { + id: 'spaceNameEq', + title: 'Space Name Equals', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_spaces' }, + }, + { + id: 'spaceNameCn', + title: 'Space Name Contains', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_spaces' }, + }, + { + id: 'spaceDocumentNameEq', + title: 'Document Name Equals', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_space_documents' }, + }, + { + id: 'spaceDocumentNameCn', + title: 'Document Name Contains', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_space_documents' }, + }, + { + id: 'spaceIdEq', + title: 'Space ID Filter', + type: 'short-input', + placeholder: 'e.g. 21', + mode: 'advanced', + condition: { field: 'operation', value: 'list_space_documents' }, + }, + + // List filters — resource allocations + { + id: 'memberIdEq', + title: 'Member ID Filter', + type: 'short-input', + placeholder: 'e.g. 1001', + mode: 'advanced', + condition: { field: 'operation', value: 'list_resource_allocations' }, + }, + { + id: 'placeholderIdEq', + title: 'Placeholder ID Filter', + type: 'short-input', + placeholder: 'e.g. 12', + mode: 'advanced', + condition: { field: 'operation', value: 'list_resource_allocations' }, + }, + + // List filters — invoices + { + id: 'invoiceStatusEq', + title: 'Invoice Status', + type: 'short-input', + placeholder: 'e.g. DRAFT', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + { + id: 'invoiceStatusOneOf', + title: 'Invoice Status One Of', + type: 'short-input', + placeholder: 'e.g. DRAFT,PAID', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + { + id: 'invoiceNumberEq', + title: 'Invoice Number Equals', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + { + id: 'invoiceNumberCn', + title: 'Invoice Number Contains', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + { + id: 'companyIdEq', + title: 'Company ID Filter', + type: 'short-input', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + { + id: 'companyIdOneOf', + title: 'Company ID One Of', + type: 'short-input', + placeholder: 'Comma-separated company IDs', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + { + id: 'dateOfIssueGe', + title: 'Issued On or After', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + { + id: 'dateOfIssueLe', + title: 'Issued On or Before', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + { + id: 'dueDateGe', + title: 'Due On or After', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + { + id: 'dueDateLe', + title: 'Due On or Before', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + { + id: 'amountGe', + title: 'Amount At Least', + type: 'short-input', + placeholder: 'e.g. 1000', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + { + id: 'amountLe', + title: 'Amount At Most', + type: 'short-input', + placeholder: 'e.g. 10000', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + { + id: 'amountOutstandingGt', + title: 'Amount Outstanding Greater Than', + type: 'short-input', + placeholder: 'e.g. 0', + mode: 'advanced', + condition: { field: 'operation', value: 'list_invoices' }, + }, + + // Shared list controls + { + id: 'sortBy', + title: 'Sort By', + type: 'short-input', + placeholder: 'Field to sort by (varies per operation)', + mode: 'advanced', + condition: { field: 'operation', value: SORT_MATCH_OPS }, + }, + { + id: 'sortOrder', + title: 'Sort Order', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'Ascending', id: 'ASC' }, + { label: 'Descending', id: 'DESC' }, + ], + value: () => '', + mode: 'advanced', + condition: { field: 'operation', value: SORT_MATCH_OPS }, + }, + { + id: 'match', + title: 'Filter Match', + type: 'dropdown', + options: [ + { label: 'Default', id: '' }, + { label: 'All filters (AND)', id: 'all' }, + { label: 'Any filter (OR)', id: 'any' }, + ], + value: () => '', + mode: 'advanced', + condition: { field: 'operation', value: SORT_MATCH_OPS }, + }, + { + id: 'includeFields', + title: 'Include Fields', + type: 'short-input', + placeholder: 'Comma-separated extra response fields', + mode: 'advanced', + condition: { field: 'operation', value: INCLUDE_FIELDS_OPS }, + }, + { + id: 'includeAllFields', + title: 'Include All Fields', + type: 'switch', + mode: 'advanced', + condition: { field: 'operation', value: INCLUDE_ALL_FIELDS_OPS }, + }, + { + id: 'pageSize', + title: 'Page Size', + type: 'short-input', + placeholder: 'Results per page (default 100)', + mode: 'advanced', + condition: { field: 'operation', value: PAGINATED_OPS }, + }, + { + id: 'pageToken', + title: 'Page Token', + type: 'short-input', + placeholder: 'Token from a previous response', + mode: 'advanced', + condition: { field: 'operation', value: PAGINATED_OPS }, + }, + + // Credential + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + placeholder: 'Enter your Rocketlane API key', + password: true, + required: true, + }, + ], + + tools: { + access: [ + 'rocketlane_create_project', + 'rocketlane_get_project', + 'rocketlane_list_projects', + 'rocketlane_update_project', + 'rocketlane_archive_project', + 'rocketlane_delete_project', + 'rocketlane_add_project_members', + 'rocketlane_remove_project_members', + 'rocketlane_import_template', + 'rocketlane_list_placeholders', + 'rocketlane_assign_placeholders', + 'rocketlane_unassign_placeholders', + 'rocketlane_create_task', + 'rocketlane_get_task', + 'rocketlane_list_tasks', + 'rocketlane_update_task', + 'rocketlane_delete_task', + 'rocketlane_move_task_to_phase', + 'rocketlane_add_task_assignees', + 'rocketlane_remove_task_assignees', + 'rocketlane_add_task_followers', + 'rocketlane_remove_task_followers', + 'rocketlane_add_task_dependencies', + 'rocketlane_remove_task_dependencies', + 'rocketlane_create_phase', + 'rocketlane_get_phase', + 'rocketlane_list_phases', + 'rocketlane_update_phase', + 'rocketlane_delete_phase', + 'rocketlane_create_field', + 'rocketlane_get_field', + 'rocketlane_list_fields', + 'rocketlane_update_field', + 'rocketlane_delete_field', + 'rocketlane_add_field_option', + 'rocketlane_update_field_option', + 'rocketlane_create_time_entry', + 'rocketlane_get_time_entry', + 'rocketlane_list_time_entries', + 'rocketlane_search_time_entries', + 'rocketlane_update_time_entry', + 'rocketlane_delete_time_entry', + 'rocketlane_list_time_entry_categories', + 'rocketlane_create_time_off', + 'rocketlane_get_time_off', + 'rocketlane_list_time_offs', + 'rocketlane_delete_time_off', + 'rocketlane_get_user', + 'rocketlane_list_users', + 'rocketlane_create_space', + 'rocketlane_get_space', + 'rocketlane_list_spaces', + 'rocketlane_update_space', + 'rocketlane_delete_space', + 'rocketlane_create_space_document', + 'rocketlane_get_space_document', + 'rocketlane_list_space_documents', + 'rocketlane_update_space_document', + 'rocketlane_delete_space_document', + 'rocketlane_list_resource_allocations', + 'rocketlane_get_invoice', + 'rocketlane_list_invoices', + 'rocketlane_get_invoice_line_items', + 'rocketlane_get_invoice_payments', + ], + config: { + tool: (params) => `rocketlane_${params.operation}`, + params: (params) => ({ + ...buildOperationParams(params), + apiKey: params.apiKey, + }), + }, + }, + + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'Rocketlane API key' }, + projectId: { type: 'number', description: 'Project ID' }, + taskId: { type: 'number', description: 'Task ID' }, + phaseId: { type: 'number', description: 'Phase ID' }, + fieldId: { type: 'number', description: 'Custom field ID' }, + spaceId: { type: 'number', description: 'Space ID' }, + spaceDocumentId: { type: 'number', description: 'Space document ID' }, + timeEntryId: { type: 'number', description: 'Time entry ID' }, + timeOffId: { type: 'number', description: 'Time-off ID' }, + invoiceId: { type: 'number', description: 'Invoice ID' }, + placeholderId: { type: 'number', description: 'Placeholder ID' }, + userId: { type: 'number', description: 'User ID' }, + userEmail: { type: 'string', description: 'User email' }, + projectName: { type: 'string', description: 'Project name' }, + customerCompanyName: { type: 'string', description: 'Customer company name (exact match)' }, + ownerUserId: { type: 'number', description: 'Project owner user ID' }, + ownerEmailId: { type: 'string', description: 'Project owner email' }, + visibility: { type: 'string', description: 'Project visibility (EVERYONE or MEMBERS)' }, + customerChampionUserId: { type: 'number', description: 'Customer champion user ID' }, + customerUserIds: { type: 'string', description: 'Comma-separated customer user IDs' }, + customerEmailIds: { type: 'string', description: 'Comma-separated customer emails' }, + fields: { type: 'json', description: 'Custom field assignments (fieldId/fieldValue array)' }, + sources: { type: 'json', description: 'Project template sources (templateId/startDate array)' }, + placeholders: { type: 'json', description: 'Placeholder-to-user mappings' }, + assignProjectOwner: { + type: 'boolean', + description: 'Assign unassigned tasks to the project owner', + }, + autoCreateCompany: { + type: 'boolean', + description: 'Create the customer company if it does not exist', + }, + autoAllocation: { type: 'boolean', description: 'Enable auto allocation for the project' }, + annualizedRecurringRevenue: { type: 'number', description: 'Annualized recurring revenue' }, + projectFee: { type: 'number', description: 'Total project fee' }, + budgetedHours: { type: 'number', description: 'Total budgeted hours' }, + contractType: { + type: 'string', + description: 'Contract type (FIXED_FEE, TIME_AND_MATERIAL, NON_BILLABLE, SUBSCRIPTION)', + }, + fixedFee: { type: 'number', description: 'Fee for fixed-fee contracts' }, + projectBudget: { type: 'number', description: 'Budget for time-and-material contracts' }, + rateCardId: { type: 'number', description: 'Rate card ID for time-and-material contracts' }, + subscriptionFrequency: { + type: 'string', + description: 'Subscription interval (MONTHLY, QUARTERLY, HALF_YEARLY, YEARLY)', + }, + subscriptionStartDate: { type: 'string', description: 'Subscription start date (YYYY-MM-DD)' }, + periodMinutes: { type: 'number', description: 'Budgeted minutes per subscription period' }, + periodBudget: { type: 'number', description: 'Budget per subscription period' }, + noOfPeriods: { type: 'number', description: 'Number of subscription periods' }, + currency: { type: 'string', description: 'Currency ISO code (e.g. USD)' }, + memberUserIds: { type: 'string', description: 'Comma-separated member user IDs' }, + memberEmailIds: { type: 'string', description: 'Comma-separated member emails' }, + dependencyTaskIds: { type: 'string', description: 'Comma-separated dependency task IDs' }, + taskName: { type: 'string', description: 'Task name (also an exact-match list filter)' }, + taskDescription: { type: 'string', description: 'Task description (HTML)' }, + taskPrivateNote: { type: 'string', description: 'Private note for team members (HTML)' }, + taskType: { type: 'string', description: 'Task type (TASK or MILESTONE)' }, + effortInMinutes: { type: 'number', description: 'Expected effort in minutes' }, + progress: { type: 'number', description: 'Task progress (0-100)' }, + atRisk: { type: 'boolean', description: 'Whether the task is at risk' }, + assigneeUserIds: { type: 'string', description: 'Comma-separated assignee user IDs' }, + assigneeEmailIds: { type: 'string', description: 'Comma-separated assignee emails' }, + followerUserIds: { type: 'string', description: 'Comma-separated follower user IDs' }, + followerEmailIds: { type: 'string', description: 'Comma-separated follower emails' }, + parentTaskId: { type: 'number', description: 'Parent task ID' }, + externalReferenceId: { type: 'string', description: 'External system reference ID' }, + phaseName: { type: 'string', description: 'Phase name (also an exact-match list filter)' }, + fieldLabel: { type: 'string', description: 'Custom field label' }, + fieldType: { type: 'string', description: 'Custom field type (also a list filter)' }, + objectType: { + type: 'string', + description: 'Object the field applies to (PROJECT, TASK, USER)', + }, + fieldDescription: { type: 'string', description: 'Custom field description' }, + fieldOptions: { type: 'json', description: 'Options for choice fields (label/color array)' }, + ratingScale: { type: 'string', description: 'Rating scale (THREE, FIVE, SEVEN, TEN)' }, + enabled: { type: 'boolean', description: 'Whether the field is enabled (also a list filter)' }, + private: { type: 'boolean', description: 'Whether the resource is private' }, + optionLabel: { type: 'string', description: 'Field option label' }, + optionColor: { type: 'string', description: 'Field option color' }, + optionValue: { type: 'number', description: 'Identifier of the option to update' }, + templateId: { type: 'number', description: 'Project template ID' }, + prefix: { type: 'string', description: 'Prefix distinguishing a template import' }, + startDate: { type: 'string', description: 'Start date (YYYY-MM-DD)' }, + dueDate: { type: 'string', description: 'Due date (YYYY-MM-DD)' }, + endDate: { type: 'string', description: 'End date (YYYY-MM-DD)' }, + statusValue: { type: 'number', description: 'Numeric status value' }, + date: { type: 'string', description: 'Time entry date (YYYY-MM-DD)' }, + minutes: { type: 'number', description: 'Time entry duration in minutes (1-1440)' }, + timeEntrySource: { + type: 'string', + description: 'What the time entry tracks against (task, project, phase, activity)', + }, + timeEntryTaskId: { type: 'number', description: 'Task ID to track time against' }, + timeEntryProjectId: { type: 'number', description: 'Project ID to track time against' }, + timeEntryPhaseId: { type: 'number', description: 'Project phase ID to track time against' }, + timeEntryActivityName: { type: 'string', description: 'Ad-hoc activity to track time against' }, + activityName: { type: 'string', description: 'New name for an ad-hoc activity' }, + notes: { type: 'string', description: 'Time entry notes' }, + billable: { type: 'boolean', description: 'Whether the time entry is billable' }, + categoryId: { type: 'number', description: 'Time entry category ID' }, + timeOffType: { type: 'string', description: 'Time-off type (FULL_DAY, HALF_DAY, CUSTOM)' }, + durationInMinutes: { type: 'number', description: 'Time-off minutes per day (CUSTOM type)' }, + note: { type: 'string', description: 'Time-off note' }, + notifyProjectOwners: { type: 'boolean', description: 'Notify project owners of the time-off' }, + notifyUserIds: { type: 'string', description: 'Comma-separated user IDs to notify' }, + notifyUserEmails: { type: 'string', description: 'Comma-separated emails to notify' }, + spaceName: { type: 'string', description: 'Space name' }, + spaceDocumentType: { + type: 'string', + description: 'Space document type (ROCKETLANE_DOCUMENT or EMBEDDED_DOCUMENT)', + }, + spaceDocumentName: { type: 'string', description: 'Space document name' }, + url: { type: 'string', description: 'URL to embed in the space document' }, + documentTemplateId: { type: 'number', description: 'Document template ID' }, + projectNameContains: { type: 'string', description: 'Filter: project name contains' }, + projectNameEquals: { type: 'string', description: 'Filter: project name equals' }, + statusEquals: { type: 'string', description: 'Filter: project status equals' }, + statusOneOf: { type: 'string', description: 'Filter: project status one of (comma-separated)' }, + customerIdEquals: { type: 'string', description: 'Filter: customer company ID equals' }, + customerIdOneOf: { + type: 'string', + description: 'Filter: customer company ID one of (comma-separated)', + }, + teamMemberIdEquals: { type: 'string', description: 'Filter: project includes team member ID' }, + contractTypeEquals: { type: 'string', description: 'Filter: project contract type' }, + includeArchived: { type: 'boolean', description: 'Include archived projects' }, + externalReferenceIdEquals: { + type: 'string', + description: 'Filter: external reference ID equals', + }, + startDateAfter: { type: 'string', description: 'Filter: start date after (YYYY-MM-DD)' }, + startDateBefore: { type: 'string', description: 'Filter: start date before (YYYY-MM-DD)' }, + dueDateAfter: { type: 'string', description: 'Filter: due date after (YYYY-MM-DD)' }, + dueDateBefore: { type: 'string', description: 'Filter: due date before (YYYY-MM-DD)' }, + taskNameContains: { type: 'string', description: 'Filter: task name contains' }, + taskStatus: { type: 'string', description: 'Filter: task status value' }, + startDateFrom: { type: 'string', description: 'Filter: start date on or after (YYYY-MM-DD)' }, + startDateTo: { type: 'string', description: 'Filter: start date on or before (YYYY-MM-DD)' }, + dueDateFrom: { type: 'string', description: 'Filter: due date on or after (YYYY-MM-DD)' }, + dueDateTo: { type: 'string', description: 'Filter: due date on or before (YYYY-MM-DD)' }, + includeArchive: { type: 'boolean', description: 'Include archived tasks' }, + dateEq: { type: 'string', description: 'Filter: time entry date equals (YYYY-MM-DD)' }, + dateGe: { type: 'string', description: 'Filter: time entry date on or after (YYYY-MM-DD)' }, + dateLe: { type: 'string', description: 'Filter: time entry date on or before (YYYY-MM-DD)' }, + projectIdEq: { type: 'string', description: 'Filter: project ID equals' }, + taskIdEq: { type: 'number', description: 'Filter: task ID equals' }, + projectPhaseIdEq: { type: 'number', description: 'Filter: project phase ID equals' }, + categoryIdEq: { type: 'number', description: 'Filter: time entry category ID equals' }, + userIdEq: { type: 'string', description: 'Filter: user ID equals' }, + emailIdEq: { type: 'string', description: 'Filter: user email equals' }, + emailIdCn: { type: 'string', description: 'Filter: user email contains' }, + sourceTypeEq: { type: 'string', description: 'Filter: time entry source type' }, + activityNameEq: { type: 'string', description: 'Filter: activity name equals' }, + activityNameCn: { type: 'string', description: 'Filter: activity name contains' }, + approvalStatusEq: { type: 'string', description: 'Filter: time entry approval status' }, + billableEq: { type: 'boolean', description: 'Filter: billable time entries only' }, + includeDeletedEq: { type: 'boolean', description: 'Include deleted time entries' }, + createdAtGt: { type: 'number', description: 'Filter: created after (epoch ms)' }, + createdAtLt: { type: 'number', description: 'Filter: created before (epoch ms)' }, + updatedAtGt: { type: 'number', description: 'Filter: updated after (epoch ms)' }, + updatedAtLt: { type: 'number', description: 'Filter: updated before (epoch ms)' }, + startDateGe: { type: 'string', description: 'Filter: start date on or after (YYYY-MM-DD)' }, + startDateLe: { type: 'string', description: 'Filter: start date on or before (YYYY-MM-DD)' }, + endDateGe: { type: 'string', description: 'Filter: end date on or after (YYYY-MM-DD)' }, + endDateLe: { type: 'string', description: 'Filter: end date on or before (YYYY-MM-DD)' }, + timeOffTypeEq: { type: 'string', description: 'Filter: time-off type' }, + firstNameCn: { type: 'string', description: 'Filter: first name contains' }, + lastNameCn: { type: 'string', description: 'Filter: last name contains' }, + emailEq: { type: 'string', description: 'Filter: email equals' }, + emailCn: { type: 'string', description: 'Filter: email contains' }, + userStatusEq: { type: 'string', description: 'Filter: user status' }, + userTypeEq: { type: 'string', description: 'Filter: user type' }, + spaceNameEq: { type: 'string', description: 'Filter: space name equals' }, + spaceNameCn: { type: 'string', description: 'Filter: space name contains' }, + spaceDocumentNameEq: { type: 'string', description: 'Filter: space document name equals' }, + spaceDocumentNameCn: { type: 'string', description: 'Filter: space document name contains' }, + spaceIdEq: { type: 'number', description: 'Filter: space ID equals' }, + memberIdEq: { type: 'string', description: 'Filter: allocation member ID equals' }, + placeholderIdEq: { type: 'string', description: 'Filter: allocation placeholder ID equals' }, + invoiceStatusEq: { type: 'string', description: 'Filter: invoice status equals' }, + invoiceStatusOneOf: { + type: 'string', + description: 'Filter: invoice status one of (comma-separated)', + }, + invoiceNumberEq: { type: 'string', description: 'Filter: invoice number equals' }, + invoiceNumberCn: { type: 'string', description: 'Filter: invoice number contains' }, + companyIdEq: { type: 'string', description: 'Filter: customer company ID equals' }, + companyIdOneOf: { + type: 'string', + description: 'Filter: customer company ID one of (comma-separated)', + }, + dateOfIssueGe: { type: 'string', description: 'Filter: issued on or after (YYYY-MM-DD)' }, + dateOfIssueLe: { type: 'string', description: 'Filter: issued on or before (YYYY-MM-DD)' }, + dueDateGe: { type: 'string', description: 'Filter: invoice due on or after (YYYY-MM-DD)' }, + dueDateLe: { type: 'string', description: 'Filter: invoice due on or before (YYYY-MM-DD)' }, + amountGe: { type: 'number', description: 'Filter: invoice amount at least' }, + amountLe: { type: 'number', description: 'Filter: invoice amount at most' }, + amountOutstandingGt: { type: 'number', description: 'Filter: amount outstanding greater than' }, + sortBy: { type: 'string', description: 'Field to sort results by' }, + sortOrder: { type: 'string', description: 'Sort order (ASC or DESC)' }, + match: { type: 'string', description: 'Combine filters with all (AND) or any (OR)' }, + includeFields: { type: 'string', description: 'Comma-separated extra response fields' }, + includeAllFields: { type: 'boolean', description: 'Return all fields in the response' }, + pageSize: { type: 'number', description: 'Results per page' }, + pageToken: { type: 'string', description: 'Page token from a previous response' }, + }, + + outputs: { + project: { type: 'json', description: 'A single project' }, + projects: { type: 'json', description: 'List of projects' }, + task: { type: 'json', description: 'A single task' }, + tasks: { type: 'json', description: 'List of tasks' }, + phase: { type: 'json', description: 'A single phase' }, + phases: { type: 'json', description: 'List of phases' }, + field: { type: 'json', description: 'A single custom field' }, + fields: { type: 'json', description: 'List of custom fields' }, + option: { type: 'json', description: 'A field option' }, + placeholders: { type: 'json', description: 'Project placeholders or placeholder mappings' }, + timeEntry: { type: 'json', description: 'A single time entry' }, + timeEntries: { type: 'json', description: 'List of time entries' }, + categories: { type: 'json', description: 'List of time entry categories' }, + timeOff: { type: 'json', description: 'A single time-off' }, + timeOffs: { type: 'json', description: 'List of time-offs' }, + user: { type: 'json', description: 'A single user' }, + users: { type: 'json', description: 'List of users' }, + space: { type: 'json', description: 'A single space' }, + spaces: { type: 'json', description: 'List of spaces' }, + spaceDocument: { type: 'json', description: 'A single space document' }, + spaceDocuments: { type: 'json', description: 'List of space documents' }, + allocations: { type: 'json', description: 'List of resource allocations' }, + invoice: { type: 'json', description: 'A single invoice' }, + invoices: { type: 'json', description: 'List of invoices' }, + lineItems: { type: 'json', description: 'Invoice line items' }, + payments: { type: 'json', description: 'Invoice payments' }, + pagination: { type: 'json', description: 'Pagination details for list results' }, + deleted: { type: 'boolean', description: 'Whether the resource was deleted' }, + archived: { type: 'boolean', description: 'Whether the project was archived' }, + projectId: { type: 'number', description: 'ID of the archived or deleted project' }, + taskId: { type: 'number', description: 'ID of the deleted task' }, + phaseId: { type: 'number', description: 'ID of the deleted phase' }, + fieldId: { type: 'number', description: 'ID of the deleted field' }, + spaceId: { type: 'number', description: 'ID of the deleted space' }, + spaceDocumentId: { type: 'number', description: 'ID of the deleted space document' }, + timeEntryId: { type: 'number', description: 'ID of the deleted time entry' }, + timeOffId: { type: 'number', description: 'ID of the deleted time-off' }, + }, +} + +export const RocketlaneBlockMeta = { + tags: ['project-management', 'automation'], + url: 'https://www.rocketlane.com', + templates: [ + { + icon: RocketlaneIcon, + title: 'Client onboarding kickoff', + prompt: + 'Build a workflow that creates a Rocketlane project from an onboarding template for a new customer, assigns the implementation manager placeholder, adds the account team as members, and posts a kickoff summary with the project details to Slack.', + modules: ['agent', 'workflows'], + category: 'operations', + tags: ['project-management', 'automation'], + alsoIntegrations: ['slack'], + }, + { + icon: RocketlaneIcon, + title: 'Weekly project status digest', + prompt: + 'Create a scheduled weekly workflow that lists active Rocketlane projects with their status and due dates, summarizes progress and anything overdue per customer, and emails the digest to the delivery team every Monday morning.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['project-management', 'reporting'], + alsoIntegrations: ['gmail'], + }, + { + icon: RocketlaneIcon, + title: 'Overdue task escalation', + prompt: + 'Build a scheduled daily workflow that lists Rocketlane tasks with a due date before today that are not complete, marks them at risk, and posts an escalation to Slack tagging each task name, project, and assignees.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['project-management', 'automation'], + alsoIntegrations: ['slack'], + }, + { + icon: RocketlaneIcon, + title: 'Time tracking rollup', + prompt: + 'Create a scheduled weekly workflow that searches Rocketlane time entries for the past week, totals billable and non-billable minutes per project, and posts a formatted utilization rollup to Slack.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'operations', + tags: ['time-tracking', 'reporting'], + alsoIntegrations: ['slack'], + }, + { + icon: RocketlaneIcon, + title: 'Invoice payment monitor', + prompt: + 'Build a scheduled workflow that lists Rocketlane invoices with an outstanding amount greater than zero and a due date in the past, pulls their payments, and emails the finance team a list of overdue invoices with amounts outstanding.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'sales', + tags: ['invoicing', 'automation'], + alsoIntegrations: ['gmail'], + }, + { + icon: RocketlaneIcon, + title: 'Resource allocation report', + prompt: + 'Create a workflow that lists Rocketlane resource allocations for the next two weeks, writes each member, project, and allocation range into a table, and flags team members who appear in overlapping allocations.', + modules: ['tables', 'agent', 'workflows'], + category: 'operations', + tags: ['resource-management', 'reporting'], + }, + { + icon: RocketlaneIcon, + title: 'Timesheet reminder', + prompt: + 'Build a scheduled Friday workflow that lists active Rocketlane team members, searches this week’s time entries per user, and sends a Slack reminder to anyone who has logged less than their expected hours.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'productivity', + tags: ['time-tracking', 'automation'], + alsoIntegrations: ['slack'], + }, + { + icon: RocketlaneIcon, + title: 'New project scaffolding', + prompt: + 'Create a workflow that scaffolds a delivery project in Rocketlane: create the project, add Discovery, Implementation, and Go-live phases with dates, create kickoff tasks in each phase with assignees, and set up a shared space with a kickoff document.', + modules: ['agent', 'workflows'], + category: 'operations', + tags: ['project-management', 'automation'], + }, + ], + skills: [ + { + name: 'kickoff-onboarding-project', + description: + 'Spin up a Rocketlane onboarding project from a template when a deal closes — create the project, apply the template, assign placeholders, and add the account team. Use for CRM-triggered kickoffs.', + content: + '# Kickoff Onboarding Project\n\nLaunch a client onboarding project in Rocketlane from a closed-won deal or intake request.\n\n## Steps\n1. Extract the customer company name, project name, owner, start date, and any template to apply from the request.\n2. Create the project with `create_project`, passing the customer company (enable auto-create if the company may not exist yet) and template sources so phases and tasks are scaffolded automatically. For an existing project, apply a plan with `import_template` instead.\n3. List the project placeholders with `list_placeholders` and map each one to a real person using `assign_placeholders` (by user ID or email).\n4. Add the delivery and account team with `add_project_members`, resolving people via `list_users` when only names are given.\n\n## Output\nReport the created project ID and name, which template was applied, placeholder assignments made, and the members added.', + }, + { + name: 'project-status-report', + description: + 'Compile a delivery status report across Rocketlane projects — phases, task progress, overdue work, and at-risk items. Use for weekly digests and exec updates.', + content: + '# Project Status Report\n\nSummarize the health of one or more Rocketlane projects.\n\n## Steps\n1. Resolve the scope: a single project via `get_project`, or all active projects via `list_projects` (filter by status, customer, or due-date window; page with the returned page token until done or a sensible cap).\n2. For each project, pull `list_phases` for stage-level progress and `list_tasks` for task status, using due-date filters to isolate overdue or due-soon work.\n3. Group findings per project: current phase, tasks completed vs open, overdue tasks with assignees, and anything flagged at risk.\n\n## Output\nA per-project status summary — phase, progress counts, overdue items called out by name and owner — with an overall headline of projects on track vs slipping.', + }, + { + name: 'escalate-overdue-tasks', + description: + 'Find overdue or slipping Rocketlane tasks, mark them at risk, and loop in the right people. Use for daily delivery hygiene automations.', + content: + '# Escalate Overdue Tasks\n\nCatch slipping work before it derails a project.\n\n## Steps\n1. Query `list_tasks` with a due-date-to filter of today (optionally scoped to a project or phase) to find tasks past their due date that are not complete.\n2. For each overdue task, set the at-risk flag with `update_task` so it is visible on the project plan.\n3. Add the project owner or delivery lead as a follower with `add_task_followers` so they get notified of further changes.\n4. Reassign stalled tasks when instructed, using `add_task_assignees` / `remove_task_assignees`.\n\n## Output\nList each escalated task with its project, assignees, days overdue, and the action taken (flagged, follower added, reassigned).', + }, + { + name: 'log-time-entry', + description: + 'Record time in Rocketlane against a task, phase, project, or ad-hoc activity with the right category and billable flag. Use to log work from chat or other systems.', + content: + '# Log Time Entry\n\nCreate an accurate Rocketlane time entry from a natural-language description of work done.\n\n## Steps\n1. Extract the date, duration in minutes, what the time was spent on, and whether it is billable.\n2. Resolve the target: find the task with `list_tasks` (or the project/phase with `list_projects`/`list_phases`); for non-project work use an ad-hoc activity name instead.\n3. Match a category with `list_time_entry_categories` when the team uses categories.\n4. Create the entry with `create_time_entry` against exactly one source (task, project, phase, or activity), including notes and the billable flag. Fix mistakes with `update_time_entry`.\n\n## Output\nConfirm the logged entry: date, minutes, target, category, and billable state.', + }, + { + name: 'utilization-rollup', + description: + 'Roll up Rocketlane time entries, allocations, and time-offs into a utilization picture per person or project. Use for capacity planning and weekly utilization reviews.', + content: + '# Utilization Rollup\n\nBuild a utilization and capacity snapshot for a date range.\n\n## Steps\n1. Pull logged time for the period with `search_time_entries` (filter by date range; page through results with the page token, keeping a sensible page cap).\n2. Total billable vs non-billable minutes per user and per project.\n3. Fetch planned capacity with `list_resource_allocations` for the same window and subtract `list_time_offs` to get true availability.\n4. Compare logged time against allocation to flag under-logged members and over-allocated ones.\n\n## Output\nA table-style rollup per person: allocated minutes, logged billable/non-billable minutes, time off, and a utilization percentage, plus any members flagged for follow-up.', + }, + { + name: 'overdue-invoice-followup', + description: + 'Track Rocketlane invoices with outstanding balances past their due date and assemble follow-up details. Use for finance and collections automations.', + content: + '# Overdue Invoice Follow-up\n\nFind unpaid invoices and prepare a collections summary.\n\n## Steps\n1. Query `list_invoices` filtered to invoices with an outstanding amount greater than zero and a due date in the past.\n2. For each hit, fetch `get_invoice_payments` to confirm what has already been paid and `get_invoice_line_items` when the follow-up needs a breakdown.\n3. Sort by amount outstanding and days overdue so the largest, oldest balances lead.\n\n## Output\nA prioritized list of overdue invoices: invoice number, customer company, amount outstanding, days overdue, and payments received to date.', + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/registry-maps.ts b/apps/sim/blocks/registry-maps.ts index 109bc55b652..65a48b4c310 100644 --- a/apps/sim/blocks/registry-maps.ts +++ b/apps/sim/blocks/registry-maps.ts @@ -254,6 +254,7 @@ import { ResendBlock, ResendBlockMeta } from '@/blocks/blocks/resend' import { ResponseBlock } from '@/blocks/blocks/response' import { RevenueCatBlock, RevenueCatBlockMeta } from '@/blocks/blocks/revenuecat' import { RipplingBlock, RipplingBlockMeta } from '@/blocks/blocks/rippling' +import { RocketlaneBlock, RocketlaneBlockMeta } from '@/blocks/blocks/rocketlane' import { RootlyBlock, RootlyBlockMeta } from '@/blocks/blocks/rootly' import { RouterBlock, RouterV2Block } from '@/blocks/blocks/router' import { RssBlock, RssBlockMeta } from '@/blocks/blocks/rss' @@ -566,6 +567,7 @@ export const BLOCK_REGISTRY: Record = { response: ResponseBlock, revenuecat: RevenueCatBlock, rippling: RipplingBlock, + rocketlane: RocketlaneBlock, rootly: RootlyBlock, router: RouterBlock, router_v2: RouterV2Block, @@ -853,6 +855,7 @@ export const BLOCK_META_REGISTRY: Record = { resend: ResendBlockMeta, revenuecat: RevenueCatBlockMeta, rippling: RipplingBlockMeta, + rocketlane: RocketlaneBlockMeta, rootly: RootlyBlockMeta, rss: RssBlockMeta, s3: S3BlockMeta, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index bae77686e5f..1ebe7b408bc 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -8710,3 +8710,24 @@ export function JupyterIcon(props: SVGProps) { ) } + +export function RocketlaneIcon(props: SVGProps) { + return ( + + + + + + + ) +} diff --git a/apps/sim/lib/integrations/icon-mapping.ts b/apps/sim/lib/integrations/icon-mapping.ts index 185e321db5c..1ab1b49b330 100644 --- a/apps/sim/lib/integrations/icon-mapping.ts +++ b/apps/sim/lib/integrations/icon-mapping.ts @@ -181,6 +181,7 @@ import { ResendIcon, RevenueCatIcon, RipplingIcon, + RocketlaneIcon, RootlyIcon, S3Icon, SalesforceIcon, @@ -421,6 +422,7 @@ export const blockTypeToIconMap: Record = { resend: ResendIcon, revenuecat: RevenueCatIcon, rippling: RipplingIcon, + rocketlane: RocketlaneIcon, rootly: RootlyIcon, s3: S3Icon, salesforce: SalesforceIcon, diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index c8b0ad7fe1e..0b597e2e7e2 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -1,5 +1,5 @@ { - "updatedAt": "2026-07-13", + "updatedAt": "2026-07-16", "integrations": [ { "type": "onepassword", @@ -15089,6 +15089,281 @@ "integrationType": "hr", "tags": ["hiring"] }, + { + "type": "rocketlane", + "slug": "rocketlane", + "name": "Rocketlane", + "description": "Manage client onboarding projects, tasks, time tracking, and invoices", + "longDescription": "Integrate Rocketlane into your workflow. Rocketlane is a professional-services automation platform for client onboarding and project delivery. Create and manage projects, tasks, phases, custom fields, time entries, time-offs, spaces, documents, resource allocations, and invoices.", + "bgColor": "#000000", + "iconName": "RocketlaneIcon", + "docsUrl": "https://docs.sim.ai/integrations/rocketlane", + "operations": [ + { + "name": "Create Project", + "description": "Create a new Rocketlane project with a customer, owner, dates, team members, templates, financials, and custom fields" + }, + { + "name": "Get Project", + "description": "Retrieve a Rocketlane project by its unique identifier" + }, + { + "name": "List Projects", + "description": "List Rocketlane projects with optional filters, sorting, and token-based pagination" + }, + { + "name": "Update Project", + "description": "Update a Rocketlane project by ID, including name, dates, visibility, owner, status, and custom fields" + }, + { + "name": "Archive Project", + "description": "Archive a Rocketlane project by ID, making it dormant while preserving its details and history" + }, + { + "name": "Delete Project", + "description": "Permanently delete a Rocketlane project by ID (irreversible; only Admins, Super Users, and Project Owners can delete)" + }, + { + "name": "Add Project Members", + "description": "Add team members and customer stakeholders to a Rocketlane project" + }, + { + "name": "Remove Project Members", + "description": "Remove team members from a Rocketlane project" + }, + { + "name": "Import Template", + "description": "Import a project template into an existing Rocketlane project" + }, + { + "name": "List Placeholders", + "description": "List the placeholders of a Rocketlane project" + }, + { + "name": "Assign Placeholder", + "description": "Assign a placeholder in a Rocketlane project to a user" + }, + { + "name": "Unassign Placeholder", + "description": "Unassign a placeholder from its user in a Rocketlane project" + }, + { + "name": "Create Task", + "description": "Create a new task in a Rocketlane project" + }, + { + "name": "Get Task", + "description": "Retrieve detailed information about a Rocketlane task by its unique identifier" + }, + { + "name": "List Tasks", + "description": "Retrieve all Rocketlane tasks with optional filters, sorting, and pagination" + }, + { + "name": "Update Task", + "description": "Update the properties of an existing Rocketlane task by its unique identifier" + }, + { + "name": "Delete Task", + "description": "Permanently delete a Rocketlane task by its unique identifier" + }, + { + "name": "Move Task to Phase", + "description": "Move a Rocketlane task to a given phase, associating the task with that phase" + }, + { + "name": "Add Task Assignees", + "description": "Add members as assignees to a Rocketlane task" + }, + { + "name": "Remove Task Assignees", + "description": "Remove assignees from a Rocketlane task" + }, + { + "name": "Add Task Followers", + "description": "Add members as followers to a Rocketlane task" + }, + { + "name": "Remove Task Followers", + "description": "Remove followers from a Rocketlane task" + }, + { + "name": "Add Task Dependencies", + "description": "Add finish-to-start dependencies between a Rocketlane task and other tasks" + }, + { + "name": "Remove Task Dependencies", + "description": "Remove dependencies from a Rocketlane task" + }, + { + "name": "Create Phase", + "description": "Create a new phase in a Rocketlane project" + }, + { + "name": "Get Phase", + "description": "Retrieve a single Rocketlane phase by ID" + }, + { + "name": "List Phases", + "description": "List phases of a Rocketlane project, with optional filters, sorting, and pagination" + }, + { + "name": "Update Phase", + "description": "Update the name, dates, status, or privacy of an existing Rocketlane phase" + }, + { + "name": "Delete Phase", + "description": "Permanently delete a Rocketlane phase by ID" + }, + { + "name": "Create Field", + "description": "Create a custom field in your Rocketlane account, with options for SINGLE_CHOICE and MULTIPLE_CHOICE field types" + }, + { + "name": "Get Field", + "description": "Retrieve a single Rocketlane field by ID" + }, + { + "name": "List Fields", + "description": "List fields in your Rocketlane account, with optional filters, sorting, and pagination" + }, + { + "name": "Update Field", + "description": "Update the label, description, enabled state, or privacy of an existing Rocketlane field" + }, + { + "name": "Delete Field", + "description": "Permanently delete a Rocketlane field by ID" + }, + { + "name": "Add Field Option", + "description": "Add a new option to an existing SINGLE_CHOICE or MULTIPLE_CHOICE Rocketlane field" + }, + { + "name": "Update Field Option", + "description": "Update the label or color of an existing option on a SINGLE_CHOICE or MULTIPLE_CHOICE Rocketlane field" + }, + { + "name": "Create Time Entry", + "description": "Create a time entry in Rocketlane against an adhoc activity, task, project phase, or project" + }, + { + "name": "Get Time Entry", + "description": "Get a single Rocketlane time entry by ID" + }, + { + "name": "List Time Entries", + "description": "List Rocketlane time entries with optional filters, sorting, and cursor-based pagination" + }, + { + "name": "Search Time Entries", + "description": "Search Rocketlane time entries with filters, sorting, and pagination (deprecated by Rocketlane in favor of listing time entries with filters)" + }, + { + "name": "Update Time Entry", + "description": "Update a Rocketlane time entry by ID. The activityName, notes, billable, and minutes properties can be updated" + }, + { + "name": "Delete Time Entry", + "description": "Permanently delete a Rocketlane time entry by ID" + }, + { + "name": "List Time Entry Categories", + "description": "List the time entry categories configured in Rocketlane" + }, + { + "name": "Create Time-Off", + "description": "Create a time-off for a team member in Rocketlane. Holidays and weekends within the date range are automatically excluded from the duration." + }, + { + "name": "Get Time-Off", + "description": "Retrieve a Rocketlane time-off by its ID" + }, + { + "name": "List Time-Offs", + "description": "List time-offs in Rocketlane with optional date, type, and user filters, sorting, and pagination" + }, + { + "name": "Delete Time-Off", + "description": "Permanently delete a Rocketlane time-off by its ID" + }, + { + "name": "Get User", + "description": "Retrieve a Rocketlane user by their ID" + }, + { + "name": "List Users", + "description": "List users in your Rocketlane account, with optional filters, sorting, and pagination" + }, + { + "name": "Create Space", + "description": "Create a new space in a Rocketlane project" + }, + { + "name": "Get Space", + "description": "Retrieve a Rocketlane space by its ID" + }, + { + "name": "List Spaces", + "description": "List spaces in a Rocketlane project, with optional filters, sorting, and pagination" + }, + { + "name": "Update Space", + "description": "Update a Rocketlane space by its ID" + }, + { + "name": "Delete Space", + "description": "Permanently delete a Rocketlane space by its ID" + }, + { + "name": "Create Space Document", + "description": "Create a new space document in a Rocketlane space" + }, + { + "name": "Get Space Document", + "description": "Retrieve a Rocketlane space document by its ID" + }, + { + "name": "List Space Documents", + "description": "List space documents in a Rocketlane project, with optional filters, sorting, and pagination" + }, + { + "name": "Update Space Document", + "description": "Update a Rocketlane space document by its ID" + }, + { + "name": "Delete Space Document", + "description": "Permanently delete a Rocketlane space document by its ID" + }, + { + "name": "List Resource Allocations", + "description": "List resource allocations in Rocketlane within a date range, with optional member, project, and placeholder filters, sorting, and pagination" + }, + { + "name": "Get Invoice", + "description": "Retrieve a Rocketlane invoice by its ID" + }, + { + "name": "List Invoices", + "description": "Search invoices in Rocketlane with date, amount, company, invoice-number, and status filters, sorting, and pagination" + }, + { + "name": "Get Invoice Line Items", + "description": "List line items of a Rocketlane invoice" + }, + { + "name": "Get Invoice Payments", + "description": "List payments recorded against a Rocketlane invoice" + } + ], + "operationCount": 64, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationType": "productivity", + "tags": ["project-management", "automation"] + }, { "type": "rootly", "slug": "rootly", diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 3629be229e3..8b6cf27f996 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -3164,6 +3164,72 @@ import { ripplingUpdateTitleTool, ripplingUpdateWorkLocationTool, } from '@/tools/rippling' +import { + rocketlaneAddFieldOptionTool, + rocketlaneAddProjectMembersTool, + rocketlaneAddTaskAssigneesTool, + rocketlaneAddTaskDependenciesTool, + rocketlaneAddTaskFollowersTool, + rocketlaneArchiveProjectTool, + rocketlaneAssignPlaceholdersTool, + rocketlaneCreateFieldTool, + rocketlaneCreatePhaseTool, + rocketlaneCreateProjectTool, + rocketlaneCreateSpaceDocumentTool, + rocketlaneCreateSpaceTool, + rocketlaneCreateTaskTool, + rocketlaneCreateTimeEntryTool, + rocketlaneCreateTimeOffTool, + rocketlaneDeleteFieldTool, + rocketlaneDeletePhaseTool, + rocketlaneDeleteProjectTool, + rocketlaneDeleteSpaceDocumentTool, + rocketlaneDeleteSpaceTool, + rocketlaneDeleteTaskTool, + rocketlaneDeleteTimeEntryTool, + rocketlaneDeleteTimeOffTool, + rocketlaneGetFieldTool, + rocketlaneGetInvoiceLineItemsTool, + rocketlaneGetInvoicePaymentsTool, + rocketlaneGetInvoiceTool, + rocketlaneGetPhaseTool, + rocketlaneGetProjectTool, + rocketlaneGetSpaceDocumentTool, + rocketlaneGetSpaceTool, + rocketlaneGetTaskTool, + rocketlaneGetTimeEntryTool, + rocketlaneGetTimeOffTool, + rocketlaneGetUserTool, + rocketlaneImportTemplateTool, + rocketlaneListFieldsTool, + rocketlaneListInvoicesTool, + rocketlaneListPhasesTool, + rocketlaneListPlaceholdersTool, + rocketlaneListProjectsTool, + rocketlaneListResourceAllocationsTool, + rocketlaneListSpaceDocumentsTool, + rocketlaneListSpacesTool, + rocketlaneListTasksTool, + rocketlaneListTimeEntriesTool, + rocketlaneListTimeEntryCategoriesTool, + rocketlaneListTimeOffsTool, + rocketlaneListUsersTool, + rocketlaneMoveTaskToPhaseTool, + rocketlaneRemoveProjectMembersTool, + rocketlaneRemoveTaskAssigneesTool, + rocketlaneRemoveTaskDependenciesTool, + rocketlaneRemoveTaskFollowersTool, + rocketlaneSearchTimeEntriesTool, + rocketlaneUnassignPlaceholdersTool, + rocketlaneUpdateFieldOptionTool, + rocketlaneUpdateFieldTool, + rocketlaneUpdatePhaseTool, + rocketlaneUpdateProjectTool, + rocketlaneUpdateSpaceDocumentTool, + rocketlaneUpdateSpaceTool, + rocketlaneUpdateTaskTool, + rocketlaneUpdateTimeEntryTool, +} from '@/tools/rocketlane' import { rootlyAcknowledgeAlertTool, rootlyAddIncidentEventTool, @@ -6569,6 +6635,70 @@ export const tools: Record = { rippling_update_supergroup_inclusion_members: ripplingUpdateSupergroupInclusionMembersTool, rippling_update_title: ripplingUpdateTitleTool, rippling_update_work_location: ripplingUpdateWorkLocationTool, + rocketlane_add_field_option: rocketlaneAddFieldOptionTool, + rocketlane_add_project_members: rocketlaneAddProjectMembersTool, + rocketlane_add_task_assignees: rocketlaneAddTaskAssigneesTool, + rocketlane_add_task_dependencies: rocketlaneAddTaskDependenciesTool, + rocketlane_add_task_followers: rocketlaneAddTaskFollowersTool, + rocketlane_archive_project: rocketlaneArchiveProjectTool, + rocketlane_assign_placeholders: rocketlaneAssignPlaceholdersTool, + rocketlane_create_field: rocketlaneCreateFieldTool, + rocketlane_create_phase: rocketlaneCreatePhaseTool, + rocketlane_create_project: rocketlaneCreateProjectTool, + rocketlane_create_space: rocketlaneCreateSpaceTool, + rocketlane_create_space_document: rocketlaneCreateSpaceDocumentTool, + rocketlane_create_task: rocketlaneCreateTaskTool, + rocketlane_create_time_entry: rocketlaneCreateTimeEntryTool, + rocketlane_create_time_off: rocketlaneCreateTimeOffTool, + rocketlane_delete_field: rocketlaneDeleteFieldTool, + rocketlane_delete_phase: rocketlaneDeletePhaseTool, + rocketlane_delete_project: rocketlaneDeleteProjectTool, + rocketlane_delete_space: rocketlaneDeleteSpaceTool, + rocketlane_delete_space_document: rocketlaneDeleteSpaceDocumentTool, + rocketlane_delete_task: rocketlaneDeleteTaskTool, + rocketlane_delete_time_entry: rocketlaneDeleteTimeEntryTool, + rocketlane_delete_time_off: rocketlaneDeleteTimeOffTool, + rocketlane_get_field: rocketlaneGetFieldTool, + rocketlane_get_invoice: rocketlaneGetInvoiceTool, + rocketlane_get_invoice_line_items: rocketlaneGetInvoiceLineItemsTool, + rocketlane_get_invoice_payments: rocketlaneGetInvoicePaymentsTool, + rocketlane_get_phase: rocketlaneGetPhaseTool, + rocketlane_get_project: rocketlaneGetProjectTool, + rocketlane_get_space: rocketlaneGetSpaceTool, + rocketlane_get_space_document: rocketlaneGetSpaceDocumentTool, + rocketlane_get_task: rocketlaneGetTaskTool, + rocketlane_get_time_entry: rocketlaneGetTimeEntryTool, + rocketlane_get_time_off: rocketlaneGetTimeOffTool, + rocketlane_get_user: rocketlaneGetUserTool, + rocketlane_import_template: rocketlaneImportTemplateTool, + rocketlane_list_fields: rocketlaneListFieldsTool, + rocketlane_list_invoices: rocketlaneListInvoicesTool, + rocketlane_list_phases: rocketlaneListPhasesTool, + rocketlane_list_placeholders: rocketlaneListPlaceholdersTool, + rocketlane_list_projects: rocketlaneListProjectsTool, + rocketlane_list_resource_allocations: rocketlaneListResourceAllocationsTool, + rocketlane_list_space_documents: rocketlaneListSpaceDocumentsTool, + rocketlane_list_spaces: rocketlaneListSpacesTool, + rocketlane_list_tasks: rocketlaneListTasksTool, + rocketlane_list_time_entries: rocketlaneListTimeEntriesTool, + rocketlane_list_time_entry_categories: rocketlaneListTimeEntryCategoriesTool, + rocketlane_list_time_offs: rocketlaneListTimeOffsTool, + rocketlane_list_users: rocketlaneListUsersTool, + rocketlane_move_task_to_phase: rocketlaneMoveTaskToPhaseTool, + rocketlane_remove_project_members: rocketlaneRemoveProjectMembersTool, + rocketlane_remove_task_assignees: rocketlaneRemoveTaskAssigneesTool, + rocketlane_remove_task_dependencies: rocketlaneRemoveTaskDependenciesTool, + rocketlane_remove_task_followers: rocketlaneRemoveTaskFollowersTool, + rocketlane_search_time_entries: rocketlaneSearchTimeEntriesTool, + rocketlane_unassign_placeholders: rocketlaneUnassignPlaceholdersTool, + rocketlane_update_field: rocketlaneUpdateFieldTool, + rocketlane_update_field_option: rocketlaneUpdateFieldOptionTool, + rocketlane_update_phase: rocketlaneUpdatePhaseTool, + rocketlane_update_project: rocketlaneUpdateProjectTool, + rocketlane_update_space: rocketlaneUpdateSpaceTool, + rocketlane_update_space_document: rocketlaneUpdateSpaceDocumentTool, + rocketlane_update_task: rocketlaneUpdateTaskTool, + rocketlane_update_time_entry: rocketlaneUpdateTimeEntryTool, rootly_acknowledge_alert: rootlyAcknowledgeAlertTool, rootly_add_incident_event: rootlyAddIncidentEventTool, rootly_add_subscribers: rootlyAddSubscribersTool, diff --git a/apps/sim/tools/rocketlane/add_field_option.ts b/apps/sim/tools/rocketlane/add_field_option.ts new file mode 100644 index 00000000000..a8ff193246a --- /dev/null +++ b/apps/sim/tools/rocketlane/add_field_option.ts @@ -0,0 +1,78 @@ +import { + FIELD_OPTION_OUTPUT_PROPERTIES, + mapFieldOption, + ROCKETLANE_API_BASE, + type RocketlaneAddFieldOptionParams, + type RocketlaneFieldOptionResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneAddFieldOptionTool: ToolConfig< + RocketlaneAddFieldOptionParams, + RocketlaneFieldOptionResponse +> = { + id: 'rocketlane_add_field_option', + name: 'Rocketlane Add Field Option', + description: 'Add a new option to an existing SINGLE_CHOICE or MULTIPLE_CHOICE Rocketlane field', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + fieldId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the field to add the option to', + }, + optionLabel: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Display label of the new option', + }, + optionColor: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Color of the new option (RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY)', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/fields/${encodeURIComponent(String(params.fieldId))}/add-option`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + optionLabel: params.optionLabel, + optionColor: params.optionColor, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { option: mapFieldOption(data) }, + } + }, + + outputs: { + option: { + type: 'object', + description: 'The created field option', + properties: FIELD_OPTION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/add_project_members.ts b/apps/sim/tools/rocketlane/add_project_members.ts new file mode 100644 index 00000000000..bf006e1af16 --- /dev/null +++ b/apps/sim/tools/rocketlane/add_project_members.ts @@ -0,0 +1,103 @@ +import { + mapProject, + PROJECT_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneAddProjectMembersParams, + type RocketlaneProjectResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneAddProjectMembersTool: ToolConfig< + RocketlaneAddProjectMembersParams, + RocketlaneProjectResponse +> = { + id: 'rocketlane_add_project_members', + name: 'Rocketlane Add Project Members', + description: 'Add team members and customer stakeholders to a Rocketlane project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the project', + }, + memberUserIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'User IDs of team members from your organization to add', + items: { type: 'number', description: 'User ID of a team member' }, + }, + memberEmailIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Emails of team members from your organization to add', + items: { type: 'string', description: 'Email of a team member' }, + }, + customerUserIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'User IDs of customer stakeholders to add', + items: { type: 'number', description: 'User ID of a customer' }, + }, + customerEmailIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Emails of customer stakeholders to add', + items: { type: 'string', description: 'Email of a customer' }, + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/projects/${encodeURIComponent(String(params.projectId))}/add-members`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const body: Record = {} + const members = [ + ...(params.memberUserIds ?? []).map((userId) => ({ userId })), + ...(params.memberEmailIds ?? []).map((emailId) => ({ emailId })), + ] + if (members.length > 0) body.members = members + const customers = [ + ...(params.customerUserIds ?? []).map((userId) => ({ userId })), + ...(params.customerEmailIds ?? []).map((emailId) => ({ emailId })), + ] + if (customers.length > 0) body.customers = customers + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { project: mapProject(data) }, + } + }, + + outputs: { + project: { + type: 'object', + description: 'The project with its updated team members', + properties: PROJECT_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/add_task_assignees.ts b/apps/sim/tools/rocketlane/add_task_assignees.ts new file mode 100644 index 00000000000..91aeb777bfe --- /dev/null +++ b/apps/sim/tools/rocketlane/add_task_assignees.ts @@ -0,0 +1,79 @@ +import { + buildTaskMembers, + mapTask, + ROCKETLANE_API_BASE, + type RocketlaneTaskAssigneesParams, + type RocketlaneTaskResponse, + rocketlaneError, + rocketlaneHeaders, + TASK_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneAddTaskAssigneesTool: ToolConfig< + RocketlaneTaskAssigneesParams, + RocketlaneTaskResponse +> = { + id: 'rocketlane_add_task_assignees', + name: 'Rocketlane Add Task Assignees', + description: 'Add members as assignees to a Rocketlane task', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + taskId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the task to add assignees to', + }, + memberUserIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'User IDs of members to add as assignees', + items: { type: 'number' }, + }, + memberEmailIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Email addresses of members to add as assignees', + items: { type: 'string' }, + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/tasks/${encodeURIComponent(String(params.taskId))}/add-assignees`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + members: buildTaskMembers(params.memberUserIds, params.memberEmailIds), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { task: mapTask(data) }, + } + }, + + outputs: { + task: { + type: 'object', + description: 'The task with its updated assignees', + properties: TASK_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/add_task_dependencies.ts b/apps/sim/tools/rocketlane/add_task_dependencies.ts new file mode 100644 index 00000000000..4c90db40751 --- /dev/null +++ b/apps/sim/tools/rocketlane/add_task_dependencies.ts @@ -0,0 +1,71 @@ +import { + mapTask, + ROCKETLANE_API_BASE, + type RocketlaneTaskDependenciesParams, + type RocketlaneTaskResponse, + rocketlaneError, + rocketlaneHeaders, + TASK_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneAddTaskDependenciesTool: ToolConfig< + RocketlaneTaskDependenciesParams, + RocketlaneTaskResponse +> = { + id: 'rocketlane_add_task_dependencies', + name: 'Rocketlane Add Task Dependencies', + description: 'Add finish-to-start dependencies between a Rocketlane task and other tasks', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + taskId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the task to add dependencies to', + }, + dependencyTaskIds: { + type: 'array', + required: true, + visibility: 'user-or-llm', + description: 'Task IDs the task should depend on', + items: { type: 'number' }, + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/tasks/${encodeURIComponent(String(params.taskId))}/add-dependencies`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + dependencies: params.dependencyTaskIds.map((taskId) => ({ taskId })), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { task: mapTask(data) }, + } + }, + + outputs: { + task: { + type: 'object', + description: 'The task with its updated dependencies', + properties: TASK_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/add_task_followers.ts b/apps/sim/tools/rocketlane/add_task_followers.ts new file mode 100644 index 00000000000..7c2aef02879 --- /dev/null +++ b/apps/sim/tools/rocketlane/add_task_followers.ts @@ -0,0 +1,79 @@ +import { + buildTaskMembers, + mapTask, + ROCKETLANE_API_BASE, + type RocketlaneTaskFollowersParams, + type RocketlaneTaskResponse, + rocketlaneError, + rocketlaneHeaders, + TASK_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneAddTaskFollowersTool: ToolConfig< + RocketlaneTaskFollowersParams, + RocketlaneTaskResponse +> = { + id: 'rocketlane_add_task_followers', + name: 'Rocketlane Add Task Followers', + description: 'Add members as followers to a Rocketlane task', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + taskId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the task to add followers to', + }, + memberUserIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'User IDs of members to add as followers', + items: { type: 'number' }, + }, + memberEmailIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Email addresses of members to add as followers', + items: { type: 'string' }, + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/tasks/${encodeURIComponent(String(params.taskId))}/add-followers`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + members: buildTaskMembers(params.memberUserIds, params.memberEmailIds), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { task: mapTask(data) }, + } + }, + + outputs: { + task: { + type: 'object', + description: 'The task with its updated followers', + properties: TASK_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/archive_project.ts b/apps/sim/tools/rocketlane/archive_project.ts new file mode 100644 index 00000000000..24db4bdb472 --- /dev/null +++ b/apps/sim/tools/rocketlane/archive_project.ts @@ -0,0 +1,60 @@ +import { + ROCKETLANE_API_BASE, + type RocketlaneArchiveProjectParams, + type RocketlaneProjectArchiveResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneArchiveProjectTool: ToolConfig< + RocketlaneArchiveProjectParams, + RocketlaneProjectArchiveResponse +> = { + id: 'rocketlane_archive_project', + name: 'Rocketlane Archive Project', + description: + 'Archive a Rocketlane project by ID, making it dormant while preserving its details and history', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the project to archive', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/projects/${encodeURIComponent(String(params.projectId))}/archive`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response, params?: RocketlaneArchiveProjectParams) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + return { + success: true, + output: { archived: true, projectId: params?.projectId ?? null }, + } + }, + + outputs: { + archived: { type: 'boolean', description: 'Whether the project was archived' }, + projectId: { + type: 'number', + description: 'Unique identifier of the archived project', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/assign_placeholders.ts b/apps/sim/tools/rocketlane/assign_placeholders.ts new file mode 100644 index 00000000000..07c077bf3c0 --- /dev/null +++ b/apps/sim/tools/rocketlane/assign_placeholders.ts @@ -0,0 +1,99 @@ +import { + mapPlaceholderMapping, + mapProject, + PLACEHOLDER_MAPPING_OUTPUT_PROPERTIES, + PROJECT_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneAssignPlaceholdersParams, + type RocketlaneProjectPlaceholdersResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneAssignPlaceholdersTool: ToolConfig< + RocketlaneAssignPlaceholdersParams, + RocketlaneProjectPlaceholdersResponse +> = { + id: 'rocketlane_assign_placeholders', + name: 'Rocketlane Assign Placeholders', + description: 'Assign a placeholder in a Rocketlane project to a user', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the project', + }, + placeholderId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the placeholder to assign', + }, + userId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'User ID of the project member to assign (either userId or userEmailId must be provided; must be a customer user for CUSTOMER placeholders)', + }, + userEmailId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Email of the project member to assign (either userId or userEmailId must be provided)', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/projects/${encodeURIComponent(String(params.projectId))}/assign-placeholders`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const user: Record = {} + if (params.userId != null) user.userId = params.userId + if (params.userEmailId) user.emailId = params.userEmailId + return [{ placeholderId: params.placeholderId, user }] + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { + project: mapProject(data), + placeholders: Array.isArray(data?.placeholders) + ? data.placeholders.map(mapPlaceholderMapping) + : [], + }, + } + }, + + outputs: { + project: { + type: 'object', + description: 'The project after the placeholder assignment', + properties: PROJECT_OUTPUT_PROPERTIES, + }, + placeholders: { + type: 'array', + description: 'Placeholder-to-user mappings on the project', + items: { type: 'object', properties: PLACEHOLDER_MAPPING_OUTPUT_PROPERTIES }, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/create_field.ts b/apps/sim/tools/rocketlane/create_field.ts new file mode 100644 index 00000000000..68cd4ebb602 --- /dev/null +++ b/apps/sim/tools/rocketlane/create_field.ts @@ -0,0 +1,146 @@ +import { + FIELD_OUTPUT_PROPERTIES, + mapField, + ROCKETLANE_API_BASE, + type RocketlaneCreateFieldParams, + type RocketlaneFieldResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneCreateFieldTool: ToolConfig< + RocketlaneCreateFieldParams, + RocketlaneFieldResponse +> = { + id: 'rocketlane_create_field', + name: 'Rocketlane Create Field', + description: + 'Create a custom field in your Rocketlane account, with options for SINGLE_CHOICE and MULTIPLE_CHOICE field types', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + fieldLabel: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the field', + }, + fieldType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Type of the field (TEXT, MULTI_LINE_TEXT, YES_OR_NO, DATE, SINGLE_CHOICE, MULTIPLE_CHOICE, SINGLE_USER, MULTIPLE_USER, NUMBER, NOTE, RATING)', + }, + objectType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Object the field is associated with (PROJECT, TASK, or USER)', + }, + fieldDescription: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Description of the field', + }, + fieldOptions: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Options for SINGLE_CHOICE and MULTIPLE_CHOICE fields; the order provided is preserved', + items: { + type: 'object', + properties: { + optionLabel: { type: 'string', description: 'Display label of the option' }, + optionColor: { + type: 'string', + description: + 'Color of the option (RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY)', + }, + }, + }, + }, + ratingScale: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Number of stars for RATING fields (THREE, FIVE, SEVEN, TEN)', + }, + enabled: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the field is enabled (defaults to true)', + }, + private: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the field is private', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra field properties to include in the response (supported: options)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to return all field properties in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/fields`) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + fieldLabel: params.fieldLabel, + fieldType: params.fieldType, + objectType: params.objectType, + ...(params.fieldDescription != null && { fieldDescription: params.fieldDescription }), + ...(params.fieldOptions != null && { fieldOptions: params.fieldOptions }), + ...(params.ratingScale != null && { ratingScale: params.ratingScale }), + ...(params.enabled != null && { enabled: params.enabled }), + ...(params.private != null && { private: params.private }), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { field: mapField(data) }, + } + }, + + outputs: { + field: { + type: 'object', + description: 'The created field', + properties: FIELD_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/create_phase.ts b/apps/sim/tools/rocketlane/create_phase.ts new file mode 100644 index 00000000000..3e427918905 --- /dev/null +++ b/apps/sim/tools/rocketlane/create_phase.ts @@ -0,0 +1,118 @@ +import { + mapPhase, + PHASE_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneCreatePhaseParams, + type RocketlanePhaseResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneCreatePhaseTool: ToolConfig< + RocketlaneCreatePhaseParams, + RocketlanePhaseResponse +> = { + id: 'rocketlane_create_phase', + name: 'Rocketlane Create Phase', + description: 'Create a new phase in a Rocketlane project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + phaseName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the phase', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the project to create the phase in', + }, + startDate: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Planned start date of the phase (YYYY-MM-DD)', + }, + dueDate: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Planned due date of the phase (YYYY-MM-DD)', + }, + statusValue: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Numeric status value to set on the phase', + }, + private: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the phase is private', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra phase properties to include in the response (supported: startDateActual, dueDateActual)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to return all phase properties in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/phases`) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + phaseName: params.phaseName, + project: { projectId: params.projectId }, + startDate: params.startDate, + dueDate: params.dueDate, + ...(params.statusValue != null && { status: { value: params.statusValue } }), + ...(params.private != null && { private: params.private }), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { phase: mapPhase(data) }, + } + }, + + outputs: { + phase: { + type: 'object', + description: 'The created phase', + properties: PHASE_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/create_project.ts b/apps/sim/tools/rocketlane/create_project.ts new file mode 100644 index 00000000000..23a2a73d25c --- /dev/null +++ b/apps/sim/tools/rocketlane/create_project.ts @@ -0,0 +1,363 @@ +import { + mapProject, + PROJECT_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneCreateProjectParams, + type RocketlaneProjectResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneCreateProjectTool: ToolConfig< + RocketlaneCreateProjectParams, + RocketlaneProjectResponse +> = { + id: 'rocketlane_create_project', + name: 'Rocketlane Create Project', + description: + 'Create a new Rocketlane project with a customer, owner, dates, team members, templates, financials, and custom fields', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the project', + }, + customerCompanyName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Name of the customer company (case-sensitive exact match; cannot be changed after creation)', + }, + ownerUserId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'User ID of the project owner (either ownerUserId or ownerEmailId must be provided)', + }, + ownerEmailId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Email of the project owner (either ownerUserId or ownerEmailId must be provided)', + }, + startDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Date on which the project begins (YYYY-MM-DD); required when sources are provided', + }, + dueDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Date on which the project is planned to complete (YYYY-MM-DD, on or after startDate)', + }, + visibility: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Who can see the project: EVERYONE or MEMBERS', + }, + statusValue: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Value (identifier) of the project status', + }, + memberUserIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'User IDs of team members from your organization to add to the project', + items: { type: 'number', description: 'User ID of a team member' }, + }, + customerUserIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'User IDs of customer stakeholders to add to the project', + items: { type: 'number', description: 'User ID of a customer' }, + }, + customerChampionUserId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'User ID of the customer champion', + }, + fields: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Custom field assignments, each with fieldId and fieldValue (string, number, or number array matching the field type)', + items: { + type: 'object', + description: 'Custom field assignment', + properties: { + fieldId: { type: 'number', description: 'Unique identifier of the field' }, + fieldValue: { + type: 'string', + description: 'Value of the field (string, number, or number array)', + }, + }, + }, + }, + sources: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Project templates to import at creation, each with templateId, startDate (YYYY-MM-DD), and optional prefix', + items: { + type: 'object', + description: 'Template source', + properties: { + templateId: { type: 'number', description: 'Unique identifier of the template' }, + startDate: { type: 'string', description: 'Date the template takes effect (YYYY-MM-DD)' }, + prefix: { type: 'string', description: 'Prefix distinguishing this template' }, + }, + }, + }, + placeholders: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Placeholder-to-user mappings, each with placeholderId and user ({ userId } or { emailId }); ignored unless the project is built from sources', + items: { + type: 'object', + description: 'Placeholder mapping', + properties: { + placeholderId: { type: 'number', description: 'Unique identifier of the placeholder' }, + user: { type: 'object', description: 'User to assign ({ userId } or { emailId })' }, + }, + }, + }, + assignProjectOwner: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: + 'Automatically assign unassigned tasks to the project owner (skipped when no sources are used)', + }, + annualizedRecurringRevenue: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Recurring revenue of the customer subscriptions for a single calendar year', + }, + projectFee: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Total fee charged for the project', + }, + autoAllocation: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether auto allocation is enabled for the project', + }, + autoCreateCompany: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Create the customer company if it does not already exist', + }, + budgetedHours: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Total hours allocated for project execution (decimal, up to two places)', + }, + contractType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Contract type for the project financials: FIXED_FEE, TIME_AND_MATERIAL, NON_BILLABLE, or SUBSCRIPTION', + }, + fixedFee: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Project fee for FIXED_FEE contract type projects', + }, + projectBudget: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Project budget for TIME_AND_MATERIAL contract type projects', + }, + rateCardId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Rate card ID for TIME_AND_MATERIAL contract type projects', + }, + subscriptionFrequency: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Subscription renewal interval for SUBSCRIPTION contracts: MONTHLY, QUARTERLY, HALF_YEARLY, or YEARLY', + }, + subscriptionStartDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Date when the subscription interval begins (YYYY-MM-DD)', + }, + periodMinutes: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Budgeted minutes for each subscription period', + }, + periodBudget: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Fixed budget of every subscription period', + }, + noOfPeriods: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of periods in the subscription', + }, + currency: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Currency for the project financials (ISO code, e.g. USD); cannot be changed once set', + }, + externalReferenceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Identifier linking the project to an external system', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra fields to return in the response (e.g. budgetedHours,progressPercentage)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Return all fields in the response body', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/projects`) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + return url.toString() + }, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const body: Record = { + projectName: params.projectName, + customer: { companyName: params.customerCompanyName }, + } + const owner: Record = {} + if (params.ownerUserId != null) owner.userId = params.ownerUserId + if (params.ownerEmailId) owner.emailId = params.ownerEmailId + if (Object.keys(owner).length > 0) body.owner = owner + if (params.startDate) body.startDate = params.startDate + if (params.dueDate) body.dueDate = params.dueDate + if (params.visibility) body.visibility = params.visibility + if (params.statusValue != null) body.status = { value: params.statusValue } + const teamMembers: Record = {} + if (params.memberUserIds && params.memberUserIds.length > 0) { + teamMembers.members = params.memberUserIds.map((userId) => ({ userId })) + } + if (params.customerUserIds && params.customerUserIds.length > 0) { + teamMembers.customers = params.customerUserIds.map((userId) => ({ userId })) + } + if (params.customerChampionUserId != null) { + teamMembers.customerChampion = { userId: params.customerChampionUserId } + } + if (Object.keys(teamMembers).length > 0) body.teamMembers = teamMembers + if (params.fields && params.fields.length > 0) body.fields = params.fields + if (params.sources && params.sources.length > 0) body.sources = params.sources + if (params.placeholders && params.placeholders.length > 0) + body.placeholders = params.placeholders + if (params.assignProjectOwner != null) body.assignProjectOwner = params.assignProjectOwner + if (params.annualizedRecurringRevenue != null) + body.annualizedRecurringRevenue = params.annualizedRecurringRevenue + if (params.projectFee != null) body.projectFee = params.projectFee + if (params.autoAllocation != null) body.autoAllocation = params.autoAllocation + if (params.autoCreateCompany != null) body.autoCreateCompany = params.autoCreateCompany + if (params.budgetedHours != null) body.budgetedHours = params.budgetedHours + if (params.contractType) { + const financials: Record = { contractType: params.contractType } + if (params.fixedFee != null) financials.fixedFeeContract = { fixedFee: params.fixedFee } + const timeAndMaterialContract: Record = {} + if (params.rateCardId != null) + timeAndMaterialContract.rateCard = { rateCardId: params.rateCardId } + if (params.projectBudget != null) + timeAndMaterialContract.projectBudget = params.projectBudget + if (Object.keys(timeAndMaterialContract).length > 0) + financials.timeAndMaterialContract = timeAndMaterialContract + const subscriptionContract: Record = {} + if (params.subscriptionFrequency) + subscriptionContract.subscriptionFrequency = params.subscriptionFrequency + if (params.subscriptionStartDate) + subscriptionContract.subscriptionStartDate = params.subscriptionStartDate + if (params.periodMinutes != null) subscriptionContract.periodMinutes = params.periodMinutes + if (params.periodBudget != null) subscriptionContract.periodBudget = params.periodBudget + if (params.noOfPeriods != null) subscriptionContract.noOfPeriods = params.noOfPeriods + if (Object.keys(subscriptionContract).length > 0) + financials.subscriptionContract = subscriptionContract + body.financials = financials + } + if (params.currency) body.currency = params.currency + if (params.externalReferenceId) body.externalReferenceId = params.externalReferenceId + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { project: mapProject(data) }, + } + }, + + outputs: { + project: { + type: 'object', + description: 'The created project', + properties: PROJECT_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/create_space.ts b/apps/sim/tools/rocketlane/create_space.ts new file mode 100644 index 00000000000..82055589493 --- /dev/null +++ b/apps/sim/tools/rocketlane/create_space.ts @@ -0,0 +1,80 @@ +import { + mapSpace, + ROCKETLANE_API_BASE, + type RocketlaneCreateSpaceParams, + type RocketlaneSpaceResponse, + rocketlaneError, + rocketlaneHeaders, + SPACE_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneCreateSpaceTool: ToolConfig< + RocketlaneCreateSpaceParams, + RocketlaneSpaceResponse +> = { + id: 'rocketlane_create_space', + name: 'Rocketlane Create Space', + description: 'Create a new space in a Rocketlane project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the project the space belongs to', + }, + spaceName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the space', + }, + private: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the space is private or shared (defaults to false)', + }, + }, + + request: { + url: () => `${ROCKETLANE_API_BASE}/spaces`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const body: Record = { + spaceName: params.spaceName, + project: { projectId: params.projectId }, + } + if (params.private != null) body.private = params.private + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { space: mapSpace(data) }, + } + }, + + outputs: { + space: { + type: 'object', + description: 'The created space', + properties: SPACE_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/create_space_document.ts b/apps/sim/tools/rocketlane/create_space_document.ts new file mode 100644 index 00000000000..54b16159acd --- /dev/null +++ b/apps/sim/tools/rocketlane/create_space_document.ts @@ -0,0 +1,94 @@ +import { + mapSpaceDocument, + ROCKETLANE_API_BASE, + type RocketlaneCreateSpaceDocumentParams, + type RocketlaneSpaceDocumentResponse, + rocketlaneError, + rocketlaneHeaders, + SPACE_DOCUMENT_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneCreateSpaceDocumentTool: ToolConfig< + RocketlaneCreateSpaceDocumentParams, + RocketlaneSpaceDocumentResponse +> = { + id: 'rocketlane_create_space_document', + name: 'Rocketlane Create Space Document', + description: 'Create a new space document in a Rocketlane space', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + spaceId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the space the document belongs to', + }, + spaceDocumentType: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Type of the space document: ROCKETLANE_DOCUMENT or EMBEDDED_DOCUMENT', + }, + spaceDocumentName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Name of the space document (defaults to Untitled)', + }, + url: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'URL to embed in the space document (for embedded documents)', + }, + templateId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'ID of the document template to create the document from', + }, + }, + + request: { + url: () => `${ROCKETLANE_API_BASE}/space-documents`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const body: Record = { + space: { spaceId: params.spaceId }, + spaceDocumentType: params.spaceDocumentType, + } + if (params.spaceDocumentName != null) body.spaceDocumentName = params.spaceDocumentName + if (params.url != null) body.url = params.url + if (params.templateId != null) body.source = { templateId: params.templateId } + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { spaceDocument: mapSpaceDocument(data) }, + } + }, + + outputs: { + spaceDocument: { + type: 'object', + description: 'The created space document', + properties: SPACE_DOCUMENT_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/create_task.ts b/apps/sim/tools/rocketlane/create_task.ts new file mode 100644 index 00000000000..6964a6e4774 --- /dev/null +++ b/apps/sim/tools/rocketlane/create_task.ts @@ -0,0 +1,222 @@ +import { + buildTaskMembers, + mapTask, + ROCKETLANE_API_BASE, + type RocketlaneCreateTaskParams, + type RocketlaneTaskResponse, + rocketlaneError, + rocketlaneHeaders, + TASK_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneCreateTaskTool: ToolConfig< + RocketlaneCreateTaskParams, + RocketlaneTaskResponse +> = { + id: 'rocketlane_create_task', + name: 'Rocketlane Create Task', + description: 'Create a new task in a Rocketlane project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + taskName: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Name of the task', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the project the task belongs to', + }, + taskDescription: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Description of the task in HTML format', + }, + taskPrivateNote: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Private note visible only to team members, in HTML format', + }, + startDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Date when the task starts (YYYY-MM-DD)', + }, + dueDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Date when the task is due, on or after the start date (YYYY-MM-DD)', + }, + effortInMinutes: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Expected effort to complete the task, in minutes', + }, + progress: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Progress of the task (0-100)', + }, + atRisk: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the task is marked as At Risk', + }, + type: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Type of the task: TASK or MILESTONE (defaults to TASK)', + }, + phaseId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'ID of the phase to associate the task with (must belong to the project)', + }, + statusValue: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Status value to set on the task', + }, + assigneeUserIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'User IDs of members to assign to the task', + items: { type: 'number' }, + }, + assigneeEmailIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Email addresses of members to assign to the task', + items: { type: 'string' }, + }, + followerUserIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'User IDs of members to add as followers of the task', + items: { type: 'number' }, + }, + followerEmailIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Email addresses of members to add as followers of the task', + items: { type: 'string' }, + }, + parentTaskId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'ID of the parent task', + }, + externalReferenceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'External reference identifier linking the task to an external system', + }, + private: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the task is private', + }, + includeFields: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Extra fields to include in the response (startDateActual, dueDateActual, type, phase, assignees, followers, dependencies, billable, csatEnabled, priority, timeEntryCategory, financialsBudget, taskPrivateNote, parent, externalReferenceId)', + items: { type: 'string' }, + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether all fields should be returned in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/tasks`) + if (params.includeFields && params.includeFields.length > 0) { + url.searchParams.set('includeFields', params.includeFields.join(',')) + } + if (params.includeAllFields !== undefined) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const body: Record = { + taskName: params.taskName, + project: { projectId: params.projectId }, + } + if (params.taskDescription !== undefined) body.taskDescription = params.taskDescription + if (params.taskPrivateNote !== undefined) body.taskPrivateNote = params.taskPrivateNote + if (params.startDate !== undefined) body.startDate = params.startDate + if (params.dueDate !== undefined) body.dueDate = params.dueDate + if (params.effortInMinutes !== undefined) body.effortInMinutes = params.effortInMinutes + if (params.progress !== undefined) body.progress = params.progress + if (params.atRisk !== undefined) body.atRisk = params.atRisk + if (params.type !== undefined) body.type = params.type + if (params.phaseId !== undefined) body.phase = { phaseId: params.phaseId } + if (params.statusValue !== undefined) body.status = { value: params.statusValue } + const assignees = buildTaskMembers(params.assigneeUserIds, params.assigneeEmailIds) + if (assignees.length > 0) body.assignees = { members: assignees } + const followers = buildTaskMembers(params.followerUserIds, params.followerEmailIds) + if (followers.length > 0) body.followers = { members: followers } + if (params.parentTaskId !== undefined) body.parent = { taskId: params.parentTaskId } + if (params.externalReferenceId !== undefined) { + body.externalReferenceId = params.externalReferenceId + } + if (params.private !== undefined) body.private = params.private + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { task: mapTask(data) }, + } + }, + + outputs: { + task: { + type: 'object', + description: 'The created task', + properties: TASK_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/create_time_entry.ts b/apps/sim/tools/rocketlane/create_time_entry.ts new file mode 100644 index 00000000000..d939ce1ae72 --- /dev/null +++ b/apps/sim/tools/rocketlane/create_time_entry.ts @@ -0,0 +1,165 @@ +import { + mapTimeEntry, + ROCKETLANE_API_BASE, + type RocketlaneCreateTimeEntryParams, + type RocketlaneTimeEntryResponse, + rocketlaneError, + rocketlaneHeaders, + TIME_ENTRY_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneCreateTimeEntryTool: ToolConfig< + RocketlaneCreateTimeEntryParams, + RocketlaneTimeEntryResponse +> = { + id: 'rocketlane_create_time_entry', + name: 'Rocketlane Create Time Entry', + description: + 'Create a time entry in Rocketlane against an adhoc activity, task, project phase, or project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + date: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Date of the time entry in YYYY-MM-DD format', + }, + minutes: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Duration of the time entry in minutes (1-1440)', + }, + activityName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Name of the adhoc activity to track time against. Exactly one source is required among activityName, taskId, projectPhaseId, and projectId — providing more than one results in an error', + }, + taskId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'ID of the task to track time against. Exactly one source is required among activityName, taskId, projectPhaseId, and projectId — providing more than one results in an error', + }, + projectPhaseId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'ID of the project phase to track time against. Exactly one source is required among activityName, taskId, projectPhaseId, and projectId — providing more than one results in an error', + }, + projectId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'ID of the project to track time against. Exactly one source is required among activityName, taskId, projectPhaseId, and projectId — providing more than one results in an error', + }, + billable: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the time entry is billable (defaults to true)', + }, + userId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'ID of the user the time entry belongs to', + }, + userEmail: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Email of the user the time entry belongs to (alternative to userId)', + }, + notes: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Notes for the time entry', + }, + categoryId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'ID of the time entry category', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra fields to include in the response (notes, sourceType, deleted, status, submittedBy, submittedAt, approvedBy, approvedAt, rejectedBy, rejectedAt, costRate, billRate)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether all fields should be returned in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/time-entries`) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const body: Record = { + date: params.date, + minutes: params.minutes, + } + if (params.activityName) body.activityName = params.activityName + if (params.taskId != null) body.task = { taskId: params.taskId } + if (params.projectPhaseId != null) body.projectPhase = { phaseId: params.projectPhaseId } + if (params.projectId != null) body.project = { projectId: params.projectId } + if (params.billable != null) body.billable = params.billable + if (params.userId != null || params.userEmail) { + const user: Record = {} + if (params.userId != null) user.userId = params.userId + if (params.userEmail) user.emailId = params.userEmail + body.user = user + } + if (params.notes) body.notes = params.notes + if (params.categoryId != null) body.category = { categoryId: params.categoryId } + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { timeEntry: mapTimeEntry(data) }, + } + }, + + outputs: { + timeEntry: { + type: 'object', + description: 'The created time entry', + properties: TIME_ENTRY_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/create_time_off.ts b/apps/sim/tools/rocketlane/create_time_off.ts new file mode 100644 index 00000000000..6f59e88fad2 --- /dev/null +++ b/apps/sim/tools/rocketlane/create_time_off.ts @@ -0,0 +1,166 @@ +import { + mapTimeOff, + ROCKETLANE_API_BASE, + type RocketlaneTimeOffCreateParams, + type RocketlaneTimeOffResponse, + rocketlaneError, + rocketlaneHeaders, + TIME_OFF_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneCreateTimeOffTool: ToolConfig< + RocketlaneTimeOffCreateParams, + RocketlaneTimeOffResponse +> = { + id: 'rocketlane_create_time_off', + name: 'Rocketlane Create Time-Off', + description: + 'Create a time-off for a team member in Rocketlane. Holidays and weekends within the date range are automatically excluded from the duration.', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + userId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'User ID of the team member taking the time-off (provide this or the user email)', + }, + userEmail: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Email of the team member taking the time-off (provide this or the user ID)', + }, + startDate: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Time-off start date in YYYY-MM-DD format', + }, + endDate: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Time-off end date in YYYY-MM-DD format (on or after the start date)', + }, + type: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Type of the time-off: FULL_DAY, HALF_DAY, or CUSTOM', + }, + durationInMinutes: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Duration in minutes per day; required when type is CUSTOM', + }, + note: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Note or comment about the time-off', + }, + notifyProjectOwners: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Notify project owners of projects the user is part of', + }, + notifyUserIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'User IDs of additional users to notify about the time-off', + items: { type: 'number' }, + }, + notifyUserEmails: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Emails of additional users to notify about the time-off', + items: { type: 'string' }, + }, + includeFields: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Optional fields to include in the response: note, notifyUsers', + items: { type: 'string' }, + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Return all fields in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/time-offs`) + if (params.includeFields?.length) { + url.searchParams.set('includeFields', params.includeFields.join(',')) + } + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const user: Record = {} + if (params.userId != null) user.userId = params.userId + if (params.userEmail) user.emailId = params.userEmail + const body: Record = { + user, + startDate: params.startDate, + endDate: params.endDate, + type: params.type, + } + if (params.note) body.note = params.note + if (params.durationInMinutes != null) body.durationInMinutes = params.durationInMinutes + const others = [ + ...(params.notifyUserIds ?? []).map((userId) => ({ userId })), + ...(params.notifyUserEmails ?? []).map((emailId) => ({ emailId })), + ] + if (params.notifyProjectOwners != null || others.length > 0) { + const notifyUsers: Record = {} + if (params.notifyProjectOwners != null) { + notifyUsers.projectOwners = params.notifyProjectOwners + } + if (others.length > 0) notifyUsers.others = others + body.notifyUsers = notifyUsers + } + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { timeOff: mapTimeOff(data) }, + } + }, + + outputs: { + timeOff: { + type: 'object', + description: 'The created time-off', + properties: TIME_OFF_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/delete_field.ts b/apps/sim/tools/rocketlane/delete_field.ts new file mode 100644 index 00000000000..78b35cb8cdd --- /dev/null +++ b/apps/sim/tools/rocketlane/delete_field.ts @@ -0,0 +1,54 @@ +import { + ROCKETLANE_API_BASE, + type RocketlaneDeleteFieldParams, + type RocketlaneFieldDeleteResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneDeleteFieldTool: ToolConfig< + RocketlaneDeleteFieldParams, + RocketlaneFieldDeleteResponse +> = { + id: 'rocketlane_delete_field', + name: 'Rocketlane Delete Field', + description: 'Permanently delete a Rocketlane field by ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + fieldId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the field to delete', + }, + }, + + request: { + url: (params) => `${ROCKETLANE_API_BASE}/fields/${encodeURIComponent(String(params.fieldId))}`, + method: 'DELETE', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response, params?: RocketlaneDeleteFieldParams) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + return { + success: true, + output: { deleted: true, fieldId: params?.fieldId ?? null }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the field was deleted' }, + fieldId: { type: 'number', description: 'ID of the deleted field', optional: true }, + }, +} diff --git a/apps/sim/tools/rocketlane/delete_phase.ts b/apps/sim/tools/rocketlane/delete_phase.ts new file mode 100644 index 00000000000..88296907c8c --- /dev/null +++ b/apps/sim/tools/rocketlane/delete_phase.ts @@ -0,0 +1,54 @@ +import { + ROCKETLANE_API_BASE, + type RocketlaneDeletePhaseParams, + type RocketlanePhaseDeleteResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneDeletePhaseTool: ToolConfig< + RocketlaneDeletePhaseParams, + RocketlanePhaseDeleteResponse +> = { + id: 'rocketlane_delete_phase', + name: 'Rocketlane Delete Phase', + description: 'Permanently delete a Rocketlane phase by ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + phaseId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the phase to delete', + }, + }, + + request: { + url: (params) => `${ROCKETLANE_API_BASE}/phases/${encodeURIComponent(String(params.phaseId))}`, + method: 'DELETE', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response, params?: RocketlaneDeletePhaseParams) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + return { + success: true, + output: { deleted: true, phaseId: params?.phaseId ?? null }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the phase was deleted' }, + phaseId: { type: 'number', description: 'ID of the deleted phase', optional: true }, + }, +} diff --git a/apps/sim/tools/rocketlane/delete_project.ts b/apps/sim/tools/rocketlane/delete_project.ts new file mode 100644 index 00000000000..c4e834b71a9 --- /dev/null +++ b/apps/sim/tools/rocketlane/delete_project.ts @@ -0,0 +1,60 @@ +import { + ROCKETLANE_API_BASE, + type RocketlaneDeleteProjectParams, + type RocketlaneProjectDeleteResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneDeleteProjectTool: ToolConfig< + RocketlaneDeleteProjectParams, + RocketlaneProjectDeleteResponse +> = { + id: 'rocketlane_delete_project', + name: 'Rocketlane Delete Project', + description: + 'Permanently delete a Rocketlane project by ID (irreversible; only Admins, Super Users, and Project Owners can delete)', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the project to delete', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/projects/${encodeURIComponent(String(params.projectId))}`, + method: 'DELETE', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response, params?: RocketlaneDeleteProjectParams) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + return { + success: true, + output: { deleted: true, projectId: params?.projectId ?? null }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the project was deleted' }, + projectId: { + type: 'number', + description: 'Unique identifier of the deleted project', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/delete_space.ts b/apps/sim/tools/rocketlane/delete_space.ts new file mode 100644 index 00000000000..38569b073af --- /dev/null +++ b/apps/sim/tools/rocketlane/delete_space.ts @@ -0,0 +1,54 @@ +import { + ROCKETLANE_API_BASE, + type RocketlaneDeleteSpaceParams, + type RocketlaneDeleteSpaceResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneDeleteSpaceTool: ToolConfig< + RocketlaneDeleteSpaceParams, + RocketlaneDeleteSpaceResponse +> = { + id: 'rocketlane_delete_space', + name: 'Rocketlane Delete Space', + description: 'Permanently delete a Rocketlane space by its ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + spaceId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the space to delete', + }, + }, + + request: { + url: (params) => `${ROCKETLANE_API_BASE}/spaces/${encodeURIComponent(params.spaceId)}`, + method: 'DELETE', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response, params?: RocketlaneDeleteSpaceParams) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + return { + success: true, + output: { deleted: true, spaceId: params?.spaceId ?? null }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the space was deleted' }, + spaceId: { type: 'number', description: 'ID of the deleted space', optional: true }, + }, +} diff --git a/apps/sim/tools/rocketlane/delete_space_document.ts b/apps/sim/tools/rocketlane/delete_space_document.ts new file mode 100644 index 00000000000..4303e612058 --- /dev/null +++ b/apps/sim/tools/rocketlane/delete_space_document.ts @@ -0,0 +1,59 @@ +import { + ROCKETLANE_API_BASE, + type RocketlaneDeleteSpaceDocumentParams, + type RocketlaneDeleteSpaceDocumentResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneDeleteSpaceDocumentTool: ToolConfig< + RocketlaneDeleteSpaceDocumentParams, + RocketlaneDeleteSpaceDocumentResponse +> = { + id: 'rocketlane_delete_space_document', + name: 'Rocketlane Delete Space Document', + description: 'Permanently delete a Rocketlane space document by its ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + spaceDocumentId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the space document to delete', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/space-documents/${encodeURIComponent(params.spaceDocumentId)}`, + method: 'DELETE', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response, params?: RocketlaneDeleteSpaceDocumentParams) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + return { + success: true, + output: { deleted: true, spaceDocumentId: params?.spaceDocumentId ?? null }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the space document was deleted' }, + spaceDocumentId: { + type: 'number', + description: 'ID of the deleted space document', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/delete_task.ts b/apps/sim/tools/rocketlane/delete_task.ts new file mode 100644 index 00000000000..7e2a65dc2bd --- /dev/null +++ b/apps/sim/tools/rocketlane/delete_task.ts @@ -0,0 +1,54 @@ +import { + ROCKETLANE_API_BASE, + type RocketlaneDeleteTaskParams, + type RocketlaneTaskDeleteResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneDeleteTaskTool: ToolConfig< + RocketlaneDeleteTaskParams, + RocketlaneTaskDeleteResponse +> = { + id: 'rocketlane_delete_task', + name: 'Rocketlane Delete Task', + description: 'Permanently delete a Rocketlane task by its unique identifier', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + taskId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the task to delete', + }, + }, + + request: { + url: (params) => `${ROCKETLANE_API_BASE}/tasks/${encodeURIComponent(String(params.taskId))}`, + method: 'DELETE', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response, params?: RocketlaneDeleteTaskParams) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + return { + success: true, + output: { deleted: true, taskId: params?.taskId ?? null }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the task was deleted' }, + taskId: { type: 'number', description: 'ID of the deleted task', optional: true }, + }, +} diff --git a/apps/sim/tools/rocketlane/delete_time_entry.ts b/apps/sim/tools/rocketlane/delete_time_entry.ts new file mode 100644 index 00000000000..46b140387b2 --- /dev/null +++ b/apps/sim/tools/rocketlane/delete_time_entry.ts @@ -0,0 +1,59 @@ +import { + ROCKETLANE_API_BASE, + type RocketlaneDeleteTimeEntryParams, + type RocketlaneDeleteTimeEntryResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneDeleteTimeEntryTool: ToolConfig< + RocketlaneDeleteTimeEntryParams, + RocketlaneDeleteTimeEntryResponse +> = { + id: 'rocketlane_delete_time_entry', + name: 'Rocketlane Delete Time Entry', + description: 'Permanently delete a Rocketlane time entry by ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + timeEntryId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the time entry to delete', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/time-entries/${encodeURIComponent(params.timeEntryId)}`, + method: 'DELETE', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response, params?: RocketlaneDeleteTimeEntryParams) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + return { + success: true, + output: { deleted: true, timeEntryId: params?.timeEntryId ?? null }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the time entry was deleted' }, + timeEntryId: { + type: 'number', + description: 'ID of the deleted time entry', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/delete_time_off.ts b/apps/sim/tools/rocketlane/delete_time_off.ts new file mode 100644 index 00000000000..18a5c3f819b --- /dev/null +++ b/apps/sim/tools/rocketlane/delete_time_off.ts @@ -0,0 +1,58 @@ +import { + ROCKETLANE_API_BASE, + type RocketlaneTimeOffDeleteParams, + type RocketlaneTimeOffDeleteResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneDeleteTimeOffTool: ToolConfig< + RocketlaneTimeOffDeleteParams, + RocketlaneTimeOffDeleteResponse +> = { + id: 'rocketlane_delete_time_off', + name: 'Rocketlane Delete Time-Off', + description: 'Permanently delete a Rocketlane time-off by its ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + timeOffId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the time-off to delete', + }, + }, + + request: { + url: (params) => `${ROCKETLANE_API_BASE}/time-offs/${encodeURIComponent(params.timeOffId)}`, + method: 'DELETE', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response, params?: RocketlaneTimeOffDeleteParams) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + return { + success: true, + output: { deleted: true, timeOffId: params?.timeOffId ?? null }, + } + }, + + outputs: { + deleted: { type: 'boolean', description: 'Whether the time-off was deleted' }, + timeOffId: { + type: 'number', + description: 'ID of the deleted time-off', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/get_field.ts b/apps/sim/tools/rocketlane/get_field.ts new file mode 100644 index 00000000000..eb8f31e44a5 --- /dev/null +++ b/apps/sim/tools/rocketlane/get_field.ts @@ -0,0 +1,80 @@ +import { + FIELD_OUTPUT_PROPERTIES, + mapField, + ROCKETLANE_API_BASE, + type RocketlaneFieldResponse, + type RocketlaneGetFieldParams, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneGetFieldTool: ToolConfig = + { + id: 'rocketlane_get_field', + name: 'Rocketlane Get Field', + description: 'Retrieve a single Rocketlane field by ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + fieldId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the field to retrieve', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra field properties to include in the response (supported: options)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to return all field properties in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/fields/${encodeURIComponent(String(params.fieldId))}` + ) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { field: mapField(data) }, + } + }, + + outputs: { + field: { + type: 'object', + description: 'The requested field', + properties: FIELD_OUTPUT_PROPERTIES, + }, + }, + } diff --git a/apps/sim/tools/rocketlane/get_invoice.ts b/apps/sim/tools/rocketlane/get_invoice.ts new file mode 100644 index 00000000000..1ca218b8511 --- /dev/null +++ b/apps/sim/tools/rocketlane/get_invoice.ts @@ -0,0 +1,82 @@ +import { + INVOICE_OUTPUT_PROPERTIES, + mapInvoice, + ROCKETLANE_API_BASE, + type RocketlaneInvoiceGetParams, + type RocketlaneInvoiceResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneGetInvoiceTool: ToolConfig< + RocketlaneInvoiceGetParams, + RocketlaneInvoiceResponse +> = { + id: 'rocketlane_get_invoice', + name: 'Rocketlane Get Invoice', + description: 'Retrieve a Rocketlane invoice by its ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + invoiceId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the invoice', + }, + includeFields: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Optional fields to include in the response: notes, attachments', + items: { type: 'string' }, + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Return all fields in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/invoices/${encodeURIComponent(params.invoiceId)}`) + if (params.includeFields?.length) { + url.searchParams.set('includeFields', params.includeFields.join(',')) + } + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { invoice: mapInvoice(data) }, + } + }, + + outputs: { + invoice: { + type: 'object', + description: 'The requested invoice', + properties: INVOICE_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/get_invoice_line_items.ts b/apps/sim/tools/rocketlane/get_invoice_line_items.ts new file mode 100644 index 00000000000..5ff7c7daaee --- /dev/null +++ b/apps/sim/tools/rocketlane/get_invoice_line_items.ts @@ -0,0 +1,90 @@ +import { + INVOICE_LINE_ITEM_OUTPUT_PROPERTIES, + mapInvoiceLineItem, + mapPagination, + PAGINATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneInvoiceLineItemListResponse, + type RocketlaneInvoiceLineItemsParams, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneGetInvoiceLineItemsTool: ToolConfig< + RocketlaneInvoiceLineItemsParams, + RocketlaneInvoiceLineItemListResponse +> = { + id: 'rocketlane_get_invoice_line_items', + name: 'Rocketlane Get Invoice Line Items', + description: 'List line items of a Rocketlane invoice', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + invoiceId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the invoice', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of line items per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous response (valid for 15 minutes)', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/invoices/${encodeURIComponent(params.invoiceId)}/lines` + ) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const lineItems = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + lineItems: lineItems.map(mapInvoiceLineItem), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + lineItems: { + type: 'array', + description: 'List of invoice line items', + items: { type: 'object', properties: INVOICE_LINE_ITEM_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for the result set', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/get_invoice_payments.ts b/apps/sim/tools/rocketlane/get_invoice_payments.ts new file mode 100644 index 00000000000..d35564dd642 --- /dev/null +++ b/apps/sim/tools/rocketlane/get_invoice_payments.ts @@ -0,0 +1,90 @@ +import { + INVOICE_PAYMENT_OUTPUT_PROPERTIES, + mapInvoicePayment, + mapPagination, + PAGINATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneInvoicePaymentListResponse, + type RocketlaneInvoicePaymentsParams, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneGetInvoicePaymentsTool: ToolConfig< + RocketlaneInvoicePaymentsParams, + RocketlaneInvoicePaymentListResponse +> = { + id: 'rocketlane_get_invoice_payments', + name: 'Rocketlane Get Invoice Payments', + description: 'List payments recorded against a Rocketlane invoice', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + invoiceId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the invoice', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of payments per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous response (valid for 15 minutes)', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/invoices/${encodeURIComponent(params.invoiceId)}/payments` + ) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const payments = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + payments: payments.map(mapInvoicePayment), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + payments: { + type: 'array', + description: 'List of payments recorded against the invoice', + items: { type: 'object', properties: INVOICE_PAYMENT_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for the result set', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/get_phase.ts b/apps/sim/tools/rocketlane/get_phase.ts new file mode 100644 index 00000000000..73996a265f9 --- /dev/null +++ b/apps/sim/tools/rocketlane/get_phase.ts @@ -0,0 +1,80 @@ +import { + mapPhase, + PHASE_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneGetPhaseParams, + type RocketlanePhaseResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneGetPhaseTool: ToolConfig = + { + id: 'rocketlane_get_phase', + name: 'Rocketlane Get Phase', + description: 'Retrieve a single Rocketlane phase by ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + phaseId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the phase to retrieve', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra phase properties to include in the response (supported: startDateActual, dueDateActual)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to return all phase properties in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/phases/${encodeURIComponent(String(params.phaseId))}` + ) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { phase: mapPhase(data) }, + } + }, + + outputs: { + phase: { + type: 'object', + description: 'The requested phase', + properties: PHASE_OUTPUT_PROPERTIES, + }, + }, + } diff --git a/apps/sim/tools/rocketlane/get_project.ts b/apps/sim/tools/rocketlane/get_project.ts new file mode 100644 index 00000000000..d29e42e83b6 --- /dev/null +++ b/apps/sim/tools/rocketlane/get_project.ts @@ -0,0 +1,81 @@ +import { + mapProject, + PROJECT_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneGetProjectParams, + type RocketlaneProjectResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneGetProjectTool: ToolConfig< + RocketlaneGetProjectParams, + RocketlaneProjectResponse +> = { + id: 'rocketlane_get_project', + name: 'Rocketlane Get Project', + description: 'Retrieve a Rocketlane project by its unique identifier', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the project', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra fields to return in the response (e.g. budgetedHours,progressPercentage)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Return all fields in the response body', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/projects/${encodeURIComponent(String(params.projectId))}` + ) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { project: mapProject(data) }, + } + }, + + outputs: { + project: { + type: 'object', + description: 'The requested project', + properties: PROJECT_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/get_space.ts b/apps/sim/tools/rocketlane/get_space.ts new file mode 100644 index 00000000000..376254f5088 --- /dev/null +++ b/apps/sim/tools/rocketlane/get_space.ts @@ -0,0 +1,58 @@ +import { + mapSpace, + ROCKETLANE_API_BASE, + type RocketlaneGetSpaceParams, + type RocketlaneSpaceResponse, + rocketlaneError, + rocketlaneHeaders, + SPACE_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneGetSpaceTool: ToolConfig = + { + id: 'rocketlane_get_space', + name: 'Rocketlane Get Space', + description: 'Retrieve a Rocketlane space by its ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + spaceId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the space to retrieve', + }, + }, + + request: { + url: (params) => `${ROCKETLANE_API_BASE}/spaces/${encodeURIComponent(params.spaceId)}`, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { space: mapSpace(data) }, + } + }, + + outputs: { + space: { + type: 'object', + description: 'The requested space', + properties: SPACE_OUTPUT_PROPERTIES, + }, + }, + } diff --git a/apps/sim/tools/rocketlane/get_space_document.ts b/apps/sim/tools/rocketlane/get_space_document.ts new file mode 100644 index 00000000000..a1a002fa3e8 --- /dev/null +++ b/apps/sim/tools/rocketlane/get_space_document.ts @@ -0,0 +1,61 @@ +import { + mapSpaceDocument, + ROCKETLANE_API_BASE, + type RocketlaneGetSpaceDocumentParams, + type RocketlaneSpaceDocumentResponse, + rocketlaneError, + rocketlaneHeaders, + SPACE_DOCUMENT_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneGetSpaceDocumentTool: ToolConfig< + RocketlaneGetSpaceDocumentParams, + RocketlaneSpaceDocumentResponse +> = { + id: 'rocketlane_get_space_document', + name: 'Rocketlane Get Space Document', + description: 'Retrieve a Rocketlane space document by its ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + spaceDocumentId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the space document to retrieve', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/space-documents/${encodeURIComponent(params.spaceDocumentId)}`, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { spaceDocument: mapSpaceDocument(data) }, + } + }, + + outputs: { + spaceDocument: { + type: 'object', + description: 'The requested space document', + properties: SPACE_DOCUMENT_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/get_task.ts b/apps/sim/tools/rocketlane/get_task.ts new file mode 100644 index 00000000000..40e6470a348 --- /dev/null +++ b/apps/sim/tools/rocketlane/get_task.ts @@ -0,0 +1,82 @@ +import { + mapTask, + ROCKETLANE_API_BASE, + type RocketlaneGetTaskParams, + type RocketlaneTaskResponse, + rocketlaneError, + rocketlaneHeaders, + TASK_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneGetTaskTool: ToolConfig = { + id: 'rocketlane_get_task', + name: 'Rocketlane Get Task', + description: 'Retrieve detailed information about a Rocketlane task by its unique identifier', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + taskId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the task to retrieve', + }, + includeFields: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Extra fields to include in the response (startDateActual, dueDateActual, type, phase, assignees, followers, dependencies, billable, csatEnabled, priority, timeEntryCategory, financialsBudget, taskPrivateNote, parent, externalReferenceId)', + items: { type: 'string' }, + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether all fields should be returned in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/tasks/${encodeURIComponent(String(params.taskId))}` + ) + if (params.includeFields && params.includeFields.length > 0) { + url.searchParams.set('includeFields', params.includeFields.join(',')) + } + if (params.includeAllFields !== undefined) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { task: mapTask(data) }, + } + }, + + outputs: { + task: { + type: 'object', + description: 'The requested task', + properties: TASK_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/get_time_entry.ts b/apps/sim/tools/rocketlane/get_time_entry.ts new file mode 100644 index 00000000000..fe899339117 --- /dev/null +++ b/apps/sim/tools/rocketlane/get_time_entry.ts @@ -0,0 +1,82 @@ +import { + mapTimeEntry, + ROCKETLANE_API_BASE, + type RocketlaneGetTimeEntryParams, + type RocketlaneTimeEntryResponse, + rocketlaneError, + rocketlaneHeaders, + TIME_ENTRY_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneGetTimeEntryTool: ToolConfig< + RocketlaneGetTimeEntryParams, + RocketlaneTimeEntryResponse +> = { + id: 'rocketlane_get_time_entry', + name: 'Rocketlane Get Time Entry', + description: 'Get a single Rocketlane time entry by ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + timeEntryId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the time entry to retrieve', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra fields to include in the response (notes, sourceType, deleted, status, submittedBy, submittedAt, approvedBy, approvedAt, rejectedBy, rejectedAt, costRate, billRate)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether all fields should be returned in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/time-entries/${encodeURIComponent(params.timeEntryId)}` + ) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { timeEntry: mapTimeEntry(data) }, + } + }, + + outputs: { + timeEntry: { + type: 'object', + description: 'The requested time entry', + properties: TIME_ENTRY_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/get_time_off.ts b/apps/sim/tools/rocketlane/get_time_off.ts new file mode 100644 index 00000000000..d858ffff874 --- /dev/null +++ b/apps/sim/tools/rocketlane/get_time_off.ts @@ -0,0 +1,84 @@ +import { + mapTimeOff, + ROCKETLANE_API_BASE, + type RocketlaneTimeOffGetParams, + type RocketlaneTimeOffResponse, + rocketlaneError, + rocketlaneHeaders, + TIME_OFF_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneGetTimeOffTool: ToolConfig< + RocketlaneTimeOffGetParams, + RocketlaneTimeOffResponse +> = { + id: 'rocketlane_get_time_off', + name: 'Rocketlane Get Time-Off', + description: 'Retrieve a Rocketlane time-off by its ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + timeOffId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the time-off', + }, + includeFields: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Optional fields to include in the response: note, notifyUsers', + items: { type: 'string' }, + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Return all fields in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/time-offs/${encodeURIComponent(params.timeOffId)}` + ) + if (params.includeFields?.length) { + url.searchParams.set('includeFields', params.includeFields.join(',')) + } + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { timeOff: mapTimeOff(data) }, + } + }, + + outputs: { + timeOff: { + type: 'object', + description: 'The requested time-off', + properties: TIME_OFF_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/get_user.ts b/apps/sim/tools/rocketlane/get_user.ts new file mode 100644 index 00000000000..262170f9830 --- /dev/null +++ b/apps/sim/tools/rocketlane/get_user.ts @@ -0,0 +1,76 @@ +import { + mapUser, + ROCKETLANE_API_BASE, + type RocketlaneGetUserParams, + type RocketlaneUserResponse, + rocketlaneError, + rocketlaneHeaders, + USER_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneGetUserTool: ToolConfig = { + id: 'rocketlane_get_user', + name: 'Rocketlane Get User', + description: 'Retrieve a Rocketlane user by their ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + userId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the user to retrieve', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra fields to include: role, company, permission, holidayCalendar, capacityInMinutes, profilePictureUrl', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to include all fields in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/users/${encodeURIComponent(params.userId)}`) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { user: mapUser(data) }, + } + }, + + outputs: { + user: { + type: 'object', + description: 'The requested user', + properties: USER_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/import_template.ts b/apps/sim/tools/rocketlane/import_template.ts new file mode 100644 index 00000000000..b66b232c59a --- /dev/null +++ b/apps/sim/tools/rocketlane/import_template.ts @@ -0,0 +1,88 @@ +import { + mapProject, + PROJECT_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneImportTemplateParams, + type RocketlaneProjectResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneImportTemplateTool: ToolConfig< + RocketlaneImportTemplateParams, + RocketlaneProjectResponse +> = { + id: 'rocketlane_import_template', + name: 'Rocketlane Import Template', + description: 'Import a project template into an existing Rocketlane project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the project to import the template into', + }, + templateId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the template to import', + }, + startDate: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Date on which the template goes into effect for the project (YYYY-MM-DD)', + }, + prefix: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Prefix distinguishing which phase or task corresponds to this template when importing multiple templates', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/projects/${encodeURIComponent(String(params.projectId))}/import-template`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const source: Record = { + templateId: params.templateId, + startDate: params.startDate, + } + if (params.prefix) source.prefix = params.prefix + return [source] + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { project: mapProject(data) }, + } + }, + + outputs: { + project: { + type: 'object', + description: 'The project after the template import (including its sources)', + properties: PROJECT_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/index.ts b/apps/sim/tools/rocketlane/index.ts new file mode 100644 index 00000000000..1cd69057f78 --- /dev/null +++ b/apps/sim/tools/rocketlane/index.ts @@ -0,0 +1,131 @@ +import { rocketlaneAddFieldOptionTool } from '@/tools/rocketlane/add_field_option' +import { rocketlaneAddProjectMembersTool } from '@/tools/rocketlane/add_project_members' +import { rocketlaneAddTaskAssigneesTool } from '@/tools/rocketlane/add_task_assignees' +import { rocketlaneAddTaskDependenciesTool } from '@/tools/rocketlane/add_task_dependencies' +import { rocketlaneAddTaskFollowersTool } from '@/tools/rocketlane/add_task_followers' +import { rocketlaneArchiveProjectTool } from '@/tools/rocketlane/archive_project' +import { rocketlaneAssignPlaceholdersTool } from '@/tools/rocketlane/assign_placeholders' +import { rocketlaneCreateFieldTool } from '@/tools/rocketlane/create_field' +import { rocketlaneCreatePhaseTool } from '@/tools/rocketlane/create_phase' +import { rocketlaneCreateProjectTool } from '@/tools/rocketlane/create_project' +import { rocketlaneCreateSpaceTool } from '@/tools/rocketlane/create_space' +import { rocketlaneCreateSpaceDocumentTool } from '@/tools/rocketlane/create_space_document' +import { rocketlaneCreateTaskTool } from '@/tools/rocketlane/create_task' +import { rocketlaneCreateTimeEntryTool } from '@/tools/rocketlane/create_time_entry' +import { rocketlaneCreateTimeOffTool } from '@/tools/rocketlane/create_time_off' +import { rocketlaneDeleteFieldTool } from '@/tools/rocketlane/delete_field' +import { rocketlaneDeletePhaseTool } from '@/tools/rocketlane/delete_phase' +import { rocketlaneDeleteProjectTool } from '@/tools/rocketlane/delete_project' +import { rocketlaneDeleteSpaceTool } from '@/tools/rocketlane/delete_space' +import { rocketlaneDeleteSpaceDocumentTool } from '@/tools/rocketlane/delete_space_document' +import { rocketlaneDeleteTaskTool } from '@/tools/rocketlane/delete_task' +import { rocketlaneDeleteTimeEntryTool } from '@/tools/rocketlane/delete_time_entry' +import { rocketlaneDeleteTimeOffTool } from '@/tools/rocketlane/delete_time_off' +import { rocketlaneGetFieldTool } from '@/tools/rocketlane/get_field' +import { rocketlaneGetInvoiceTool } from '@/tools/rocketlane/get_invoice' +import { rocketlaneGetInvoiceLineItemsTool } from '@/tools/rocketlane/get_invoice_line_items' +import { rocketlaneGetInvoicePaymentsTool } from '@/tools/rocketlane/get_invoice_payments' +import { rocketlaneGetPhaseTool } from '@/tools/rocketlane/get_phase' +import { rocketlaneGetProjectTool } from '@/tools/rocketlane/get_project' +import { rocketlaneGetSpaceTool } from '@/tools/rocketlane/get_space' +import { rocketlaneGetSpaceDocumentTool } from '@/tools/rocketlane/get_space_document' +import { rocketlaneGetTaskTool } from '@/tools/rocketlane/get_task' +import { rocketlaneGetTimeEntryTool } from '@/tools/rocketlane/get_time_entry' +import { rocketlaneGetTimeOffTool } from '@/tools/rocketlane/get_time_off' +import { rocketlaneGetUserTool } from '@/tools/rocketlane/get_user' +import { rocketlaneImportTemplateTool } from '@/tools/rocketlane/import_template' +import { rocketlaneListFieldsTool } from '@/tools/rocketlane/list_fields' +import { rocketlaneListInvoicesTool } from '@/tools/rocketlane/list_invoices' +import { rocketlaneListPhasesTool } from '@/tools/rocketlane/list_phases' +import { rocketlaneListPlaceholdersTool } from '@/tools/rocketlane/list_placeholders' +import { rocketlaneListProjectsTool } from '@/tools/rocketlane/list_projects' +import { rocketlaneListResourceAllocationsTool } from '@/tools/rocketlane/list_resource_allocations' +import { rocketlaneListSpaceDocumentsTool } from '@/tools/rocketlane/list_space_documents' +import { rocketlaneListSpacesTool } from '@/tools/rocketlane/list_spaces' +import { rocketlaneListTasksTool } from '@/tools/rocketlane/list_tasks' +import { rocketlaneListTimeEntriesTool } from '@/tools/rocketlane/list_time_entries' +import { rocketlaneListTimeEntryCategoriesTool } from '@/tools/rocketlane/list_time_entry_categories' +import { rocketlaneListTimeOffsTool } from '@/tools/rocketlane/list_time_offs' +import { rocketlaneListUsersTool } from '@/tools/rocketlane/list_users' +import { rocketlaneMoveTaskToPhaseTool } from '@/tools/rocketlane/move_task_to_phase' +import { rocketlaneRemoveProjectMembersTool } from '@/tools/rocketlane/remove_project_members' +import { rocketlaneRemoveTaskAssigneesTool } from '@/tools/rocketlane/remove_task_assignees' +import { rocketlaneRemoveTaskDependenciesTool } from '@/tools/rocketlane/remove_task_dependencies' +import { rocketlaneRemoveTaskFollowersTool } from '@/tools/rocketlane/remove_task_followers' +import { rocketlaneSearchTimeEntriesTool } from '@/tools/rocketlane/search_time_entries' +import { rocketlaneUnassignPlaceholdersTool } from '@/tools/rocketlane/unassign_placeholders' +import { rocketlaneUpdateFieldTool } from '@/tools/rocketlane/update_field' +import { rocketlaneUpdateFieldOptionTool } from '@/tools/rocketlane/update_field_option' +import { rocketlaneUpdatePhaseTool } from '@/tools/rocketlane/update_phase' +import { rocketlaneUpdateProjectTool } from '@/tools/rocketlane/update_project' +import { rocketlaneUpdateSpaceTool } from '@/tools/rocketlane/update_space' +import { rocketlaneUpdateSpaceDocumentTool } from '@/tools/rocketlane/update_space_document' +import { rocketlaneUpdateTaskTool } from '@/tools/rocketlane/update_task' +import { rocketlaneUpdateTimeEntryTool } from '@/tools/rocketlane/update_time_entry' + +export { + rocketlaneAddFieldOptionTool, + rocketlaneAddProjectMembersTool, + rocketlaneAddTaskAssigneesTool, + rocketlaneAddTaskDependenciesTool, + rocketlaneAddTaskFollowersTool, + rocketlaneArchiveProjectTool, + rocketlaneAssignPlaceholdersTool, + rocketlaneCreateFieldTool, + rocketlaneCreatePhaseTool, + rocketlaneCreateProjectTool, + rocketlaneCreateSpaceDocumentTool, + rocketlaneCreateSpaceTool, + rocketlaneCreateTaskTool, + rocketlaneCreateTimeEntryTool, + rocketlaneCreateTimeOffTool, + rocketlaneDeleteFieldTool, + rocketlaneDeletePhaseTool, + rocketlaneDeleteProjectTool, + rocketlaneDeleteSpaceDocumentTool, + rocketlaneDeleteSpaceTool, + rocketlaneDeleteTaskTool, + rocketlaneDeleteTimeEntryTool, + rocketlaneDeleteTimeOffTool, + rocketlaneGetFieldTool, + rocketlaneGetInvoiceLineItemsTool, + rocketlaneGetInvoicePaymentsTool, + rocketlaneGetInvoiceTool, + rocketlaneGetPhaseTool, + rocketlaneGetProjectTool, + rocketlaneGetSpaceDocumentTool, + rocketlaneGetSpaceTool, + rocketlaneGetTaskTool, + rocketlaneGetTimeEntryTool, + rocketlaneGetTimeOffTool, + rocketlaneGetUserTool, + rocketlaneImportTemplateTool, + rocketlaneListFieldsTool, + rocketlaneListInvoicesTool, + rocketlaneListPhasesTool, + rocketlaneListPlaceholdersTool, + rocketlaneListProjectsTool, + rocketlaneListResourceAllocationsTool, + rocketlaneListSpaceDocumentsTool, + rocketlaneListSpacesTool, + rocketlaneListTasksTool, + rocketlaneListTimeEntriesTool, + rocketlaneListTimeEntryCategoriesTool, + rocketlaneListTimeOffsTool, + rocketlaneListUsersTool, + rocketlaneMoveTaskToPhaseTool, + rocketlaneRemoveProjectMembersTool, + rocketlaneRemoveTaskAssigneesTool, + rocketlaneRemoveTaskDependenciesTool, + rocketlaneRemoveTaskFollowersTool, + rocketlaneSearchTimeEntriesTool, + rocketlaneUnassignPlaceholdersTool, + rocketlaneUpdateFieldOptionTool, + rocketlaneUpdateFieldTool, + rocketlaneUpdatePhaseTool, + rocketlaneUpdateProjectTool, + rocketlaneUpdateSpaceDocumentTool, + rocketlaneUpdateSpaceTool, + rocketlaneUpdateTaskTool, + rocketlaneUpdateTimeEntryTool, +} diff --git a/apps/sim/tools/rocketlane/list_fields.ts b/apps/sim/tools/rocketlane/list_fields.ts new file mode 100644 index 00000000000..b5a8100ff26 --- /dev/null +++ b/apps/sim/tools/rocketlane/list_fields.ts @@ -0,0 +1,150 @@ +import { + FIELD_OUTPUT_PROPERTIES, + mapField, + mapPagination, + PAGINATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneFieldListResponse, + type RocketlaneListFieldsParams, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListFieldsTool: ToolConfig< + RocketlaneListFieldsParams, + RocketlaneFieldListResponse +> = { + id: 'rocketlane_list_fields', + name: 'Rocketlane List Fields', + description: + 'List fields in your Rocketlane account, with optional filters, sorting, and pagination', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of fields per page', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token returned by a previous request (valid for 15 minutes)', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra field properties to include in the response (supported: options)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to return all field properties in the response', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Property to sort by (supported: fieldLabel)', + }, + sortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order (ASC or DESC)', + }, + match: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Whether results must match all filters or any filter (all or any)', + }, + objectType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by associated object type (PROJECT, TASK, or USER)', + }, + fieldType: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Filter by field type (TEXT, MULTI_LINE_TEXT, YES_OR_NO, DATE, SINGLE_CHOICE, MULTIPLE_CHOICE, SINGLE_USER, MULTIPLE_USER, NUMBER, NOTE, RATING)', + }, + enabled: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Filter by enabled state', + }, + private: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Filter by privacy setting', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/fields`) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + if (params.sortBy) url.searchParams.set('sortBy', params.sortBy) + if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder) + if (params.match) url.searchParams.set('match', params.match) + if (params.objectType) url.searchParams.set('objectType.eq', params.objectType) + if (params.fieldType) url.searchParams.set('fieldType.eq', params.fieldType) + if (params.enabled != null) url.searchParams.set('enabled.eq', String(params.enabled)) + if (params.private != null) url.searchParams.set('private.eq', String(params.private)) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const fields = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + fields: fields.map(mapField), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + fields: { + type: 'array', + description: 'List of fields', + items: { type: 'object', properties: FIELD_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for the result set', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/list_invoices.ts b/apps/sim/tools/rocketlane/list_invoices.ts new file mode 100644 index 00000000000..915d45dd5c7 --- /dev/null +++ b/apps/sim/tools/rocketlane/list_invoices.ts @@ -0,0 +1,481 @@ +import { + INVOICE_OUTPUT_PROPERTIES, + mapInvoice, + mapPagination, + PAGINATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneInvoiceListParams, + type RocketlaneInvoiceListResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListInvoicesTool: ToolConfig< + RocketlaneInvoiceListParams, + RocketlaneInvoiceListResponse +> = { + id: 'rocketlane_list_invoices', + name: 'Rocketlane List Invoices', + description: + 'Search invoices in Rocketlane with date, amount, company, invoice-number, and status filters, sorting, and pagination', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of invoices per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous response (valid for 15 minutes)', + }, + includeFields: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Optional fields to include in the response: notes, attachments', + items: { type: 'string' }, + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Return all fields in the response', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Field to sort by: createdAt or invoiceNumber', + }, + sortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order: ASC or DESC (defaults to DESC)', + }, + match: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Combine filters with AND (all) or OR (any); defaults to all', + }, + dateOfIssueEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by date of issue equal to this date (YYYY-MM-DD)', + }, + dateOfIssueGt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by date of issue greater than this date (YYYY-MM-DD)', + }, + dateOfIssueGe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by date of issue greater than or equal to this date (YYYY-MM-DD)', + }, + dateOfIssueLt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by date of issue less than this date (YYYY-MM-DD)', + }, + dateOfIssueLe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by date of issue less than or equal to this date (YYYY-MM-DD)', + }, + dueDateEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by due date equal to this date (YYYY-MM-DD)', + }, + dueDateGt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by due date greater than this date (YYYY-MM-DD)', + }, + dueDateGe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by due date greater than or equal to this date (YYYY-MM-DD)', + }, + dueDateLt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by due date less than this date (YYYY-MM-DD)', + }, + dueDateLe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by due date less than or equal to this date (YYYY-MM-DD)', + }, + amountEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by total amount equal to this value', + }, + amountGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by total amount greater than this value', + }, + amountGe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by total amount greater than or equal to this value', + }, + amountLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by total amount less than this value', + }, + amountLe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by total amount less than or equal to this value', + }, + amountOutstandingEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount outstanding equal to this value', + }, + amountOutstandingGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount outstanding greater than this value', + }, + amountOutstandingGe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount outstanding greater than or equal to this value', + }, + amountOutstandingLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount outstanding less than this value', + }, + amountOutstandingLe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount outstanding less than or equal to this value', + }, + amountPaidEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount paid equal to this value', + }, + amountPaidGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount paid greater than this value', + }, + amountPaidGe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount paid greater than or equal to this value', + }, + amountPaidLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount paid less than this value', + }, + amountPaidLe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount paid less than or equal to this value', + }, + amountWrittenOffEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount written off equal to this value', + }, + amountWrittenOffGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount written off greater than this value', + }, + amountWrittenOffGe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount written off greater than or equal to this value', + }, + amountWrittenOffLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount written off less than this value', + }, + amountWrittenOffLe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by amount written off less than or equal to this value', + }, + createdAtEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by created timestamp equal to this value (epoch milliseconds)', + }, + createdAtGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by created timestamp greater than this value (epoch milliseconds)', + }, + createdAtGe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'Filter by created timestamp greater than or equal to this value (epoch milliseconds)', + }, + createdAtLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter by created timestamp less than this value (epoch milliseconds)', + }, + createdAtLe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'Filter by created timestamp less than or equal to this value (epoch milliseconds)', + }, + companyIdEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return invoices that exactly match this customer company ID', + }, + companyIdOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated customer company IDs to match any of', + }, + companyIdNoneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated customer company IDs to match none of', + }, + invoiceNumberEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return invoices whose invoice number equals this value', + }, + invoiceNumberCn: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return invoices whose invoice number contains this text', + }, + invoiceNumberNc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return invoices whose invoice number does not contain this text', + }, + statusEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return invoices that equal this status (e.g. DRAFT)', + }, + statusOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated statuses to match any of (e.g. DRAFT,PAID)', + }, + statusNoneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated statuses to match none of (e.g. DRAFT,PAID)', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/invoices`) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + if (params.includeFields?.length) { + url.searchParams.set('includeFields', params.includeFields.join(',')) + } + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + if (params.sortBy) url.searchParams.set('sortBy', params.sortBy) + if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder) + if (params.match) url.searchParams.set('match', params.match) + if (params.dateOfIssueEq) url.searchParams.set('dateOfIssue.eq', params.dateOfIssueEq) + if (params.dateOfIssueGt) url.searchParams.set('dateOfIssue.gt', params.dateOfIssueGt) + if (params.dateOfIssueGe) url.searchParams.set('dateOfIssue.ge', params.dateOfIssueGe) + if (params.dateOfIssueLt) url.searchParams.set('dateOfIssue.lt', params.dateOfIssueLt) + if (params.dateOfIssueLe) url.searchParams.set('dateOfIssue.le', params.dateOfIssueLe) + if (params.dueDateEq) url.searchParams.set('dueDate.eq', params.dueDateEq) + if (params.dueDateGt) url.searchParams.set('dueDate.gt', params.dueDateGt) + if (params.dueDateGe) url.searchParams.set('dueDate.ge', params.dueDateGe) + if (params.dueDateLt) url.searchParams.set('dueDate.lt', params.dueDateLt) + if (params.dueDateLe) url.searchParams.set('dueDate.le', params.dueDateLe) + if (params.amountEq != null) url.searchParams.set('amount.eq', String(params.amountEq)) + if (params.amountGt != null) url.searchParams.set('amount.gt', String(params.amountGt)) + if (params.amountGe != null) url.searchParams.set('amount.ge', String(params.amountGe)) + if (params.amountLt != null) url.searchParams.set('amount.lt', String(params.amountLt)) + if (params.amountLe != null) url.searchParams.set('amount.le', String(params.amountLe)) + if (params.amountOutstandingEq != null) { + url.searchParams.set('amountOutstanding.eq', String(params.amountOutstandingEq)) + } + if (params.amountOutstandingGt != null) { + url.searchParams.set('amountOutstanding.gt', String(params.amountOutstandingGt)) + } + if (params.amountOutstandingGe != null) { + url.searchParams.set('amountOutstanding.ge', String(params.amountOutstandingGe)) + } + if (params.amountOutstandingLt != null) { + url.searchParams.set('amountOutstanding.lt', String(params.amountOutstandingLt)) + } + if (params.amountOutstandingLe != null) { + url.searchParams.set('amountOutstanding.le', String(params.amountOutstandingLe)) + } + if (params.amountPaidEq != null) { + url.searchParams.set('amountPaid.eq', String(params.amountPaidEq)) + } + if (params.amountPaidGt != null) { + url.searchParams.set('amountPaid.gt', String(params.amountPaidGt)) + } + if (params.amountPaidGe != null) { + url.searchParams.set('amountPaid.ge', String(params.amountPaidGe)) + } + if (params.amountPaidLt != null) { + url.searchParams.set('amountPaid.lt', String(params.amountPaidLt)) + } + if (params.amountPaidLe != null) { + url.searchParams.set('amountPaid.le', String(params.amountPaidLe)) + } + if (params.amountWrittenOffEq != null) { + url.searchParams.set('amountWrittenOff.eq', String(params.amountWrittenOffEq)) + } + if (params.amountWrittenOffGt != null) { + url.searchParams.set('amountWrittenOff.gt', String(params.amountWrittenOffGt)) + } + if (params.amountWrittenOffGe != null) { + url.searchParams.set('amountWrittenOff.ge', String(params.amountWrittenOffGe)) + } + if (params.amountWrittenOffLt != null) { + url.searchParams.set('amountWrittenOff.lt', String(params.amountWrittenOffLt)) + } + if (params.amountWrittenOffLe != null) { + url.searchParams.set('amountWrittenOff.le', String(params.amountWrittenOffLe)) + } + if (params.createdAtEq != null) { + url.searchParams.set('createdAt.eq', String(params.createdAtEq)) + } + if (params.createdAtGt != null) { + url.searchParams.set('createdAt.gt', String(params.createdAtGt)) + } + if (params.createdAtGe != null) { + url.searchParams.set('createdAt.ge', String(params.createdAtGe)) + } + if (params.createdAtLt != null) { + url.searchParams.set('createdAt.lt', String(params.createdAtLt)) + } + if (params.createdAtLe != null) { + url.searchParams.set('createdAt.le', String(params.createdAtLe)) + } + if (params.companyIdEq) url.searchParams.set('companyId.eq', params.companyIdEq) + if (params.companyIdOneOf) url.searchParams.set('companyId.oneOf', params.companyIdOneOf) + if (params.companyIdNoneOf) { + url.searchParams.set('companyId.noneOf', params.companyIdNoneOf) + } + if (params.invoiceNumberEq) { + url.searchParams.set('invoiceNumber.eq', params.invoiceNumberEq) + } + if (params.invoiceNumberCn) { + url.searchParams.set('invoiceNumber.cn', params.invoiceNumberCn) + } + if (params.invoiceNumberNc) { + url.searchParams.set('invoiceNumber.nc', params.invoiceNumberNc) + } + if (params.statusEq) url.searchParams.set('status.eq', params.statusEq) + if (params.statusOneOf) url.searchParams.set('status.oneOf', params.statusOneOf) + if (params.statusNoneOf) url.searchParams.set('status.noneOf', params.statusNoneOf) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const invoices = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + invoices: invoices.map(mapInvoice), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + invoices: { + type: 'array', + description: 'List of invoices', + items: { type: 'object', properties: INVOICE_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for the result set', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/list_phases.ts b/apps/sim/tools/rocketlane/list_phases.ts new file mode 100644 index 00000000000..7731f776b2e --- /dev/null +++ b/apps/sim/tools/rocketlane/list_phases.ts @@ -0,0 +1,136 @@ +import { + mapPagination, + mapPhase, + PAGINATION_OUTPUT_PROPERTIES, + PHASE_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneListPhasesParams, + type RocketlanePhaseListResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListPhasesTool: ToolConfig< + RocketlaneListPhasesParams, + RocketlanePhaseListResponse +> = { + id: 'rocketlane_list_phases', + name: 'Rocketlane List Phases', + description: + 'List phases of a Rocketlane project, with optional filters, sorting, and pagination', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the project to list phases for', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of phases per page', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token returned by a previous request (valid for 15 minutes)', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra phase properties to include in the response (supported: startDateActual, dueDateActual)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to return all phase properties in the response', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Property to sort by (phaseName, startDate, dueDate, startDateActual, dueDateActual)', + }, + sortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order (ASC or DESC)', + }, + match: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Whether results must match all filters or any filter (all or any)', + }, + phaseName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by exact phase name', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/phases`) + url.searchParams.set('projectId', String(params.projectId)) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + if (params.sortBy) url.searchParams.set('sortBy', params.sortBy) + if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder) + if (params.match) url.searchParams.set('match', params.match) + if (params.phaseName) url.searchParams.set('phaseName.eq', params.phaseName) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const phases = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + phases: phases.map(mapPhase), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + phases: { + type: 'array', + description: 'List of phases', + items: { type: 'object', properties: PHASE_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for the result set', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/list_placeholders.ts b/apps/sim/tools/rocketlane/list_placeholders.ts new file mode 100644 index 00000000000..6d1b2573f4b --- /dev/null +++ b/apps/sim/tools/rocketlane/list_placeholders.ts @@ -0,0 +1,71 @@ +import { + mapPagination, + mapPlaceholder, + PAGINATION_OUTPUT_PROPERTIES, + PLACEHOLDER_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneListPlaceholdersParams, + type RocketlanePlaceholderListResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListPlaceholdersTool: ToolConfig< + RocketlaneListPlaceholdersParams, + RocketlanePlaceholderListResponse +> = { + id: 'rocketlane_list_placeholders', + name: 'Rocketlane List Placeholders', + description: 'List the placeholders of a Rocketlane project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the project', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/projects/${encodeURIComponent(String(params.projectId))}/get-placeholders`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { + placeholders: Array.isArray(data?.data) ? data.data.map(mapPlaceholder) : [], + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + placeholders: { + type: 'array', + description: 'Placeholders of the project', + items: { type: 'object', properties: PLACEHOLDER_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for fetching further pages', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/list_projects.ts b/apps/sim/tools/rocketlane/list_projects.ts new file mode 100644 index 00000000000..1b587f27ff9 --- /dev/null +++ b/apps/sim/tools/rocketlane/list_projects.ts @@ -0,0 +1,224 @@ +import { + mapPagination, + mapProject, + PAGINATION_OUTPUT_PROPERTIES, + PROJECT_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneListProjectsParams, + type RocketlaneProjectListResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListProjectsTool: ToolConfig< + RocketlaneListProjectsParams, + RocketlaneProjectListResponse +> = { + id: 'rocketlane_list_projects', + name: 'Rocketlane List Projects', + description: + 'List Rocketlane projects with optional filters, sorting, and token-based pagination', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of projects per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous response (valid for 15 minutes)', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra fields to return in the response (e.g. budgetedHours,progressPercentage)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Return all fields in the response body', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Field to sort by: projectName, startDate, dueDate, startDateActual, dueDateActual, annualizedRecurringRevenue, or projectFee', + }, + sortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order: ASC or DESC (defaults to DESC)', + }, + match: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Combine filters with AND (all) or OR (any); defaults to all', + }, + projectNameContains: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return projects whose name contains this value', + }, + projectNameEquals: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return projects whose name exactly matches this value', + }, + statusEquals: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return projects with this status value', + }, + statusOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated status values; returns projects matching any of them', + }, + customerIdEquals: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return projects for this customer company ID', + }, + customerIdOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated customer company IDs; returns projects matching any of them', + }, + teamMemberIdEquals: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return projects that include this team member ID', + }, + contractTypeEquals: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Only return projects with this contract type: FIXED_FEE, TIME_AND_MATERIAL, SUBSCRIPTION, or NON_BILLABLE', + }, + includeArchived: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to include archived projects in the results', + }, + externalReferenceIdEquals: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return projects with this external reference ID', + }, + startDateAfter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return projects whose start date is after this date (YYYY-MM-DD)', + }, + startDateBefore: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return projects whose start date is before this date (YYYY-MM-DD)', + }, + dueDateAfter: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return projects whose due date is after this date (YYYY-MM-DD)', + }, + dueDateBefore: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only return projects whose due date is before this date (YYYY-MM-DD)', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/projects`) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + if (params.sortBy) url.searchParams.set('sortBy', params.sortBy) + if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder) + if (params.match) url.searchParams.set('match', params.match) + if (params.projectNameContains) + url.searchParams.set('projectName.cn', params.projectNameContains) + if (params.projectNameEquals) url.searchParams.set('projectName.eq', params.projectNameEquals) + if (params.statusEquals) url.searchParams.set('status.eq', params.statusEquals) + if (params.statusOneOf) url.searchParams.set('status.oneOf', params.statusOneOf) + if (params.customerIdEquals) url.searchParams.set('customerId.eq', params.customerIdEquals) + if (params.customerIdOneOf) url.searchParams.set('customerId.oneOf', params.customerIdOneOf) + if (params.teamMemberIdEquals) + url.searchParams.set('teamMemberId.eq', params.teamMemberIdEquals) + if (params.contractTypeEquals) + url.searchParams.set('contractType.eq', params.contractTypeEquals) + if (params.includeArchived != null) + url.searchParams.set('includeArchive.eq', String(params.includeArchived)) + if (params.externalReferenceIdEquals) + url.searchParams.set('externalReferenceId.eq', params.externalReferenceIdEquals) + if (params.startDateAfter) url.searchParams.set('startDate.gt', params.startDateAfter) + if (params.startDateBefore) url.searchParams.set('startDate.lt', params.startDateBefore) + if (params.dueDateAfter) url.searchParams.set('dueDate.gt', params.dueDateAfter) + if (params.dueDateBefore) url.searchParams.set('dueDate.lt', params.dueDateBefore) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { + projects: Array.isArray(data?.data) ? data.data.map(mapProject) : [], + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + projects: { + type: 'array', + description: 'List of projects', + items: { type: 'object', properties: PROJECT_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for fetching further pages', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/list_resource_allocations.ts b/apps/sim/tools/rocketlane/list_resource_allocations.ts new file mode 100644 index 00000000000..33c5edf8516 --- /dev/null +++ b/apps/sim/tools/rocketlane/list_resource_allocations.ts @@ -0,0 +1,209 @@ +import { + mapPagination, + mapResourceAllocation, + PAGINATION_OUTPUT_PROPERTIES, + RESOURCE_ALLOCATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneResourceAllocationListParams, + type RocketlaneResourceAllocationListResponse, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListResourceAllocationsTool: ToolConfig< + RocketlaneResourceAllocationListParams, + RocketlaneResourceAllocationListResponse +> = { + id: 'rocketlane_list_resource_allocations', + name: 'Rocketlane List Resource Allocations', + description: + 'List resource allocations in Rocketlane within a date range, with optional member, project, and placeholder filters, sorting, and pagination', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + startDate: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Return allocations that start on or after this date (YYYY-MM-DD)', + }, + endDate: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Return allocations that end on or before this date (YYYY-MM-DD)', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of allocations per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous response (valid for 15 minutes)', + }, + includeFields: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Optional fields to include in the response: member, task, placeholder, duration', + items: { type: 'string' }, + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Return all fields in the response', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Field to sort by: startDate, endDate, allocationType, or allocationFor', + }, + sortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order: ASC or DESC (defaults to DESC)', + }, + match: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Combine filters with AND (all) or OR (any); defaults to all', + }, + memberIdEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return allocations that exactly match this member ID', + }, + memberIdOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated member IDs to match any of', + }, + memberIdNoneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated member IDs to exclude', + }, + projectIdEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return allocations that exactly match this project ID', + }, + projectIdOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated project IDs to match any of', + }, + projectIdNoneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated project IDs to exclude', + }, + placeholderIdEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return allocations that exactly match this placeholder ID', + }, + placeholderIdOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated placeholder IDs to match any of', + }, + placeholderIdNoneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated placeholder IDs to exclude', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/resource-allocations`) + url.searchParams.set('startDate', params.startDate) + url.searchParams.set('endDate', params.endDate) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + if (params.includeFields?.length) { + url.searchParams.set('includeFields', params.includeFields.join(',')) + } + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + if (params.sortBy) url.searchParams.set('sortBy', params.sortBy) + if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder) + if (params.match) url.searchParams.set('match', params.match) + if (params.memberIdEq) url.searchParams.set('memberId.eq', params.memberIdEq) + if (params.memberIdOneOf) url.searchParams.set('memberId.oneOf', params.memberIdOneOf) + if (params.memberIdNoneOf) url.searchParams.set('memberId.noneOf', params.memberIdNoneOf) + if (params.projectIdEq) url.searchParams.set('projectId.eq', params.projectIdEq) + if (params.projectIdOneOf) url.searchParams.set('projectId.oneOf', params.projectIdOneOf) + if (params.projectIdNoneOf) { + url.searchParams.set('projectId.noneOf', params.projectIdNoneOf) + } + if (params.placeholderIdEq) { + url.searchParams.set('placeholderId.eq', params.placeholderIdEq) + } + if (params.placeholderIdOneOf) { + url.searchParams.set('placeholderId.oneOf', params.placeholderIdOneOf) + } + if (params.placeholderIdNoneOf) { + url.searchParams.set('placeholderId.noneOf', params.placeholderIdNoneOf) + } + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const allocations = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + allocations: allocations.map(mapResourceAllocation), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + allocations: { + type: 'array', + description: 'List of resource allocations', + items: { type: 'object', properties: RESOURCE_ALLOCATION_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for the result set', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/list_space_documents.ts b/apps/sim/tools/rocketlane/list_space_documents.ts new file mode 100644 index 00000000000..58c122abec4 --- /dev/null +++ b/apps/sim/tools/rocketlane/list_space_documents.ts @@ -0,0 +1,222 @@ +import { + mapPagination, + mapSpaceDocument, + PAGINATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneListSpaceDocumentsParams, + type RocketlaneListSpaceDocumentsResponse, + rocketlaneError, + rocketlaneHeaders, + SPACE_DOCUMENT_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListSpaceDocumentsTool: ToolConfig< + RocketlaneListSpaceDocumentsParams, + RocketlaneListSpaceDocumentsResponse +> = { + id: 'rocketlane_list_space_documents', + name: 'Rocketlane List Space Documents', + description: + 'List space documents in a Rocketlane project, with optional filters, sorting, and pagination', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the project whose space documents to list', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of space documents per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous request (valid for 15 minutes)', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Field to sort by (spaceTabName)', + }, + sortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order: ASC or DESC (defaults to DESC)', + }, + match: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'How to combine filters: all (AND) or any (OR); defaults to all', + }, + spaceDocumentNameEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents whose name exactly matches this value', + }, + spaceDocumentNameCn: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents whose name contains this value', + }, + spaceDocumentNameNc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Exclude space documents whose name contains this value', + }, + spaceIdEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents in the space with this ID', + }, + createdAtGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents created after this time (epoch millis)', + }, + createdAtEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents created at exactly this time (epoch millis)', + }, + createdAtLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents created before this time (epoch millis)', + }, + createdAtGe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents created at or after this time (epoch millis)', + }, + createdAtLe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents created at or before this time (epoch millis)', + }, + updatedAtGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents updated after this time (epoch millis)', + }, + updatedAtEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents updated at exactly this time (epoch millis)', + }, + updatedAtLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents updated before this time (epoch millis)', + }, + updatedAtGe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents updated at or after this time (epoch millis)', + }, + updatedAtLe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include space documents updated at or before this time (epoch millis)', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/space-documents`) + url.searchParams.set('projectId', String(params.projectId)) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + if (params.sortBy) url.searchParams.set('sortBy', params.sortBy) + if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder) + if (params.match) url.searchParams.set('match', params.match) + if (params.spaceDocumentNameEq) + url.searchParams.set('spaceDocumentName.eq', params.spaceDocumentNameEq) + if (params.spaceDocumentNameCn) + url.searchParams.set('spaceDocumentName.cn', params.spaceDocumentNameCn) + if (params.spaceDocumentNameNc) + url.searchParams.set('spaceDocumentName.nc', params.spaceDocumentNameNc) + if (params.spaceIdEq != null) url.searchParams.set('spaceId.eq', String(params.spaceIdEq)) + if (params.createdAtGt != null) + url.searchParams.set('createdAt.gt', String(params.createdAtGt)) + if (params.createdAtEq != null) + url.searchParams.set('createdAt.eq', String(params.createdAtEq)) + if (params.createdAtLt != null) + url.searchParams.set('createdAt.lt', String(params.createdAtLt)) + if (params.createdAtGe != null) + url.searchParams.set('createdAt.ge', String(params.createdAtGe)) + if (params.createdAtLe != null) + url.searchParams.set('createdAt.le', String(params.createdAtLe)) + if (params.updatedAtGt != null) + url.searchParams.set('updatedAt.gt', String(params.updatedAtGt)) + if (params.updatedAtEq != null) + url.searchParams.set('updatedAt.eq', String(params.updatedAtEq)) + if (params.updatedAtLt != null) + url.searchParams.set('updatedAt.lt', String(params.updatedAtLt)) + if (params.updatedAtGe != null) + url.searchParams.set('updatedAt.ge', String(params.updatedAtGe)) + if (params.updatedAtLe != null) + url.searchParams.set('updatedAt.le', String(params.updatedAtLe)) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const spaceDocuments = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + spaceDocuments: spaceDocuments.map(mapSpaceDocument), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + spaceDocuments: { + type: 'array', + description: 'List of space documents', + items: { type: 'object', properties: SPACE_DOCUMENT_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/list_spaces.ts b/apps/sim/tools/rocketlane/list_spaces.ts new file mode 100644 index 00000000000..d363123cba7 --- /dev/null +++ b/apps/sim/tools/rocketlane/list_spaces.ts @@ -0,0 +1,212 @@ +import { + mapPagination, + mapSpace, + PAGINATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneListSpacesParams, + type RocketlaneListSpacesResponse, + rocketlaneError, + rocketlaneHeaders, + SPACE_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListSpacesTool: ToolConfig< + RocketlaneListSpacesParams, + RocketlaneListSpacesResponse +> = { + id: 'rocketlane_list_spaces', + name: 'Rocketlane List Spaces', + description: + 'List spaces in a Rocketlane project, with optional filters, sorting, and pagination', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the project whose spaces to list', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of spaces per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous request (valid for 15 minutes)', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Field to sort by (spaceName)', + }, + sortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order: ASC or DESC (defaults to DESC)', + }, + match: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'How to combine filters: all (AND) or any (OR); defaults to all', + }, + spaceNameEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include spaces whose name exactly matches this value', + }, + spaceNameCn: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include spaces whose name contains this value', + }, + spaceNameNc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Exclude spaces whose name contains this value', + }, + createdAtGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include spaces created after this time (epoch millis)', + }, + createdAtEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include spaces created at exactly this time (epoch millis)', + }, + createdAtLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include spaces created before this time (epoch millis)', + }, + createdAtGe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include spaces created at or after this time (epoch millis)', + }, + createdAtLe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include spaces created at or before this time (epoch millis)', + }, + updatedAtGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include spaces updated after this time (epoch millis)', + }, + updatedAtEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include spaces updated at exactly this time (epoch millis)', + }, + updatedAtLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include spaces updated before this time (epoch millis)', + }, + updatedAtGe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include spaces updated at or after this time (epoch millis)', + }, + updatedAtLe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include spaces updated at or before this time (epoch millis)', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/spaces`) + url.searchParams.set('projectId', String(params.projectId)) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + if (params.sortBy) url.searchParams.set('sortBy', params.sortBy) + if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder) + if (params.match) url.searchParams.set('match', params.match) + if (params.spaceNameEq) url.searchParams.set('spaceName.eq', params.spaceNameEq) + if (params.spaceNameCn) url.searchParams.set('spaceName.cn', params.spaceNameCn) + if (params.spaceNameNc) url.searchParams.set('spaceName.nc', params.spaceNameNc) + if (params.createdAtGt != null) + url.searchParams.set('createdAt.gt', String(params.createdAtGt)) + if (params.createdAtEq != null) + url.searchParams.set('createdAt.eq', String(params.createdAtEq)) + if (params.createdAtLt != null) + url.searchParams.set('createdAt.lt', String(params.createdAtLt)) + if (params.createdAtGe != null) + url.searchParams.set('createdAt.ge', String(params.createdAtGe)) + if (params.createdAtLe != null) + url.searchParams.set('createdAt.le', String(params.createdAtLe)) + if (params.updatedAtGt != null) + url.searchParams.set('updatedAt.gt', String(params.updatedAtGt)) + if (params.updatedAtEq != null) + url.searchParams.set('updatedAt.eq', String(params.updatedAtEq)) + if (params.updatedAtLt != null) + url.searchParams.set('updatedAt.lt', String(params.updatedAtLt)) + if (params.updatedAtGe != null) + url.searchParams.set('updatedAt.ge', String(params.updatedAtGe)) + if (params.updatedAtLe != null) + url.searchParams.set('updatedAt.le', String(params.updatedAtLe)) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const spaces = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + spaces: spaces.map(mapSpace), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + spaces: { + type: 'array', + description: 'List of spaces', + items: { type: 'object', properties: SPACE_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/list_tasks.ts b/apps/sim/tools/rocketlane/list_tasks.ts new file mode 100644 index 00000000000..bf719ca2ef9 --- /dev/null +++ b/apps/sim/tools/rocketlane/list_tasks.ts @@ -0,0 +1,209 @@ +import { + mapPagination, + mapTask, + PAGINATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneListTasksParams, + type RocketlaneTaskListResponse, + rocketlaneError, + rocketlaneHeaders, + TASK_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListTasksTool: ToolConfig< + RocketlaneListTasksParams, + RocketlaneTaskListResponse +> = { + id: 'rocketlane_list_tasks', + name: 'Rocketlane List Tasks', + description: 'Retrieve all Rocketlane tasks with optional filters, sorting, and pagination', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of tasks per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Token pointing to the next page of results, from a previous response', + }, + includeFields: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Extra fields to include in the response (startDateActual, dueDateActual, type, phase, assignees, followers, dependencies, billable, csatEnabled, priority, timeEntryCategory, financialsBudget, taskPrivateNote, parent, externalReferenceId)', + items: { type: 'string' }, + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether all fields should be returned in the response', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Field to sort by: taskName, startDate, dueDate, startDateActual, or dueDateActual', + }, + sortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order: ASC or DESC', + }, + match: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'How multiple filters combine: all (AND) or any (OR)', + }, + projectId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter tasks by project ID', + }, + phaseId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Filter tasks by phase ID', + }, + taskName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter tasks whose name exactly matches this value', + }, + taskNameContains: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter tasks whose name contains this value', + }, + taskStatus: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter tasks by task status value', + }, + startDateFrom: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter tasks with a start date on or after this date (YYYY-MM-DD)', + }, + startDateTo: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter tasks with a start date on or before this date (YYYY-MM-DD)', + }, + dueDateFrom: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter tasks with a due date on or after this date (YYYY-MM-DD)', + }, + dueDateTo: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter tasks with a due date on or before this date (YYYY-MM-DD)', + }, + includeArchive: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether archived tasks should be included in the results', + }, + externalReferenceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter tasks by external reference identifier', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/tasks`) + if (params.pageSize !== undefined) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + if (params.includeFields && params.includeFields.length > 0) { + url.searchParams.set('includeFields', params.includeFields.join(',')) + } + if (params.includeAllFields !== undefined) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + if (params.sortBy) url.searchParams.set('sortBy', params.sortBy) + if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder) + if (params.match) url.searchParams.set('match', params.match) + if (params.projectId !== undefined) { + url.searchParams.set('projectId.eq', String(params.projectId)) + } + if (params.phaseId !== undefined) { + url.searchParams.set('phaseId.eq', String(params.phaseId)) + } + if (params.taskName) url.searchParams.set('taskName.eq', params.taskName) + if (params.taskNameContains) url.searchParams.set('taskName.cn', params.taskNameContains) + if (params.taskStatus) url.searchParams.set('task.status.eq', params.taskStatus) + if (params.startDateFrom) url.searchParams.set('startDate.ge', params.startDateFrom) + if (params.startDateTo) url.searchParams.set('startDate.le', params.startDateTo) + if (params.dueDateFrom) url.searchParams.set('dueDate.ge', params.dueDateFrom) + if (params.dueDateTo) url.searchParams.set('dueDate.le', params.dueDateTo) + if (params.includeArchive !== undefined) { + url.searchParams.set('includeArchive.eq', String(params.includeArchive)) + } + if (params.externalReferenceId) { + url.searchParams.set('externalReferenceId.eq', params.externalReferenceId) + } + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const tasks = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + tasks: tasks.map(mapTask), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + tasks: { + type: 'array', + description: 'List of tasks matching the filters', + items: { type: 'object', properties: TASK_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for the result set', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/list_time_entries.ts b/apps/sim/tools/rocketlane/list_time_entries.ts new file mode 100644 index 00000000000..bf9794cb92b --- /dev/null +++ b/apps/sim/tools/rocketlane/list_time_entries.ts @@ -0,0 +1,315 @@ +import { + mapPagination, + mapTimeEntry, + PAGINATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneListTimeEntriesParams, + type RocketlaneTimeEntryListResponse, + rocketlaneError, + rocketlaneHeaders, + TIME_ENTRY_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListTimeEntriesTool: ToolConfig< + RocketlaneListTimeEntriesParams, + RocketlaneTimeEntryListResponse +> = { + id: 'rocketlane_list_time_entries', + name: 'Rocketlane List Time Entries', + description: + 'List Rocketlane time entries with optional filters, sorting, and cursor-based pagination', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + dateEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries on this exact date (YYYY-MM-DD)', + }, + dateGt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries after this date (YYYY-MM-DD)', + }, + dateGe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries on or after this date (YYYY-MM-DD)', + }, + dateLt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries before this date (YYYY-MM-DD)', + }, + dateLe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries on or before this date (YYYY-MM-DD)', + }, + projectIdEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries for this project ID', + }, + taskIdEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries for this task ID', + }, + projectPhaseIdEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries for this project phase ID', + }, + categoryIdEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries with this category ID', + }, + userIdEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries belonging to this user ID', + }, + emailIdEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries belonging to the user with this exact email', + }, + emailIdCn: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries whose user email contains this text', + }, + sourceTypeEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Only entries with this source type (GOOGLE_CALENDAR, OUTLOOK_CALENDAR, TASK, PROJECT, PHASE, ADHOC, MILESTONE)', + }, + activityNameEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries with this exact activity name', + }, + activityNameCn: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries whose activity name contains this text', + }, + approvalStatusEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Only entries with this approval status (NOT_SUBMITTED, SUBMITTED, APPROVED, REJECTED)', + }, + billableEq: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Only billable (true) or non-billable (false) entries', + }, + includeDeletedEq: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether deleted time entries are included in the response', + }, + submittedByEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries submitted by this user ID', + }, + approvedByEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries approved by this user ID', + }, + rejectedByEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries rejected by this user ID', + }, + createdAtGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries created after this epoch-millisecond timestamp', + }, + createdAtLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries created before this epoch-millisecond timestamp', + }, + updatedAtGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries updated after this epoch-millisecond timestamp', + }, + updatedAtLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries updated before this epoch-millisecond timestamp', + }, + match: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'How to combine filters: all (AND, default) or any (OR)', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Field to sort by (minutes, date, id, billable)', + }, + sortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order: ASC or DESC (default DESC)', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra fields to include in the response (notes, sourceType, deleted, status, submittedBy, submittedAt, approvedBy, approvedAt, rejectedBy, rejectedAt, costRate, billRate)', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of entries per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous response for fetching the next page', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/time-entries`) + if (params.dateEq) url.searchParams.set('date.eq', params.dateEq) + if (params.dateGt) url.searchParams.set('date.gt', params.dateGt) + if (params.dateGe) url.searchParams.set('date.ge', params.dateGe) + if (params.dateLt) url.searchParams.set('date.lt', params.dateLt) + if (params.dateLe) url.searchParams.set('date.le', params.dateLe) + if (params.projectIdEq != null) { + url.searchParams.set('projectId.eq', String(params.projectIdEq)) + } + if (params.taskIdEq != null) url.searchParams.set('taskId.eq', String(params.taskIdEq)) + if (params.projectPhaseIdEq != null) { + url.searchParams.set('projectPhase.eq', String(params.projectPhaseIdEq)) + } + if (params.categoryIdEq != null) { + url.searchParams.set('category.eq', String(params.categoryIdEq)) + } + if (params.userIdEq != null) url.searchParams.set('user.eq', String(params.userIdEq)) + if (params.emailIdEq) url.searchParams.set('emailId.eq', params.emailIdEq) + if (params.emailIdCn) url.searchParams.set('emailId.cn', params.emailIdCn) + if (params.sourceTypeEq) url.searchParams.set('sourceType.eq', params.sourceTypeEq) + if (params.activityNameEq) url.searchParams.set('activityName.eq', params.activityNameEq) + if (params.activityNameCn) url.searchParams.set('activityName.cn', params.activityNameCn) + if (params.approvalStatusEq) { + url.searchParams.set('approvalStatus.eq', params.approvalStatusEq) + } + if (params.billableEq != null) { + url.searchParams.set('billable.eq', String(params.billableEq)) + } + if (params.includeDeletedEq != null) { + url.searchParams.set('includeDeleted.eq', String(params.includeDeletedEq)) + } + if (params.submittedByEq != null) { + url.searchParams.set('submittedBy.eq', String(params.submittedByEq)) + } + if (params.approvedByEq != null) { + url.searchParams.set('approvedBy.eq', String(params.approvedByEq)) + } + if (params.rejectedByEq != null) { + url.searchParams.set('rejectedBy.eq', String(params.rejectedByEq)) + } + if (params.createdAtGt != null) { + url.searchParams.set('createdAt.gt', String(params.createdAtGt)) + } + if (params.createdAtLt != null) { + url.searchParams.set('createdAt.lt', String(params.createdAtLt)) + } + if (params.updatedAtGt != null) { + url.searchParams.set('updatedAt.gt', String(params.updatedAtGt)) + } + if (params.updatedAtLt != null) { + url.searchParams.set('updatedAt.lt', String(params.updatedAtLt)) + } + if (params.match) url.searchParams.set('match', params.match) + if (params.sortBy) url.searchParams.set('sortBy', params.sortBy) + if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const entries = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + timeEntries: entries.map(mapTimeEntry), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + timeEntries: { + type: 'array', + description: 'List of time entries matching the filters', + items: { type: 'object', properties: TIME_ENTRY_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for the result set', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/list_time_entry_categories.ts b/apps/sim/tools/rocketlane/list_time_entry_categories.ts new file mode 100644 index 00000000000..e65c751b37c --- /dev/null +++ b/apps/sim/tools/rocketlane/list_time_entry_categories.ts @@ -0,0 +1,89 @@ +import { + mapPagination, + mapTimeEntryCategory, + PAGINATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneListTimeEntryCategoriesParams, + type RocketlaneTimeEntryCategory, + type RocketlaneTimeEntryCategoryListResponse, + rocketlaneError, + rocketlaneHeaders, + TIME_ENTRY_CATEGORY_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListTimeEntryCategoriesTool: ToolConfig< + RocketlaneListTimeEntryCategoriesParams, + RocketlaneTimeEntryCategoryListResponse +> = { + id: 'rocketlane_list_time_entry_categories', + name: 'Rocketlane List Time Entry Categories', + description: 'List the time entry categories configured in Rocketlane', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of categories per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous response for fetching the next page', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/time-entries/categories`) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const rawCategories = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + categories: rawCategories + .map(mapTimeEntryCategory) + .filter( + ( + category: RocketlaneTimeEntryCategory | null + ): category is RocketlaneTimeEntryCategory => category !== null + ), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + categories: { + type: 'array', + description: 'List of time entry categories', + items: { type: 'object', properties: TIME_ENTRY_CATEGORY_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for the result set', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/list_time_offs.ts b/apps/sim/tools/rocketlane/list_time_offs.ts new file mode 100644 index 00000000000..19aac6a4682 --- /dev/null +++ b/apps/sim/tools/rocketlane/list_time_offs.ts @@ -0,0 +1,259 @@ +import { + mapPagination, + mapTimeOff, + PAGINATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneTimeOffListParams, + type RocketlaneTimeOffListResponse, + rocketlaneError, + rocketlaneHeaders, + TIME_OFF_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListTimeOffsTool: ToolConfig< + RocketlaneTimeOffListParams, + RocketlaneTimeOffListResponse +> = { + id: 'rocketlane_list_time_offs', + name: 'Rocketlane List Time-Offs', + description: + 'List time-offs in Rocketlane with optional date, type, and user filters, sorting, and pagination', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of time-offs per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous response (valid for 15 minutes)', + }, + includeFields: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Optional fields to include in the response: note, notifyUsers', + items: { type: 'string' }, + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Return all fields in the response', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Field to sort by: startDate, endDate, or createdAt', + }, + sortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order: ASC or DESC (defaults to DESC)', + }, + match: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Combine filters with AND (all) or OR (any); defaults to all', + }, + startDateGt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return time-offs with start dates greater than this date (YYYY-MM-DD)', + }, + startDateEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return time-offs with start dates equal to this date (YYYY-MM-DD)', + }, + startDateLt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return time-offs with start dates lesser than this date (YYYY-MM-DD)', + }, + startDateGe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Return time-offs with start dates greater than or equal to this date (YYYY-MM-DD)', + }, + startDateLe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Return time-offs with start dates lesser than or equal to this date (YYYY-MM-DD)', + }, + endDateGt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return time-offs with end dates greater than this date (YYYY-MM-DD)', + }, + endDateEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return time-offs with end dates equal to this date (YYYY-MM-DD)', + }, + endDateLt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return time-offs with end dates lesser than this date (YYYY-MM-DD)', + }, + endDateGe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Return time-offs with end dates greater than or equal to this date (YYYY-MM-DD)', + }, + endDateLe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return time-offs with end dates lesser than or equal to this date (YYYY-MM-DD)', + }, + typeEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return time-offs matching this type: FULL_DAY, HALF_DAY, or CUSTOM', + }, + typeOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated time-off types to match any of (FULL_DAY, HALF_DAY, CUSTOM)', + }, + typeNoneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated time-off types to match none of (FULL_DAY, HALF_DAY, CUSTOM)', + }, + userIdEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return time-offs that exactly match this user ID', + }, + userIdOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated user IDs to match any of', + }, + userIdNoneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated user IDs to match none of', + }, + emailIdEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Return time-offs that exactly match this user email', + }, + emailIdOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated user emails to match any of', + }, + emailIdNoneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated user emails to match none of', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/time-offs`) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + if (params.includeFields?.length) { + url.searchParams.set('includeFields', params.includeFields.join(',')) + } + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + if (params.sortBy) url.searchParams.set('sortBy', params.sortBy) + if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder) + if (params.match) url.searchParams.set('match', params.match) + if (params.startDateGt) url.searchParams.set('startDate.gt', params.startDateGt) + if (params.startDateEq) url.searchParams.set('startDate.eq', params.startDateEq) + if (params.startDateLt) url.searchParams.set('startDate.lt', params.startDateLt) + if (params.startDateGe) url.searchParams.set('startDate.ge', params.startDateGe) + if (params.startDateLe) url.searchParams.set('startDate.le', params.startDateLe) + if (params.endDateGt) url.searchParams.set('endDate.gt', params.endDateGt) + if (params.endDateEq) url.searchParams.set('endDate.eq', params.endDateEq) + if (params.endDateLt) url.searchParams.set('endDate.lt', params.endDateLt) + if (params.endDateGe) url.searchParams.set('endDate.ge', params.endDateGe) + if (params.endDateLe) url.searchParams.set('endDate.le', params.endDateLe) + if (params.typeEq) url.searchParams.set('type.eq', params.typeEq) + if (params.typeOneOf) url.searchParams.set('type.oneOf', params.typeOneOf) + if (params.typeNoneOf) url.searchParams.set('type.noneOf', params.typeNoneOf) + if (params.userIdEq) url.searchParams.set('userId.eq', params.userIdEq) + if (params.userIdOneOf) url.searchParams.set('userId.oneOf', params.userIdOneOf) + if (params.userIdNoneOf) url.searchParams.set('userId.noneOf', params.userIdNoneOf) + if (params.emailIdEq) url.searchParams.set('emailId.eq', params.emailIdEq) + if (params.emailIdOneOf) url.searchParams.set('emailId.oneOf', params.emailIdOneOf) + if (params.emailIdNoneOf) url.searchParams.set('emailId.noneOf', params.emailIdNoneOf) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const timeOffs = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + timeOffs: timeOffs.map(mapTimeOff), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + timeOffs: { + type: 'array', + description: 'List of time-offs', + items: { type: 'object', properties: TIME_OFF_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for the result set', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/list_users.ts b/apps/sim/tools/rocketlane/list_users.ts new file mode 100644 index 00000000000..e8c2401f7d4 --- /dev/null +++ b/apps/sim/tools/rocketlane/list_users.ts @@ -0,0 +1,389 @@ +import { + mapPagination, + mapUser, + PAGINATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneListUsersParams, + type RocketlaneListUsersResponse, + rocketlaneError, + rocketlaneHeaders, + USER_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneListUsersTool: ToolConfig< + RocketlaneListUsersParams, + RocketlaneListUsersResponse +> = { + id: 'rocketlane_list_users', + name: 'Rocketlane List Users', + description: + 'List users in your Rocketlane account, with optional filters, sorting, and pagination', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of users per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous request (valid for 15 minutes)', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra fields to include: role, company, permission, holidayCalendar, capacityInMinutes, profilePictureUrl', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to include all fields in the response', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Field to sort by: email, firstName, lastName, type, status, or capacityInMinutes', + }, + sortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order: ASC or DESC (defaults to DESC)', + }, + match: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'How to combine filters: all (AND) or any (OR); defaults to all', + }, + firstNameEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include users whose first name exactly matches this value', + }, + firstNameCn: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include users whose first name contains this value', + }, + firstNameNc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Exclude users whose first name contains this value', + }, + lastNameEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include users whose last name exactly matches this value', + }, + lastNameCn: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include users whose last name contains this value', + }, + lastNameNc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Exclude users whose last name contains this value', + }, + emailEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include users whose email exactly matches this value', + }, + emailCn: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include users whose email contains this value', + }, + emailNc: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Exclude users whose email contains this value', + }, + statusEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include users with this status: INACTIVE, INVITED, ACTIVE, or PASSIVE', + }, + statusOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated statuses; only include users matching one of them (INACTIVE, INVITED, ACTIVE, PASSIVE)', + }, + statusNoneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated statuses; exclude users matching any of them (INACTIVE, INVITED, ACTIVE, PASSIVE)', + }, + typeEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Only include users of this type: TEAM_MEMBER, PARTNER, CUSTOMER, or EXTERNAL_PARTNER', + }, + typeOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated types; only include users matching one of them (TEAM_MEMBER, PARTNER, CUSTOMER, EXTERNAL_PARTNER)', + }, + roleIdEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include users with this role ID', + }, + roleIdOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated role IDs; only include users matching one of them', + }, + roleIdNoneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated role IDs; exclude users matching any of them', + }, + permissionIdEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only include users with this permission ID', + }, + permissionIdOneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated permission IDs; only include users matching one of them', + }, + permissionIdNoneOf: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated permission IDs; exclude users matching any of them', + }, + capacityInMinutesEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users whose capacity in minutes equals this value', + }, + capacityInMinutesGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users whose capacity in minutes is greater than this value', + }, + capacityInMinutesGe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'Only include users whose capacity in minutes is greater than or equal to this value', + }, + capacityInMinutesLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users whose capacity in minutes is less than this value', + }, + capacityInMinutesLe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'Only include users whose capacity in minutes is less than or equal to this value', + }, + createdAtGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users created after this time (epoch millis)', + }, + createdAtEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users created at exactly this time (epoch millis)', + }, + createdAtLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users created before this time (epoch millis)', + }, + createdAtGe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users created at or after this time (epoch millis)', + }, + createdAtLe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users created at or before this time (epoch millis)', + }, + updatedAtGt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users updated after this time (epoch millis)', + }, + updatedAtEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users updated at exactly this time (epoch millis)', + }, + updatedAtLt: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users updated before this time (epoch millis)', + }, + updatedAtGe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users updated at or after this time (epoch millis)', + }, + updatedAtLe: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only include users updated at or before this time (epoch millis)', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/users`) + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + if (params.sortBy) url.searchParams.set('sortBy', params.sortBy) + if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder) + if (params.match) url.searchParams.set('match', params.match) + if (params.firstNameEq) url.searchParams.set('firstName.eq', params.firstNameEq) + if (params.firstNameCn) url.searchParams.set('firstName.cn', params.firstNameCn) + if (params.firstNameNc) url.searchParams.set('firstName.nc', params.firstNameNc) + if (params.lastNameEq) url.searchParams.set('lastName.eq', params.lastNameEq) + if (params.lastNameCn) url.searchParams.set('lastName.cn', params.lastNameCn) + if (params.lastNameNc) url.searchParams.set('lastName.nc', params.lastNameNc) + if (params.emailEq) url.searchParams.set('email.eq', params.emailEq) + if (params.emailCn) url.searchParams.set('email.cn', params.emailCn) + if (params.emailNc) url.searchParams.set('email.nc', params.emailNc) + if (params.statusEq) url.searchParams.set('status.eq', params.statusEq) + if (params.statusOneOf) url.searchParams.set('status.oneOf', params.statusOneOf) + if (params.statusNoneOf) url.searchParams.set('status.noneOf', params.statusNoneOf) + if (params.typeEq) url.searchParams.set('type.eq', params.typeEq) + if (params.typeOneOf) url.searchParams.set('type.oneOf', params.typeOneOf) + if (params.roleIdEq) url.searchParams.set('roleId.eq', params.roleIdEq) + if (params.roleIdOneOf) url.searchParams.set('roleId.oneOf', params.roleIdOneOf) + if (params.roleIdNoneOf) url.searchParams.set('roleId.noneOf', params.roleIdNoneOf) + if (params.permissionIdEq) url.searchParams.set('permissionId.eq', params.permissionIdEq) + if (params.permissionIdOneOf) + url.searchParams.set('permissionId.oneOf', params.permissionIdOneOf) + if (params.permissionIdNoneOf) + url.searchParams.set('permissionId.noneOf', params.permissionIdNoneOf) + if (params.capacityInMinutesEq != null) + url.searchParams.set('capacityInMinutes.eq', String(params.capacityInMinutesEq)) + if (params.capacityInMinutesGt != null) + url.searchParams.set('capacityInMinutes.gt', String(params.capacityInMinutesGt)) + if (params.capacityInMinutesGe != null) + url.searchParams.set('capacityInMinutes.ge', String(params.capacityInMinutesGe)) + if (params.capacityInMinutesLt != null) + url.searchParams.set('capacityInMinutes.lt', String(params.capacityInMinutesLt)) + if (params.capacityInMinutesLe != null) + url.searchParams.set('capacityInMinutes.le', String(params.capacityInMinutesLe)) + if (params.createdAtGt != null) + url.searchParams.set('createdAt.gt', String(params.createdAtGt)) + if (params.createdAtEq != null) + url.searchParams.set('createdAt.eq', String(params.createdAtEq)) + if (params.createdAtLt != null) + url.searchParams.set('createdAt.lt', String(params.createdAtLt)) + if (params.createdAtGe != null) + url.searchParams.set('createdAt.ge', String(params.createdAtGe)) + if (params.createdAtLe != null) + url.searchParams.set('createdAt.le', String(params.createdAtLe)) + if (params.updatedAtGt != null) + url.searchParams.set('updatedAt.gt', String(params.updatedAtGt)) + if (params.updatedAtEq != null) + url.searchParams.set('updatedAt.eq', String(params.updatedAtEq)) + if (params.updatedAtLt != null) + url.searchParams.set('updatedAt.lt', String(params.updatedAtLt)) + if (params.updatedAtGe != null) + url.searchParams.set('updatedAt.ge', String(params.updatedAtGe)) + if (params.updatedAtLe != null) + url.searchParams.set('updatedAt.le', String(params.updatedAtLe)) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const users = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + users: users.map(mapUser), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + users: { + type: 'array', + description: 'List of users', + items: { type: 'object', properties: USER_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/move_task_to_phase.ts b/apps/sim/tools/rocketlane/move_task_to_phase.ts new file mode 100644 index 00000000000..7c211906bd4 --- /dev/null +++ b/apps/sim/tools/rocketlane/move_task_to_phase.ts @@ -0,0 +1,70 @@ +import { + mapTask, + ROCKETLANE_API_BASE, + type RocketlaneMoveTaskToPhaseParams, + type RocketlaneTaskResponse, + rocketlaneError, + rocketlaneHeaders, + TASK_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneMoveTaskToPhaseTool: ToolConfig< + RocketlaneMoveTaskToPhaseParams, + RocketlaneTaskResponse +> = { + id: 'rocketlane_move_task_to_phase', + name: 'Rocketlane Move Task To Phase', + description: 'Move a Rocketlane task to a given phase, associating the task with that phase', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + taskId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the task to move', + }, + phaseId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the phase to move the task to', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/tasks/${encodeURIComponent(String(params.taskId))}/move-phase`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + phase: { phaseId: params.phaseId }, + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { task: mapTask(data) }, + } + }, + + outputs: { + task: { + type: 'object', + description: 'The task with its updated phase', + properties: TASK_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/remove_project_members.ts b/apps/sim/tools/rocketlane/remove_project_members.ts new file mode 100644 index 00000000000..669383a9a47 --- /dev/null +++ b/apps/sim/tools/rocketlane/remove_project_members.ts @@ -0,0 +1,84 @@ +import { + mapProject, + PROJECT_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneProjectResponse, + type RocketlaneRemoveProjectMembersParams, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneRemoveProjectMembersTool: ToolConfig< + RocketlaneRemoveProjectMembersParams, + RocketlaneProjectResponse +> = { + id: 'rocketlane_remove_project_members', + name: 'Rocketlane Remove Project Members', + description: 'Remove team members from a Rocketlane project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the project', + }, + memberUserIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'User IDs of team members to remove (at least one of memberUserIds or memberEmailIds is required)', + items: { type: 'number', description: 'User ID of a team member' }, + }, + memberEmailIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Emails of team members to remove (at least one of memberUserIds or memberEmailIds is required)', + items: { type: 'string', description: 'Email of a team member' }, + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/projects/${encodeURIComponent(String(params.projectId))}/remove-members`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const members = [ + ...(params.memberUserIds ?? []).map((userId) => ({ userId })), + ...(params.memberEmailIds ?? []).map((emailId) => ({ emailId })), + ] + return { members } + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { project: mapProject(data) }, + } + }, + + outputs: { + project: { + type: 'object', + description: 'The project with its updated team members', + properties: PROJECT_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/remove_task_assignees.ts b/apps/sim/tools/rocketlane/remove_task_assignees.ts new file mode 100644 index 00000000000..c184e7e5433 --- /dev/null +++ b/apps/sim/tools/rocketlane/remove_task_assignees.ts @@ -0,0 +1,79 @@ +import { + buildTaskMembers, + mapTask, + ROCKETLANE_API_BASE, + type RocketlaneTaskAssigneesParams, + type RocketlaneTaskResponse, + rocketlaneError, + rocketlaneHeaders, + TASK_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneRemoveTaskAssigneesTool: ToolConfig< + RocketlaneTaskAssigneesParams, + RocketlaneTaskResponse +> = { + id: 'rocketlane_remove_task_assignees', + name: 'Rocketlane Remove Task Assignees', + description: 'Remove assignees from a Rocketlane task', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + taskId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the task to remove assignees from', + }, + memberUserIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'User IDs of members to remove from the assignees', + items: { type: 'number' }, + }, + memberEmailIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Email addresses of members to remove from the assignees', + items: { type: 'string' }, + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/tasks/${encodeURIComponent(String(params.taskId))}/remove-assignees`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + members: buildTaskMembers(params.memberUserIds, params.memberEmailIds), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { task: mapTask(data) }, + } + }, + + outputs: { + task: { + type: 'object', + description: 'The task with its updated assignees', + properties: TASK_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/remove_task_dependencies.ts b/apps/sim/tools/rocketlane/remove_task_dependencies.ts new file mode 100644 index 00000000000..7e79e9713d0 --- /dev/null +++ b/apps/sim/tools/rocketlane/remove_task_dependencies.ts @@ -0,0 +1,71 @@ +import { + mapTask, + ROCKETLANE_API_BASE, + type RocketlaneTaskDependenciesParams, + type RocketlaneTaskResponse, + rocketlaneError, + rocketlaneHeaders, + TASK_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneRemoveTaskDependenciesTool: ToolConfig< + RocketlaneTaskDependenciesParams, + RocketlaneTaskResponse +> = { + id: 'rocketlane_remove_task_dependencies', + name: 'Rocketlane Remove Task Dependencies', + description: 'Remove dependencies from a Rocketlane task', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + taskId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the task to remove dependencies from', + }, + dependencyTaskIds: { + type: 'array', + required: true, + visibility: 'user-or-llm', + description: 'Task IDs to remove from the task dependencies', + items: { type: 'number' }, + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/tasks/${encodeURIComponent(String(params.taskId))}/remove-dependencies`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + dependencies: params.dependencyTaskIds.map((taskId) => ({ taskId })), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { task: mapTask(data) }, + } + }, + + outputs: { + task: { + type: 'object', + description: 'The task with its updated dependencies', + properties: TASK_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/remove_task_followers.ts b/apps/sim/tools/rocketlane/remove_task_followers.ts new file mode 100644 index 00000000000..393444ef6ce --- /dev/null +++ b/apps/sim/tools/rocketlane/remove_task_followers.ts @@ -0,0 +1,79 @@ +import { + buildTaskMembers, + mapTask, + ROCKETLANE_API_BASE, + type RocketlaneTaskFollowersParams, + type RocketlaneTaskResponse, + rocketlaneError, + rocketlaneHeaders, + TASK_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneRemoveTaskFollowersTool: ToolConfig< + RocketlaneTaskFollowersParams, + RocketlaneTaskResponse +> = { + id: 'rocketlane_remove_task_followers', + name: 'Rocketlane Remove Task Followers', + description: 'Remove followers from a Rocketlane task', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + taskId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the task to remove followers from', + }, + memberUserIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'User IDs of members to remove from the followers', + items: { type: 'number' }, + }, + memberEmailIds: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: 'Email addresses of members to remove from the followers', + items: { type: 'string' }, + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/tasks/${encodeURIComponent(String(params.taskId))}/remove-followers`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + members: buildTaskMembers(params.memberUserIds, params.memberEmailIds), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { task: mapTask(data) }, + } + }, + + outputs: { + task: { + type: 'object', + description: 'The task with its updated followers', + properties: TASK_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/search_time_entries.ts b/apps/sim/tools/rocketlane/search_time_entries.ts new file mode 100644 index 00000000000..8781269469f --- /dev/null +++ b/apps/sim/tools/rocketlane/search_time_entries.ts @@ -0,0 +1,227 @@ +import { + mapPagination, + mapTimeEntry, + PAGINATION_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneSearchTimeEntriesParams, + type RocketlaneTimeEntryListResponse, + rocketlaneError, + rocketlaneHeaders, + TIME_ENTRY_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneSearchTimeEntriesTool: ToolConfig< + RocketlaneSearchTimeEntriesParams, + RocketlaneTimeEntryListResponse +> = { + id: 'rocketlane_search_time_entries', + name: 'Rocketlane Search Time Entries', + description: + 'Search Rocketlane time entries with filters, sorting, and pagination (deprecated by Rocketlane in favor of listing time entries with filters)', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + dateEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries on this exact date (YYYY-MM-DD)', + }, + dateGt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries after this date (YYYY-MM-DD)', + }, + dateGe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries on or after this date (YYYY-MM-DD)', + }, + dateLt: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries before this date (YYYY-MM-DD)', + }, + dateLe: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries on or before this date (YYYY-MM-DD)', + }, + projectEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries for this project ID', + }, + taskEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries for this task ID', + }, + projectPhaseIdEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries for this project phase ID', + }, + categoryIdEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries with this category ID', + }, + userIdEq: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Only entries belonging to this user ID', + }, + sourceTypeEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Only entries with this source type (GOOGLE_CALENDAR, OUTLOOK_CALENDAR, TASK, PROJECT, PHASE, ADHOC, MILESTONE)', + }, + activityNameEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries with this exact activity name', + }, + activityNameCn: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only entries whose activity name contains this text', + }, + approvalStatusEq: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Only entries with this approval status (NOT_SUBMITTED, SUBMITTED, APPROVED, REJECTED)', + }, + match: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'How to combine filters: all (AND, default) or any (OR)', + }, + sortBy: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Field to sort by (minutes, date, id, billable)', + }, + sortOrder: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort order: ASC or DESC (default DESC)', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra fields to include in the response (notes, sourceType, deleted, status, submittedBy, submittedAt, approvedBy, approvedAt, rejectedBy, rejectedAt, costRate, billRate)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether all fields should be returned in the response', + }, + pageSize: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of entries per page (defaults to 100)', + }, + pageToken: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Page token from a previous response for fetching the next page', + }, + }, + + request: { + url: (params) => { + const url = new URL(`${ROCKETLANE_API_BASE}/time-entries/search`) + if (params.dateEq) url.searchParams.set('date.eq', params.dateEq) + if (params.dateGt) url.searchParams.set('date.gt', params.dateGt) + if (params.dateGe) url.searchParams.set('date.ge', params.dateGe) + if (params.dateLt) url.searchParams.set('date.lt', params.dateLt) + if (params.dateLe) url.searchParams.set('date.le', params.dateLe) + if (params.projectEq != null) url.searchParams.set('project.eq', String(params.projectEq)) + if (params.taskEq != null) url.searchParams.set('task.eq', String(params.taskEq)) + if (params.projectPhaseIdEq != null) { + url.searchParams.set('projectPhase.eq', String(params.projectPhaseIdEq)) + } + if (params.categoryIdEq != null) { + url.searchParams.set('category.eq', String(params.categoryIdEq)) + } + if (params.userIdEq != null) url.searchParams.set('user.eq', String(params.userIdEq)) + if (params.sourceTypeEq) url.searchParams.set('sourceType.eq', params.sourceTypeEq) + if (params.activityNameEq) url.searchParams.set('activityName.eq', params.activityNameEq) + if (params.activityNameCn) url.searchParams.set('activityName.cn', params.activityNameCn) + if (params.approvalStatusEq) { + url.searchParams.set('approvalStatus.eq', params.approvalStatusEq) + } + if (params.match) url.searchParams.set('match', params.match) + if (params.sortBy) url.searchParams.set('sortBy', params.sortBy) + if (params.sortOrder) url.searchParams.set('sortOrder', params.sortOrder) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + if (params.pageSize != null) url.searchParams.set('pageSize', String(params.pageSize)) + if (params.pageToken) url.searchParams.set('pageToken', params.pageToken) + return url.toString() + }, + method: 'GET', + headers: (params) => rocketlaneHeaders(params.apiKey), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + const entries = Array.isArray(data?.data) ? data.data : [] + return { + success: true, + output: { + timeEntries: entries.map(mapTimeEntry), + pagination: mapPagination(data?.pagination), + }, + } + }, + + outputs: { + timeEntries: { + type: 'array', + description: 'List of time entries matching the search filters', + items: { type: 'object', properties: TIME_ENTRY_OUTPUT_PROPERTIES }, + }, + pagination: { + type: 'object', + description: 'Pagination details for the result set', + properties: PAGINATION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/types.ts b/apps/sim/tools/rocketlane/types.ts new file mode 100644 index 00000000000..8f7705c58a3 --- /dev/null +++ b/apps/sim/tools/rocketlane/types.ts @@ -0,0 +1,4372 @@ +import type { OutputProperty, ToolResponse } from '@/tools/types' + +/** Base URL for the Rocketlane REST API (v1.0). */ +export const ROCKETLANE_API_BASE = 'https://api.rocketlane.com/api/1.0' + +/** Every Rocketlane tool authenticates with the account API key via the `api-key` header. */ +export interface RocketlaneBaseParams { + apiKey: string +} + +/** + * Builds the standard auth headers shared by every Rocketlane request. + */ +export function rocketlaneHeaders(apiKey: string): Record { + return { + 'api-key': apiKey, + 'Content-Type': 'application/json', + } +} + +/** + * Extracts a human-readable error message from a non-OK Rocketlane response. + * Errors are returned as `{ errors: [{ errorCode, errorMessage, field }] }`. + */ +export async function rocketlaneError(response: Response): Promise { + const text = await response.text() + try { + const parsed = JSON.parse(text) + const errors = Array.isArray(parsed?.errors) ? parsed.errors : [] + const messages = errors + .map((e: { errorMessage?: string; field?: string }) => + e?.errorMessage + ? e.field + ? `${e.errorMessage} (field: ${e.field})` + : e.errorMessage + : null + ) + .filter(Boolean) + if (messages.length > 0) return messages.join('; ') + } catch { + // Fall through to the raw body. + } + return text || `Rocketlane API error (HTTP ${response.status})` +} + +type Raw = Record + +function asString(value: unknown): string | null { + return typeof value === 'string' ? value : null +} + +function asNumber(value: unknown): number | null { + return typeof value === 'number' ? value : null +} + +function asBoolean(value: unknown): boolean | null { + return typeof value === 'boolean' ? value : null +} + +function asObject(value: unknown): Raw | null { + return value && typeof value === 'object' && !Array.isArray(value) ? (value as Raw) : null +} + +function asArray(value: unknown): unknown[] { + return Array.isArray(value) ? value : [] +} + +// region Shared object shapes + +/** Compact user reference returned inside most Rocketlane resources. */ +export interface RocketlaneUserSummary { + userId: number | null + firstName: string | null + lastName: string | null + emailId: string | null +} + +export function mapUserSummary(value: unknown): RocketlaneUserSummary | null { + const raw = asObject(value) + if (!raw) return null + return { + userId: asNumber(raw.userId), + firstName: asString(raw.firstName), + lastName: asString(raw.lastName), + emailId: asString(raw.emailId), + } +} + +export const USER_SUMMARY_OUTPUT_PROPERTIES = { + userId: { type: 'number', description: 'Unique identifier of the user', nullable: true }, + firstName: { type: 'string', description: 'First name of the user', nullable: true }, + lastName: { type: 'string', description: 'Last name of the user', nullable: true }, + emailId: { type: 'string', description: 'Email address of the user', nullable: true }, +} satisfies Record + +/** Pagination envelope returned by every Rocketlane list endpoint. */ +export interface RocketlanePagination { + pageSize: number | null + hasMore: boolean | null + totalRecordCount: number | null + nextPageToken: string | null +} + +export function mapPagination(value: unknown): RocketlanePagination { + const raw = asObject(value) ?? {} + return { + pageSize: asNumber(raw.pageSize), + hasMore: asBoolean(raw.hasMore), + totalRecordCount: asNumber(raw.totalRecordCount), + nextPageToken: asString(raw.nextPageToken), + } +} + +export const PAGINATION_OUTPUT_PROPERTIES = { + pageSize: { + type: 'number', + description: 'Page size used for the current request', + nullable: true, + }, + hasMore: { type: 'boolean', description: 'Whether more results are available', nullable: true }, + totalRecordCount: { + type: 'number', + description: 'Total number of records matching the request', + nullable: true, + }, + nextPageToken: { + type: 'string', + description: 'Token for fetching the next page (valid for 15 minutes)', + nullable: true, + }, +} satisfies Record + +// endregion + +// region Tasks + +/** Compact project reference returned inside a Rocketlane task. */ +export interface RocketlaneTaskProjectRef { + projectId: number | null + projectName: string | null +} + +/** Compact phase reference returned inside a Rocketlane task. */ +export interface RocketlaneTaskPhaseRef { + phaseId: number | null + phaseName: string | null +} + +/** Value/label pair used for task status and priority fields. */ +export interface RocketlaneTaskChoice { + value: number | null + label: string | null +} + +/** Role associated with a placeholder assignee. */ +export interface RocketlaneTaskRole { + roleId: number | null + roleName: string | null +} + +/** Placeholder assignee on a task, associated with a role. */ +export interface RocketlaneTaskPlaceholder { + placeholderId: number | null + placeholderName: string | null + role: RocketlaneTaskRole | null +} + +/** Assignees of a task: members (team members or customers) and placeholders. */ +export interface RocketlaneTaskAssignees { + members: RocketlaneUserSummary[] + placeholders: RocketlaneTaskPlaceholder[] +} + +/** Followers of a task (members only). */ +export interface RocketlaneTaskFollowers { + members: RocketlaneUserSummary[] +} + +/** Lite task reference used for dependencies and the parent task. */ +export interface RocketlaneTaskLite { + taskId: number | null + taskName: string | null +} + +/** Custom field value attached to a task. */ +export interface RocketlaneTaskField { + fieldId: number | null + fieldLabel: string | null + fieldValue: unknown + fieldValueLabel: string | null +} + +/** Time entry category associated with a task. */ +export interface RocketlaneTaskTimeEntryCategory { + categoryId: number | null + categoryName: string | null +} + +/** Financials budget in which the task's time entry is added. */ +export interface RocketlaneTaskBudget { + budgetId: number | null + budgetName: string | null +} + +/** A Rocketlane task as returned by the Tasks endpoints. */ +export interface RocketlaneTask { + taskId: number | null + taskName: string | null + taskDescription: string | null + taskPrivateNote: string | null + startDate: string | null + dueDate: string | null + startDateActual: string | null + dueDateActual: string | null + archived: boolean | null + effortInMinutes: number | null + progress: number | null + atRisk: boolean | null + type: string | null + createdAt: number | null + updatedAt: number | null + createdBy: RocketlaneUserSummary | null + updatedBy: RocketlaneUserSummary | null + project: RocketlaneTaskProjectRef | null + phase: RocketlaneTaskPhaseRef | null + status: RocketlaneTaskChoice | null + priority: RocketlaneTaskChoice | null + fields: RocketlaneTaskField[] + assignees: RocketlaneTaskAssignees | null + followers: RocketlaneTaskFollowers | null + dependencies: RocketlaneTaskLite[] + parent: RocketlaneTaskLite | null + externalReferenceId: string | null + billable: boolean | null + timeEntryCategory: RocketlaneTaskTimeEntryCategory | null + financialsBudgets: RocketlaneTaskBudget[] + csatEnabled: boolean | null + private: boolean | null +} + +function mapTaskProjectRef(value: unknown): RocketlaneTaskProjectRef | null { + const raw = asObject(value) + if (!raw) return null + return { + projectId: asNumber(raw.projectId), + projectName: asString(raw.projectName), + } +} + +function mapTaskPhaseRef(value: unknown): RocketlaneTaskPhaseRef | null { + const raw = asObject(value) + if (!raw) return null + return { + phaseId: asNumber(raw.phaseId), + phaseName: asString(raw.phaseName), + } +} + +function mapTaskChoice(value: unknown): RocketlaneTaskChoice | null { + const raw = asObject(value) + if (!raw) return null + return { + value: asNumber(raw.value), + label: asString(raw.label), + } +} + +function mapTaskRole(value: unknown): RocketlaneTaskRole | null { + const raw = asObject(value) + if (!raw) return null + return { + roleId: asNumber(raw.roleId), + roleName: asString(raw.roleName), + } +} + +function mapTaskPlaceholder(value: unknown): RocketlaneTaskPlaceholder { + const raw = asObject(value) ?? {} + return { + placeholderId: asNumber(raw.placeholderId), + placeholderName: asString(raw.placeholderName), + role: mapTaskRole(raw.role), + } +} + +function mapTaskAssignees(value: unknown): RocketlaneTaskAssignees | null { + const raw = asObject(value) + if (!raw) return null + return { + members: asArray(raw.members) + .map(mapUserSummary) + .filter((member): member is RocketlaneUserSummary => member !== null), + placeholders: asArray(raw.placeholders).map(mapTaskPlaceholder), + } +} + +function mapTaskFollowers(value: unknown): RocketlaneTaskFollowers | null { + const raw = asObject(value) + if (!raw) return null + return { + members: asArray(raw.members) + .map(mapUserSummary) + .filter((member): member is RocketlaneUserSummary => member !== null), + } +} + +function mapTaskLite(value: unknown): RocketlaneTaskLite { + const raw = asObject(value) ?? {} + return { + taskId: asNumber(raw.taskId), + taskName: asString(raw.taskName), + } +} + +function mapTaskField(value: unknown): RocketlaneTaskField { + const raw = asObject(value) ?? {} + return { + fieldId: asNumber(raw.fieldId), + fieldLabel: asString(raw.fieldLabel), + fieldValue: raw.fieldValue ?? null, + fieldValueLabel: asString(raw.fieldValueLabel), + } +} + +function mapTaskTimeEntryCategory(value: unknown): RocketlaneTaskTimeEntryCategory | null { + const raw = asObject(value) + if (!raw) return null + return { + categoryId: asNumber(raw.categoryId), + categoryName: asString(raw.categoryName), + } +} + +function mapTaskBudget(value: unknown): RocketlaneTaskBudget { + const raw = asObject(value) ?? {} + return { + budgetId: asNumber(raw.budgetId), + budgetName: asString(raw.budgetName), + } +} + +export function mapTask(value: unknown): RocketlaneTask { + const raw = asObject(value) ?? {} + return { + taskId: asNumber(raw.taskId), + taskName: asString(raw.taskName), + taskDescription: asString(raw.taskDescription), + taskPrivateNote: asString(raw.taskPrivateNote), + startDate: asString(raw.startDate), + dueDate: asString(raw.dueDate), + startDateActual: asString(raw.startDateActual), + dueDateActual: asString(raw.dueDateActual), + archived: asBoolean(raw.archived), + effortInMinutes: asNumber(raw.effortInMinutes), + progress: asNumber(raw.progress), + atRisk: asBoolean(raw.atRisk), + type: asString(raw.type), + createdAt: asNumber(raw.createdAt), + updatedAt: asNumber(raw.updatedAt), + createdBy: mapUserSummary(raw.createdBy), + updatedBy: mapUserSummary(raw.updatedBy), + project: mapTaskProjectRef(raw.project), + phase: mapTaskPhaseRef(raw.phase), + status: mapTaskChoice(raw.status), + priority: mapTaskChoice(raw.priority), + fields: asArray(raw.fields).map(mapTaskField), + assignees: mapTaskAssignees(raw.assignees), + followers: mapTaskFollowers(raw.followers), + dependencies: asArray(raw.dependencies).map(mapTaskLite), + parent: asObject(raw.parent) ? mapTaskLite(raw.parent) : null, + externalReferenceId: asString(raw.externalReferenceId), + billable: asBoolean(raw.billable), + timeEntryCategory: mapTaskTimeEntryCategory(raw.timeEntryCategory), + financialsBudgets: asArray(raw.financialsBudgets).map(mapTaskBudget), + csatEnabled: asBoolean(raw.csatEnabled), + private: asBoolean(raw.private), + } +} + +/** + * Builds the `members` array used by assignee/follower request bodies from + * user IDs and/or email IDs. Each ID becomes its own member entry, so a user + * referenced by both a user ID and an email produces two entries. + */ +export function buildTaskMembers( + userIds?: number[], + emailIds?: string[] +): Array> { + const members: Array> = [] + for (const userId of userIds ?? []) { + members.push({ userId }) + } + for (const emailId of emailIds ?? []) { + members.push({ emailId }) + } + return members +} + +const TASK_LITE_OUTPUT_PROPERTIES = { + taskId: { type: 'number', description: 'Unique identifier of the task', nullable: true }, + taskName: { type: 'string', description: 'Name of the task', nullable: true }, +} satisfies Record + +const TASK_CHOICE_OUTPUT_PROPERTIES = { + value: { type: 'number', description: 'Unique identifier of the choice', nullable: true }, + label: { type: 'string', description: 'Label of the choice', nullable: true }, +} satisfies Record + +export const TASK_OUTPUT_PROPERTIES = { + taskId: { type: 'number', description: 'Unique identifier of the task', nullable: true }, + taskName: { type: 'string', description: 'Name of the task', nullable: true }, + taskDescription: { + type: 'string', + description: 'Description of the task in HTML format', + nullable: true, + }, + taskPrivateNote: { + type: 'string', + description: 'Private note visible only to team members, in HTML format', + nullable: true, + }, + startDate: { + type: 'string', + description: 'Date when the task starts (YYYY-MM-DD)', + nullable: true, + }, + dueDate: { + type: 'string', + description: 'Date when the task is due (YYYY-MM-DD)', + nullable: true, + }, + startDateActual: { + type: 'string', + description: 'Date the task status changed to In Progress (YYYY-MM-DD)', + nullable: true, + }, + dueDateActual: { + type: 'string', + description: 'Date the task status changed to Completed (YYYY-MM-DD)', + nullable: true, + }, + archived: { type: 'boolean', description: 'Whether the task is archived', nullable: true }, + effortInMinutes: { + type: 'number', + description: 'Expected effort to complete the task, in minutes', + nullable: true, + }, + progress: { type: 'number', description: 'Progress of the task (0-100)', nullable: true }, + atRisk: { + type: 'boolean', + description: 'Whether the task is marked as At Risk', + nullable: true, + }, + type: { type: 'string', description: 'Type of the task: TASK or MILESTONE', nullable: true }, + createdAt: { + type: 'number', + description: 'Time the task was created, in epoch millis', + nullable: true, + }, + updatedAt: { + type: 'number', + description: 'Time the task was last updated, in epoch millis', + nullable: true, + }, + createdBy: { + type: 'object', + description: 'User who created the task', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + updatedBy: { + type: 'object', + description: 'User who last updated the task', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + project: { + type: 'object', + description: 'Project associated with the task', + nullable: true, + properties: { + projectId: { + type: 'number', + description: 'Unique identifier of the project', + nullable: true, + }, + projectName: { type: 'string', description: 'Name of the project', nullable: true }, + }, + }, + phase: { + type: 'object', + description: 'Phase associated with the task', + nullable: true, + properties: { + phaseId: { type: 'number', description: 'Unique identifier of the phase', nullable: true }, + phaseName: { type: 'string', description: 'Name of the phase', nullable: true }, + }, + }, + status: { + type: 'object', + description: 'Status of the task (value and label)', + nullable: true, + properties: TASK_CHOICE_OUTPUT_PROPERTIES, + }, + priority: { + type: 'object', + description: 'Priority of the task (value and label)', + nullable: true, + properties: TASK_CHOICE_OUTPUT_PROPERTIES, + }, + fields: { + type: 'array', + description: 'Custom field values set on the task', + items: { + type: 'object', + properties: { + fieldId: { type: 'number', description: 'Unique identifier of the field', nullable: true }, + fieldLabel: { type: 'string', description: 'Label of the field', nullable: true }, + fieldValue: { + type: 'json', + description: 'Value of the field (string, number, or array)', + nullable: true, + }, + fieldValueLabel: { + type: 'string', + description: 'String representation of the field value', + nullable: true, + }, + }, + }, + }, + assignees: { + type: 'object', + description: 'Assignees of the task (members and placeholders)', + nullable: true, + properties: { + members: { + type: 'array', + description: 'Team members and customers assigned to the task', + items: { type: 'object', properties: USER_SUMMARY_OUTPUT_PROPERTIES }, + }, + placeholders: { + type: 'array', + description: 'Placeholders assigned to the task', + items: { + type: 'object', + properties: { + placeholderId: { + type: 'number', + description: 'Unique identifier of the placeholder', + nullable: true, + }, + placeholderName: { + type: 'string', + description: 'Name of the placeholder', + nullable: true, + }, + role: { + type: 'object', + description: 'Role associated with the placeholder', + nullable: true, + properties: { + roleId: { + type: 'number', + description: 'Unique identifier of the role', + nullable: true, + }, + roleName: { type: 'string', description: 'Name of the role', nullable: true }, + }, + }, + }, + }, + }, + }, + }, + followers: { + type: 'object', + description: 'Followers of the task', + nullable: true, + properties: { + members: { + type: 'array', + description: 'Team members and customers following the task', + items: { type: 'object', properties: USER_SUMMARY_OUTPUT_PROPERTIES }, + }, + }, + }, + dependencies: { + type: 'array', + description: 'Tasks this task depends on (finish-to-start dependencies)', + items: { type: 'object', properties: TASK_LITE_OUTPUT_PROPERTIES }, + }, + parent: { + type: 'object', + description: 'Parent task of the task', + nullable: true, + properties: TASK_LITE_OUTPUT_PROPERTIES, + }, + externalReferenceId: { + type: 'string', + description: 'External reference identifier linking the task to an external system', + nullable: true, + }, + billable: { type: 'boolean', description: 'Whether the task is billable', nullable: true }, + timeEntryCategory: { + type: 'object', + description: 'Category in which the task time entries are added', + nullable: true, + properties: { + categoryId: { + type: 'number', + description: 'Unique identifier of the category', + nullable: true, + }, + categoryName: { type: 'string', description: 'Name of the category', nullable: true }, + }, + }, + financialsBudgets: { + type: 'array', + description: 'Financials budgets in which the task time entries are added', + items: { + type: 'object', + properties: { + budgetId: { + type: 'number', + description: 'Unique identifier of the budget', + nullable: true, + }, + budgetName: { type: 'string', description: 'Name of the budget', nullable: true }, + }, + }, + }, + csatEnabled: { + type: 'boolean', + description: 'Whether a CSAT survey is sent on completion (milestone tasks)', + nullable: true, + }, + private: { type: 'boolean', description: 'Whether the task is private', nullable: true }, +} satisfies Record + +export interface RocketlaneCreateTaskParams extends RocketlaneBaseParams { + taskName: string + projectId: number + taskDescription?: string + taskPrivateNote?: string + startDate?: string + dueDate?: string + effortInMinutes?: number + progress?: number + atRisk?: boolean + type?: string + phaseId?: number + statusValue?: number + assigneeUserIds?: number[] + assigneeEmailIds?: string[] + followerUserIds?: number[] + followerEmailIds?: string[] + parentTaskId?: number + externalReferenceId?: string + private?: boolean + includeFields?: string[] + includeAllFields?: boolean +} + +export interface RocketlaneGetTaskParams extends RocketlaneBaseParams { + taskId: number + includeFields?: string[] + includeAllFields?: boolean +} + +export interface RocketlaneUpdateTaskParams extends RocketlaneBaseParams { + taskId: number + taskName?: string + taskDescription?: string + taskPrivateNote?: string + startDate?: string + dueDate?: string + effortInMinutes?: number + progress?: number + atRisk?: boolean + type?: string + statusValue?: number + externalReferenceId?: string + private?: boolean + includeFields?: string[] + includeAllFields?: boolean +} + +export interface RocketlaneDeleteTaskParams extends RocketlaneBaseParams { + taskId: number +} + +export interface RocketlaneListTasksParams extends RocketlaneBaseParams { + pageSize?: number + pageToken?: string + includeFields?: string[] + includeAllFields?: boolean + sortBy?: string + sortOrder?: string + match?: string + projectId?: number + phaseId?: number + taskName?: string + taskNameContains?: string + taskStatus?: string + startDateFrom?: string + startDateTo?: string + dueDateFrom?: string + dueDateTo?: string + includeArchive?: boolean + externalReferenceId?: string +} + +export interface RocketlaneTaskAssigneesParams extends RocketlaneBaseParams { + taskId: number + memberUserIds?: number[] + memberEmailIds?: string[] +} + +export interface RocketlaneTaskFollowersParams extends RocketlaneBaseParams { + taskId: number + memberUserIds?: number[] + memberEmailIds?: string[] +} + +export interface RocketlaneTaskDependenciesParams extends RocketlaneBaseParams { + taskId: number + dependencyTaskIds: number[] +} + +export interface RocketlaneMoveTaskToPhaseParams extends RocketlaneBaseParams { + taskId: number + phaseId: number +} + +export interface RocketlaneTaskResponse extends ToolResponse { + output: { + task: RocketlaneTask + } +} + +export interface RocketlaneTaskListResponse extends ToolResponse { + output: { + tasks: RocketlaneTask[] + pagination: RocketlanePagination + } +} + +export interface RocketlaneTaskDeleteResponse extends ToolResponse { + output: { + deleted: boolean + taskId: number | null + } +} + +// endregion + +// region Projects + +/** Company reference (customer or partner) returned on Rocketlane projects. */ +export interface RocketlaneProjectCompany { + companyId: number | null + companyName: string | null + companyUrl: string | null +} + +export function mapProjectCompany(value: unknown): RocketlaneProjectCompany | null { + const raw = asObject(value) + if (!raw) return null + return { + companyId: asNumber(raw.companyId), + companyName: asString(raw.companyName), + companyUrl: asString(raw.companyUrl), + } +} + +export const PROJECT_COMPANY_OUTPUT_PROPERTIES = { + companyId: { type: 'number', description: 'Unique identifier of the company', nullable: true }, + companyName: { type: 'string', description: 'Name of the company', nullable: true }, + companyUrl: { type: 'string', description: 'Website URL of the company', nullable: true }, +} satisfies Record + +/** Project status value/label pair. */ +export interface RocketlaneProjectStatus { + value: number | null + label: string | null +} + +export function mapProjectStatus(value: unknown): RocketlaneProjectStatus | null { + const raw = asObject(value) + if (!raw) return null + return { + value: asNumber(raw.value), + label: asString(raw.label), + } +} + +export const PROJECT_STATUS_OUTPUT_PROPERTIES = { + value: { type: 'number', description: 'Unique identifier of the status', nullable: true }, + label: { type: 'string', description: 'Name of the status', nullable: true }, +} satisfies Record + +/** Custom project field value returned on Rocketlane projects. */ +export interface RocketlaneProjectField { + fieldId: number | null + fieldLabel: string | null + fieldValue: string | null + fieldValueLabel: string | null +} + +export function mapProjectField(value: unknown): RocketlaneProjectField { + const raw = asObject(value) ?? {} + return { + fieldId: asNumber(raw.fieldId), + fieldLabel: asString(raw.fieldLabel), + fieldValue: asString(raw.fieldValue), + fieldValueLabel: asString(raw.fieldValueLabel), + } +} + +export const PROJECT_FIELD_OUTPUT_PROPERTIES = { + fieldId: { type: 'number', description: 'Unique identifier of the custom field', nullable: true }, + fieldLabel: { type: 'string', description: 'Name of the custom project field', nullable: true }, + fieldValue: { type: 'string', description: 'Value assigned to the field', nullable: true }, + fieldValueLabel: { + type: 'string', + description: 'String representation of the field value', + nullable: true, + }, +} satisfies Record + +/** In-progress phase reference returned on Rocketlane projects. */ +export interface RocketlaneProjectPhase { + phaseId: number | null + phaseName: string | null +} + +export function mapProjectPhase(value: unknown): RocketlaneProjectPhase { + const raw = asObject(value) ?? {} + return { + phaseId: asNumber(raw.phaseId), + phaseName: asString(raw.phaseName), + } +} + +export const PROJECT_PHASE_OUTPUT_PROPERTIES = { + phaseId: { type: 'number', description: 'Unique identifier of the phase', nullable: true }, + phaseName: { type: 'string', description: 'Name of the phase', nullable: true }, +} satisfies Record + +/** Template source imported into a Rocketlane project. */ +export interface RocketlaneProjectSource { + prefix: string | null + startDate: string | null + templateId: number | null + templateName: string | null +} + +export function mapProjectSource(value: unknown): RocketlaneProjectSource { + const raw = asObject(value) ?? {} + return { + prefix: asString(raw.prefix), + startDate: asString(raw.startDate), + templateId: asNumber(raw.templateId), + templateName: asString(raw.templateName), + } +} + +export const PROJECT_SOURCE_OUTPUT_PROPERTIES = { + prefix: { + type: 'string', + description: 'Prefix distinguishing which phase or task corresponds to which template', + nullable: true, + }, + startDate: { + type: 'string', + description: 'Date on which the template goes into effect (YYYY-MM-DD)', + nullable: true, + }, + templateId: { type: 'number', description: 'Unique identifier of the template', nullable: true }, + templateName: { type: 'string', description: 'Name of the template', nullable: true }, +} satisfies Record + +/** Project members, customers, and customer champion. */ +export interface RocketlaneProjectTeamMembers { + members: RocketlaneUserSummary[] + customers: RocketlaneUserSummary[] + customerChampion: RocketlaneUserSummary | null +} + +export function mapProjectTeamMembers(value: unknown): RocketlaneProjectTeamMembers { + const raw = asObject(value) ?? {} + return { + members: asArray(raw.members) + .map(mapUserSummary) + .filter((m): m is RocketlaneUserSummary => m !== null), + customers: asArray(raw.customers) + .map(mapUserSummary) + .filter((m): m is RocketlaneUserSummary => m !== null), + customerChampion: mapUserSummary(raw.customerChampion), + } +} + +export const PROJECT_TEAM_MEMBERS_OUTPUT_PROPERTIES = { + members: { + type: 'array', + description: 'Team members working on the project', + items: { type: 'object', properties: USER_SUMMARY_OUTPUT_PROPERTIES }, + }, + customers: { + type: 'array', + description: 'Customer stakeholders involved in the project', + items: { type: 'object', properties: USER_SUMMARY_OUTPUT_PROPERTIES }, + }, + customerChampion: { + type: 'object', + description: 'Customer champion of the project', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, +} satisfies Record + +/** + * Flattened project financials (contract type plus the per-contract-type fields + * from fixedFeeContract, timeAndMaterialContract, and subscriptionContract). + */ +export interface RocketlaneProjectFinancials { + contractType: string | null + revenueRecognitionType: string | null + fixedFee: number | null + projectBudget: number | null + rateCardId: number | null + rateCardName: string | null + subscriptionFrequency: string | null + subscriptionStartDate: string | null + periodMinutes: number | null + periodBudget: number | null + noOfPeriods: number | null +} + +export function mapProjectFinancials(value: unknown): RocketlaneProjectFinancials | null { + const raw = asObject(value) + if (!raw) return null + const fixedFeeContract = asObject(raw.fixedFeeContract) ?? {} + const timeAndMaterialContract = asObject(raw.timeAndMaterialContract) ?? {} + const rateCard = asObject(timeAndMaterialContract.rateCard) ?? {} + const subscriptionContract = asObject(raw.subscriptionContract) ?? {} + return { + contractType: asString(raw.contractType), + revenueRecognitionType: asString(raw.revenueRecognitionType), + fixedFee: asNumber(fixedFeeContract.fixedFee), + projectBudget: asNumber(timeAndMaterialContract.projectBudget), + rateCardId: asNumber(rateCard.rateCardId), + rateCardName: asString(rateCard.rateCardName), + subscriptionFrequency: asString(subscriptionContract.subscriptionFrequency), + subscriptionStartDate: asString(subscriptionContract.subscriptionStartDate), + periodMinutes: asNumber(subscriptionContract.periodMinutes), + periodBudget: asNumber(subscriptionContract.periodBudget), + noOfPeriods: asNumber(subscriptionContract.noOfPeriods), + } +} + +export const PROJECT_FINANCIALS_OUTPUT_PROPERTIES = { + contractType: { + type: 'string', + description: + 'Contract type for the project financials (FIXED_FEE, TIME_AND_MATERIAL, SUBSCRIPTION, or NON_BILLABLE)', + nullable: true, + }, + revenueRecognitionType: { + type: 'string', + description: 'Method used for revenue recognition', + nullable: true, + }, + fixedFee: { + type: 'number', + description: 'Project fee for Fixed fee contract type projects', + nullable: true, + }, + projectBudget: { + type: 'number', + description: 'Budget allocated for Time & Material contract type projects', + nullable: true, + }, + rateCardId: { type: 'number', description: 'Unique identifier of the rate card', nullable: true }, + rateCardName: { type: 'string', description: 'Name of the rate card', nullable: true }, + subscriptionFrequency: { + type: 'string', + description: + 'Interval at which the subscription renews (MONTHLY, QUARTERLY, HALF_YEARLY, YEARLY)', + nullable: true, + }, + subscriptionStartDate: { + type: 'string', + description: 'Date when the subscription interval begins (YYYY-MM-DD)', + nullable: true, + }, + periodMinutes: { + type: 'number', + description: 'Budgeted minutes for each subscription period', + nullable: true, + }, + periodBudget: { + type: 'number', + description: 'Fixed budget of every subscription period', + nullable: true, + }, + noOfPeriods: { + type: 'number', + description: 'Number of periods in the subscription', + nullable: true, + }, +} satisfies Record + +/** A Rocketlane project as returned by the Projects endpoints. */ +export interface RocketlaneProject { + projectId: number | null + projectName: string | null + startDate: string | null + dueDate: string | null + createdAt: number | null + updatedAt: number | null + owner: RocketlaneUserSummary | null + teamMembers: RocketlaneProjectTeamMembers + status: RocketlaneProjectStatus | null + fields: RocketlaneProjectField[] + customer: RocketlaneProjectCompany | null + partnerCompanies: RocketlaneProjectCompany[] + archived: boolean | null + visibility: string | null + createdBy: RocketlaneUserSummary | null + updatedBy: RocketlaneUserSummary | null + currency: string | null + financials: RocketlaneProjectFinancials | null + startDateActual: string | null + dueDateActual: string | null + annualizedRecurringRevenue: number | null + projectFee: number | null + budgetedHours: number | null + percentageBudgetedHoursConsumed: number | null + percentageBudgetConsumed: number | null + trackedHours: number | null + trackedMinutes: number | null + allocatedHours: number | null + allocatedMinutes: number | null + billableHours: number | null + billableMinutes: number | null + nonBillableHours: number | null + nonBillableMinutes: number | null + remainingHours: number | null + remainingMinutes: number | null + progressPercentage: number | null + currentPhases: RocketlaneProjectPhase[] + autoAllocation: boolean | null + sources: RocketlaneProjectSource[] + plannedDurationInDays: number | null + inferredProgress: string | null + projectAgeInDays: number | null + customersInvited: number | null + customersJoined: number | null + externalReferenceId: string | null +} + +export function mapProject(value: unknown): RocketlaneProject { + const raw = asObject(value) ?? {} + return { + projectId: asNumber(raw.projectId), + projectName: asString(raw.projectName), + startDate: asString(raw.startDate), + dueDate: asString(raw.dueDate), + createdAt: asNumber(raw.createdAt), + updatedAt: asNumber(raw.updatedAt), + owner: mapUserSummary(raw.owner), + teamMembers: mapProjectTeamMembers(raw.teamMembers), + status: mapProjectStatus(raw.status), + fields: asArray(raw.fields).map(mapProjectField), + customer: mapProjectCompany(raw.customer), + partnerCompanies: asArray(raw.partnerCompanies) + .map(mapProjectCompany) + .filter((c): c is RocketlaneProjectCompany => c !== null), + archived: asBoolean(raw.archived), + visibility: asString(raw.visibility), + createdBy: mapUserSummary(raw.createdBy), + updatedBy: mapUserSummary(raw.updatedBy), + currency: asString(raw.currency), + financials: mapProjectFinancials(raw.financials), + startDateActual: asString(raw.startDateActual), + dueDateActual: asString(raw.dueDateActual), + annualizedRecurringRevenue: asNumber(raw.annualizedRecurringRevenue), + projectFee: asNumber(raw.projectFee), + budgetedHours: asNumber(raw.budgetedHours), + percentageBudgetedHoursConsumed: asNumber(raw.percentageBudgetedHoursConsumed), + percentageBudgetConsumed: asNumber(raw.percentageBudgetConsumed), + trackedHours: asNumber(raw.trackedHours), + trackedMinutes: asNumber(raw.trackedMinutes), + allocatedHours: asNumber(raw.allocatedHours), + allocatedMinutes: asNumber(raw.allocatedMinutes), + billableHours: asNumber(raw.billableHours), + billableMinutes: asNumber(raw.billableMinutes), + nonBillableHours: asNumber(raw.nonBillableHours), + nonBillableMinutes: asNumber(raw.nonBillableMinutes), + remainingHours: asNumber(raw.remainingHours), + remainingMinutes: asNumber(raw.remainingMinutes), + progressPercentage: asNumber(raw.progressPercentage), + currentPhases: asArray(raw.currentPhases).map(mapProjectPhase), + autoAllocation: asBoolean(raw.autoAllocation), + sources: asArray(raw.sources).map(mapProjectSource), + plannedDurationInDays: asNumber(raw.plannedDurationInDays), + inferredProgress: asString(raw.inferredProgress), + projectAgeInDays: asNumber(raw.projectAgeInDays), + customersInvited: asNumber(raw.customersInvited), + customersJoined: asNumber(raw.customersJoined), + externalReferenceId: asString(raw.externalReferenceId), + } +} + +export const PROJECT_OUTPUT_PROPERTIES = { + projectId: { type: 'number', description: 'Unique identifier of the project', nullable: true }, + projectName: { type: 'string', description: 'Name of the project', nullable: true }, + startDate: { + type: 'string', + description: 'Date on which the project execution begins (YYYY-MM-DD)', + nullable: true, + }, + dueDate: { + type: 'string', + description: 'Date on which the project execution is planned to complete (YYYY-MM-DD)', + nullable: true, + }, + createdAt: { + type: 'number', + description: 'Time when the project was created (epoch millis)', + nullable: true, + }, + updatedAt: { + type: 'number', + description: 'Time when the project was last updated (epoch millis)', + nullable: true, + }, + owner: { + type: 'object', + description: 'Project owner', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + teamMembers: { + type: 'object', + description: 'Project members, customers, and customer champion', + properties: PROJECT_TEAM_MEMBERS_OUTPUT_PROPERTIES, + }, + status: { + type: 'object', + description: 'Project status value and label', + nullable: true, + properties: PROJECT_STATUS_OUTPUT_PROPERTIES, + }, + fields: { + type: 'array', + description: 'Custom project field values', + items: { type: 'object', properties: PROJECT_FIELD_OUTPUT_PROPERTIES }, + }, + customer: { + type: 'object', + description: 'Customer company of the project', + nullable: true, + properties: PROJECT_COMPANY_OUTPUT_PROPERTIES, + }, + partnerCompanies: { + type: 'array', + description: 'Partner companies on the project', + items: { type: 'object', properties: PROJECT_COMPANY_OUTPUT_PROPERTIES }, + }, + archived: { type: 'boolean', description: 'Whether the project is archived', nullable: true }, + visibility: { + type: 'string', + description: 'Project visibility (EVERYONE, MEMBERS, or GROUP)', + nullable: true, + }, + createdBy: { + type: 'object', + description: 'Team member who created the project', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + updatedBy: { + type: 'object', + description: 'Team member who last updated the project', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + currency: { + type: 'string', + description: 'Currency for the project financials (ISO code)', + nullable: true, + }, + financials: { + type: 'object', + description: 'Project financials (contract type and per-contract-type fields)', + nullable: true, + properties: PROJECT_FINANCIALS_OUTPUT_PROPERTIES, + }, + startDateActual: { + type: 'string', + description: 'Date on which the project status changed to in progress (YYYY-MM-DD)', + nullable: true, + }, + dueDateActual: { + type: 'string', + description: 'Date on which the project status changed to completed (YYYY-MM-DD)', + nullable: true, + }, + annualizedRecurringRevenue: { + type: 'number', + description: 'Recurring revenue of the customer subscriptions for a single calendar year', + nullable: true, + }, + projectFee: { + type: 'number', + description: 'Total fee charged for the project', + nullable: true, + }, + budgetedHours: { + type: 'number', + description: 'Total hours allocated for project execution', + nullable: true, + }, + percentageBudgetedHoursConsumed: { + type: 'number', + description: 'Budgeted hours consumed percentage', + nullable: true, + }, + percentageBudgetConsumed: { + type: 'number', + description: 'Budget consumed percentage', + nullable: true, + }, + trackedHours: { + type: 'number', + description: 'Hours tracked as part of submitted time entries', + nullable: true, + }, + trackedMinutes: { + type: 'number', + description: 'Minutes tracked as part of submitted time entries', + nullable: true, + }, + allocatedHours: { + type: 'number', + description: 'Allocated hours against users or placeholders', + nullable: true, + }, + allocatedMinutes: { + type: 'number', + description: 'Allocated minutes against users or placeholders', + nullable: true, + }, + billableHours: { + type: 'number', + description: 'Hours of time entries tracked as billable', + nullable: true, + }, + billableMinutes: { + type: 'number', + description: 'Minutes of time entries tracked as billable', + nullable: true, + }, + nonBillableHours: { + type: 'number', + description: 'Hours of time entries tracked as non-billable', + nullable: true, + }, + nonBillableMinutes: { + type: 'number', + description: 'Minutes of time entries tracked as non-billable', + nullable: true, + }, + remainingHours: { + type: 'number', + description: 'Hours left to complete the project based on tracked and budgeted hours', + nullable: true, + }, + remainingMinutes: { + type: 'number', + description: 'Minutes left to complete the project (complements remainingHours)', + nullable: true, + }, + progressPercentage: { + type: 'number', + description: 'Progress based on completed tasks vs total tasks', + nullable: true, + }, + currentPhases: { + type: 'array', + description: 'Phases currently marked as in progress', + items: { type: 'object', properties: PROJECT_PHASE_OUTPUT_PROPERTIES }, + }, + autoAllocation: { + type: 'boolean', + description: 'Whether auto allocation is enabled for the project', + nullable: true, + }, + sources: { + type: 'array', + description: 'Project templates imported into the project', + items: { type: 'object', properties: PROJECT_SOURCE_OUTPUT_PROPERTIES }, + }, + plannedDurationInDays: { + type: 'number', + description: 'Difference between startDate and dueDate in days', + nullable: true, + }, + inferredProgress: { + type: 'string', + description: 'Inferred progress (ON_TRACK, AHEAD_OF_TIME, RUNNING_LATE, or NONE)', + nullable: true, + }, + projectAgeInDays: { + type: 'number', + description: 'Age of the project in days based on actual dates', + nullable: true, + }, + customersInvited: { + type: 'number', + description: 'Number of customers invited to the project', + nullable: true, + }, + customersJoined: { + type: 'number', + description: 'Number of customers who joined the project', + nullable: true, + }, + externalReferenceId: { + type: 'string', + description: 'Identifier linking the project to an external system', + nullable: true, + }, +} satisfies Record + +/** A project placeholder as returned by the Get placeholders endpoint. */ +export interface RocketlanePlaceholder { + placeholderId: number | null + placeholderName: string | null + project: RocketlanePlaceholderProjectRef | null + role: RocketlanePlaceholderRole | null + placeholderType: string | null + createdAt: number | null + updatedAt: number | null +} + +/** Project reference attached to a placeholder. */ +export interface RocketlanePlaceholderProjectRef { + projectId: number | null + projectName: string | null +} + +/** Role reference attached to a placeholder. */ +export interface RocketlanePlaceholderRole { + roleId: number | null + roleName: string | null +} + +export function mapPlaceholder(value: unknown): RocketlanePlaceholder { + const raw = asObject(value) ?? {} + const project = asObject(raw.project) + const role = asObject(raw.role) + return { + placeholderId: asNumber(raw.placeholderId), + placeholderName: asString(raw.placeholderName), + project: project + ? { projectId: asNumber(project.projectId), projectName: asString(project.projectName) } + : null, + role: role ? { roleId: asNumber(role.roleId), roleName: asString(role.roleName) } : null, + placeholderType: asString(raw.placeholderType), + createdAt: asNumber(raw.createdAt), + updatedAt: asNumber(raw.updatedAt), + } +} + +export const PLACEHOLDER_OUTPUT_PROPERTIES = { + placeholderId: { + type: 'number', + description: 'Unique identifier of the placeholder', + nullable: true, + }, + placeholderName: { type: 'string', description: 'Name of the placeholder', nullable: true }, + project: { + type: 'object', + description: 'Project of the placeholder', + nullable: true, + properties: { + projectId: { + type: 'number', + description: 'Unique identifier of the project', + nullable: true, + }, + projectName: { type: 'string', description: 'Name of the project', nullable: true }, + }, + }, + role: { + type: 'object', + description: 'Role of the placeholder', + nullable: true, + properties: { + roleId: { type: 'number', description: 'Unique identifier of the role', nullable: true }, + roleName: { type: 'string', description: 'Name of the role', nullable: true }, + }, + }, + placeholderType: { + type: 'string', + description: 'Type of the placeholder (NATIVE or EXTERNAL)', + nullable: true, + }, + createdAt: { + type: 'number', + description: 'Time when the placeholder was created (epoch millis)', + nullable: true, + }, + updatedAt: { + type: 'number', + description: 'Time when the placeholder was last updated (epoch millis)', + nullable: true, + }, +} satisfies Record + +/** User assigned to a placeholder mapping (user summary plus role name). */ +export interface RocketlanePlaceholderMappingUser { + userId: number | null + firstName: string | null + lastName: string | null + emailId: string | null + role: string | null +} + +/** Placeholder-to-user mapping returned by assign/unassign placeholder endpoints. */ +export interface RocketlanePlaceholderMapping { + placeholder: RocketlanePlaceholderRef | null + placeholderStatus: string | null + user: RocketlanePlaceholderMappingUser | null + hourlyCostRate: number | null + costRateCurrency: string | null + hourlyBillRate: number | null + billRateCurrency: string | null +} + +/** Compact placeholder reference inside a placeholder mapping. */ +export interface RocketlanePlaceholderRef { + placeholderId: number | null + placeholderName: string | null +} + +export function mapPlaceholderMapping(value: unknown): RocketlanePlaceholderMapping { + const raw = asObject(value) ?? {} + const placeholder = asObject(raw.placeholder) + const user = asObject(raw.user) + return { + placeholder: placeholder + ? { + placeholderId: asNumber(placeholder.placeholderId), + placeholderName: asString(placeholder.placeholderName), + } + : null, + placeholderStatus: asString(raw.placeholderStatus), + user: user + ? { + userId: asNumber(user.userId), + firstName: asString(user.firstName), + lastName: asString(user.lastName), + emailId: asString(user.emailId), + role: asString(user.role), + } + : null, + hourlyCostRate: asNumber(raw.hourlyCostRate), + costRateCurrency: asString(raw.costRateCurrency), + hourlyBillRate: asNumber(raw.hourlyBillRate), + billRateCurrency: asString(raw.billRateCurrency), + } +} + +export const PLACEHOLDER_MAPPING_OUTPUT_PROPERTIES = { + placeholder: { + type: 'object', + description: 'Placeholder being mapped', + nullable: true, + properties: { + placeholderId: { + type: 'number', + description: 'Unique identifier of the placeholder', + nullable: true, + }, + placeholderName: { type: 'string', description: 'Name of the placeholder', nullable: true }, + }, + }, + placeholderStatus: { + type: 'string', + description: 'Status of the placeholder (ASSIGNED or UNASSIGNED)', + nullable: true, + }, + user: { + type: 'object', + description: 'User assigned to the placeholder', + nullable: true, + properties: { + userId: { type: 'number', description: 'Unique identifier of the user', nullable: true }, + firstName: { type: 'string', description: 'First name of the user', nullable: true }, + lastName: { type: 'string', description: 'Last name of the user', nullable: true }, + emailId: { type: 'string', description: 'Email address of the user', nullable: true }, + role: { type: 'string', description: 'Role name of the assigned user', nullable: true }, + }, + }, + hourlyCostRate: { + type: 'number', + description: 'Latest hourly cost rate for the placeholder', + nullable: true, + }, + costRateCurrency: { + type: 'string', + description: 'Currency for the cost rate', + nullable: true, + }, + hourlyBillRate: { + type: 'number', + description: 'Latest hourly bill rate for the placeholder', + nullable: true, + }, + billRateCurrency: { + type: 'string', + description: 'Currency for the bill rate', + nullable: true, + }, +} satisfies Record + +/** Custom field assignment sent when creating or updating a project. */ +export interface RocketlaneProjectFieldInput { + fieldId: number + fieldValue: string | number | number[] +} + +/** Template source sent when creating a project. */ +export interface RocketlaneProjectSourceInput { + templateId: number + startDate: string + prefix?: string +} + +/** Placeholder-to-user mapping sent when creating a project or assigning placeholders. */ +export interface RocketlaneProjectPlaceholderInput { + placeholderId: number + user: { + userId?: number + emailId?: string + } +} + +export interface RocketlaneCreateProjectParams extends RocketlaneBaseParams { + projectName: string + customerCompanyName: string + ownerUserId?: number + ownerEmailId?: string + startDate?: string + dueDate?: string + visibility?: string + statusValue?: number + memberUserIds?: number[] + customerUserIds?: number[] + customerChampionUserId?: number + fields?: RocketlaneProjectFieldInput[] + sources?: RocketlaneProjectSourceInput[] + placeholders?: RocketlaneProjectPlaceholderInput[] + assignProjectOwner?: boolean + annualizedRecurringRevenue?: number + projectFee?: number + autoAllocation?: boolean + autoCreateCompany?: boolean + budgetedHours?: number + contractType?: string + fixedFee?: number + projectBudget?: number + rateCardId?: number + subscriptionFrequency?: string + subscriptionStartDate?: string + periodMinutes?: number + periodBudget?: number + noOfPeriods?: number + currency?: string + externalReferenceId?: string + includeFields?: string + includeAllFields?: boolean +} + +export interface RocketlaneGetProjectParams extends RocketlaneBaseParams { + projectId: number + includeFields?: string + includeAllFields?: boolean +} + +export interface RocketlaneUpdateProjectParams extends RocketlaneBaseParams { + projectId: number + projectName?: string + startDate?: string + dueDate?: string + visibility?: string + ownerUserId?: number + ownerEmailId?: string + statusValue?: number + fields?: RocketlaneProjectFieldInput[] + annualizedRecurringRevenue?: number + projectFee?: number + autoAllocation?: boolean + budgetedHours?: number + externalReferenceId?: string + includeFields?: string + includeAllFields?: boolean +} + +export interface RocketlaneDeleteProjectParams extends RocketlaneBaseParams { + projectId: number +} + +export interface RocketlaneListProjectsParams extends RocketlaneBaseParams { + pageSize?: number + pageToken?: string + includeFields?: string + includeAllFields?: boolean + sortBy?: string + sortOrder?: string + match?: string + projectNameContains?: string + projectNameEquals?: string + statusEquals?: string + statusOneOf?: string + customerIdEquals?: string + customerIdOneOf?: string + teamMemberIdEquals?: string + contractTypeEquals?: string + includeArchived?: boolean + externalReferenceIdEquals?: string + startDateAfter?: string + startDateBefore?: string + dueDateAfter?: string + dueDateBefore?: string +} + +export interface RocketlaneArchiveProjectParams extends RocketlaneBaseParams { + projectId: number +} + +export interface RocketlaneAddProjectMembersParams extends RocketlaneBaseParams { + projectId: number + memberUserIds?: number[] + memberEmailIds?: string[] + customerUserIds?: number[] + customerEmailIds?: string[] +} + +export interface RocketlaneRemoveProjectMembersParams extends RocketlaneBaseParams { + projectId: number + memberUserIds?: number[] + memberEmailIds?: string[] +} + +export interface RocketlaneImportTemplateParams extends RocketlaneBaseParams { + projectId: number + templateId: number + startDate: string + prefix?: string +} + +export interface RocketlaneAssignPlaceholdersParams extends RocketlaneBaseParams { + projectId: number + placeholderId: number + userId?: number + userEmailId?: string +} + +export interface RocketlaneUnassignPlaceholdersParams extends RocketlaneBaseParams { + projectId: number + placeholderId: number +} + +export interface RocketlaneListPlaceholdersParams extends RocketlaneBaseParams { + projectId: number +} + +export interface RocketlaneProjectResponse extends ToolResponse { + output: { + project: RocketlaneProject + } +} + +export interface RocketlaneProjectListResponse extends ToolResponse { + output: { + projects: RocketlaneProject[] + pagination: RocketlanePagination + } +} + +export interface RocketlaneProjectDeleteResponse extends ToolResponse { + output: { + deleted: boolean + projectId: number | null + } +} + +export interface RocketlaneProjectArchiveResponse extends ToolResponse { + output: { + archived: boolean + projectId: number | null + } +} + +export interface RocketlaneProjectPlaceholdersResponse extends ToolResponse { + output: { + project: RocketlaneProject + placeholders: RocketlanePlaceholderMapping[] + } +} + +export interface RocketlanePlaceholderListResponse extends ToolResponse { + output: { + placeholders: RocketlanePlaceholder[] + pagination: RocketlanePagination + } +} + +// endregion + +// region Fields + +/** Choice option attached to a `SINGLE_CHOICE` or `MULTIPLE_CHOICE` field. */ +export interface RocketlaneFieldOption { + optionValue: number | null + optionLabel: string | null + optionColor: string | null +} + +export function mapFieldOption(value: unknown): RocketlaneFieldOption { + const raw = asObject(value) ?? {} + return { + optionValue: asNumber(raw.optionValue), + optionLabel: asString(raw.optionLabel), + optionColor: asString(raw.optionColor), + } +} + +export const FIELD_OPTION_OUTPUT_PROPERTIES = { + optionValue: { + type: 'number', + description: 'Unique identifier of the option within the field', + nullable: true, + }, + optionLabel: { type: 'string', description: 'Display label of the option', nullable: true }, + optionColor: { + type: 'string', + description: + 'Color of the option (RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY)', + nullable: true, + }, +} satisfies Record + +/** Custom field defined on a Rocketlane account. */ +export interface RocketlaneField { + fieldId: number | null + fieldLabel: string | null + fieldDescription: string | null + fieldType: string | null + objectType: string | null + fieldOptions: RocketlaneFieldOption[] + ratingScale: string | null + createdBy: RocketlaneUserSummary | null + updatedBy: RocketlaneUserSummary | null + createdAt: number | null + updatedAt: number | null + enabled: boolean | null + private: boolean | null +} + +export function mapField(value: unknown): RocketlaneField { + const raw = asObject(value) ?? {} + return { + fieldId: asNumber(raw.fieldId), + fieldLabel: asString(raw.fieldLabel), + fieldDescription: asString(raw.fieldDescription), + fieldType: asString(raw.fieldType), + objectType: asString(raw.objectType), + fieldOptions: asArray(raw.fieldOptions).map(mapFieldOption), + ratingScale: asString(raw.ratingScale), + createdBy: mapUserSummary(raw.createdBy), + updatedBy: mapUserSummary(raw.updatedBy), + createdAt: asNumber(raw.createdAt), + updatedAt: asNumber(raw.updatedAt), + enabled: asBoolean(raw.enabled), + private: asBoolean(raw.private), + } +} + +export const FIELD_OUTPUT_PROPERTIES = { + fieldId: { type: 'number', description: 'Unique identifier of the field', nullable: true }, + fieldLabel: { type: 'string', description: 'Name of the field', nullable: true }, + fieldDescription: { type: 'string', description: 'Description of the field', nullable: true }, + fieldType: { + type: 'string', + description: + 'Type of the field (TEXT, MULTI_LINE_TEXT, YES_OR_NO, DATE, SINGLE_CHOICE, MULTIPLE_CHOICE, SINGLE_USER, MULTIPLE_USER, NUMBER, NOTE, RATING)', + nullable: true, + }, + objectType: { + type: 'string', + description: 'Object the field is associated with (PROJECT, TASK, or USER)', + nullable: true, + }, + fieldOptions: { + type: 'array', + description: 'Options available for SINGLE_CHOICE and MULTIPLE_CHOICE fields', + items: { type: 'object', properties: FIELD_OPTION_OUTPUT_PROPERTIES }, + }, + ratingScale: { + type: 'string', + description: 'Rating scale for RATING fields (THREE, FIVE, SEVEN, TEN)', + nullable: true, + }, + createdBy: { + type: 'object', + description: 'Team member who created the field', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + updatedBy: { + type: 'object', + description: 'Team member who last updated the field', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + createdAt: { + type: 'number', + description: 'Time the field was created, in epoch milliseconds', + nullable: true, + }, + updatedAt: { + type: 'number', + description: 'Time the field was last updated, in epoch milliseconds', + nullable: true, + }, + enabled: { type: 'boolean', description: 'Whether the field is enabled', nullable: true }, + private: { type: 'boolean', description: 'Whether the field is private', nullable: true }, +} satisfies Record + +/** Option payload accepted when creating a field. */ +export interface RocketlaneFieldOptionInput { + optionLabel: string + optionColor: string +} + +export interface RocketlaneCreateFieldParams extends RocketlaneBaseParams { + fieldLabel: string + fieldType: string + objectType: string + fieldDescription?: string + fieldOptions?: RocketlaneFieldOptionInput[] + ratingScale?: string + enabled?: boolean + private?: boolean + includeFields?: string + includeAllFields?: boolean +} + +export interface RocketlaneGetFieldParams extends RocketlaneBaseParams { + fieldId: number + includeFields?: string + includeAllFields?: boolean +} + +export interface RocketlaneUpdateFieldParams extends RocketlaneBaseParams { + fieldId: number + fieldLabel?: string + fieldDescription?: string + enabled?: boolean + private?: boolean + includeFields?: string + includeAllFields?: boolean +} + +export interface RocketlaneDeleteFieldParams extends RocketlaneBaseParams { + fieldId: number +} + +export interface RocketlaneListFieldsParams extends RocketlaneBaseParams { + pageSize?: number + pageToken?: string + includeFields?: string + includeAllFields?: boolean + sortBy?: string + sortOrder?: string + match?: string + objectType?: string + fieldType?: string + enabled?: boolean + private?: boolean +} + +export interface RocketlaneAddFieldOptionParams extends RocketlaneBaseParams { + fieldId: number + optionLabel: string + optionColor: string +} + +export interface RocketlaneUpdateFieldOptionParams extends RocketlaneBaseParams { + fieldId: number + optionValue: number + optionLabel?: string + optionColor?: string +} + +export interface RocketlaneFieldResponse extends ToolResponse { + output: { + field: RocketlaneField + } +} + +export interface RocketlaneFieldListResponse extends ToolResponse { + output: { + fields: RocketlaneField[] + pagination: RocketlanePagination + } +} + +export interface RocketlaneFieldDeleteResponse extends ToolResponse { + output: { + deleted: boolean + fieldId: number | null + } +} + +export interface RocketlaneFieldOptionResponse extends ToolResponse { + output: { + option: RocketlaneFieldOption + } +} + +// endregion + +// region Phases + +/** Compact project reference returned inside a phase. */ +export interface RocketlanePhaseProject { + projectId: number | null + projectName: string | null +} + +/** Status of a phase, as a numeric value with a display label. */ +export interface RocketlanePhaseStatus { + value: number | null + label: string | null +} + +/** Phase of a Rocketlane project. */ +export interface RocketlanePhase { + phaseId: number | null + phaseName: string | null + project: RocketlanePhaseProject | null + startDate: string | null + dueDate: string | null + startDateActual: string | null + dueDateActual: string | null + createdAt: number | null + updatedAt: number | null + createdBy: RocketlaneUserSummary | null + updatedBy: RocketlaneUserSummary | null + status: RocketlanePhaseStatus | null + private: boolean | null +} + +export function mapPhase(value: unknown): RocketlanePhase { + const raw = asObject(value) ?? {} + const project = asObject(raw.project) + const status = asObject(raw.status) + return { + phaseId: asNumber(raw.phaseId), + phaseName: asString(raw.phaseName), + project: project + ? { projectId: asNumber(project.projectId), projectName: asString(project.projectName) } + : null, + startDate: asString(raw.startDate), + dueDate: asString(raw.dueDate), + startDateActual: asString(raw.startDateActual), + dueDateActual: asString(raw.dueDateActual), + createdAt: asNumber(raw.createdAt), + updatedAt: asNumber(raw.updatedAt), + createdBy: mapUserSummary(raw.createdBy), + updatedBy: mapUserSummary(raw.updatedBy), + status: status ? { value: asNumber(status.value), label: asString(status.label) } : null, + private: asBoolean(raw.private), + } +} + +export const PHASE_OUTPUT_PROPERTIES = { + phaseId: { type: 'number', description: 'Unique identifier of the phase', nullable: true }, + phaseName: { type: 'string', description: 'Name of the phase', nullable: true }, + project: { + type: 'object', + description: 'Project the phase belongs to', + nullable: true, + properties: { + projectId: { + type: 'number', + description: 'Unique identifier of the project', + nullable: true, + }, + projectName: { type: 'string', description: 'Name of the project', nullable: true }, + }, + }, + startDate: { + type: 'string', + description: 'Planned start date of the phase (YYYY-MM-DD)', + nullable: true, + }, + dueDate: { + type: 'string', + description: 'Planned due date of the phase (YYYY-MM-DD)', + nullable: true, + }, + startDateActual: { + type: 'string', + description: 'Actual start date of the phase (YYYY-MM-DD)', + nullable: true, + }, + dueDateActual: { + type: 'string', + description: 'Actual due date of the phase (YYYY-MM-DD)', + nullable: true, + }, + createdAt: { + type: 'number', + description: 'Time the phase was created, in epoch milliseconds', + nullable: true, + }, + updatedAt: { + type: 'number', + description: 'Time the phase was last updated, in epoch milliseconds', + nullable: true, + }, + createdBy: { + type: 'object', + description: 'Team member who created the phase', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + updatedBy: { + type: 'object', + description: 'Team member who last updated the phase', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + status: { + type: 'object', + description: 'Status of the phase', + nullable: true, + properties: { + value: { type: 'number', description: 'Numeric status value', nullable: true }, + label: { type: 'string', description: 'Display label of the status', nullable: true }, + }, + }, + private: { type: 'boolean', description: 'Whether the phase is private', nullable: true }, +} satisfies Record + +export interface RocketlaneCreatePhaseParams extends RocketlaneBaseParams { + phaseName: string + projectId: number + startDate: string + dueDate: string + statusValue?: number + private?: boolean + includeFields?: string + includeAllFields?: boolean +} + +export interface RocketlaneGetPhaseParams extends RocketlaneBaseParams { + phaseId: number + includeFields?: string + includeAllFields?: boolean +} + +export interface RocketlaneUpdatePhaseParams extends RocketlaneBaseParams { + phaseId: number + phaseName?: string + startDate?: string + dueDate?: string + statusValue?: number + private?: boolean + includeFields?: string + includeAllFields?: boolean +} + +export interface RocketlaneDeletePhaseParams extends RocketlaneBaseParams { + phaseId: number +} + +export interface RocketlaneListPhasesParams extends RocketlaneBaseParams { + projectId: number + pageSize?: number + pageToken?: string + includeFields?: string + includeAllFields?: boolean + sortBy?: string + sortOrder?: string + match?: string + phaseName?: string +} + +export interface RocketlanePhaseResponse extends ToolResponse { + output: { + phase: RocketlanePhase + } +} + +export interface RocketlanePhaseListResponse extends ToolResponse { + output: { + phases: RocketlanePhase[] + pagination: RocketlanePagination + } +} + +export interface RocketlanePhaseDeleteResponse extends ToolResponse { + output: { + deleted: boolean + phaseId: number | null + } +} + +// endregion + +// region Time Entries + +/** Project reference embedded in a time entry. */ +export interface RocketlaneTimeEntryProject { + projectId: number | null + projectName: string | null +} + +/** Task reference embedded in a time entry. */ +export interface RocketlaneTimeEntryTask { + taskId: number | null + taskName: string | null +} + +/** Project phase reference embedded in a time entry. */ +export interface RocketlaneTimeEntryPhase { + phaseId: number | null + phaseName: string | null +} + +/** Category associated with a time entry. */ +export interface RocketlaneTimeEntryCategory { + categoryId: number | null + categoryName: string | null +} + +/** Hourly cost/bill rate attached to a time entry. */ +export interface RocketlaneTimeEntryRate { + rate: number | null + currency: string | null +} + +/** Custom field value attached to a time entry. */ +export interface RocketlaneTimeEntryField { + fieldId: number | null + fieldLabel: string | null + fieldValue: unknown + fieldValueLabel: string | null +} + +/** Normalized Rocketlane time entry returned by the Time Tracking endpoints. */ +export interface RocketlaneTimeEntry { + timeEntryId: number | null + date: string | null + minutes: number | null + activityName: string | null + project: RocketlaneTimeEntryProject | null + task: RocketlaneTimeEntryTask | null + projectPhase: RocketlaneTimeEntryPhase | null + billable: boolean | null + user: RocketlaneUserSummary | null + notes: string | null + category: RocketlaneTimeEntryCategory | null + sourceType: string | null + status: string | null + createdAt: number | null + updatedAt: number | null + createdBy: RocketlaneUserSummary | null + updatedBy: RocketlaneUserSummary | null + submittedBy: RocketlaneUserSummary | null + submittedAt: number | null + approvedBy: RocketlaneUserSummary | null + approvedAt: number | null + rejectedBy: RocketlaneUserSummary | null + rejectedAt: number | null + deleted: boolean | null + costRate: RocketlaneTimeEntryRate | null + billRate: RocketlaneTimeEntryRate | null + fields: RocketlaneTimeEntryField[] +} + +function mapTimeEntryProject(value: unknown): RocketlaneTimeEntryProject | null { + const raw = asObject(value) + if (!raw) return null + return { + projectId: asNumber(raw.projectId), + projectName: asString(raw.projectName), + } +} + +function mapTimeEntryTask(value: unknown): RocketlaneTimeEntryTask | null { + const raw = asObject(value) + if (!raw) return null + return { + taskId: asNumber(raw.taskId), + taskName: asString(raw.taskName), + } +} + +function mapTimeEntryPhase(value: unknown): RocketlaneTimeEntryPhase | null { + const raw = asObject(value) + if (!raw) return null + return { + phaseId: asNumber(raw.phaseId), + phaseName: asString(raw.phaseName), + } +} + +export function mapTimeEntryCategory(value: unknown): RocketlaneTimeEntryCategory | null { + const raw = asObject(value) + if (!raw) return null + return { + categoryId: asNumber(raw.categoryId), + categoryName: asString(raw.categoryName), + } +} + +function mapTimeEntryRate(value: unknown): RocketlaneTimeEntryRate | null { + const raw = asObject(value) + if (!raw) return null + return { + rate: asNumber(raw.rate), + currency: asString(raw.currency), + } +} + +function mapTimeEntryField(value: unknown): RocketlaneTimeEntryField { + const raw = asObject(value) ?? {} + return { + fieldId: asNumber(raw.fieldId), + fieldLabel: asString(raw.fieldLabel), + fieldValue: raw.fieldValue ?? null, + fieldValueLabel: asString(raw.fieldValueLabel), + } +} + +export function mapTimeEntry(value: unknown): RocketlaneTimeEntry { + const raw = asObject(value) ?? {} + return { + timeEntryId: asNumber(raw.timeEntryId), + date: asString(raw.date), + minutes: asNumber(raw.minutes), + activityName: asString(raw.activityName), + project: mapTimeEntryProject(raw.project), + task: mapTimeEntryTask(raw.task), + projectPhase: mapTimeEntryPhase(raw.projectPhase), + billable: asBoolean(raw.billable), + user: mapUserSummary(raw.user), + notes: asString(raw.notes), + category: mapTimeEntryCategory(raw.category), + sourceType: asString(raw.sourceType), + status: asString(raw.status), + createdAt: asNumber(raw.createdAt), + updatedAt: asNumber(raw.updatedAt), + createdBy: mapUserSummary(raw.createdBy), + updatedBy: mapUserSummary(raw.updatedBy), + submittedBy: mapUserSummary(raw.submittedBy), + submittedAt: asNumber(raw.submittedAt), + approvedBy: mapUserSummary(raw.approvedBy), + approvedAt: asNumber(raw.approvedAt), + rejectedBy: mapUserSummary(raw.rejectedBy), + rejectedAt: asNumber(raw.rejectedAt), + deleted: asBoolean(raw.deleted), + costRate: mapTimeEntryRate(raw.costRate), + billRate: mapTimeEntryRate(raw.billRate), + fields: asArray(raw.fields).map(mapTimeEntryField), + } +} + +export const TIME_ENTRY_CATEGORY_OUTPUT_PROPERTIES = { + categoryId: { type: 'number', description: 'Unique identifier of the category', nullable: true }, + categoryName: { type: 'string', description: 'Name of the category', nullable: true }, +} satisfies Record + +const TIME_ENTRY_RATE_OUTPUT_PROPERTIES = { + rate: { type: 'number', description: 'Hourly monetary rate', nullable: true }, + currency: { type: 'string', description: 'Three-letter ISO currency code', nullable: true }, +} satisfies Record + +export const TIME_ENTRY_OUTPUT_PROPERTIES = { + timeEntryId: { + type: 'number', + description: 'Unique identifier of the time entry', + nullable: true, + }, + date: { + type: 'string', + description: 'Date of the time entry (YYYY-MM-DD)', + nullable: true, + }, + minutes: { + type: 'number', + description: 'Duration of the time entry in minutes', + nullable: true, + }, + activityName: { + type: 'string', + description: 'Name of the adhoc activity, when the entry is tracked against an activity', + nullable: true, + }, + project: { + type: 'object', + description: 'Project associated with the time entry', + nullable: true, + properties: { + projectId: { + type: 'number', + description: 'Unique identifier of the project', + nullable: true, + }, + projectName: { type: 'string', description: 'Name of the project', nullable: true }, + }, + }, + task: { + type: 'object', + description: 'Task associated with the time entry', + nullable: true, + properties: { + taskId: { type: 'number', description: 'Unique identifier of the task', nullable: true }, + taskName: { type: 'string', description: 'Name of the task', nullable: true }, + }, + }, + projectPhase: { + type: 'object', + description: 'Project phase associated with the time entry', + nullable: true, + properties: { + phaseId: { type: 'number', description: 'Unique identifier of the phase', nullable: true }, + phaseName: { type: 'string', description: 'Name of the phase', nullable: true }, + }, + }, + billable: { + type: 'boolean', + description: 'Whether the time entry is billable', + nullable: true, + }, + user: { + type: 'object', + description: 'User the time entry belongs to', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + notes: { + type: 'string', + description: 'Notes for the time entry', + nullable: true, + }, + category: { + type: 'object', + description: 'Category associated with the time entry', + nullable: true, + properties: TIME_ENTRY_CATEGORY_OUTPUT_PROPERTIES, + }, + sourceType: { + type: 'string', + description: + 'Source of the time entry (GOOGLE_CALENDAR, OUTLOOK_CALENDAR, TASK, PROJECT, PHASE, ADHOC, MILESTONE)', + nullable: true, + }, + status: { + type: 'string', + description: 'Approval status of the time entry (NOT_SUBMITTED, SUBMITTED, APPROVED, REJECTED)', + nullable: true, + }, + createdAt: { + type: 'number', + description: 'Creation timestamp in epoch milliseconds', + nullable: true, + }, + updatedAt: { + type: 'number', + description: 'Last-updated timestamp in epoch milliseconds', + nullable: true, + }, + createdBy: { + type: 'object', + description: 'User who created the time entry', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + updatedBy: { + type: 'object', + description: 'User who last updated the time entry', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + submittedBy: { + type: 'object', + description: + 'User who submitted the time entry (may be null even for approved/rejected entries)', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + submittedAt: { + type: 'number', + description: 'Submission timestamp in epoch milliseconds', + nullable: true, + }, + approvedBy: { + type: 'object', + description: 'User who approved the time entry', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + approvedAt: { + type: 'number', + description: 'Approval timestamp in epoch milliseconds', + nullable: true, + }, + rejectedBy: { + type: 'object', + description: 'User who rejected the time entry', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + rejectedAt: { + type: 'number', + description: 'Rejection timestamp in epoch milliseconds', + nullable: true, + }, + deleted: { + type: 'boolean', + description: 'Whether the time entry is deleted', + nullable: true, + }, + costRate: { + type: 'object', + description: 'Hourly cost rate assigned to the user for this entry', + nullable: true, + properties: TIME_ENTRY_RATE_OUTPUT_PROPERTIES, + }, + billRate: { + type: 'object', + description: 'Hourly rate billed to the customer for this entry', + nullable: true, + properties: TIME_ENTRY_RATE_OUTPUT_PROPERTIES, + }, + fields: { + type: 'array', + description: 'Custom fields associated with the time entry', + items: { + type: 'object', + properties: { + fieldId: { type: 'number', description: 'Unique identifier of the field', nullable: true }, + fieldLabel: { type: 'string', description: 'Label of the field', nullable: true }, + fieldValue: { type: 'json', description: 'Value of the field', nullable: true }, + fieldValueLabel: { + type: 'string', + description: 'String representation of the field value', + nullable: true, + }, + }, + }, + }, +} satisfies Record + +/** Params for `rocketlane_create_time_entry`. */ +export interface RocketlaneCreateTimeEntryParams extends RocketlaneBaseParams { + date: string + minutes: number + activityName?: string + taskId?: number + projectPhaseId?: number + projectId?: number + billable?: boolean + userId?: number + userEmail?: string + notes?: string + categoryId?: number + includeFields?: string + includeAllFields?: boolean +} + +/** Params for `rocketlane_get_time_entry`. */ +export interface RocketlaneGetTimeEntryParams extends RocketlaneBaseParams { + timeEntryId: number + includeFields?: string + includeAllFields?: boolean +} + +/** Params for `rocketlane_update_time_entry`. */ +export interface RocketlaneUpdateTimeEntryParams extends RocketlaneBaseParams { + timeEntryId: number + date: string + minutes: number + activityName?: string + notes?: string + billable?: boolean + categoryId?: number + includeFields?: string + includeAllFields?: boolean +} + +/** Params for `rocketlane_delete_time_entry`. */ +export interface RocketlaneDeleteTimeEntryParams extends RocketlaneBaseParams { + timeEntryId: number +} + +/** Filter/sort/pagination params shared by the list and search time entry tools. */ +export interface RocketlaneTimeEntryFilterParams extends RocketlaneBaseParams { + sortBy?: string + sortOrder?: string + match?: string + dateEq?: string + dateGt?: string + dateGe?: string + dateLt?: string + dateLe?: string + projectPhaseIdEq?: number + categoryIdEq?: number + userIdEq?: number + sourceTypeEq?: string + activityNameEq?: string + activityNameCn?: string + approvalStatusEq?: string + pageSize?: number + pageToken?: string +} + +/** Params for `rocketlane_list_time_entries`. */ +export interface RocketlaneListTimeEntriesParams extends RocketlaneTimeEntryFilterParams { + projectIdEq?: number + taskIdEq?: number + emailIdEq?: string + emailIdCn?: string + billableEq?: boolean + includeDeletedEq?: boolean + submittedByEq?: number + approvedByEq?: number + rejectedByEq?: number + createdAtGt?: number + createdAtLt?: number + updatedAtGt?: number + updatedAtLt?: number + includeFields?: string +} + +/** Params for `rocketlane_search_time_entries` (deprecated search endpoint). */ +export interface RocketlaneSearchTimeEntriesParams extends RocketlaneTimeEntryFilterParams { + projectEq?: number + taskEq?: number + includeFields?: string + includeAllFields?: boolean +} + +/** Params for `rocketlane_list_time_entry_categories`. */ +export interface RocketlaneListTimeEntryCategoriesParams extends RocketlaneBaseParams { + pageSize?: number + pageToken?: string +} + +/** Response carrying a single time entry. */ +export interface RocketlaneTimeEntryResponse extends ToolResponse { + output: { + timeEntry: RocketlaneTimeEntry + } +} + +/** Response for the delete time entry tool. */ +export interface RocketlaneDeleteTimeEntryResponse extends ToolResponse { + output: { + deleted: boolean + timeEntryId: number | null + } +} + +/** Response carrying a paginated list of time entries. */ +export interface RocketlaneTimeEntryListResponse extends ToolResponse { + output: { + timeEntries: RocketlaneTimeEntry[] + pagination: RocketlanePagination + } +} + +/** Response carrying a paginated list of time entry categories. */ +export interface RocketlaneTimeEntryCategoryListResponse extends ToolResponse { + output: { + categories: RocketlaneTimeEntryCategory[] + pagination: RocketlanePagination + } +} + +// endregion + +// region Spaces + +/** The project a space belongs to. */ +export interface RocketlaneSpaceProject { + projectId: number | null + projectName: string | null +} + +/** A Rocketlane space. */ +export interface RocketlaneSpace { + spaceId: number | null + spaceName: string | null + project: RocketlaneSpaceProject | null + createdAt: number | null + createdBy: RocketlaneUserSummary | null + updatedAt: number | null + updatedBy: RocketlaneUserSummary | null + private: boolean | null +} + +export function mapSpace(value: unknown): RocketlaneSpace { + const raw = asObject(value) ?? {} + const project = asObject(raw.project) + return { + spaceId: asNumber(raw.spaceId), + spaceName: asString(raw.spaceName), + project: project + ? { + projectId: asNumber(project.projectId), + projectName: asString(project.projectName), + } + : null, + createdAt: asNumber(raw.createdAt), + createdBy: mapUserSummary(raw.createdBy), + updatedAt: asNumber(raw.updatedAt), + updatedBy: mapUserSummary(raw.updatedBy), + private: asBoolean(raw.private), + } +} + +export const SPACE_OUTPUT_PROPERTIES = { + spaceId: { type: 'number', description: 'Unique identifier of the space', nullable: true }, + spaceName: { type: 'string', description: 'Name of the space', nullable: true }, + project: { + type: 'object', + description: 'Project the space belongs to', + nullable: true, + properties: { + projectId: { + type: 'number', + description: 'Unique identifier of the project', + nullable: true, + }, + projectName: { type: 'string', description: 'Name of the project', nullable: true }, + }, + }, + createdAt: { + type: 'number', + description: 'Timestamp when the space was created (epoch millis)', + nullable: true, + }, + createdBy: { + type: 'object', + description: 'Team member who created the space', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + updatedAt: { + type: 'number', + description: 'Timestamp when the space was last updated (epoch millis)', + nullable: true, + }, + updatedBy: { + type: 'object', + description: 'Team member who last updated the space', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + private: { + type: 'boolean', + description: 'Whether the space is private or shared', + nullable: true, + }, +} satisfies Record + +export interface RocketlaneCreateSpaceParams extends RocketlaneBaseParams { + projectId: number + spaceName: string + private?: boolean +} + +export interface RocketlaneGetSpaceParams extends RocketlaneBaseParams { + spaceId: number +} + +export interface RocketlaneUpdateSpaceParams extends RocketlaneBaseParams { + spaceId: number + spaceName?: string +} + +export interface RocketlaneDeleteSpaceParams extends RocketlaneBaseParams { + spaceId: number +} + +export interface RocketlaneListSpacesParams extends RocketlaneBaseParams { + projectId: number + pageSize?: number + pageToken?: string + sortBy?: string + sortOrder?: string + match?: string + spaceNameEq?: string + spaceNameCn?: string + spaceNameNc?: string + createdAtGt?: number + createdAtEq?: number + createdAtLt?: number + createdAtGe?: number + createdAtLe?: number + updatedAtGt?: number + updatedAtEq?: number + updatedAtLt?: number + updatedAtGe?: number + updatedAtLe?: number +} + +export interface RocketlaneSpaceResponse extends ToolResponse { + output: { + space: RocketlaneSpace + } +} + +export interface RocketlaneDeleteSpaceResponse extends ToolResponse { + output: { + deleted: boolean + spaceId: number | null + } +} + +export interface RocketlaneListSpacesResponse extends ToolResponse { + output: { + spaces: RocketlaneSpace[] + pagination: RocketlanePagination + } +} + +// endregion + +// region Space Documents + +/** The space a space document belongs to. */ +export interface RocketlaneSpaceDocumentSpaceRef { + spaceId: number | null + spaceName: string | null +} + +/** The document template a space document was created from. */ +export interface RocketlaneSpaceDocumentSource { + templateId: number | null + templateName: string | null +} + +/** A Rocketlane space document (space tab). */ +export interface RocketlaneSpaceDocument { + spaceDocumentId: number | null + spaceDocumentName: string | null + space: RocketlaneSpaceDocumentSpaceRef | null + spaceDocumentType: string | null + url: string | null + source: RocketlaneSpaceDocumentSource | null + createdAt: number | null + createdBy: RocketlaneUserSummary | null + updatedAt: number | null + updatedBy: RocketlaneUserSummary | null + private: boolean | null +} + +export function mapSpaceDocument(value: unknown): RocketlaneSpaceDocument { + const raw = asObject(value) ?? {} + const space = asObject(raw.space) + const source = asObject(raw.source) + return { + spaceDocumentId: asNumber(raw.spaceDocumentId), + spaceDocumentName: asString(raw.spaceDocumentName), + space: space + ? { + spaceId: asNumber(space.spaceId), + spaceName: asString(space.spaceName), + } + : null, + spaceDocumentType: asString(raw.spaceDocumentType), + url: asString(raw.url), + source: source + ? { + templateId: asNumber(source.templateId), + templateName: asString(source.templateName), + } + : null, + createdAt: asNumber(raw.createdAt), + createdBy: mapUserSummary(raw.createdBy), + updatedAt: asNumber(raw.updatedAt), + updatedBy: mapUserSummary(raw.updatedBy), + private: asBoolean(raw.private), + } +} + +export const SPACE_DOCUMENT_OUTPUT_PROPERTIES = { + spaceDocumentId: { + type: 'number', + description: 'Unique identifier of the space document', + nullable: true, + }, + spaceDocumentName: { + type: 'string', + description: 'Name of the space document', + nullable: true, + }, + space: { + type: 'object', + description: 'Space the document belongs to', + nullable: true, + properties: { + spaceId: { type: 'number', description: 'Unique identifier of the space', nullable: true }, + spaceName: { type: 'string', description: 'Name of the space', nullable: true }, + }, + }, + spaceDocumentType: { + type: 'string', + description: 'Type of the space document (ROCKETLANE_DOCUMENT or EMBEDDED_DOCUMENT)', + nullable: true, + }, + url: { + type: 'string', + description: 'URL embedded in the space document', + nullable: true, + }, + source: { + type: 'object', + description: 'Document template the space document was created from', + nullable: true, + properties: { + templateId: { + type: 'number', + description: 'Unique identifier of the template', + nullable: true, + }, + templateName: { type: 'string', description: 'Name of the template', nullable: true }, + }, + }, + createdAt: { + type: 'number', + description: 'Timestamp when the space document was created (epoch millis)', + nullable: true, + }, + createdBy: { + type: 'object', + description: 'Team member who created the space document', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + updatedAt: { + type: 'number', + description: 'Timestamp when the space document was last updated (epoch millis)', + nullable: true, + }, + updatedBy: { + type: 'object', + description: 'Team member who last updated the space document', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + private: { + type: 'boolean', + description: 'Whether the space document is private or shared', + nullable: true, + }, +} satisfies Record + +export interface RocketlaneCreateSpaceDocumentParams extends RocketlaneBaseParams { + spaceId: number + spaceDocumentType: string + spaceDocumentName?: string + url?: string + templateId?: number +} + +export interface RocketlaneGetSpaceDocumentParams extends RocketlaneBaseParams { + spaceDocumentId: number +} + +export interface RocketlaneUpdateSpaceDocumentParams extends RocketlaneBaseParams { + spaceDocumentId: number + spaceDocumentName?: string + url?: string +} + +export interface RocketlaneDeleteSpaceDocumentParams extends RocketlaneBaseParams { + spaceDocumentId: number +} + +export interface RocketlaneListSpaceDocumentsParams extends RocketlaneBaseParams { + projectId: number + pageSize?: number + pageToken?: string + sortBy?: string + sortOrder?: string + match?: string + spaceDocumentNameEq?: string + spaceDocumentNameCn?: string + spaceDocumentNameNc?: string + spaceIdEq?: number + createdAtGt?: number + createdAtEq?: number + createdAtLt?: number + createdAtGe?: number + createdAtLe?: number + updatedAtGt?: number + updatedAtEq?: number + updatedAtLt?: number + updatedAtGe?: number + updatedAtLe?: number +} + +export interface RocketlaneSpaceDocumentResponse extends ToolResponse { + output: { + spaceDocument: RocketlaneSpaceDocument + } +} + +export interface RocketlaneDeleteSpaceDocumentResponse extends ToolResponse { + output: { + deleted: boolean + spaceDocumentId: number | null + } +} + +export interface RocketlaneListSpaceDocumentsResponse extends ToolResponse { + output: { + spaceDocuments: RocketlaneSpaceDocument[] + pagination: RocketlanePagination + } +} + +// endregion + +// region Users + +/** The role assigned to a user. */ +export interface RocketlaneUserRole { + roleId: number | null + roleName: string | null +} + +/** The company a user belongs to. */ +export interface RocketlaneUserCompany { + companyId: number | null + companyName: string | null +} + +/** The permission level of a user. */ +export interface RocketlaneUserPermission { + permissionId: number | null + permissionName: string | null +} + +/** The holiday calendar assigned to a user. Field names use the API's `calender` spelling. */ +export interface RocketlaneUserHolidayCalendar { + calenderId: number | null + calenderName: string | null +} + +/** A custom user field value. */ +export interface RocketlaneUserField { + fieldId: number | null + fieldLabel: string | null + fieldValue: string | null + fieldValueLabel: string | null +} + +/** A full Rocketlane user (distinct from the compact RocketlaneUserSummary reference). */ +export interface RocketlaneUser { + userId: number | null + email: string | null + firstName: string | null + lastName: string | null + type: string | null + status: string | null + role: RocketlaneUserRole | null + company: RocketlaneUserCompany | null + permission: RocketlaneUserPermission | null + fields: RocketlaneUserField[] + capacityInMinutes: number | null + holidayCalendar: RocketlaneUserHolidayCalendar | null + profilePictureUrl: string | null + createdAt: number | null + createdBy: RocketlaneUserSummary | null + updatedAt: number | null + updatedBy: RocketlaneUserSummary | null +} + +export function mapUser(value: unknown): RocketlaneUser { + const raw = asObject(value) ?? {} + const role = asObject(raw.role) + const company = asObject(raw.company) + const permission = asObject(raw.permission) + const holidayCalendar = asObject(raw.holidayCalendar) + return { + userId: asNumber(raw.userId), + email: asString(raw.email), + firstName: asString(raw.firstName), + lastName: asString(raw.lastName), + type: asString(raw.type), + status: asString(raw.status), + role: role + ? { + roleId: asNumber(role.roleId), + roleName: asString(role.roleName), + } + : null, + company: company + ? { + companyId: asNumber(company.companyId), + companyName: asString(company.companyName), + } + : null, + permission: permission + ? { + permissionId: asNumber(permission.permissionId), + permissionName: asString(permission.permissionName), + } + : null, + fields: asArray(raw.fields).map((field) => { + const fieldRaw = asObject(field) ?? {} + return { + fieldId: asNumber(fieldRaw.fieldId), + fieldLabel: asString(fieldRaw.fieldLabel), + fieldValue: asString(fieldRaw.fieldValue), + fieldValueLabel: asString(fieldRaw.fieldValueLabel), + } + }), + capacityInMinutes: asNumber(raw.capacityInMinutes), + holidayCalendar: holidayCalendar + ? { + calenderId: asNumber(holidayCalendar.calenderId), + calenderName: asString(holidayCalendar.calenderName), + } + : null, + profilePictureUrl: asString(raw.profilePictureUrl), + createdAt: asNumber(raw.createdAt), + createdBy: mapUserSummary(raw.createdBy), + updatedAt: asNumber(raw.updatedAt), + updatedBy: mapUserSummary(raw.updatedBy), + } +} + +export const USER_OUTPUT_PROPERTIES = { + userId: { type: 'number', description: 'Unique identifier of the user', nullable: true }, + email: { type: 'string', description: 'Email address of the user', nullable: true }, + firstName: { type: 'string', description: 'First name of the user', nullable: true }, + lastName: { type: 'string', description: 'Last name of the user', nullable: true }, + type: { + type: 'string', + description: 'Type of the user (TEAM_MEMBER, PARTNER, CUSTOMER, or EXTERNAL_PARTNER)', + nullable: true, + }, + status: { + type: 'string', + description: 'Status of the user (INACTIVE, INVITED, ACTIVE, or PASSIVE)', + nullable: true, + }, + role: { + type: 'object', + description: 'Role of the user', + nullable: true, + properties: { + roleId: { type: 'number', description: 'Unique identifier of the role', nullable: true }, + roleName: { type: 'string', description: 'Name of the role', nullable: true }, + }, + }, + company: { + type: 'object', + description: 'Company of the user', + nullable: true, + properties: { + companyId: { + type: 'number', + description: 'Unique identifier of the company', + nullable: true, + }, + companyName: { type: 'string', description: 'Name of the company', nullable: true }, + }, + }, + permission: { + type: 'object', + description: 'Permission of the user', + nullable: true, + properties: { + permissionId: { + type: 'number', + description: 'Unique identifier of the permission', + nullable: true, + }, + permissionName: { type: 'string', description: 'Name of the permission', nullable: true }, + }, + }, + fields: { + type: 'array', + description: 'Custom user field values', + items: { + type: 'object', + properties: { + fieldId: { type: 'number', description: 'Unique identifier of the field', nullable: true }, + fieldLabel: { + type: 'string', + description: 'Name of the custom user field', + nullable: true, + }, + fieldValue: { + type: 'string', + description: 'Value of the custom user field', + nullable: true, + }, + fieldValueLabel: { + type: 'string', + description: 'String representation of the field value', + nullable: true, + }, + }, + }, + }, + capacityInMinutes: { + type: 'number', + description: 'Capacity of the user in minutes', + nullable: true, + }, + holidayCalendar: { + type: 'object', + description: 'Holiday calendar of the user', + nullable: true, + properties: { + calenderId: { + type: 'number', + description: 'Unique identifier of the holiday calendar', + nullable: true, + }, + calenderName: { + type: 'string', + description: 'Name of the holiday calendar', + nullable: true, + }, + }, + }, + profilePictureUrl: { + type: 'string', + description: "URL of the user's profile picture", + nullable: true, + }, + createdAt: { + type: 'number', + description: 'Timestamp when the user was created (epoch millis)', + nullable: true, + }, + createdBy: { + type: 'object', + description: 'Team member who created the user', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + updatedAt: { + type: 'number', + description: 'Timestamp when the user was last updated (epoch millis)', + nullable: true, + }, + updatedBy: { + type: 'object', + description: 'Team member who last updated the user', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, +} satisfies Record + +export interface RocketlaneGetUserParams extends RocketlaneBaseParams { + userId: number + includeFields?: string + includeAllFields?: boolean +} + +export interface RocketlaneListUsersParams extends RocketlaneBaseParams { + pageSize?: number + pageToken?: string + includeFields?: string + includeAllFields?: boolean + sortBy?: string + sortOrder?: string + match?: string + firstNameEq?: string + firstNameCn?: string + firstNameNc?: string + lastNameEq?: string + lastNameCn?: string + lastNameNc?: string + emailEq?: string + emailCn?: string + emailNc?: string + statusEq?: string + statusOneOf?: string + statusNoneOf?: string + typeEq?: string + typeOneOf?: string + roleIdEq?: string + roleIdOneOf?: string + roleIdNoneOf?: string + permissionIdEq?: string + permissionIdOneOf?: string + permissionIdNoneOf?: string + capacityInMinutesEq?: number + capacityInMinutesGt?: number + capacityInMinutesGe?: number + capacityInMinutesLt?: number + capacityInMinutesLe?: number + createdAtGt?: number + createdAtEq?: number + createdAtLt?: number + createdAtGe?: number + createdAtLe?: number + updatedAtGt?: number + updatedAtEq?: number + updatedAtLt?: number + updatedAtGe?: number + updatedAtLe?: number +} + +export interface RocketlaneUserResponse extends ToolResponse { + output: { + user: RocketlaneUser + } +} + +export interface RocketlaneListUsersResponse extends ToolResponse { + output: { + users: RocketlaneUser[] + pagination: RocketlanePagination + } +} + +// endregion + +// region Time-Offs + +/** Params for creating a time-off. The API requires identifying the user by `userId` or `userEmail`. */ +export interface RocketlaneTimeOffCreateParams extends RocketlaneBaseParams { + userId?: number + userEmail?: string + startDate: string + endDate: string + type: string + durationInMinutes?: number + note?: string + notifyProjectOwners?: boolean + notifyUserIds?: number[] + notifyUserEmails?: string[] + includeFields?: string[] + includeAllFields?: boolean +} + +/** Params for fetching a single time-off by ID. */ +export interface RocketlaneTimeOffGetParams extends RocketlaneBaseParams { + timeOffId: number + includeFields?: string[] + includeAllFields?: boolean +} + +/** Params for deleting a time-off by ID. */ +export interface RocketlaneTimeOffDeleteParams extends RocketlaneBaseParams { + timeOffId: number +} + +/** Params for listing time-offs with filters, sorting, and pagination. */ +export interface RocketlaneTimeOffListParams extends RocketlaneBaseParams { + pageSize?: number + pageToken?: string + includeFields?: string[] + includeAllFields?: boolean + sortBy?: string + sortOrder?: string + match?: string + startDateGt?: string + startDateEq?: string + startDateLt?: string + startDateGe?: string + startDateLe?: string + endDateGt?: string + endDateEq?: string + endDateLt?: string + endDateGe?: string + endDateLe?: string + typeEq?: string + typeOneOf?: string + typeNoneOf?: string + userIdEq?: string + userIdOneOf?: string + userIdNoneOf?: string + emailIdEq?: string + emailIdOneOf?: string + emailIdNoneOf?: string +} + +/** The notify-users preferences attached to a time-off. */ +export interface RocketlaneTimeOffNotifyUsers { + projectOwners: boolean | null + others: RocketlaneUserSummary[] +} + +/** A Rocketlane time-off entry. */ +export interface RocketlaneTimeOff { + timeOffId: number | null + user: RocketlaneUserSummary | null + note: string | null + startDate: string | null + endDate: string | null + durationInMinutes: number | null + type: string | null + notifyUsers: RocketlaneTimeOffNotifyUsers | null + createdAt: number | null + createdBy: RocketlaneUserSummary | null +} + +function mapTimeOffNotifyUsers(value: unknown): RocketlaneTimeOffNotifyUsers | null { + const raw = asObject(value) + if (!raw) return null + return { + projectOwners: asBoolean(raw.projectOwners), + others: asArray(raw.others) + .map(mapUserSummary) + .filter((user): user is RocketlaneUserSummary => user !== null), + } +} + +/** + * Maps a raw time-off payload to the normalized {@link RocketlaneTimeOff} shape. + */ +export function mapTimeOff(value: unknown): RocketlaneTimeOff { + const raw = asObject(value) ?? {} + return { + timeOffId: asNumber(raw.timeOffId), + user: mapUserSummary(raw.user), + note: asString(raw.note), + startDate: asString(raw.startDate), + endDate: asString(raw.endDate), + durationInMinutes: asNumber(raw.durationInMinutes), + type: asString(raw.type), + notifyUsers: mapTimeOffNotifyUsers(raw.notifyUsers), + createdAt: asNumber(raw.createdAt), + createdBy: mapUserSummary(raw.createdBy), + } +} + +export const TIME_OFF_OUTPUT_PROPERTIES = { + timeOffId: { type: 'number', description: 'Unique identifier of the time-off', nullable: true }, + user: { + type: 'object', + description: 'The team member the time-off belongs to', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + note: { type: 'string', description: 'Note or comment about the time-off', nullable: true }, + startDate: { + type: 'string', + description: 'Time-off start date (YYYY-MM-DD)', + nullable: true, + }, + endDate: { type: 'string', description: 'Time-off end date (YYYY-MM-DD)', nullable: true }, + durationInMinutes: { + type: 'number', + description: 'Duration in minutes per day for the time-off interval', + nullable: true, + }, + type: { + type: 'string', + description: 'Type of the time-off (FULL_DAY, HALF_DAY, or CUSTOM)', + nullable: true, + }, + notifyUsers: { + type: 'object', + description: 'Users notified about the time-off', + nullable: true, + properties: { + projectOwners: { + type: 'boolean', + description: 'Whether project owners of projects the user is part of are notified', + nullable: true, + }, + others: { + type: 'array', + description: 'Other users notified about the time-off', + items: { type: 'object', properties: USER_SUMMARY_OUTPUT_PROPERTIES }, + }, + }, + }, + createdAt: { + type: 'number', + description: 'Timestamp when the time-off was created (epoch milliseconds)', + nullable: true, + }, + createdBy: { + type: 'object', + description: 'The team member who created the time-off', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, +} satisfies Record + +/** Response containing a single time-off. */ +export interface RocketlaneTimeOffResponse extends ToolResponse { + output: { + timeOff: RocketlaneTimeOff + } +} + +/** Response for a time-off deletion. */ +export interface RocketlaneTimeOffDeleteResponse extends ToolResponse { + output: { + deleted: boolean + timeOffId: number | null + } +} + +/** Response containing a page of time-offs. */ +export interface RocketlaneTimeOffListResponse extends ToolResponse { + output: { + timeOffs: RocketlaneTimeOff[] + pagination: RocketlanePagination + } +} + +// endregion + +// region Resource Allocations + +/** Params for listing resource allocations. `startDate` and `endDate` are required by the API. */ +export interface RocketlaneResourceAllocationListParams extends RocketlaneBaseParams { + startDate: string + endDate: string + pageSize?: number + pageToken?: string + includeFields?: string[] + includeAllFields?: boolean + sortBy?: string + sortOrder?: string + match?: string + memberIdEq?: string + memberIdOneOf?: string + memberIdNoneOf?: string + projectIdEq?: string + projectIdOneOf?: string + projectIdNoneOf?: string + placeholderIdEq?: string + placeholderIdOneOf?: string + placeholderIdNoneOf?: string +} + +/** A role attached to an allocation member or placeholder. */ +export interface RocketlaneResourceAllocationRole { + roleId: number | null + roleName: string | null +} + +/** The team member an allocation is made for, including their role. */ +export interface RocketlaneResourceAllocationMember extends RocketlaneUserSummary { + role: RocketlaneResourceAllocationRole | null +} + +/** The placeholder an allocation is made for. */ +export interface RocketlaneResourceAllocationPlaceholder { + placeholderId: number | null + placeholderName: string | null + role: RocketlaneResourceAllocationRole | null +} + +/** The project associated with an allocation. */ +export interface RocketlaneResourceAllocationProject { + projectId: number | null + projectName: string | null +} + +/** A task associated with an allocation. */ +export interface RocketlaneResourceAllocationTask { + taskId: number | null + taskName: string | null +} + +/** Total duration figures for an allocation between its start and end dates. */ +export interface RocketlaneResourceAllocationDuration { + daysConsider: number | null + seconds: number | null + minutes: number | null + hours: number | null +} + +/** A Rocketlane resource allocation. The API exposes no allocation identifier. */ +export interface RocketlaneResourceAllocation { + startDate: string | null + endDate: string | null + secondsPerDay: number | null + minutesPerDay: number | null + hoursPerDay: number | null + duration: RocketlaneResourceAllocationDuration | null + allocationType: string | null + allocationFor: string | null + project: RocketlaneResourceAllocationProject | null + tasks: RocketlaneResourceAllocationTask[] + member: RocketlaneResourceAllocationMember | null + placeholder: RocketlaneResourceAllocationPlaceholder | null + createdAt: number | null + updatedAt: number | null + createdBy: RocketlaneUserSummary | null + updatedBy: RocketlaneUserSummary | null +} + +function mapResourceAllocationRole(value: unknown): RocketlaneResourceAllocationRole | null { + const raw = asObject(value) + if (!raw) return null + return { + roleId: asNumber(raw.roleId), + roleName: asString(raw.roleName), + } +} + +function mapResourceAllocationMember(value: unknown): RocketlaneResourceAllocationMember | null { + const raw = asObject(value) + if (!raw) return null + const user = mapUserSummary(raw) + if (!user) return null + return { + ...user, + role: mapResourceAllocationRole(raw.role), + } +} + +function mapResourceAllocationPlaceholder( + value: unknown +): RocketlaneResourceAllocationPlaceholder | null { + const raw = asObject(value) + if (!raw) return null + return { + placeholderId: asNumber(raw.placeholderId), + placeholderName: asString(raw.placeholderName), + role: mapResourceAllocationRole(raw.role), + } +} + +function mapResourceAllocationDuration( + value: unknown +): RocketlaneResourceAllocationDuration | null { + const raw = asObject(value) + if (!raw) return null + return { + daysConsider: asNumber(raw.daysConsider), + seconds: asNumber(raw.seconds), + minutes: asNumber(raw.minutes), + hours: asNumber(raw.hours), + } +} + +function mapResourceAllocationProject(value: unknown): RocketlaneResourceAllocationProject | null { + const raw = asObject(value) + if (!raw) return null + return { + projectId: asNumber(raw.projectId), + projectName: asString(raw.projectName), + } +} + +function mapResourceAllocationTask(value: unknown): RocketlaneResourceAllocationTask { + const raw = asObject(value) ?? {} + return { + taskId: asNumber(raw.taskId), + taskName: asString(raw.taskName), + } +} + +/** + * Maps a raw resource-allocation payload to the normalized + * {@link RocketlaneResourceAllocation} shape. + */ +export function mapResourceAllocation(value: unknown): RocketlaneResourceAllocation { + const raw = asObject(value) ?? {} + return { + startDate: asString(raw.startDate), + endDate: asString(raw.endDate), + secondsPerDay: asNumber(raw.secondsPerDay), + minutesPerDay: asNumber(raw.minutesPerDay), + hoursPerDay: asNumber(raw.hoursPerDay), + duration: mapResourceAllocationDuration(raw.duration), + allocationType: asString(raw.allocationType), + allocationFor: asString(raw.allocationFor), + project: mapResourceAllocationProject(raw.project), + tasks: asArray(raw.tasks).map(mapResourceAllocationTask), + member: mapResourceAllocationMember(raw.member), + placeholder: mapResourceAllocationPlaceholder(raw.placeholder), + createdAt: asNumber(raw.createdAt), + updatedAt: asNumber(raw.updatedAt), + createdBy: mapUserSummary(raw.createdBy), + updatedBy: mapUserSummary(raw.updatedBy), + } +} + +const RESOURCE_ALLOCATION_ROLE_OUTPUT_PROPERTIES = { + roleId: { type: 'number', description: 'Unique identifier of the role', nullable: true }, + roleName: { type: 'string', description: 'Name of the role', nullable: true }, +} satisfies Record + +export const RESOURCE_ALLOCATION_OUTPUT_PROPERTIES = { + startDate: { + type: 'string', + description: 'Allocation start date (YYYY-MM-DD)', + nullable: true, + }, + endDate: { type: 'string', description: 'Allocation end date (YYYY-MM-DD)', nullable: true }, + secondsPerDay: { type: 'number', description: 'Allocated seconds per day', nullable: true }, + minutesPerDay: { type: 'number', description: 'Allocated minutes per day', nullable: true }, + hoursPerDay: { type: 'number', description: 'Allocated hours per day', nullable: true }, + duration: { + type: 'object', + description: 'Total allocation duration between the start and end dates', + nullable: true, + properties: { + daysConsider: { + type: 'number', + description: 'Number of week days considered for the duration computation', + nullable: true, + }, + seconds: { type: 'number', description: 'Total allocation seconds', nullable: true }, + minutes: { type: 'number', description: 'Total allocation minutes', nullable: true }, + hours: { type: 'number', description: 'Total allocation hours', nullable: true }, + }, + }, + allocationType: { + type: 'string', + description: 'Type of allocation (SOFT or HARD)', + nullable: true, + }, + allocationFor: { + type: 'string', + description: 'Who the allocation is for (TEAM_MEMBER or PLACEHOLDER)', + nullable: true, + }, + project: { + type: 'object', + description: 'The project associated with the allocation', + nullable: true, + properties: { + projectId: { + type: 'number', + description: 'Unique identifier of the project', + nullable: true, + }, + projectName: { type: 'string', description: 'Name of the project', nullable: true }, + }, + }, + tasks: { + type: 'array', + description: 'Tasks associated with the allocation', + items: { + type: 'object', + properties: { + taskId: { type: 'number', description: 'Unique identifier of the task', nullable: true }, + taskName: { type: 'string', description: 'Name of the task', nullable: true }, + }, + }, + }, + member: { + type: 'object', + description: 'The team member allocated when allocationFor is TEAM_MEMBER', + nullable: true, + properties: { + ...USER_SUMMARY_OUTPUT_PROPERTIES, + role: { + type: 'object', + description: 'Role of the member', + nullable: true, + properties: RESOURCE_ALLOCATION_ROLE_OUTPUT_PROPERTIES, + }, + }, + }, + placeholder: { + type: 'object', + description: 'The placeholder allocated when allocationFor is PLACEHOLDER', + nullable: true, + properties: { + placeholderId: { + type: 'number', + description: 'Unique identifier of the placeholder', + nullable: true, + }, + placeholderName: { + type: 'string', + description: 'Name of the placeholder', + nullable: true, + }, + role: { + type: 'object', + description: 'Role of the placeholder', + nullable: true, + properties: RESOURCE_ALLOCATION_ROLE_OUTPUT_PROPERTIES, + }, + }, + }, + createdAt: { + type: 'number', + description: 'Timestamp when the allocation was created (epoch milliseconds)', + nullable: true, + }, + updatedAt: { + type: 'number', + description: 'Timestamp when the allocation was last updated (epoch milliseconds)', + nullable: true, + }, + createdBy: { + type: 'object', + description: 'The team member who created the allocation', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + updatedBy: { + type: 'object', + description: 'The team member who last updated the allocation', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, +} satisfies Record + +/** Response containing a page of resource allocations. */ +export interface RocketlaneResourceAllocationListResponse extends ToolResponse { + output: { + allocations: RocketlaneResourceAllocation[] + pagination: RocketlanePagination + } +} + +// endregion + +// region Invoices + +/** Params for fetching a single invoice by ID. */ +export interface RocketlaneInvoiceGetParams extends RocketlaneBaseParams { + invoiceId: number + includeFields?: string[] + includeAllFields?: boolean +} + +/** Params for searching invoices with filters, sorting, and pagination. */ +export interface RocketlaneInvoiceListParams extends RocketlaneBaseParams { + pageSize?: number + pageToken?: string + includeFields?: string[] + includeAllFields?: boolean + sortBy?: string + sortOrder?: string + match?: string + dateOfIssueEq?: string + dateOfIssueGt?: string + dateOfIssueGe?: string + dateOfIssueLt?: string + dateOfIssueLe?: string + dueDateEq?: string + dueDateGt?: string + dueDateGe?: string + dueDateLt?: string + dueDateLe?: string + amountEq?: number + amountGt?: number + amountGe?: number + amountLt?: number + amountLe?: number + amountOutstandingEq?: number + amountOutstandingGt?: number + amountOutstandingGe?: number + amountOutstandingLt?: number + amountOutstandingLe?: number + amountPaidEq?: number + amountPaidGt?: number + amountPaidGe?: number + amountPaidLt?: number + amountPaidLe?: number + amountWrittenOffEq?: number + amountWrittenOffGt?: number + amountWrittenOffGe?: number + amountWrittenOffLt?: number + amountWrittenOffLe?: number + createdAtEq?: number + createdAtGt?: number + createdAtGe?: number + createdAtLt?: number + createdAtLe?: number + companyIdEq?: string + companyIdOneOf?: string + companyIdNoneOf?: string + invoiceNumberEq?: string + invoiceNumberCn?: string + invoiceNumberNc?: string + statusEq?: string + statusOneOf?: string + statusNoneOf?: string +} + +/** Params for listing payments recorded against an invoice. */ +export interface RocketlaneInvoicePaymentsParams extends RocketlaneBaseParams { + invoiceId: number + pageSize?: number + pageToken?: string +} + +/** Params for listing line items of an invoice. */ +export interface RocketlaneInvoiceLineItemsParams extends RocketlaneBaseParams { + invoiceId: number + pageSize?: number + pageToken?: string +} + +/** Customer company details on an invoice. */ +export interface RocketlaneInvoiceCompany { + companyId: number | null + companyName: string | null + companyUrl: string | null +} + +/** A project mapped to an invoice. */ +export interface RocketlaneInvoiceProject { + projectId: number | null + projectName: string | null +} + +/** A custom field value attached to an invoice. */ +export interface RocketlaneInvoiceField { + fieldId: number | null + fieldLabel: string | null + fieldValue: unknown + fieldValueLabel: string | null +} + +/** An attachment associated with an invoice. */ +export interface RocketlaneInvoiceAttachment { + attachmentId: number | null + attachmentName: string | null + createdAt: number | null + location: string | null + thumbLocation: string | null + visibility: boolean | null +} + +/** A Rocketlane invoice. */ +export interface RocketlaneInvoice { + invoiceId: number | null + invoiceNumber: string | null + dateOfIssue: string | null + dueDate: string | null + currency: string | null + status: string | null + amount: number | null + tax: number | null + subTotal: number | null + amountOutstanding: number | null + amountPaid: number | null + amountWrittenOff: number | null + notes: string | null + createdAt: number | null + updatedAt: number | null + createdBy: RocketlaneUserSummary | null + updatedBy: RocketlaneUserSummary | null + company: RocketlaneInvoiceCompany | null + projects: RocketlaneInvoiceProject[] + fields: RocketlaneInvoiceField[] + attachments: RocketlaneInvoiceAttachment[] +} + +/** A payment recorded against an invoice. */ +export interface RocketlaneInvoicePayment { + paymentId: number | null + paymentRecordType: string | null + currency: string | null + paymentDate: string | null + amount: number | null + notes: string | null +} + +/** Tax code information for an invoice line item. */ +export interface RocketlaneInvoiceLineItemTaxCode { + taxCodeId: number | null + taxCodeName: string | null + taxCodeRate: number | null + taxCodeAmount: number | null +} + +/** A tax component that makes up a line item's tax code. */ +export interface RocketlaneInvoiceLineItemTaxComponent { + taxComponentId: number | null + taxComponentName: string | null + taxComponentRate: number | null + taxComponentAmount: number | null + taxComponentType: string | null +} + +/** A line item on an invoice. */ +export interface RocketlaneInvoiceLineItem { + invoiceLineItemId: number | null + description: string | null + quantity: number | null + unitPrice: number | null + amount: number | null + sourceId: number | null + sourceType: string | null + taxCode: RocketlaneInvoiceLineItemTaxCode | null + taxComponents: RocketlaneInvoiceLineItemTaxComponent[] +} + +function mapInvoiceCompany(value: unknown): RocketlaneInvoiceCompany | null { + const raw = asObject(value) + if (!raw) return null + return { + companyId: asNumber(raw.companyId), + companyName: asString(raw.companyName), + companyUrl: asString(raw.companyUrl), + } +} + +function mapInvoiceProject(value: unknown): RocketlaneInvoiceProject { + const raw = asObject(value) ?? {} + return { + projectId: asNumber(raw.projectId), + projectName: asString(raw.projectName), + } +} + +function mapInvoiceField(value: unknown): RocketlaneInvoiceField { + const raw = asObject(value) ?? {} + return { + fieldId: asNumber(raw.fieldId), + fieldLabel: asString(raw.fieldLabel), + fieldValue: raw.fieldValue ?? null, + fieldValueLabel: asString(raw.fieldValueLabel), + } +} + +function mapInvoiceAttachment(value: unknown): RocketlaneInvoiceAttachment { + const raw = asObject(value) ?? {} + return { + attachmentId: asNumber(raw.attachmentId), + attachmentName: asString(raw.attachmentName), + createdAt: asNumber(raw.createdAt), + location: asString(raw.location), + thumbLocation: asString(raw.thumbLocation), + visibility: asBoolean(raw.visibility), + } +} + +/** + * Maps a raw invoice payload to the normalized {@link RocketlaneInvoice} shape. + */ +export function mapInvoice(value: unknown): RocketlaneInvoice { + const raw = asObject(value) ?? {} + return { + invoiceId: asNumber(raw.invoiceId), + invoiceNumber: asString(raw.invoiceNumber), + dateOfIssue: asString(raw.dateOfIssue), + dueDate: asString(raw.dueDate), + currency: asString(raw.currency), + status: asString(raw.status), + amount: asNumber(raw.amount), + tax: asNumber(raw.tax), + subTotal: asNumber(raw.subTotal), + amountOutstanding: asNumber(raw.amountOutstanding), + amountPaid: asNumber(raw.amountPaid), + amountWrittenOff: asNumber(raw.amountWrittenOff), + notes: asString(raw.notes), + createdAt: asNumber(raw.createdAt), + updatedAt: asNumber(raw.updatedAt), + createdBy: mapUserSummary(raw.createdBy), + updatedBy: mapUserSummary(raw.updatedBy), + company: mapInvoiceCompany(raw.company), + projects: asArray(raw.projects).map(mapInvoiceProject), + fields: asArray(raw.fields).map(mapInvoiceField), + attachments: asArray(raw.attachments).map(mapInvoiceAttachment), + } +} + +/** + * Maps a raw payment-record payload to the normalized {@link RocketlaneInvoicePayment} shape. + */ +export function mapInvoicePayment(value: unknown): RocketlaneInvoicePayment { + const raw = asObject(value) ?? {} + return { + paymentId: asNumber(raw.paymentId), + paymentRecordType: asString(raw.paymentRecordType), + currency: asString(raw.currency), + paymentDate: asString(raw.paymentDate), + amount: asNumber(raw.amount), + notes: asString(raw.notes), + } +} + +function mapInvoiceLineItemTaxCode(value: unknown): RocketlaneInvoiceLineItemTaxCode | null { + const raw = asObject(value) + if (!raw) return null + return { + taxCodeId: asNumber(raw.taxCodeId), + taxCodeName: asString(raw.taxCodeName), + taxCodeRate: asNumber(raw.taxCodeRate), + taxCodeAmount: asNumber(raw.taxCodeAmount), + } +} + +function mapInvoiceLineItemTaxComponent(value: unknown): RocketlaneInvoiceLineItemTaxComponent { + const raw = asObject(value) ?? {} + return { + taxComponentId: asNumber(raw.taxComponentId), + taxComponentName: asString(raw.taxComponentName), + taxComponentRate: asNumber(raw.taxComponentRate), + taxComponentAmount: asNumber(raw.taxComponentAmount), + taxComponentType: asString(raw.taxComponentType), + } +} + +/** + * Maps a raw invoice line-item payload to the normalized {@link RocketlaneInvoiceLineItem} shape. + */ +export function mapInvoiceLineItem(value: unknown): RocketlaneInvoiceLineItem { + const raw = asObject(value) ?? {} + return { + invoiceLineItemId: asNumber(raw.invoiceLineItemId), + description: asString(raw.description), + quantity: asNumber(raw.quantity), + unitPrice: asNumber(raw.unitPrice), + amount: asNumber(raw.amount), + sourceId: asNumber(raw.sourceId), + sourceType: asString(raw.sourceType), + taxCode: mapInvoiceLineItemTaxCode(raw.taxCode), + taxComponents: asArray(raw.taxComponents).map(mapInvoiceLineItemTaxComponent), + } +} + +export const INVOICE_OUTPUT_PROPERTIES = { + invoiceId: { type: 'number', description: 'Unique identifier of the invoice', nullable: true }, + invoiceNumber: { + type: 'string', + description: 'Invoice number assigned to this invoice', + nullable: true, + }, + dateOfIssue: { + type: 'string', + description: 'Date when the invoice was issued (YYYY-MM-DD)', + nullable: true, + }, + dueDate: { + type: 'string', + description: 'Due date for the invoice payment (YYYY-MM-DD)', + nullable: true, + }, + currency: { + type: 'string', + description: 'Currency of the invoice amount (e.g. USD)', + nullable: true, + }, + status: { type: 'string', description: 'Current status of the invoice', nullable: true }, + amount: { + type: 'number', + description: 'Total amount of the invoice including tax', + nullable: true, + }, + tax: { type: 'number', description: 'Tax amount applied to the invoice', nullable: true }, + subTotal: { + type: 'number', + description: 'Total amount of the invoice excluding tax', + nullable: true, + }, + amountOutstanding: { + type: 'number', + description: 'Balance amount remaining to be paid', + nullable: true, + }, + amountPaid: { + type: 'number', + description: 'Total amount paid for this invoice', + nullable: true, + }, + amountWrittenOff: { + type: 'number', + description: 'Total amount written off for this invoice', + nullable: true, + }, + notes: { + type: 'string', + description: 'Notes or additional information about the invoice', + nullable: true, + }, + createdAt: { + type: 'number', + description: 'Timestamp when the invoice was created (epoch milliseconds)', + nullable: true, + }, + updatedAt: { + type: 'number', + description: 'Timestamp when the invoice was last updated (epoch milliseconds)', + nullable: true, + }, + createdBy: { + type: 'object', + description: 'The team member who created the invoice', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + updatedBy: { + type: 'object', + description: 'The team member who last updated the invoice', + nullable: true, + properties: USER_SUMMARY_OUTPUT_PROPERTIES, + }, + company: { + type: 'object', + description: 'Customer company details for the invoice', + nullable: true, + properties: { + companyId: { + type: 'number', + description: 'Unique identifier of the customer company', + nullable: true, + }, + companyName: { + type: 'string', + description: 'Name of the customer company', + nullable: true, + }, + companyUrl: { + type: 'string', + description: 'URL of the customer company website', + nullable: true, + }, + }, + }, + projects: { + type: 'array', + description: 'Projects mapped to this invoice', + items: { + type: 'object', + properties: { + projectId: { + type: 'number', + description: 'Unique identifier of the project', + nullable: true, + }, + projectName: { type: 'string', description: 'Name of the project', nullable: true }, + }, + }, + }, + fields: { + type: 'array', + description: 'Custom invoice fields with their values', + items: { + type: 'object', + properties: { + fieldId: { + type: 'number', + description: 'Unique identifier of the field', + nullable: true, + }, + fieldLabel: { type: 'string', description: 'Label of the field', nullable: true }, + fieldValue: { + type: 'json', + description: 'Value of the field (string, number, or array depending on field type)', + nullable: true, + }, + fieldValueLabel: { + type: 'string', + description: 'String representation of the field value', + nullable: true, + }, + }, + }, + }, + attachments: { + type: 'array', + description: 'Attachments associated with the invoice', + items: { + type: 'object', + properties: { + attachmentId: { + type: 'number', + description: 'Unique identifier of the attachment', + nullable: true, + }, + attachmentName: { + type: 'string', + description: 'Name of the attachment', + nullable: true, + }, + createdAt: { + type: 'number', + description: 'Timestamp when the attachment was created (epoch milliseconds)', + nullable: true, + }, + location: { type: 'string', description: 'URL of the attachment', nullable: true }, + thumbLocation: { + type: 'string', + description: 'Thumbnail URL of the attachment', + nullable: true, + }, + visibility: { + type: 'boolean', + description: 'Visibility of the attachment', + nullable: true, + }, + }, + }, + }, +} satisfies Record + +export const INVOICE_PAYMENT_OUTPUT_PROPERTIES = { + paymentId: { + type: 'number', + description: 'Unique identifier of the payment record', + nullable: true, + }, + paymentRecordType: { + type: 'string', + description: 'Type of the payment record (PAID or WRITE_OFF)', + nullable: true, + }, + currency: { + type: 'string', + description: 'Currency of the payment amount (e.g. USD)', + nullable: true, + }, + paymentDate: { + type: 'string', + description: 'Date when the payment was made (YYYY-MM-DD)', + nullable: true, + }, + amount: { type: 'number', description: 'Amount of the payment', nullable: true }, + notes: { + type: 'string', + description: 'Additional notes or comments regarding the payment', + nullable: true, + }, +} satisfies Record + +export const INVOICE_LINE_ITEM_OUTPUT_PROPERTIES = { + invoiceLineItemId: { + type: 'number', + description: 'Unique identifier of the invoice line item', + nullable: true, + }, + description: { + type: 'string', + description: 'Description of the line item or service provided', + nullable: true, + }, + quantity: { type: 'number', description: 'Quantity of the item or service', nullable: true }, + unitPrice: { + type: 'number', + description: 'Unit price for the item or service', + nullable: true, + }, + amount: { + type: 'number', + description: 'Total amount for this line item (quantity times unit price)', + nullable: true, + }, + sourceId: { + type: 'number', + description: 'Unique identifier of the source entity (e.g. project ID)', + nullable: true, + }, + sourceType: { + type: 'string', + description: 'Type of source entity this line item is associated with (e.g. PROJECT)', + nullable: true, + }, + taxCode: { + type: 'object', + description: 'Tax code information for this line item', + nullable: true, + properties: { + taxCodeId: { + type: 'number', + description: 'Unique identifier of the tax code', + nullable: true, + }, + taxCodeName: { type: 'string', description: 'Name of the tax code', nullable: true }, + taxCodeRate: { + type: 'number', + description: 'Tax rate percentage for the tax code', + nullable: true, + }, + taxCodeAmount: { + type: 'number', + description: 'Tax amount calculated for this tax code', + nullable: true, + }, + }, + }, + taxComponents: { + type: 'array', + description: 'Tax components that make up the tax code', + items: { + type: 'object', + properties: { + taxComponentId: { + type: 'number', + description: 'Unique identifier of the tax component', + nullable: true, + }, + taxComponentName: { + type: 'string', + description: 'Name of the tax component', + nullable: true, + }, + taxComponentRate: { + type: 'number', + description: 'Tax rate percentage for the tax component', + nullable: true, + }, + taxComponentAmount: { + type: 'number', + description: 'Tax amount calculated for this tax component', + nullable: true, + }, + taxComponentType: { + type: 'string', + description: 'Type of the tax component (e.g. GST, VAT)', + nullable: true, + }, + }, + }, + }, +} satisfies Record + +/** Response containing a single invoice. */ +export interface RocketlaneInvoiceResponse extends ToolResponse { + output: { + invoice: RocketlaneInvoice + } +} + +/** Response containing a page of invoices. */ +export interface RocketlaneInvoiceListResponse extends ToolResponse { + output: { + invoices: RocketlaneInvoice[] + pagination: RocketlanePagination + } +} + +/** Response containing a page of invoice payments. */ +export interface RocketlaneInvoicePaymentListResponse extends ToolResponse { + output: { + payments: RocketlaneInvoicePayment[] + pagination: RocketlanePagination + } +} + +/** Response containing a page of invoice line items. */ +export interface RocketlaneInvoiceLineItemListResponse extends ToolResponse { + output: { + lineItems: RocketlaneInvoiceLineItem[] + pagination: RocketlanePagination + } +} + +// endregion diff --git a/apps/sim/tools/rocketlane/unassign_placeholders.ts b/apps/sim/tools/rocketlane/unassign_placeholders.ts new file mode 100644 index 00000000000..bf207d8ffbf --- /dev/null +++ b/apps/sim/tools/rocketlane/unassign_placeholders.ts @@ -0,0 +1,80 @@ +import { + mapPlaceholderMapping, + mapProject, + PLACEHOLDER_MAPPING_OUTPUT_PROPERTIES, + PROJECT_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneProjectPlaceholdersResponse, + type RocketlaneUnassignPlaceholdersParams, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneUnassignPlaceholdersTool: ToolConfig< + RocketlaneUnassignPlaceholdersParams, + RocketlaneProjectPlaceholdersResponse +> = { + id: 'rocketlane_unassign_placeholders', + name: 'Rocketlane Unassign Placeholders', + description: 'Unassign a placeholder from its user in a Rocketlane project', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the project', + }, + placeholderId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the placeholder to unassign', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/projects/${encodeURIComponent(String(params.projectId))}/unassign-placeholders`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => [{ placeholderId: params.placeholderId }], + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { + project: mapProject(data), + placeholders: Array.isArray(data?.placeholders) + ? data.placeholders.map(mapPlaceholderMapping) + : [], + }, + } + }, + + outputs: { + project: { + type: 'object', + description: 'The project after the placeholder was unassigned', + properties: PROJECT_OUTPUT_PROPERTIES, + }, + placeholders: { + type: 'array', + description: 'Placeholder-to-user mappings on the project', + items: { type: 'object', properties: PLACEHOLDER_MAPPING_OUTPUT_PROPERTIES }, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/update_field.ts b/apps/sim/tools/rocketlane/update_field.ts new file mode 100644 index 00000000000..2efbdec1955 --- /dev/null +++ b/apps/sim/tools/rocketlane/update_field.ts @@ -0,0 +1,113 @@ +import { + FIELD_OUTPUT_PROPERTIES, + mapField, + ROCKETLANE_API_BASE, + type RocketlaneFieldResponse, + type RocketlaneUpdateFieldParams, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneUpdateFieldTool: ToolConfig< + RocketlaneUpdateFieldParams, + RocketlaneFieldResponse +> = { + id: 'rocketlane_update_field', + name: 'Rocketlane Update Field', + description: + 'Update the label, description, enabled state, or privacy of an existing Rocketlane field', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + fieldId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the field to update', + }, + fieldLabel: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name of the field', + }, + fieldDescription: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New description of the field', + }, + enabled: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the field is enabled', + }, + private: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the field is private', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra field properties to include in the response (supported: options)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to return all field properties in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/fields/${encodeURIComponent(String(params.fieldId))}` + ) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'PUT', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + ...(params.fieldLabel != null && { fieldLabel: params.fieldLabel }), + ...(params.fieldDescription != null && { fieldDescription: params.fieldDescription }), + ...(params.enabled != null && { enabled: params.enabled }), + ...(params.private != null && { private: params.private }), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { field: mapField(data) }, + } + }, + + outputs: { + field: { + type: 'object', + description: 'The updated field', + properties: FIELD_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/update_field_option.ts b/apps/sim/tools/rocketlane/update_field_option.ts new file mode 100644 index 00000000000..5730aef43b7 --- /dev/null +++ b/apps/sim/tools/rocketlane/update_field_option.ts @@ -0,0 +1,86 @@ +import { + FIELD_OPTION_OUTPUT_PROPERTIES, + mapFieldOption, + ROCKETLANE_API_BASE, + type RocketlaneFieldOptionResponse, + type RocketlaneUpdateFieldOptionParams, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneUpdateFieldOptionTool: ToolConfig< + RocketlaneUpdateFieldOptionParams, + RocketlaneFieldOptionResponse +> = { + id: 'rocketlane_update_field_option', + name: 'Rocketlane Update Field Option', + description: + 'Update the label or color of an existing option on a SINGLE_CHOICE or MULTIPLE_CHOICE Rocketlane field', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + fieldId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the field the option belongs to', + }, + optionValue: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the option to update', + }, + optionLabel: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New display label of the option', + }, + optionColor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'New color of the option (RED, YELLOW, GREEN, TEAL, CYAN, BLUE, PURPLE, MAGENTA, GRAY, COOL_GRAY)', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/fields/${encodeURIComponent(String(params.fieldId))}/update-option`, + method: 'POST', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + optionValue: params.optionValue, + ...(params.optionLabel != null && { optionLabel: params.optionLabel }), + ...(params.optionColor != null && { optionColor: params.optionColor }), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { option: mapFieldOption(data) }, + } + }, + + outputs: { + option: { + type: 'object', + description: 'The updated field option', + properties: FIELD_OPTION_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/update_phase.ts b/apps/sim/tools/rocketlane/update_phase.ts new file mode 100644 index 00000000000..6446ee2512f --- /dev/null +++ b/apps/sim/tools/rocketlane/update_phase.ts @@ -0,0 +1,119 @@ +import { + mapPhase, + PHASE_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlanePhaseResponse, + type RocketlaneUpdatePhaseParams, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneUpdatePhaseTool: ToolConfig< + RocketlaneUpdatePhaseParams, + RocketlanePhaseResponse +> = { + id: 'rocketlane_update_phase', + name: 'Rocketlane Update Phase', + description: 'Update the name, dates, status, or privacy of an existing Rocketlane phase', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + phaseId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the phase to update', + }, + phaseName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name of the phase', + }, + startDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New planned start date of the phase (YYYY-MM-DD)', + }, + dueDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New planned due date of the phase (YYYY-MM-DD)', + }, + statusValue: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'New numeric status value for the phase', + }, + private: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the phase is private', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra phase properties to include in the response (supported: startDateActual, dueDateActual)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether to return all phase properties in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/phases/${encodeURIComponent(String(params.phaseId))}` + ) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'PUT', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => ({ + ...(params.phaseName != null && { phaseName: params.phaseName }), + ...(params.startDate != null && { startDate: params.startDate }), + ...(params.dueDate != null && { dueDate: params.dueDate }), + ...(params.statusValue != null && { status: { value: params.statusValue } }), + ...(params.private != null && { private: params.private }), + }), + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { phase: mapPhase(data) }, + } + }, + + outputs: { + phase: { + type: 'object', + description: 'The updated phase', + properties: PHASE_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/update_project.ts b/apps/sim/tools/rocketlane/update_project.ts new file mode 100644 index 00000000000..79483cdb11d --- /dev/null +++ b/apps/sim/tools/rocketlane/update_project.ts @@ -0,0 +1,194 @@ +import { + mapProject, + PROJECT_OUTPUT_PROPERTIES, + ROCKETLANE_API_BASE, + type RocketlaneProjectResponse, + type RocketlaneUpdateProjectParams, + rocketlaneError, + rocketlaneHeaders, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneUpdateProjectTool: ToolConfig< + RocketlaneUpdateProjectParams, + RocketlaneProjectResponse +> = { + id: 'rocketlane_update_project', + name: 'Rocketlane Update Project', + description: + 'Update a Rocketlane project by ID, including name, dates, visibility, owner, status, and custom fields', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + projectId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the project to update', + }, + projectName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name of the project', + }, + startDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Date on which the project begins (YYYY-MM-DD)', + }, + dueDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Date on which the project is planned to complete (YYYY-MM-DD, on or after startDate)', + }, + visibility: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Who can see the project: EVERYONE or MEMBERS', + }, + ownerUserId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: + 'User ID of the new project owner (transfers ownership and revokes access for the previous owner)', + }, + ownerEmailId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Email of the new project owner', + }, + statusValue: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Value (identifier) of the project status', + }, + fields: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Custom field assignments, each with fieldId and fieldValue (string, number, or number array matching the field type)', + items: { + type: 'object', + description: 'Custom field assignment', + properties: { + fieldId: { type: 'number', description: 'Unique identifier of the field' }, + fieldValue: { + type: 'string', + description: 'Value of the field (string, number, or number array)', + }, + }, + }, + }, + annualizedRecurringRevenue: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Recurring revenue of the customer subscriptions for a single calendar year', + }, + projectFee: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Total fee charged for the project', + }, + autoAllocation: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether auto allocation is enabled for the project', + }, + budgetedHours: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Total hours allocated for project execution (decimal, up to two places)', + }, + externalReferenceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Identifier linking the project to an external system', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra fields to return in the response (e.g. budgetedHours,progressPercentage)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Return all fields in the response body', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/projects/${encodeURIComponent(String(params.projectId))}` + ) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + return url.toString() + }, + method: 'PUT', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const body: Record = {} + if (params.projectName) body.projectName = params.projectName + if (params.startDate) body.startDate = params.startDate + if (params.dueDate) body.dueDate = params.dueDate + if (params.visibility) body.visibility = params.visibility + const owner: Record = {} + if (params.ownerUserId != null) owner.userId = params.ownerUserId + if (params.ownerEmailId) owner.emailId = params.ownerEmailId + if (Object.keys(owner).length > 0) body.owner = owner + if (params.statusValue != null) body.status = { value: params.statusValue } + if (params.fields && params.fields.length > 0) body.fields = params.fields + if (params.annualizedRecurringRevenue != null) + body.annualizedRecurringRevenue = params.annualizedRecurringRevenue + if (params.projectFee != null) body.projectFee = params.projectFee + if (params.autoAllocation != null) body.autoAllocation = params.autoAllocation + if (params.budgetedHours != null) body.budgetedHours = params.budgetedHours + if (params.externalReferenceId) body.externalReferenceId = params.externalReferenceId + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { project: mapProject(data) }, + } + }, + + outputs: { + project: { + type: 'object', + description: 'The updated project', + properties: PROJECT_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/update_space.ts b/apps/sim/tools/rocketlane/update_space.ts new file mode 100644 index 00000000000..baaf34c70c8 --- /dev/null +++ b/apps/sim/tools/rocketlane/update_space.ts @@ -0,0 +1,71 @@ +import { + mapSpace, + ROCKETLANE_API_BASE, + type RocketlaneSpaceResponse, + type RocketlaneUpdateSpaceParams, + rocketlaneError, + rocketlaneHeaders, + SPACE_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneUpdateSpaceTool: ToolConfig< + RocketlaneUpdateSpaceParams, + RocketlaneSpaceResponse +> = { + id: 'rocketlane_update_space', + name: 'Rocketlane Update Space', + description: 'Update a Rocketlane space by its ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + spaceId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the space to update', + }, + spaceName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name of the space', + }, + }, + + request: { + url: (params) => `${ROCKETLANE_API_BASE}/spaces/${encodeURIComponent(params.spaceId)}`, + method: 'PUT', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const body: Record = {} + if (params.spaceName != null) body.spaceName = params.spaceName + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { space: mapSpace(data) }, + } + }, + + outputs: { + space: { + type: 'object', + description: 'The updated space', + properties: SPACE_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/update_space_document.ts b/apps/sim/tools/rocketlane/update_space_document.ts new file mode 100644 index 00000000000..5f898c699f5 --- /dev/null +++ b/apps/sim/tools/rocketlane/update_space_document.ts @@ -0,0 +1,79 @@ +import { + mapSpaceDocument, + ROCKETLANE_API_BASE, + type RocketlaneSpaceDocumentResponse, + type RocketlaneUpdateSpaceDocumentParams, + rocketlaneError, + rocketlaneHeaders, + SPACE_DOCUMENT_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneUpdateSpaceDocumentTool: ToolConfig< + RocketlaneUpdateSpaceDocumentParams, + RocketlaneSpaceDocumentResponse +> = { + id: 'rocketlane_update_space_document', + name: 'Rocketlane Update Space Document', + description: 'Update a Rocketlane space document by its ID', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + spaceDocumentId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the space document to update', + }, + spaceDocumentName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name of the space document', + }, + url: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New URL to embed in the space document (for embedded documents)', + }, + }, + + request: { + url: (params) => + `${ROCKETLANE_API_BASE}/space-documents/${encodeURIComponent(params.spaceDocumentId)}`, + method: 'PUT', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const body: Record = {} + if (params.spaceDocumentName != null) body.spaceDocumentName = params.spaceDocumentName + if (params.url != null) body.url = params.url + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { spaceDocument: mapSpaceDocument(data) }, + } + }, + + outputs: { + spaceDocument: { + type: 'object', + description: 'The updated space document', + properties: SPACE_DOCUMENT_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/update_task.ts b/apps/sim/tools/rocketlane/update_task.ts new file mode 100644 index 00000000000..33c34bcac6e --- /dev/null +++ b/apps/sim/tools/rocketlane/update_task.ts @@ -0,0 +1,175 @@ +import { + mapTask, + ROCKETLANE_API_BASE, + type RocketlaneTaskResponse, + type RocketlaneUpdateTaskParams, + rocketlaneError, + rocketlaneHeaders, + TASK_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneUpdateTaskTool: ToolConfig< + RocketlaneUpdateTaskParams, + RocketlaneTaskResponse +> = { + id: 'rocketlane_update_task', + name: 'Rocketlane Update Task', + description: 'Update the properties of an existing Rocketlane task by its unique identifier', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + taskId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Unique identifier of the task to update', + }, + taskName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name of the task', + }, + taskDescription: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Description of the task in HTML format', + }, + taskPrivateNote: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Private note visible only to team members, in HTML format', + }, + startDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Date when the task starts (YYYY-MM-DD)', + }, + dueDate: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Date when the task is due, on or after the start date (YYYY-MM-DD)', + }, + effortInMinutes: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Expected effort to complete the task, in minutes', + }, + progress: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Progress of the task (0-100)', + }, + atRisk: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the task is marked as At Risk', + }, + type: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Type of the task: TASK or MILESTONE', + }, + statusValue: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Status value to set on the task', + }, + externalReferenceId: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'External reference identifier linking the task to an external system', + }, + private: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the task is private', + }, + includeFields: { + type: 'array', + required: false, + visibility: 'user-or-llm', + description: + 'Extra fields to include in the response (startDateActual, dueDateActual, type, phase, assignees, followers, dependencies, billable, csatEnabled, priority, timeEntryCategory, financialsBudget, taskPrivateNote, parent, externalReferenceId)', + items: { type: 'string' }, + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether all fields should be returned in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/tasks/${encodeURIComponent(String(params.taskId))}` + ) + if (params.includeFields && params.includeFields.length > 0) { + url.searchParams.set('includeFields', params.includeFields.join(',')) + } + if (params.includeAllFields !== undefined) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'PUT', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const body: Record = {} + if (params.taskName !== undefined) body.taskName = params.taskName + if (params.taskDescription !== undefined) body.taskDescription = params.taskDescription + if (params.taskPrivateNote !== undefined) body.taskPrivateNote = params.taskPrivateNote + if (params.startDate !== undefined) body.startDate = params.startDate + if (params.dueDate !== undefined) body.dueDate = params.dueDate + if (params.effortInMinutes !== undefined) body.effortInMinutes = params.effortInMinutes + if (params.progress !== undefined) body.progress = params.progress + if (params.atRisk !== undefined) body.atRisk = params.atRisk + if (params.type !== undefined) body.type = params.type + if (params.statusValue !== undefined) body.status = { value: params.statusValue } + if (params.externalReferenceId !== undefined) { + body.externalReferenceId = params.externalReferenceId + } + if (params.private !== undefined) body.private = params.private + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { task: mapTask(data) }, + } + }, + + outputs: { + task: { + type: 'object', + description: 'The updated task', + properties: TASK_OUTPUT_PROPERTIES, + }, + }, +} diff --git a/apps/sim/tools/rocketlane/update_time_entry.ts b/apps/sim/tools/rocketlane/update_time_entry.ts new file mode 100644 index 00000000000..16317f3395a --- /dev/null +++ b/apps/sim/tools/rocketlane/update_time_entry.ts @@ -0,0 +1,131 @@ +import { + mapTimeEntry, + ROCKETLANE_API_BASE, + type RocketlaneTimeEntryResponse, + type RocketlaneUpdateTimeEntryParams, + rocketlaneError, + rocketlaneHeaders, + TIME_ENTRY_OUTPUT_PROPERTIES, +} from '@/tools/rocketlane/types' +import type { ToolConfig } from '@/tools/types' + +export const rocketlaneUpdateTimeEntryTool: ToolConfig< + RocketlaneUpdateTimeEntryParams, + RocketlaneTimeEntryResponse +> = { + id: 'rocketlane_update_time_entry', + name: 'Rocketlane Update Time Entry', + description: + 'Update a Rocketlane time entry by ID. The activityName, notes, billable, and minutes properties can be updated', + version: '1.0.0', + + params: { + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Rocketlane API key', + }, + timeEntryId: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'ID of the time entry to update', + }, + date: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Date of the time entry in YYYY-MM-DD format (mandatory so the total time for the date does not exceed 24 hours)', + }, + minutes: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Duration of the time entry in minutes (1-1440)', + }, + activityName: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New name for the adhoc activity', + }, + notes: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'New notes for the time entry', + }, + billable: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether the time entry is billable', + }, + categoryId: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'ID of the time entry category', + }, + includeFields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Comma-separated extra fields to include in the response (notes, sourceType, deleted, status, submittedBy, submittedAt, approvedBy, approvedAt, rejectedBy, rejectedAt, costRate, billRate)', + }, + includeAllFields: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Whether all fields should be returned in the response', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `${ROCKETLANE_API_BASE}/time-entries/${encodeURIComponent(params.timeEntryId)}` + ) + if (params.includeFields) url.searchParams.set('includeFields', params.includeFields) + if (params.includeAllFields != null) { + url.searchParams.set('includeAllFields', String(params.includeAllFields)) + } + return url.toString() + }, + method: 'PUT', + headers: (params) => rocketlaneHeaders(params.apiKey), + body: (params) => { + const body: Record = { + date: params.date, + minutes: params.minutes, + } + if (params.activityName) body.activityName = params.activityName + if (params.notes) body.notes = params.notes + if (params.billable != null) body.billable = params.billable + if (params.categoryId != null) body.category = { categoryId: params.categoryId } + return body + }, + }, + + transformResponse: async (response: Response) => { + if (!response.ok) { + throw new Error(await rocketlaneError(response)) + } + const data = await response.json() + return { + success: true, + output: { timeEntry: mapTimeEntry(data) }, + } + }, + + outputs: { + timeEntry: { + type: 'object', + description: 'The updated time entry', + properties: TIME_ENTRY_OUTPUT_PROPERTIES, + }, + }, +} From dc0e40382a7071b0758a2367665cd6fd70e32b54 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 16 Jul 2026 02:55:21 -0700 Subject: [PATCH 2/4] fix(rocketlane): allow clearing optional fields on update, require a user reference for time-off and placeholder assignment --- apps/sim/tools/rocketlane/assign_placeholders.ts | 3 +++ apps/sim/tools/rocketlane/create_time_off.ts | 3 +++ apps/sim/tools/rocketlane/update_project.ts | 9 +++++---- apps/sim/tools/rocketlane/update_time_entry.ts | 4 ++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/sim/tools/rocketlane/assign_placeholders.ts b/apps/sim/tools/rocketlane/assign_placeholders.ts index 07c077bf3c0..4ef173a5e3f 100644 --- a/apps/sim/tools/rocketlane/assign_placeholders.ts +++ b/apps/sim/tools/rocketlane/assign_placeholders.ts @@ -61,6 +61,9 @@ export const rocketlaneAssignPlaceholdersTool: ToolConfig< method: 'POST', headers: (params) => rocketlaneHeaders(params.apiKey), body: (params) => { + if (params.userId == null && !params.userEmailId) { + throw new Error('Either a user ID or a user email is required to assign a placeholder') + } const user: Record = {} if (params.userId != null) user.userId = params.userId if (params.userEmailId) user.emailId = params.userEmailId diff --git a/apps/sim/tools/rocketlane/create_time_off.ts b/apps/sim/tools/rocketlane/create_time_off.ts index 6f59e88fad2..68e1d52a0d9 100644 --- a/apps/sim/tools/rocketlane/create_time_off.ts +++ b/apps/sim/tools/rocketlane/create_time_off.ts @@ -118,6 +118,9 @@ export const rocketlaneCreateTimeOffTool: ToolConfig< method: 'POST', headers: (params) => rocketlaneHeaders(params.apiKey), body: (params) => { + if (params.userId == null && !params.userEmail) { + throw new Error('Either a user ID or a user email is required to create a time-off') + } const user: Record = {} if (params.userId != null) user.userId = params.userId if (params.userEmail) user.emailId = params.userEmail diff --git a/apps/sim/tools/rocketlane/update_project.ts b/apps/sim/tools/rocketlane/update_project.ts index 79483cdb11d..8487c89fffc 100644 --- a/apps/sim/tools/rocketlane/update_project.ts +++ b/apps/sim/tools/rocketlane/update_project.ts @@ -153,9 +153,9 @@ export const rocketlaneUpdateProjectTool: ToolConfig< headers: (params) => rocketlaneHeaders(params.apiKey), body: (params) => { const body: Record = {} - if (params.projectName) body.projectName = params.projectName - if (params.startDate) body.startDate = params.startDate - if (params.dueDate) body.dueDate = params.dueDate + if (params.projectName !== undefined) body.projectName = params.projectName + if (params.startDate !== undefined) body.startDate = params.startDate + if (params.dueDate !== undefined) body.dueDate = params.dueDate if (params.visibility) body.visibility = params.visibility const owner: Record = {} if (params.ownerUserId != null) owner.userId = params.ownerUserId @@ -168,7 +168,8 @@ export const rocketlaneUpdateProjectTool: ToolConfig< if (params.projectFee != null) body.projectFee = params.projectFee if (params.autoAllocation != null) body.autoAllocation = params.autoAllocation if (params.budgetedHours != null) body.budgetedHours = params.budgetedHours - if (params.externalReferenceId) body.externalReferenceId = params.externalReferenceId + if (params.externalReferenceId !== undefined) + body.externalReferenceId = params.externalReferenceId return body }, }, diff --git a/apps/sim/tools/rocketlane/update_time_entry.ts b/apps/sim/tools/rocketlane/update_time_entry.ts index 16317f3395a..429fa806c01 100644 --- a/apps/sim/tools/rocketlane/update_time_entry.ts +++ b/apps/sim/tools/rocketlane/update_time_entry.ts @@ -102,8 +102,8 @@ export const rocketlaneUpdateTimeEntryTool: ToolConfig< date: params.date, minutes: params.minutes, } - if (params.activityName) body.activityName = params.activityName - if (params.notes) body.notes = params.notes + if (params.activityName !== undefined) body.activityName = params.activityName + if (params.notes !== undefined) body.notes = params.notes if (params.billable != null) body.billable = params.billable if (params.categoryId != null) body.category = { categoryId: params.categoryId } return body From f682759178d667756356bcb6f8faac90650fe2e9 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 16 Jul 2026 03:26:41 -0700 Subject: [PATCH 3/4] chore(rocketlane): trigger fresh review round From 84a8b914748a79cacc24e2f3872efcfa300699c9 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Thu, 16 Jul 2026 10:06:38 -0700 Subject: [PATCH 4/4] fix(rocketlane): require an owner reference when creating a project, matching the API contract --- apps/sim/tools/rocketlane/create_project.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/sim/tools/rocketlane/create_project.ts b/apps/sim/tools/rocketlane/create_project.ts index 23a2a73d25c..c1dcb745062 100644 --- a/apps/sim/tools/rocketlane/create_project.ts +++ b/apps/sim/tools/rocketlane/create_project.ts @@ -284,10 +284,13 @@ export const rocketlaneCreateProjectTool: ToolConfig< projectName: params.projectName, customer: { companyName: params.customerCompanyName }, } + if (params.ownerUserId == null && !params.ownerEmailId) { + throw new Error('Either an owner user ID or an owner email is required to create a project') + } const owner: Record = {} if (params.ownerUserId != null) owner.userId = params.ownerUserId if (params.ownerEmailId) owner.emailId = params.ownerEmailId - if (Object.keys(owner).length > 0) body.owner = owner + body.owner = owner if (params.startDate) body.startDate = params.startDate if (params.dueDate) body.dueDate = params.dueDate if (params.visibility) body.visibility = params.visibility