Files
sim/apps/sim/blocks/blocks/linear.ts
Waleed 951c8fd5e9 feat(integrations): add integrationType and tags classification to all blocks (#3702)
* feat(integrations): add integrationType and tags classification to all blocks

* improvement(integrations): replace generic api/oauth tags with use-case-oriented tags

* lint

* upgrade turbo
2026-03-21 11:45:49 -07:00

2546 lines
86 KiB
TypeScript

import { LinearIcon } from '@/components/icons'
import { getScopesForService } from '@/lib/oauth/utils'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import { normalizeFileInput } from '@/blocks/utils'
import type { LinearResponse } from '@/tools/linear/types'
import { getTrigger } from '@/triggers'
export const LinearBlock: BlockConfig<LinearResponse> = {
type: 'linear',
name: 'Linear',
description: 'Interact with Linear issues, projects, and more',
authMode: AuthMode.OAuth,
triggerAllowed: true,
longDescription:
'Integrate Linear into the workflow. Can manage issues, comments, projects, labels, workflow states, cycles, attachments, and more. Can also trigger workflows based on Linear webhook events.',
docsLink: 'https://docs.sim.ai/tools/linear',
category: 'tools',
integrationType: IntegrationType.Productivity,
tags: ['project-management', 'ticketing'],
icon: LinearIcon,
bgColor: '#5E6AD2',
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
// Issue Operations
{ label: 'Read Issues', id: 'linear_read_issues' },
{ label: 'Get Issue', id: 'linear_get_issue' },
{ label: 'Create Issue', id: 'linear_create_issue' },
{ label: 'Update Issue', id: 'linear_update_issue' },
{ label: 'Archive Issue', id: 'linear_archive_issue' },
{ label: 'Unarchive Issue', id: 'linear_unarchive_issue' },
{ label: 'Delete Issue', id: 'linear_delete_issue' },
{ label: 'Search Issues', id: 'linear_search_issues' },
{ label: 'Add Label to Issue', id: 'linear_add_label_to_issue' },
{ label: 'Remove Label from Issue', id: 'linear_remove_label_from_issue' },
// Comment Operations
{ label: 'Create Comment', id: 'linear_create_comment' },
{ label: 'Update Comment', id: 'linear_update_comment' },
{ label: 'Delete Comment', id: 'linear_delete_comment' },
{ label: 'List Comments', id: 'linear_list_comments' },
// Project Operations
{ label: 'List Projects', id: 'linear_list_projects' },
{ label: 'Get Project', id: 'linear_get_project' },
{ label: 'Create Project', id: 'linear_create_project' },
{ label: 'Update Project', id: 'linear_update_project' },
{ label: 'Archive Project', id: 'linear_archive_project' },
// User & Team Operations
{ label: 'List Users', id: 'linear_list_users' },
{ label: 'List Teams', id: 'linear_list_teams' },
{ label: 'Get Viewer', id: 'linear_get_viewer' },
// Label Operations
{ label: 'List Labels', id: 'linear_list_labels' },
{ label: 'Create Label', id: 'linear_create_label' },
{ label: 'Update Label', id: 'linear_update_label' },
{ label: 'Archive Label', id: 'linear_archive_label' },
// Workflow State Operations
{ label: 'List Workflow States', id: 'linear_list_workflow_states' },
{ label: 'Create Workflow State', id: 'linear_create_workflow_state' },
{ label: 'Update Workflow State', id: 'linear_update_workflow_state' },
// Cycle Operations
{ label: 'List Cycles', id: 'linear_list_cycles' },
{ label: 'Get Cycle', id: 'linear_get_cycle' },
{ label: 'Create Cycle', id: 'linear_create_cycle' },
{ label: 'Get Active Cycle', id: 'linear_get_active_cycle' },
// Attachment Operations
{ label: 'Create Attachment', id: 'linear_create_attachment' },
{ label: 'List Attachments', id: 'linear_list_attachments' },
{ label: 'Update Attachment', id: 'linear_update_attachment' },
{ label: 'Delete Attachment', id: 'linear_delete_attachment' },
// Issue Relation Operations
{ label: 'Create Issue Relation', id: 'linear_create_issue_relation' },
{ label: 'List Issue Relations', id: 'linear_list_issue_relations' },
{ label: 'Delete Issue Relation', id: 'linear_delete_issue_relation' },
// Favorite Operations
{ label: 'Create Favorite', id: 'linear_create_favorite' },
{ label: 'List Favorites', id: 'linear_list_favorites' },
// Project Update Operations
{ label: 'Create Project Update', id: 'linear_create_project_update' },
{ label: 'List Project Updates', id: 'linear_list_project_updates' },
// Notification Operations
{ label: 'List Notifications', id: 'linear_list_notifications' },
{ label: 'Update Notification', id: 'linear_update_notification' },
// Customer Operations
{ label: 'Create Customer', id: 'linear_create_customer' },
{ label: 'List Customers', id: 'linear_list_customers' },
// Customer Request Operations
{ label: 'Create Customer Request', id: 'linear_create_customer_request' },
{ label: 'Update Customer Request', id: 'linear_update_customer_request' },
{ label: 'List Customer Requests', id: 'linear_list_customer_requests' },
// Customer Management Operations
{ label: 'Get Customer', id: 'linear_get_customer' },
{ label: 'Update Customer', id: 'linear_update_customer' },
{ label: 'Delete Customer', id: 'linear_delete_customer' },
{ label: 'Merge Customers', id: 'linear_merge_customers' },
// Customer Status Operations
{ label: 'Create Customer Status', id: 'linear_create_customer_status' },
{ label: 'Update Customer Status', id: 'linear_update_customer_status' },
{ label: 'Delete Customer Status', id: 'linear_delete_customer_status' },
{ label: 'List Customer Statuses', id: 'linear_list_customer_statuses' },
// Customer Tier Operations
{ label: 'Create Customer Tier', id: 'linear_create_customer_tier' },
{ label: 'Update Customer Tier', id: 'linear_update_customer_tier' },
{ label: 'Delete Customer Tier', id: 'linear_delete_customer_tier' },
{ label: 'List Customer Tiers', id: 'linear_list_customer_tiers' },
// Project Management Operations
{ label: 'Delete Project', id: 'linear_delete_project' },
// Project Label Operations
{ label: 'Create Project Label', id: 'linear_create_project_label' },
{ label: 'Update Project Label', id: 'linear_update_project_label' },
{ label: 'Delete Project Label', id: 'linear_delete_project_label' },
{ label: 'List Project Labels', id: 'linear_list_project_labels' },
{ label: 'Add Label to Project', id: 'linear_add_label_to_project' },
{ label: 'Remove Label from Project', id: 'linear_remove_label_from_project' },
// Project Milestone Operations
{ label: 'Create Project Milestone', id: 'linear_create_project_milestone' },
{ label: 'Update Project Milestone', id: 'linear_update_project_milestone' },
{ label: 'Delete Project Milestone', id: 'linear_delete_project_milestone' },
{ label: 'List Project Milestones', id: 'linear_list_project_milestones' },
// Project Status Operations
{ label: 'Create Project Status', id: 'linear_create_project_status' },
{ label: 'Update Project Status', id: 'linear_update_project_status' },
{ label: 'Delete Project Status', id: 'linear_delete_project_status' },
{ label: 'List Project Statuses', id: 'linear_list_project_statuses' },
],
value: () => 'linear_read_issues',
},
{
id: 'credential',
title: 'Linear Account',
type: 'oauth-input',
canonicalParamId: 'oauthCredential',
mode: 'basic',
serviceId: 'linear',
requiredScopes: getScopesForService('linear'),
placeholder: 'Select Linear account',
required: true,
},
{
id: 'manualCredential',
title: 'Linear Account',
type: 'short-input',
canonicalParamId: 'oauthCredential',
mode: 'advanced',
placeholder: 'Enter credential ID',
required: true,
},
// Team selector (for most operations)
{
id: 'teamId',
title: 'Team',
type: 'project-selector',
canonicalParamId: 'teamId',
serviceId: 'linear',
selectorKey: 'linear.teams',
placeholder: 'Select a team',
dependsOn: ['credential'],
mode: 'basic',
required: {
field: 'operation',
value: [
'linear_create_issue',
'linear_create_project',
'linear_list_workflow_states',
'linear_create_workflow_state',
'linear_create_cycle',
'linear_get_active_cycle',
],
},
condition: {
field: 'operation',
value: [
'linear_read_issues',
'linear_create_issue',
'linear_search_issues',
'linear_list_projects',
'linear_create_project',
'linear_list_labels',
'linear_list_workflow_states',
'linear_create_workflow_state',
'linear_list_cycles',
'linear_create_cycle',
'linear_get_active_cycle',
'linear_list_project_labels',
],
},
},
// Manual team ID input (advanced mode)
{
id: 'manualTeamId',
title: 'Team ID',
type: 'short-input',
canonicalParamId: 'teamId',
placeholder: 'Enter Linear team ID',
mode: 'advanced',
required: {
field: 'operation',
value: [
'linear_create_issue',
'linear_create_project',
'linear_list_workflow_states',
'linear_create_workflow_state',
'linear_create_cycle',
'linear_get_active_cycle',
],
},
condition: {
field: 'operation',
value: [
'linear_read_issues',
'linear_create_issue',
'linear_search_issues',
'linear_list_projects',
'linear_create_project',
'linear_list_labels',
'linear_list_workflow_states',
'linear_create_workflow_state',
'linear_list_cycles',
'linear_create_cycle',
'linear_get_active_cycle',
'linear_list_project_labels',
],
},
},
// Project selector (for issue creation)
{
id: 'projectId',
title: 'Project',
type: 'project-selector',
canonicalParamId: 'projectId',
serviceId: 'linear',
selectorKey: 'linear.projects',
placeholder: 'Select a project',
dependsOn: ['credential', 'teamId'],
mode: 'basic',
required: {
field: 'operation',
value: [
'linear_get_project',
'linear_update_project',
'linear_archive_project',
'linear_delete_project',
'linear_create_project_update',
'linear_list_project_updates',
],
},
condition: {
field: 'operation',
value: [
'linear_read_issues',
'linear_create_issue',
'linear_get_project',
'linear_update_project',
'linear_archive_project',
'linear_delete_project',
'linear_create_project_update',
'linear_list_project_updates',
'linear_list_project_labels',
],
},
},
// Manual project ID input (advanced mode)
{
id: 'manualProjectId',
title: 'Project ID',
type: 'short-input',
canonicalParamId: 'projectId',
placeholder: 'Enter Linear project ID',
mode: 'advanced',
required: {
field: 'operation',
value: [
'linear_get_project',
'linear_update_project',
'linear_archive_project',
'linear_delete_project',
'linear_create_project_update',
'linear_list_project_updates',
],
},
condition: {
field: 'operation',
value: [
'linear_read_issues',
'linear_create_issue',
'linear_get_project',
'linear_update_project',
'linear_archive_project',
'linear_delete_project',
'linear_create_project_update',
'linear_list_project_updates',
'linear_list_project_labels',
],
},
},
// Issue ID input (for operations requiring issue ID)
{
id: 'issueId',
title: 'Issue ID',
type: 'short-input',
placeholder: 'Enter Linear issue ID',
required: true,
condition: {
field: 'operation',
value: [
'linear_get_issue',
'linear_update_issue',
'linear_archive_issue',
'linear_unarchive_issue',
'linear_delete_issue',
'linear_add_label_to_issue',
'linear_remove_label_from_issue',
'linear_create_comment',
'linear_list_comments',
'linear_create_attachment',
'linear_list_attachments',
'linear_create_issue_relation',
'linear_list_issue_relations',
],
},
},
// Title (for issue creation/update)
{
id: 'title',
title: 'Title',
type: 'short-input',
placeholder: 'Enter issue title',
required: true,
condition: {
field: 'operation',
value: ['linear_create_issue', 'linear_update_issue'],
},
wandConfig: {
enabled: true,
prompt: `Generate a concise Linear issue title based on the user's description.
The title should:
- Be clear and descriptive
- Capture the essence of the issue
- Be suitable for project management tracking
Return ONLY the title text - no explanations.`,
placeholder: 'Describe the issue (e.g., "login not working", "add export feature")...',
},
},
// Description (for issue creation/update, comments, projects)
{
id: 'description',
title: 'Description',
type: 'long-input',
placeholder: 'Enter description',
condition: {
field: 'operation',
value: [
'linear_create_issue',
'linear_update_issue',
'linear_create_project',
'linear_update_project',
],
},
wandConfig: {
enabled: true,
prompt: `Generate a detailed description based on the user's description.
The description should:
- Provide context and details
- Include acceptance criteria or requirements when applicable
- Be professional and clear
Return ONLY the description text - no explanations.`,
placeholder: 'Describe the details (e.g., "users report errors when logging in")...',
},
},
// Comment body
{
id: 'body',
title: 'Comment',
type: 'long-input',
placeholder: 'Enter comment text',
required: {
field: 'operation',
value: ['linear_create_comment', 'linear_create_project_update'],
},
condition: {
field: 'operation',
value: ['linear_create_comment', 'linear_update_comment', 'linear_create_project_update'],
},
wandConfig: {
enabled: true,
prompt: `Generate a comment or project update based on the user's description.
The comment should:
- Be professional and informative
- Provide relevant updates or information
- Be suitable for team collaboration
Return ONLY the comment text - no explanations.`,
placeholder:
'Describe what you want to communicate (e.g., "progress update", "request for review")...',
},
},
// Comment ID
{
id: 'commentId',
title: 'Comment ID',
type: 'short-input',
placeholder: 'Enter comment ID',
required: true,
condition: {
field: 'operation',
value: ['linear_update_comment', 'linear_delete_comment'],
},
},
// Label ID
{
id: 'labelId',
title: 'Label ID',
type: 'short-input',
placeholder: 'Enter label ID',
required: true,
condition: {
field: 'operation',
value: [
'linear_add_label_to_issue',
'linear_remove_label_from_issue',
'linear_update_label',
'linear_archive_label',
],
},
},
// Label name (for creating labels)
{
id: 'name',
title: 'Name',
type: 'short-input',
placeholder: 'Enter name',
required: {
field: 'operation',
value: ['linear_create_label', 'linear_create_project', 'linear_create_workflow_state'],
},
condition: {
field: 'operation',
value: [
'linear_create_label',
'linear_update_label',
'linear_create_project',
'linear_update_project',
'linear_create_workflow_state',
'linear_update_workflow_state',
'linear_create_cycle',
],
},
},
// Label color
{
id: 'color',
title: 'Color (hex)',
type: 'short-input',
placeholder: '#5E6AD2',
condition: {
field: 'operation',
value: [
'linear_create_label',
'linear_update_label',
'linear_create_workflow_state',
'linear_update_workflow_state',
],
},
},
// State ID (for issue updates)
{
id: 'stateId',
title: 'State ID',
type: 'short-input',
placeholder: 'Enter workflow state ID',
condition: {
field: 'operation',
value: ['linear_update_issue', 'linear_update_workflow_state'],
},
},
// Assignee ID (for issue operations)
{
id: 'assigneeId',
title: 'Assignee ID',
type: 'short-input',
placeholder: 'Enter user ID to assign',
condition: {
field: 'operation',
value: ['linear_create_issue', 'linear_update_issue'],
},
},
// Priority (for issues and projects)
{
id: 'priority',
title: 'Priority',
type: 'dropdown',
options: [
{ label: 'No Priority', id: '0' },
{ label: 'Urgent', id: '1' },
{ label: 'High', id: '2' },
{ label: 'Normal', id: '3' },
{ label: 'Low', id: '4' },
],
value: () => '0',
condition: {
field: 'operation',
value: ['linear_create_issue', 'linear_update_issue', 'linear_create_project'],
},
},
// Estimate (for issues)
{
id: 'estimate',
title: 'Estimate',
type: 'short-input',
placeholder: 'Enter estimate points',
condition: {
field: 'operation',
value: ['linear_create_issue', 'linear_update_issue'],
},
},
// Search query
{
id: 'query',
title: 'Search Query',
type: 'long-input',
placeholder: 'Enter search query',
required: true,
condition: {
field: 'operation',
value: ['linear_search_issues'],
},
wandConfig: {
enabled: true,
prompt: `Generate a search query for Linear issues based on the user's description.
The query should:
- Be specific and targeted
- Use relevant keywords
- Be suitable for finding issues
Return ONLY the search query - no explanations.`,
placeholder:
'Describe what you want to search for (e.g., "open bugs", "my assigned tasks")...',
},
},
// Include archived (for list operations)
{
id: 'includeArchived',
title: 'Include Archived',
type: 'switch',
condition: {
field: 'operation',
value: ['linear_read_issues', 'linear_search_issues', 'linear_list_projects'],
},
},
// Issue filtering options for read_issues (advanced)
{
id: 'labelIds',
title: 'Label IDs',
type: 'short-input',
placeholder: 'Array of label IDs to filter by',
mode: 'advanced',
condition: {
field: 'operation',
value: 'linear_read_issues',
},
},
{
id: 'createdAfter',
title: 'Created After',
type: 'short-input',
placeholder: 'Filter issues created after this date (ISO 8601 format)',
mode: 'advanced',
condition: {
field: 'operation',
value: 'linear_read_issues',
},
},
{
id: 'updatedAfter',
title: 'Updated After',
type: 'short-input',
placeholder: 'Filter issues updated after this date (ISO 8601 format)',
mode: 'advanced',
condition: {
field: 'operation',
value: 'linear_read_issues',
},
},
{
id: 'orderBy',
title: 'Order By',
type: 'short-input',
placeholder: 'Sort order: "createdAt" or "updatedAt" (default: "updatedAt")',
mode: 'advanced',
condition: {
field: 'operation',
value: 'linear_read_issues',
},
},
// Cycle ID
{
id: 'cycleId',
title: 'Cycle ID',
type: 'short-input',
placeholder: 'Enter cycle ID',
required: true,
condition: {
field: 'operation',
value: ['linear_get_cycle'],
},
},
// Cycle start/end dates
{
id: 'startDate',
title: 'Start Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD',
required: {
field: 'operation',
value: ['linear_create_cycle'],
},
condition: {
field: 'operation',
value: ['linear_create_cycle', 'linear_create_project'],
},
wandConfig: {
enabled: true,
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
Examples:
- "today" -> Today's date
- "next Monday" -> Calculate the next Monday
- "start of next month" -> First day of next month
- "in 2 weeks" -> Calculate 14 days from now
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
placeholder: 'Describe the start date (e.g., "next Monday", "start of next month")...',
generationType: 'timestamp',
},
},
{
id: 'endDate',
title: 'End Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD',
required: true,
condition: {
field: 'operation',
value: ['linear_create_cycle'],
},
wandConfig: {
enabled: true,
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
Examples:
- "in 2 weeks" -> Calculate 14 days from now
- "end of month" -> Last day of current month
- "next Friday" -> Calculate the next Friday
- "end of quarter" -> Last day of current quarter
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
placeholder: 'Describe the end date (e.g., "in 2 weeks", "end of month")...',
generationType: 'timestamp',
},
},
// Target date (for projects)
{
id: 'targetDate',
title: 'Target Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD',
condition: {
field: 'operation',
value: ['linear_create_project', 'linear_update_project'],
},
wandConfig: {
enabled: true,
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
Examples:
- "end of quarter" -> Last day of current quarter
- "in 3 months" -> Calculate 3 months from now
- "end of year" -> December 31 of current year
- "next month" -> First day of next month
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
placeholder: 'Describe the target date (e.g., "end of quarter", "in 3 months")...',
generationType: 'timestamp',
},
},
// Attachment file
{
id: 'attachmentFileUpload',
title: 'Attachment',
type: 'file-upload',
canonicalParamId: 'file',
placeholder: 'Upload attachment',
condition: {
field: 'operation',
value: ['linear_create_attachment'],
},
mode: 'basic',
multiple: false,
},
{
id: 'file',
title: 'File Reference',
type: 'short-input',
canonicalParamId: 'file',
placeholder: 'File reference from previous block',
condition: {
field: 'operation',
value: ['linear_create_attachment'],
},
mode: 'advanced',
},
// Attachment URL
{
id: 'url',
title: 'URL',
type: 'short-input',
placeholder: 'Enter URL',
required: false,
condition: {
field: 'operation',
value: ['linear_create_attachment'],
},
mode: 'advanced',
},
// Attachment title
{
id: 'attachmentTitle',
title: 'Title',
type: 'short-input',
placeholder: 'Enter attachment title',
required: true,
condition: {
field: 'operation',
value: ['linear_create_attachment', 'linear_update_attachment'],
},
},
// Attachment ID
{
id: 'attachmentId',
title: 'Attachment ID',
type: 'short-input',
placeholder: 'Enter attachment ID',
required: true,
condition: {
field: 'operation',
value: ['linear_update_attachment', 'linear_delete_attachment'],
},
},
// Issue relation type
{
id: 'relationType',
title: 'Relation Type',
type: 'dropdown',
options: [
{ label: 'Blocks', id: 'blocks' },
{ label: 'Blocked by', id: 'blocked' },
{ label: 'Duplicate', id: 'duplicate' },
{ label: 'Related', id: 'related' },
],
value: () => 'related',
condition: {
field: 'operation',
value: ['linear_create_issue_relation'],
},
},
// Related issue ID
{
id: 'relatedIssueId',
title: 'Related Issue ID',
type: 'short-input',
placeholder: 'Enter related issue ID',
required: true,
condition: {
field: 'operation',
value: ['linear_create_issue_relation'],
},
},
// Relation ID
{
id: 'relationId',
title: 'Relation ID',
type: 'short-input',
placeholder: 'Enter relation ID',
required: true,
condition: {
field: 'operation',
value: ['linear_delete_issue_relation'],
},
},
// Favorite type
{
id: 'favoriteType',
title: 'Favorite Type',
type: 'dropdown',
options: [
{ label: 'Issue', id: 'issue' },
{ label: 'Project', id: 'project' },
{ label: 'Cycle', id: 'cycle' },
{ label: 'Label', id: 'label' },
],
value: () => 'issue',
condition: {
field: 'operation',
value: ['linear_create_favorite'],
},
},
// Favorite target ID
{
id: 'favoriteTargetId',
title: 'Target ID',
type: 'short-input',
placeholder: 'Enter ID to favorite',
required: true,
condition: {
field: 'operation',
value: ['linear_create_favorite'],
},
},
// Pagination - First (for list operations)
{
id: 'first',
title: 'Limit',
type: 'short-input',
placeholder: 'Number of items to return (default: 50)',
condition: {
field: 'operation',
value: [
'linear_read_issues',
'linear_search_issues',
'linear_list_comments',
'linear_list_projects',
'linear_list_users',
'linear_list_teams',
'linear_list_labels',
'linear_list_workflow_states',
'linear_list_cycles',
'linear_list_attachments',
'linear_list_issue_relations',
'linear_list_favorites',
'linear_list_project_updates',
'linear_list_notifications',
'linear_list_customer_statuses',
'linear_list_customer_tiers',
'linear_list_customers',
'linear_list_customer_requests',
'linear_list_project_labels',
'linear_list_project_milestones',
'linear_list_project_statuses',
],
},
},
// Pagination - After (for list operations)
{
id: 'after',
title: 'After Cursor',
type: 'short-input',
placeholder: 'Cursor for pagination',
condition: {
field: 'operation',
value: [
'linear_read_issues',
'linear_search_issues',
'linear_list_comments',
'linear_list_projects',
'linear_list_users',
'linear_list_teams',
'linear_list_labels',
'linear_list_workflow_states',
'linear_list_cycles',
'linear_list_attachments',
'linear_list_issue_relations',
'linear_list_favorites',
'linear_list_project_updates',
'linear_list_notifications',
'linear_list_customers',
'linear_list_customer_requests',
'linear_list_customer_statuses',
'linear_list_customer_tiers',
'linear_list_project_labels',
'linear_list_project_milestones',
'linear_list_project_statuses',
],
},
},
// Project health (for project updates)
{
id: 'health',
title: 'Project Health',
type: 'dropdown',
options: [
{ label: 'On Track', id: 'onTrack' },
{ label: 'At Risk', id: 'atRisk' },
{ label: 'Off Track', id: 'offTrack' },
],
value: () => 'onTrack',
condition: {
field: 'operation',
value: ['linear_create_project_update'],
},
},
// Notification ID
{
id: 'notificationId',
title: 'Notification ID',
type: 'short-input',
placeholder: 'Enter notification ID',
required: true,
condition: {
field: 'operation',
value: ['linear_update_notification'],
},
},
// Mark as read
{
id: 'markAsRead',
title: 'Mark as Read',
type: 'switch',
condition: {
field: 'operation',
value: ['linear_update_notification'],
},
},
// Workflow state type
{
id: 'workflowType',
title: 'Workflow Type',
type: 'dropdown',
options: [
{ label: 'Backlog', id: 'backlog' },
{ label: 'Unstarted', id: 'unstarted' },
{ label: 'Started', id: 'started' },
{ label: 'Completed', id: 'completed' },
{ label: 'Canceled', id: 'canceled' },
],
value: () => 'started',
condition: {
field: 'operation',
value: ['linear_create_workflow_state'],
},
},
// Lead ID (for projects)
{
id: 'leadId',
title: 'Lead ID',
type: 'short-input',
placeholder: 'Enter user ID for project lead',
condition: {
field: 'operation',
value: ['linear_create_project', 'linear_update_project'],
},
},
// Project state
{
id: 'projectState',
title: 'Project State',
type: 'short-input',
placeholder: 'Enter project state',
condition: {
field: 'operation',
value: ['linear_update_project'],
},
},
// Customer name (for creating/updating customer)
{
id: 'customerName',
title: 'Customer Name',
type: 'short-input',
placeholder: 'Enter customer name',
required: true,
condition: {
field: 'operation',
value: ['linear_create_customer', 'linear_update_customer'],
},
},
// Customer domains
{
id: 'customerDomains',
title: 'Domains',
type: 'long-input',
placeholder: 'Enter domains (comma-separated)',
condition: {
field: 'operation',
value: ['linear_create_customer', 'linear_update_customer'],
},
},
// Customer external IDs
{
id: 'customerExternalIds',
title: 'External IDs',
type: 'long-input',
placeholder: 'Enter external IDs (comma-separated)',
condition: {
field: 'operation',
value: ['linear_create_customer', 'linear_update_customer'],
},
},
// Customer logo URL
{
id: 'customerLogoUrl',
title: 'Logo URL',
type: 'short-input',
placeholder: 'Enter logo URL',
condition: {
field: 'operation',
value: ['linear_create_customer', 'linear_update_customer'],
},
},
// Customer owner ID
{
id: 'customerOwnerId',
title: 'Owner User ID',
type: 'short-input',
placeholder: 'Enter owner user ID',
condition: {
field: 'operation',
value: ['linear_create_customer', 'linear_update_customer'],
},
},
// Customer revenue
{
id: 'customerRevenue',
title: 'Annual Revenue',
type: 'short-input',
placeholder: 'Enter annual revenue (number)',
condition: {
field: 'operation',
value: ['linear_create_customer', 'linear_update_customer'],
},
},
// Customer size
{
id: 'customerSize',
title: 'Organization Size',
type: 'short-input',
placeholder: 'Enter organization size (number)',
condition: {
field: 'operation',
value: ['linear_create_customer', 'linear_update_customer'],
},
},
// Customer ID (for customer request operations)
{
id: 'customerId',
title: 'Customer ID',
type: 'short-input',
placeholder: 'Enter customer ID',
required: true,
condition: {
field: 'operation',
value: ['linear_create_customer_request', 'linear_update_customer_request'],
},
},
// Customer request ID (for updating)
{
id: 'customerNeedId',
title: 'Customer Request ID',
type: 'short-input',
placeholder: 'Enter customer request ID',
required: true,
condition: {
field: 'operation',
value: ['linear_update_customer_request'],
},
},
// Customer request body/description
{
id: 'requestBody',
title: 'Request Description',
type: 'long-input',
placeholder: 'Enter customer request description',
condition: {
field: 'operation',
value: ['linear_create_customer_request', 'linear_update_customer_request'],
},
wandConfig: {
enabled: true,
prompt: `Generate a customer request description based on the user's description.
The description should:
- Clearly explain the customer's need or request
- Include relevant context and details
- Be professional and suitable for product feedback
Return ONLY the description text - no explanations.`,
placeholder:
'Describe the customer request (e.g., "need bulk export feature", "integration with Slack")...',
},
},
// Customer request priority/urgency
{
id: 'priority',
title: 'Urgency',
type: 'dropdown',
options: [
{ label: 'Not Important (0)', id: '0' },
{ label: 'Important (1)', id: '1' },
],
value: () => '0',
condition: {
field: 'operation',
value: ['linear_create_customer_request', 'linear_update_customer_request'],
},
},
// Link customer request to issue
{
id: 'linkedIssueId',
title: 'Link to Issue',
type: 'short-input',
placeholder: 'Enter issue ID to link',
condition: {
field: 'operation',
value: ['linear_create_customer_request', 'linear_update_customer_request'],
},
},
// Customer ID for get/update/delete/merge operations
{
id: 'customerIdTarget',
title: 'Customer ID',
type: 'short-input',
placeholder: 'Enter customer ID',
required: true,
condition: {
field: 'operation',
value: ['linear_get_customer', 'linear_update_customer', 'linear_delete_customer'],
},
},
// Source and target customer IDs for merge
{
id: 'sourceCustomerId',
title: 'Source Customer ID (to merge from)',
type: 'short-input',
placeholder: 'Customer ID to merge and delete',
required: true,
condition: {
field: 'operation',
value: ['linear_merge_customers'],
},
},
{
id: 'targetCustomerId',
title: 'Target Customer ID (to merge into)',
type: 'short-input',
placeholder: 'Customer ID to keep',
required: true,
condition: {
field: 'operation',
value: ['linear_merge_customers'],
},
},
// Customer status/tier fields
{
id: 'statusName',
title: 'Status Name',
type: 'short-input',
placeholder: 'Enter status name',
required: true,
condition: {
field: 'operation',
value: ['linear_create_customer_status'],
},
},
{
id: 'statusColor',
title: 'Status Color',
type: 'short-input',
placeholder: 'Enter hex color (e.g., #FF0000)',
required: true,
condition: {
field: 'operation',
value: [
'linear_create_customer_status',
'linear_create_customer_tier',
'linear_create_project_status',
'linear_create_project_label',
],
},
},
{
id: 'statusId',
title: 'Status ID',
type: 'short-input',
placeholder: 'Enter status ID',
required: true,
condition: {
field: 'operation',
value: ['linear_update_customer_status', 'linear_delete_customer_status'],
},
},
{
id: 'tierName',
title: 'Tier Name',
type: 'short-input',
placeholder: 'Enter tier name (e.g., Enterprise, Pro)',
required: true,
condition: {
field: 'operation',
value: ['linear_create_customer_tier'],
},
},
{
id: 'tierId',
title: 'Tier ID',
type: 'short-input',
placeholder: 'Enter tier ID',
required: true,
condition: {
field: 'operation',
value: ['linear_update_customer_tier', 'linear_delete_customer_tier'],
},
},
// Project label fields
{
id: 'projectLabelName',
title: 'Label Name',
type: 'short-input',
placeholder: 'Enter project label name',
required: true,
condition: {
field: 'operation',
value: ['linear_create_project_label', 'linear_update_project_label'],
},
},
{
id: 'projectLabelDescription',
title: 'Label Description',
type: 'long-input',
placeholder: 'Enter project label description',
condition: {
field: 'operation',
value: ['linear_create_project_label', 'linear_update_project_label'],
},
},
{
id: 'projectLabelIsGroup',
title: 'Is Label Group',
type: 'dropdown',
options: [
{ label: 'No', id: 'false' },
{ label: 'Yes', id: 'true' },
],
value: () => 'false',
condition: {
field: 'operation',
value: ['linear_create_project_label'],
},
},
{
id: 'projectLabelParentId',
title: 'Parent Label ID',
type: 'short-input',
placeholder: 'Enter parent label ID (for nested labels)',
condition: {
field: 'operation',
value: ['linear_create_project_label'],
},
},
{
id: 'projectLabelId',
title: 'Label ID',
type: 'short-input',
placeholder: 'Enter project label ID',
required: true,
condition: {
field: 'operation',
value: [
'linear_update_project_label',
'linear_delete_project_label',
'linear_add_label_to_project',
'linear_remove_label_from_project',
],
},
},
// Project milestone fields
{
id: 'milestoneName',
title: 'Milestone Name',
type: 'short-input',
placeholder: 'Enter milestone name',
required: true,
condition: {
field: 'operation',
value: ['linear_create_project_milestone'],
},
},
{
id: 'milestoneDescription',
title: 'Milestone Description',
type: 'long-input',
placeholder: 'Enter milestone description',
condition: {
field: 'operation',
value: ['linear_create_project_milestone', 'linear_update_project_milestone'],
},
},
{
id: 'milestoneId',
title: 'Milestone ID',
type: 'short-input',
placeholder: 'Enter milestone ID',
required: true,
condition: {
field: 'operation',
value: ['linear_update_project_milestone', 'linear_delete_project_milestone'],
},
},
{
id: 'milestoneTargetDate',
title: 'Target Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD',
condition: {
field: 'operation',
value: ['linear_create_project_milestone', 'linear_update_project_milestone'],
},
wandConfig: {
enabled: true,
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
Examples:
- "in 2 weeks" -> Calculate 14 days from now
- "end of sprint" -> Calculate based on typical 2-week sprint
- "next milestone" -> Calculate a reasonable next milestone date
- "end of month" -> Last day of current month
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
placeholder: 'Describe the milestone target date (e.g., "in 2 weeks", "end of month")...',
generationType: 'timestamp',
},
},
// Project status fields
{
id: 'projectStatusName',
title: 'Status Name',
type: 'short-input',
placeholder: 'Enter project status name',
required: true,
condition: {
field: 'operation',
value: ['linear_create_project_status'],
},
},
{
id: 'projectStatusType',
title: 'Status Type',
type: 'dropdown',
options: [
{ label: 'Backlog', id: 'backlog' },
{ label: 'Planned', id: 'planned' },
{ label: 'Started', id: 'started' },
{ label: 'Paused', id: 'paused' },
{ label: 'Completed', id: 'completed' },
{ label: 'Canceled', id: 'canceled' },
],
value: () => 'started',
required: true,
condition: {
field: 'operation',
value: ['linear_create_project_status'],
},
},
{
id: 'projectStatusPosition',
title: 'Position',
type: 'short-input',
placeholder: 'Enter position (e.g. 0, 1, 2...)',
required: true,
condition: {
field: 'operation',
value: ['linear_create_project_status'],
},
},
{
id: 'projectStatusId',
title: 'Status ID',
type: 'short-input',
placeholder: 'Enter project status ID',
required: true,
condition: {
field: 'operation',
value: ['linear_update_project_status', 'linear_delete_project_status'],
},
},
{
id: 'projectStatusIndefinite',
title: 'Can Stay Indefinitely',
type: 'dropdown',
options: [
{ label: 'No', id: 'false' },
{ label: 'Yes', id: 'true' },
],
value: () => 'false',
condition: {
field: 'operation',
value: ['linear_create_project_status', 'linear_update_project_status'],
},
},
// Project ID for milestone/label operations
{
id: 'projectIdForMilestone',
title: 'Project ID',
type: 'short-input',
placeholder: 'Enter project ID',
required: true,
condition: {
field: 'operation',
value: [
'linear_create_project_milestone',
'linear_list_project_milestones',
'linear_add_label_to_project',
'linear_remove_label_from_project',
],
},
},
// Trigger SubBlocks
...getTrigger('linear_issue_created').subBlocks,
...getTrigger('linear_issue_updated').subBlocks,
...getTrigger('linear_issue_removed').subBlocks,
...getTrigger('linear_comment_created').subBlocks,
...getTrigger('linear_comment_updated').subBlocks,
...getTrigger('linear_project_created').subBlocks,
...getTrigger('linear_project_updated').subBlocks,
...getTrigger('linear_cycle_created').subBlocks,
...getTrigger('linear_cycle_updated').subBlocks,
...getTrigger('linear_label_created').subBlocks,
...getTrigger('linear_label_updated').subBlocks,
...getTrigger('linear_project_update_created').subBlocks,
...getTrigger('linear_customer_request_created').subBlocks,
...getTrigger('linear_customer_request_updated').subBlocks,
...getTrigger('linear_webhook').subBlocks,
],
tools: {
access: [
'linear_read_issues',
'linear_get_issue',
'linear_create_issue',
'linear_update_issue',
'linear_archive_issue',
'linear_unarchive_issue',
'linear_delete_issue',
'linear_search_issues',
'linear_add_label_to_issue',
'linear_remove_label_from_issue',
'linear_create_comment',
'linear_update_comment',
'linear_delete_comment',
'linear_list_comments',
'linear_list_projects',
'linear_get_project',
'linear_create_project',
'linear_update_project',
'linear_archive_project',
'linear_list_users',
'linear_list_teams',
'linear_get_viewer',
'linear_list_labels',
'linear_create_label',
'linear_update_label',
'linear_archive_label',
'linear_list_workflow_states',
'linear_create_workflow_state',
'linear_update_workflow_state',
'linear_list_cycles',
'linear_get_cycle',
'linear_create_cycle',
'linear_get_active_cycle',
'linear_create_attachment',
'linear_list_attachments',
'linear_update_attachment',
'linear_delete_attachment',
'linear_create_issue_relation',
'linear_list_issue_relations',
'linear_delete_issue_relation',
'linear_create_favorite',
'linear_list_favorites',
'linear_create_project_update',
'linear_list_project_updates',
'linear_list_notifications',
'linear_update_notification',
'linear_create_customer',
'linear_list_customers',
'linear_create_customer_request',
'linear_update_customer_request',
'linear_list_customer_requests',
'linear_get_customer',
'linear_update_customer',
'linear_delete_customer',
'linear_merge_customers',
'linear_create_customer_status',
'linear_update_customer_status',
'linear_delete_customer_status',
'linear_list_customer_statuses',
'linear_create_customer_tier',
'linear_update_customer_tier',
'linear_delete_customer_tier',
'linear_list_customer_tiers',
'linear_delete_project',
'linear_create_project_label',
'linear_update_project_label',
'linear_delete_project_label',
'linear_list_project_labels',
'linear_add_label_to_project',
'linear_remove_label_from_project',
'linear_create_project_milestone',
'linear_update_project_milestone',
'linear_delete_project_milestone',
'linear_list_project_milestones',
'linear_create_project_status',
'linear_update_project_status',
'linear_delete_project_status',
'linear_list_project_statuses',
],
config: {
tool: (params) => {
return params.operation || 'linear_read_issues'
},
params: (params) => {
// Use canonical param IDs (raw subBlock IDs are deleted after serialization)
const effectiveTeamId = params.teamId ? String(params.teamId).trim() : ''
const effectiveProjectId = params.projectId ? String(params.projectId).trim() : ''
// Base params that most operations need
const baseParams: Record<string, any> = {
oauthCredential: params.oauthCredential,
}
// Operation-specific param mapping
switch (params.operation) {
case 'linear_read_issues':
return {
...baseParams,
teamId: effectiveTeamId || undefined,
projectId: effectiveProjectId || undefined,
includeArchived: params.includeArchived,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_get_issue':
if (!params.issueId?.trim()) {
throw new Error('Issue ID is required.')
}
return {
...baseParams,
issueId: params.issueId.trim(),
}
case 'linear_create_issue':
if (!effectiveTeamId) {
throw new Error('Team ID is required.')
}
if (!params.title?.trim()) {
throw new Error('Title is required.')
}
return {
...baseParams,
teamId: effectiveTeamId,
projectId: effectiveProjectId || undefined,
title: params.title.trim(),
description: params.description,
stateId: params.stateId,
assigneeId: params.assigneeId,
priority: params.priority ? Number(params.priority) : undefined,
estimate: params.estimate ? Number(params.estimate) : undefined,
}
case 'linear_update_issue':
if (!params.issueId?.trim()) {
throw new Error('Issue ID is required.')
}
return {
...baseParams,
issueId: params.issueId.trim(),
title: params.title,
description: params.description,
stateId: params.stateId,
assigneeId: params.assigneeId,
priority: params.priority ? Number(params.priority) : undefined,
estimate: params.estimate ? Number(params.estimate) : undefined,
}
case 'linear_archive_issue':
case 'linear_unarchive_issue':
case 'linear_delete_issue':
if (!params.issueId?.trim()) {
throw new Error('Issue ID is required.')
}
return {
...baseParams,
issueId: params.issueId.trim(),
}
case 'linear_search_issues':
if (!params.query?.trim()) {
throw new Error('Search query is required.')
}
return {
...baseParams,
query: params.query.trim(),
teamId: effectiveTeamId,
includeArchived: params.includeArchived,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_add_label_to_issue':
case 'linear_remove_label_from_issue':
if (!params.issueId?.trim() || !params.labelId?.trim()) {
throw new Error('Issue ID and Label ID are required.')
}
return {
...baseParams,
issueId: params.issueId.trim(),
labelId: params.labelId.trim(),
}
case 'linear_create_comment':
if (!params.issueId?.trim() || !params.body?.trim()) {
throw new Error('Issue ID and comment body are required.')
}
return {
...baseParams,
issueId: params.issueId.trim(),
body: params.body.trim(),
}
case 'linear_update_comment':
if (!params.commentId?.trim()) {
throw new Error('Comment ID is required.')
}
return {
...baseParams,
commentId: params.commentId.trim(),
body: params.body?.trim() || undefined,
}
case 'linear_delete_comment':
if (!params.commentId?.trim()) {
throw new Error('Comment ID is required.')
}
return {
...baseParams,
commentId: params.commentId.trim(),
}
case 'linear_list_comments':
if (!params.issueId?.trim()) {
throw new Error('Issue ID is required.')
}
return {
...baseParams,
issueId: params.issueId.trim(),
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_list_projects':
return {
...baseParams,
teamId: effectiveTeamId,
includeArchived: params.includeArchived,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_get_project':
if (!effectiveProjectId) {
throw new Error('Project ID is required.')
}
return {
...baseParams,
projectId: effectiveProjectId,
}
case 'linear_create_project':
if (!effectiveTeamId || !params.name?.trim()) {
throw new Error('Team ID and project name are required.')
}
return {
...baseParams,
teamId: effectiveTeamId,
name: params.name.trim(),
description: params.description,
leadId: params.leadId,
startDate: params.startDate,
targetDate: params.targetDate,
priority: params.priority ? Number(params.priority) : undefined,
}
case 'linear_update_project':
if (!effectiveProjectId) {
throw new Error('Project ID is required.')
}
return {
...baseParams,
projectId: effectiveProjectId,
name: params.name,
description: params.description,
state: params.projectState,
leadId: params.leadId,
targetDate: params.targetDate,
}
case 'linear_archive_project':
if (!effectiveProjectId) {
throw new Error('Project ID is required.')
}
return {
...baseParams,
projectId: effectiveProjectId,
}
case 'linear_list_users':
case 'linear_list_teams':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_get_viewer':
return baseParams
case 'linear_list_labels':
return {
...baseParams,
teamId: effectiveTeamId,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_create_label':
if (!params.name?.trim()) {
throw new Error('Label name is required.')
}
return {
...baseParams,
name: params.name.trim(),
color: params.color,
teamId: effectiveTeamId,
}
case 'linear_update_label':
if (!params.labelId?.trim()) {
throw new Error('Label ID is required.')
}
return {
...baseParams,
labelId: params.labelId.trim(),
name: params.name,
color: params.color,
}
case 'linear_archive_label':
if (!params.labelId?.trim()) {
throw new Error('Label ID is required.')
}
return {
...baseParams,
labelId: params.labelId.trim(),
}
case 'linear_list_workflow_states':
return {
...baseParams,
teamId: effectiveTeamId,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_create_workflow_state':
if (!effectiveTeamId || !params.name?.trim() || !params.workflowType) {
throw new Error('Team ID, name, and workflow type are required.')
}
return {
...baseParams,
teamId: effectiveTeamId,
name: params.name.trim(),
type: params.workflowType,
color: params.color?.trim() || undefined,
}
case 'linear_update_workflow_state':
if (!params.stateId?.trim()) {
throw new Error('State ID is required.')
}
return {
...baseParams,
stateId: params.stateId.trim(),
name: params.name,
color: params.color,
}
case 'linear_list_cycles':
return {
...baseParams,
teamId: effectiveTeamId,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_get_cycle':
if (!params.cycleId?.trim()) {
throw new Error('Cycle ID is required.')
}
return {
...baseParams,
cycleId: params.cycleId.trim(),
}
case 'linear_create_cycle':
if (!effectiveTeamId || !params.startDate?.trim() || !params.endDate?.trim()) {
throw new Error('Team ID, start date, and end date are required.')
}
return {
...baseParams,
teamId: effectiveTeamId,
name: params.name?.trim() || undefined,
startsAt: params.startDate.trim(),
endsAt: params.endDate.trim(),
}
case 'linear_get_active_cycle':
if (!effectiveTeamId) {
throw new Error('Team ID is required.')
}
return {
...baseParams,
teamId: effectiveTeamId,
}
case 'linear_create_attachment': {
if (!params.issueId?.trim()) {
throw new Error('Issue ID is required.')
}
// Normalize file input - use canonical param 'file' (raw subBlock IDs are deleted after serialization)
const attachmentFile = normalizeFileInput(params.file, {
single: true,
errorMessage: 'Attachment file must be a single file.',
})
const attachmentUrl =
params.url?.trim() ||
(attachmentFile ? (attachmentFile as { url?: string }).url : undefined)
if (!attachmentUrl) {
throw new Error('URL or file is required.')
}
return {
...baseParams,
issueId: params.issueId.trim(),
url: attachmentUrl,
file: attachmentFile,
title: params.attachmentTitle,
}
}
case 'linear_list_attachments':
if (!params.issueId?.trim()) {
throw new Error('Issue ID is required.')
}
return {
...baseParams,
issueId: params.issueId.trim(),
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_update_attachment':
if (!params.attachmentId?.trim()) {
throw new Error('Attachment ID is required.')
}
return {
...baseParams,
attachmentId: params.attachmentId.trim(),
title: params.attachmentTitle,
}
case 'linear_delete_attachment':
if (!params.attachmentId?.trim()) {
throw new Error('Attachment ID is required.')
}
return {
...baseParams,
attachmentId: params.attachmentId.trim(),
}
case 'linear_create_issue_relation':
if (!params.issueId?.trim() || !params.relatedIssueId?.trim() || !params.relationType) {
throw new Error('Issue ID, related issue ID, and relation type are required.')
}
return {
...baseParams,
issueId: params.issueId.trim(),
relatedIssueId: params.relatedIssueId.trim(),
type: params.relationType,
}
case 'linear_list_issue_relations':
if (!params.issueId?.trim()) {
throw new Error('Issue ID is required.')
}
return {
...baseParams,
issueId: params.issueId.trim(),
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_delete_issue_relation':
if (!params.relationId?.trim()) {
throw new Error('Relation ID is required.')
}
return {
...baseParams,
relationId: params.relationId.trim(),
}
case 'linear_create_favorite':
if (!params.favoriteTargetId?.trim() || !params.favoriteType) {
throw new Error('Target ID and favorite type are required.')
}
return {
...baseParams,
type: params.favoriteType,
[`${params.favoriteType}Id`]: params.favoriteTargetId.trim(),
}
case 'linear_list_favorites':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_create_project_update':
if (!effectiveProjectId || !params.body?.trim()) {
throw new Error('Project ID and update body are required.')
}
return {
...baseParams,
projectId: effectiveProjectId,
body: params.body.trim(),
health: params.health,
}
case 'linear_list_project_updates':
if (!effectiveProjectId) {
throw new Error('Project ID is required.')
}
return {
...baseParams,
projectId: effectiveProjectId,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_list_notifications':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_update_notification':
if (!params.notificationId?.trim()) {
throw new Error('Notification ID is required.')
}
return {
...baseParams,
notificationId: params.notificationId.trim(),
readAt: params.markAsRead ? new Date().toISOString() : null,
}
case 'linear_create_customer':
if (!params.customerName?.trim()) {
throw new Error('Customer name is required.')
}
return {
...baseParams,
name: params.customerName.trim(),
domains: params.customerDomains
? params.customerDomains
.split(',')
.map((d: string) => d.trim())
.filter(Boolean)
: undefined,
}
case 'linear_list_customers':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
includeArchived: false,
}
case 'linear_create_customer_request':
if (!params.customerId?.trim()) {
throw new Error('Customer ID is required.')
}
return {
...baseParams,
customerId: params.customerId.trim(),
body: params.requestBody?.trim(),
priority: params.priority !== undefined ? Number(params.priority) : 0,
issueId: params.linkedIssueId?.trim(),
projectId: effectiveProjectId || undefined,
}
case 'linear_update_customer_request':
if (!params.customerNeedId?.trim()) {
throw new Error('Customer Request ID is required.')
}
return {
...baseParams,
customerNeedId: params.customerNeedId.trim(),
customerId: params.customerId?.trim(),
body: params.requestBody?.trim(),
priority: params.priority !== undefined ? Number(params.priority) : undefined,
issueId: params.linkedIssueId?.trim(),
projectId: effectiveProjectId || undefined,
}
case 'linear_list_customer_requests':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
includeArchived: false,
}
// Customer Management Operations
case 'linear_get_customer':
if (!params.customerIdTarget?.trim()) {
throw new Error('Customer ID is required.')
}
return {
...baseParams,
customerId: params.customerIdTarget.trim(),
}
case 'linear_update_customer':
if (!params.customerIdTarget?.trim()) {
throw new Error('Customer ID is required.')
}
return {
...baseParams,
customerId: params.customerIdTarget.trim(),
name: params.customerName?.trim() || undefined,
domains: params.customerDomains?.trim()
? params.customerDomains.split(',').map((d: string) => d.trim())
: undefined,
externalIds: params.customerExternalIds?.trim()
? params.customerExternalIds.split(',').map((id: string) => id.trim())
: undefined,
logoUrl: params.customerLogoUrl?.trim() || undefined,
ownerId: params.customerOwnerId?.trim() || undefined,
revenue: params.customerRevenue ? Number(params.customerRevenue) : undefined,
size: params.customerSize ? Number(params.customerSize) : undefined,
statusId: params.statusId?.trim() || undefined,
tierId: params.tierId?.trim() || undefined,
}
case 'linear_delete_customer':
if (!params.customerIdTarget?.trim()) {
throw new Error('Customer ID is required.')
}
return {
...baseParams,
customerId: params.customerIdTarget.trim(),
}
case 'linear_merge_customers':
if (!params.sourceCustomerId?.trim() || !params.targetCustomerId?.trim()) {
throw new Error('Both source and target customer IDs are required.')
}
return {
...baseParams,
sourceCustomerId: params.sourceCustomerId.trim(),
targetCustomerId: params.targetCustomerId.trim(),
}
// Customer Status Operations
case 'linear_create_customer_status':
if (!params.statusName?.trim() || !params.statusColor?.trim()) {
throw new Error('Status name and color are required.')
}
return {
...baseParams,
name: params.statusName.trim(),
color: params.statusColor.trim(),
description: params.statusDescription?.trim() || undefined,
displayName: params.statusDisplayName?.trim() || undefined,
}
case 'linear_update_customer_status':
if (!params.statusId?.trim()) {
throw new Error('Status ID is required.')
}
return {
...baseParams,
statusId: params.statusId.trim(),
name: params.statusName?.trim() || undefined,
color: params.statusColor?.trim() || undefined,
description: params.statusDescription?.trim() || undefined,
displayName: params.statusDisplayName?.trim() || undefined,
}
case 'linear_delete_customer_status':
if (!params.statusId?.trim()) {
throw new Error('Status ID is required.')
}
return {
...baseParams,
statusId: params.statusId.trim(),
}
case 'linear_list_customer_statuses':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
// Customer Tier Operations
case 'linear_create_customer_tier':
if (!params.tierName?.trim() || !params.statusColor?.trim()) {
throw new Error('Tier name and color are required.')
}
return {
...baseParams,
name: params.tierName.trim(),
displayName: params.tierDisplayName?.trim() || params.tierName.trim(),
color: params.statusColor.trim(),
description: params.tierDescription?.trim() || undefined,
}
case 'linear_update_customer_tier':
if (!params.tierId?.trim()) {
throw new Error('Tier ID is required.')
}
return {
...baseParams,
tierId: params.tierId.trim(),
name: params.tierName?.trim() || undefined,
displayName: params.tierDisplayName?.trim() || undefined,
color: params.statusColor?.trim() || undefined,
description: params.tierDescription?.trim() || undefined,
}
case 'linear_delete_customer_tier':
if (!params.tierId?.trim()) {
throw new Error('Tier ID is required.')
}
return {
...baseParams,
tierId: params.tierId.trim(),
}
case 'linear_list_customer_tiers':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
// Project Management Operations
case 'linear_delete_project':
if (!effectiveProjectId) {
throw new Error('Project ID is required.')
}
return {
...baseParams,
projectId: effectiveProjectId,
}
// Project Label Operations
case 'linear_create_project_label':
if (!params.projectLabelName?.trim()) {
throw new Error('Project label name is required.')
}
return {
...baseParams,
name: params.projectLabelName.trim(),
description: params.projectLabelDescription?.trim() || undefined,
color: params.statusColor?.trim() || undefined,
isGroup: params.projectLabelIsGroup === 'true',
parentId: params.projectLabelParentId?.trim() || undefined,
}
case 'linear_update_project_label':
if (!params.projectLabelId?.trim()) {
throw new Error('Project label ID is required.')
}
return {
...baseParams,
labelId: params.projectLabelId.trim(),
name: params.projectLabelName?.trim() || undefined,
description: params.projectLabelDescription?.trim() || undefined,
color: params.statusColor?.trim() || undefined,
}
case 'linear_delete_project_label':
if (!params.projectLabelId?.trim()) {
throw new Error('Project label ID is required.')
}
return {
...baseParams,
labelId: params.projectLabelId.trim(),
}
case 'linear_list_project_labels':
return {
...baseParams,
projectId: effectiveProjectId || undefined,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
case 'linear_add_label_to_project':
if (!params.projectIdForMilestone?.trim() || !params.projectLabelId?.trim()) {
throw new Error('Project ID and label ID are required.')
}
return {
...baseParams,
projectId: params.projectIdForMilestone.trim(),
labelId: params.projectLabelId.trim(),
}
case 'linear_remove_label_from_project':
if (!params.projectIdForMilestone?.trim() || !params.projectLabelId?.trim()) {
throw new Error('Project ID and label ID are required.')
}
return {
...baseParams,
projectId: params.projectIdForMilestone.trim(),
labelId: params.projectLabelId.trim(),
}
// Project Milestone Operations
case 'linear_create_project_milestone':
if (!params.projectIdForMilestone?.trim() || !params.milestoneName?.trim()) {
throw new Error('Project ID and milestone name are required.')
}
return {
...baseParams,
projectId: params.projectIdForMilestone.trim(),
name: params.milestoneName.trim(),
description: params.milestoneDescription?.trim() || undefined,
targetDate: params.milestoneTargetDate?.trim() || undefined,
}
case 'linear_update_project_milestone':
if (!params.milestoneId?.trim()) {
throw new Error('Milestone ID is required.')
}
return {
...baseParams,
milestoneId: params.milestoneId.trim(),
name: params.milestoneName?.trim() || undefined,
description: params.milestoneDescription?.trim() || undefined,
targetDate: params.milestoneTargetDate?.trim() || undefined,
}
case 'linear_delete_project_milestone':
if (!params.milestoneId?.trim()) {
throw new Error('Milestone ID is required.')
}
return {
...baseParams,
milestoneId: params.milestoneId.trim(),
}
case 'linear_list_project_milestones':
if (!params.projectIdForMilestone?.trim()) {
throw new Error('Project ID is required.')
}
return {
...baseParams,
projectId: params.projectIdForMilestone.trim(),
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
// Project Status Operations
case 'linear_create_project_status':
if (
!params.projectStatusName?.trim() ||
!params.projectStatusType?.trim() ||
!params.statusColor?.trim() ||
!params.projectStatusPosition?.trim()
) {
throw new Error('Project status name, type, color, and position are required.')
}
return {
...baseParams,
name: params.projectStatusName.trim(),
type: params.projectStatusType.trim(),
color: params.statusColor.trim(),
position: Number.parseFloat(params.projectStatusPosition.trim()),
description: params.projectStatusDescription?.trim() || undefined,
indefinite: params.projectStatusIndefinite === 'true',
}
case 'linear_update_project_status':
if (!params.projectStatusId?.trim()) {
throw new Error('Project status ID is required.')
}
return {
...baseParams,
statusId: params.projectStatusId.trim(),
name: params.projectStatusName?.trim() || undefined,
color: params.statusColor?.trim() || undefined,
description: params.projectStatusDescription?.trim() || undefined,
indefinite: params.projectStatusIndefinite
? params.projectStatusIndefinite === 'true'
: undefined,
}
case 'linear_delete_project_status':
if (!params.projectStatusId?.trim()) {
throw new Error('Project status ID is required.')
}
return {
...baseParams,
statusId: params.projectStatusId.trim(),
}
case 'linear_list_project_statuses':
return {
...baseParams,
first: params.first ? Number(params.first) : undefined,
after: params.after,
}
default:
return baseParams
}
},
},
},
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
oauthCredential: { type: 'string', description: 'Linear access token' },
teamId: { type: 'string', description: 'Linear team identifier (canonical param)' },
projectId: { type: 'string', description: 'Linear project identifier (canonical param)' },
issueId: { type: 'string', description: 'Issue identifier' },
title: { type: 'string', description: 'Title' },
description: { type: 'string', description: 'Description' },
body: { type: 'string', description: 'Comment or update body' },
commentId: { type: 'string', description: 'Comment identifier' },
labelId: { type: 'string', description: 'Label identifier' },
name: { type: 'string', description: 'Name field' },
color: { type: 'string', description: 'Color in hex format' },
stateId: { type: 'string', description: 'Workflow state identifier' },
assigneeId: { type: 'string', description: 'Assignee user identifier' },
priority: { type: 'string', description: 'Priority level' },
estimate: { type: 'string', description: 'Estimate points' },
query: { type: 'string', description: 'Search query' },
includeArchived: { type: 'boolean', description: 'Include archived items' },
labelIds: { type: 'array', description: 'Array of label IDs to filter by' },
createdAfter: {
type: 'string',
description: 'Filter issues created after this date (ISO 8601)',
},
updatedAfter: {
type: 'string',
description: 'Filter issues updated after this date (ISO 8601)',
},
orderBy: { type: 'string', description: 'Sort order: createdAt or updatedAt' },
cycleId: { type: 'string', description: 'Cycle identifier' },
startDate: { type: 'string', description: 'Start date' },
endDate: { type: 'string', description: 'End date' },
targetDate: { type: 'string', description: 'Target date' },
url: { type: 'string', description: 'URL' },
file: { type: 'json', description: 'File to attach (canonical param)' },
attachmentTitle: { type: 'string', description: 'Attachment title' },
attachmentId: { type: 'string', description: 'Attachment identifier' },
relationType: { type: 'string', description: 'Relation type' },
relatedIssueId: { type: 'string', description: 'Related issue identifier' },
relationId: { type: 'string', description: 'Relation identifier' },
favoriteType: { type: 'string', description: 'Favorite type' },
favoriteTargetId: { type: 'string', description: 'Favorite target identifier' },
health: { type: 'string', description: 'Project health status' },
notificationId: { type: 'string', description: 'Notification identifier' },
markAsRead: { type: 'boolean', description: 'Mark as read flag' },
workflowType: { type: 'string', description: 'Workflow state type' },
leadId: { type: 'string', description: 'Project lead identifier' },
projectState: { type: 'string', description: 'Project state' },
first: { type: 'number', description: 'Number of items to return for pagination' },
after: { type: 'string', description: 'Cursor for pagination' },
customerName: { type: 'string', description: 'Customer name' },
customerDomains: { type: 'string', description: 'Customer domains (comma-separated)' },
customerId: { type: 'string', description: 'Customer identifier' },
customerNeedId: { type: 'string', description: 'Customer request identifier' },
requestBody: { type: 'string', description: 'Customer request description' },
linkedIssueId: { type: 'string', description: 'Issue ID to link to customer request' },
// New customer management inputs
customerIdTarget: { type: 'string', description: 'Customer ID for operations' },
sourceCustomerId: { type: 'string', description: 'Source customer ID for merge' },
targetCustomerId: { type: 'string', description: 'Target customer ID for merge' },
customerExternalIds: { type: 'string', description: 'Customer external IDs (comma-separated)' },
customerLogoUrl: { type: 'string', description: 'Customer logo URL' },
customerOwnerId: { type: 'string', description: 'Customer owner user ID' },
customerRevenue: { type: 'number', description: 'Customer annual revenue' },
customerSize: { type: 'number', description: 'Customer organization size' },
// Customer status and tier inputs
statusId: { type: 'string', description: 'Status identifier' },
statusName: { type: 'string', description: 'Status name' },
statusColor: { type: 'string', description: 'Status color in hex format' },
statusDescription: { type: 'string', description: 'Status description' },
statusDisplayName: { type: 'string', description: 'Status display name' },
tierId: { type: 'string', description: 'Tier identifier' },
tierName: { type: 'string', description: 'Tier name' },
tierDisplayName: { type: 'string', description: 'Tier display name' },
tierDescription: { type: 'string', description: 'Tier description' },
// Project label inputs
projectLabelId: { type: 'string', description: 'Project label identifier' },
projectLabelName: { type: 'string', description: 'Project label name' },
projectLabelDescription: { type: 'string', description: 'Project label description' },
projectLabelIsGroup: { type: 'string', description: 'Whether label is a group (true/false)' },
projectLabelParentId: {
type: 'string',
description: 'Parent label ID for hierarchical labels',
},
// Project milestone inputs
projectIdForMilestone: { type: 'string', description: 'Project ID for milestone operations' },
milestoneId: { type: 'string', description: 'Milestone identifier' },
milestoneName: { type: 'string', description: 'Milestone name' },
milestoneDescription: { type: 'string', description: 'Milestone description' },
milestoneTargetDate: { type: 'string', description: 'Milestone target date (YYYY-MM-DD)' },
// Project status inputs
projectStatusId: { type: 'string', description: 'Project status identifier' },
projectStatusName: { type: 'string', description: 'Project status name' },
projectStatusDescription: { type: 'string', description: 'Project status description' },
projectStatusIndefinite: {
type: 'string',
description: 'Whether status can persist indefinitely (true/false)',
},
},
outputs: {
// Issue outputs
issues: { type: 'json', description: 'Issues list' },
issue: { type: 'json', description: 'Single issue data' },
issueId: { type: 'string', description: 'Issue ID for operations' },
// Comment outputs
comment: { type: 'json', description: 'Comment data' },
comments: { type: 'json', description: 'Comments list' },
// Project outputs
project: { type: 'json', description: 'Project data' },
projects: { type: 'json', description: 'Projects list' },
projectId: { type: 'string', description: 'Project ID for operations' },
// User/Team outputs
users: { type: 'json', description: 'Users list' },
teams: { type: 'json', description: 'Teams list' },
user: { type: 'json', description: 'User data' },
viewer: { type: 'json', description: 'Current user data' },
// Label outputs
label: { type: 'json', description: 'Label data' },
labels: { type: 'json', description: 'Labels list' },
labelId: { type: 'string', description: 'Label ID for operations' },
// Workflow state outputs
state: { type: 'json', description: 'Workflow state data' },
states: { type: 'json', description: 'Workflow states list' },
// Cycle outputs
cycle: { type: 'json', description: 'Cycle data' },
cycles: { type: 'json', description: 'Cycles list' },
// Attachment outputs
attachment: { type: 'json', description: 'Attachment data' },
attachments: { type: 'json', description: 'Attachments list' },
// Relation outputs
relation: { type: 'json', description: 'Issue relation data' },
relations: { type: 'json', description: 'Issue relations list' },
// Favorite outputs
favorite: { type: 'json', description: 'Favorite data' },
favorites: { type: 'json', description: 'Favorites list' },
// Project update outputs
update: { type: 'json', description: 'Project update data' },
updates: { type: 'json', description: 'Project updates list' },
// Notification outputs
notification: { type: 'json', description: 'Notification data' },
notifications: { type: 'json', description: 'Notifications list' },
// Customer outputs
customer: { type: 'json', description: 'Customer data' },
customers: { type: 'json', description: 'Customers list' },
// Customer request outputs
customerNeed: { type: 'json', description: 'Customer request data' },
customerNeeds: { type: 'json', description: 'Customer requests list' },
// Customer status and tier outputs
customerStatus: { type: 'json', description: 'Customer status data' },
customerStatuses: { type: 'json', description: 'Customer statuses list' },
customerTier: { type: 'json', description: 'Customer tier data' },
customerTiers: { type: 'json', description: 'Customer tiers list' },
// Project label outputs
projectLabel: { type: 'json', description: 'Project label data' },
projectLabels: { type: 'json', description: 'Project labels list' },
// Project milestone outputs
projectMilestone: { type: 'json', description: 'Project milestone data' },
projectMilestones: { type: 'json', description: 'Project milestones list' },
// Project status outputs
projectStatus: { type: 'json', description: 'Project status data' },
projectStatuses: { type: 'json', description: 'Project statuses list' },
// Pagination
pageInfo: {
type: 'json',
description: 'Pagination information (hasNextPage, endCursor) for list operations',
},
// Success indicators
success: { type: 'boolean', description: 'Operation success status' },
// Trigger outputs
action: { type: 'string', description: 'Webhook action (create, update, remove)' },
type: {
type: 'string',
description: 'Entity type from webhook (Issue, Comment, Project, etc.)',
},
webhookId: { type: 'string', description: 'Webhook identifier' },
webhookTimestamp: { type: 'number', description: 'Webhook timestamp in milliseconds' },
organizationId: { type: 'string', description: 'Organization identifier' },
createdAt: { type: 'string', description: 'Event creation timestamp' },
actor: { type: 'json', description: 'User who triggered the event' },
data: { type: 'json', description: 'Complete entity data from webhook' },
updatedFrom: {
type: 'json',
description: 'Previous values for changed fields (update events only)',
},
},
triggers: {
enabled: true,
available: [
'linear_issue_created',
'linear_issue_updated',
'linear_issue_removed',
'linear_comment_created',
'linear_comment_updated',
'linear_project_created',
'linear_project_updated',
'linear_cycle_created',
'linear_cycle_updated',
'linear_label_created',
'linear_label_updated',
'linear_project_update_created',
'linear_customer_request_created',
'linear_customer_request_updated',
'linear_webhook',
],
},
}