feat(tools): Rocketlane integration — 64 tools across projects, tasks, phases, fields, time tracking, spaces, and invoices (#5709)

* feat(rocketlane): Rocketlane integration — 64 tools across projects, tasks, phases, fields, time tracking, spaces, and invoices

* fix(rocketlane): allow clearing optional fields on update, require a user reference for time-off and placeholder assignment

* chore(rocketlane): trigger fresh review round

* fix(rocketlane): require an owner reference when creating a project, matching the API contract
This commit is contained in:
Waleed
2026-07-16 10:10:19 -07:00
committed by GitHub
parent 2ec72b6c81
commit ff1d061897
76 changed files with 20258 additions and 1 deletions
+21
View File
@@ -8710,3 +8710,24 @@ export function JupyterIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function RocketlaneIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 19.001 16.514' fill='none' xmlns='http://www.w3.org/2000/svg'>
<g transform='translate(0 -6.486)'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M15.446 6.94198L19.0007 14.7744L15.446 22.6068C14.652 22.862 13.8054 22.9998 12.9266 22.9998C11.714 22.9998 10.5627 22.7374 9.52637 22.2664L12.9266 14.7744L9.52637 7.28242C10.5627 6.81133 11.714 6.54895 12.9266 6.54895C13.8054 6.54895 14.652 6.68677 15.446 6.94198Z'
fill='#0F62FE'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M6.03112 7.01001L9.52638 14.7114L6.03112 22.4129C5.13054 22.7516 4.15483 22.9369 3.13585 22.9369C2.02543 22.9369 0.96641 22.7168 0 22.318L3.45221 14.7114L2.49902e-08 7.10487C0.96641 6.70602 2.02543 6.48599 3.13585 6.48599C4.15483 6.48599 5.13054 6.67128 6.03112 7.01001Z'
fill='currentColor'
/>
</g>
</svg>
)
}
+2
View File
@@ -182,6 +182,7 @@ import {
ResendIcon,
RevenueCatIcon,
RipplingIcon,
RocketlaneIcon,
RootlyIcon,
S3Icon,
SalesforceIcon,
@@ -444,6 +445,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
resend: ResendIcon,
revenuecat: RevenueCatIcon,
rippling: RipplingIcon,
rocketlane: RocketlaneIcon,
rootly: RootlyIcon,
s3: S3Icon,
salesforce: SalesforceIcon,
@@ -203,6 +203,7 @@
"resend",
"revenuecat",
"rippling",
"rocketlane",
"rootly",
"s3",
"salesforce",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3
View File
@@ -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<string, BlockConfig> = {
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<string, BlockMeta> = {
resend: ResendBlockMeta,
revenuecat: RevenueCatBlockMeta,
rippling: RipplingBlockMeta,
rocketlane: RocketlaneBlockMeta,
rootly: RootlyBlockMeta,
rss: RssBlockMeta,
s3: S3BlockMeta,
+21
View File
@@ -8710,3 +8710,24 @@ export function JupyterIcon(props: SVGProps<SVGSVGElement>) {
</svg>
)
}
export function RocketlaneIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 19.001 16.514' fill='none' xmlns='http://www.w3.org/2000/svg'>
<g transform='translate(0 -6.486)'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M15.446 6.94198L19.0007 14.7744L15.446 22.6068C14.652 22.862 13.8054 22.9998 12.9266 22.9998C11.714 22.9998 10.5627 22.7374 9.52637 22.2664L12.9266 14.7744L9.52637 7.28242C10.5627 6.81133 11.714 6.54895 12.9266 6.54895C13.8054 6.54895 14.652 6.68677 15.446 6.94198Z'
fill='#0F62FE'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M6.03112 7.01001L9.52638 14.7114L6.03112 22.4129C5.13054 22.7516 4.15483 22.9369 3.13585 22.9369C2.02543 22.9369 0.96641 22.7168 0 22.318L3.45221 14.7114L2.49902e-08 7.10487C0.96641 6.70602 2.02543 6.48599 3.13585 6.48599C4.15483 6.48599 5.13054 6.67128 6.03112 7.01001Z'
fill='currentColor'
/>
</g>
</svg>
)
}
@@ -181,6 +181,7 @@ import {
ResendIcon,
RevenueCatIcon,
RipplingIcon,
RocketlaneIcon,
RootlyIcon,
S3Icon,
SalesforceIcon,
@@ -421,6 +422,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
resend: ResendIcon,
revenuecat: RevenueCatIcon,
rippling: RipplingIcon,
rocketlane: RocketlaneIcon,
rootly: RootlyIcon,
s3: S3Icon,
salesforce: SalesforceIcon,
+276 -1
View File
@@ -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",
+130
View File
@@ -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<string, ToolConfig> = {
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,
@@ -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,
},
},
}
@@ -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<string, unknown> = {}
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,
},
},
}
@@ -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,
},
},
}
@@ -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,
},
},
}
@@ -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,
},
},
}
@@ -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,
},
},
}
@@ -0,0 +1,102 @@
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) => {
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<string, unknown> = {}
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 },
},
},
}
+146
View File
@@ -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,
},
},
}
+118
View File
@@ -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,
},
},
}
+366
View File
@@ -0,0 +1,366 @@
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<string, unknown> = {
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<string, unknown> = {}
if (params.ownerUserId != null) owner.userId = params.ownerUserId
if (params.ownerEmailId) owner.emailId = params.ownerEmailId
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<string, unknown> = {}
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<string, unknown> = { contractType: params.contractType }
if (params.fixedFee != null) financials.fixedFeeContract = { fixedFee: params.fixedFee }
const timeAndMaterialContract: Record<string, unknown> = {}
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<string, unknown> = {}
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,
},
},
}
+80
View File
@@ -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<string, unknown> = {
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,
},
},
}
@@ -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<string, unknown> = {
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,
},
},
}
+222
View File
@@ -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<string, unknown> = {
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,
},
},
}
@@ -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<string, unknown> = {
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<string, unknown> = {}
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,
},
},
}
@@ -0,0 +1,169 @@
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) => {
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<string, unknown> = {}
if (params.userId != null) user.userId = params.userId
if (params.userEmail) user.emailId = params.userEmail
const body: Record<string, unknown> = {
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<string, unknown> = {}
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,
},
},
}
+54
View File
@@ -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 },
},
}
+54
View File
@@ -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 },
},
}
@@ -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,
},
},
}
+54
View File
@@ -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 },
},
}
@@ -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,
},
},
}
+54
View File
@@ -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 },
},
}
@@ -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,
},
},
}
@@ -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,
},
},
}
+80
View File
@@ -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<RocketlaneGetFieldParams, RocketlaneFieldResponse> =
{
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,
},
},
}
+82
View File
@@ -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,
},
},
}
@@ -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,
},
},
}
@@ -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,
},
},
}
+80
View File
@@ -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<RocketlaneGetPhaseParams, RocketlanePhaseResponse> =
{
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,
},
},
}
+81
View File
@@ -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,
},
},
}
+58
View File
@@ -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<RocketlaneGetSpaceParams, RocketlaneSpaceResponse> =
{
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,
},
},
}
@@ -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,
},
},
}
+82
View File
@@ -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<RocketlaneGetTaskParams, RocketlaneTaskResponse> = {
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,
},
},
}
@@ -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,
},
},
}
+84
View File
@@ -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,
},
},
}
+76
View File
@@ -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<RocketlaneGetUserParams, RocketlaneUserResponse> = {
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,
},
},
}
@@ -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<string, unknown> = {
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,
},
},
}
+131
View File
@@ -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,
}
+150
View File
@@ -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,
},
},
}
+481
View File
@@ -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,
},
},
}
+136
View File
@@ -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,
},
},
}
@@ -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,
},
},
}
+224
View File
@@ -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,
},
},
}
@@ -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,
},
},
}
@@ -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,
},
},
}
+212
View File
@@ -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,
},
},
}
+209
View File
@@ -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,
},
},
}
@@ -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,
},
},
}
@@ -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,
},
},
}
+259
View File
@@ -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,
},
},
}
+389
View File
@@ -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,
},
},
}
@@ -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,
},
},
}
@@ -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,
},
},
}
@@ -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,
},
},
}
@@ -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,
},
},
}
@@ -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,
},
},
}
@@ -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,
},
},
}
File diff suppressed because it is too large Load Diff
@@ -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 },
},
},
}
+113
View File
@@ -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,
},
},
}
@@ -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,
},
},
}
+119
View File
@@ -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,
},
},
}
+195
View File
@@ -0,0 +1,195 @@
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<string, unknown> = {}
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<string, unknown> = {}
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 !== undefined)
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,
},
},
}
+71
View File
@@ -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<string, unknown> = {}
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,
},
},
}
@@ -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<string, unknown> = {}
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,
},
},
}
+175
View File
@@ -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<string, unknown> = {}
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,
},
},
}
@@ -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<string, unknown> = {
date: params.date,
minutes: params.minutes,
}
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
},
},
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,
},
},
}