chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,94 @@
import type { LinearAddLabelResponse, LinearAddLabelToIssueParams } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearAddLabelToIssueTool: ToolConfig<
LinearAddLabelToIssueParams,
LinearAddLabelResponse
> = {
id: 'linear_add_label_to_issue',
name: 'Linear Add Label to Issue',
description: 'Add a label to an issue in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Linear issue ID',
},
labelId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Label ID to add to the issue',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation AddLabelToIssue($issueId: String!, $labelId: String!) {
issueAddLabel(id: $issueId, labelId: $labelId) {
success
issue {
id
}
}
}
`,
variables: {
issueId: params.issueId,
labelId: params.labelId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to add label to issue',
output: {},
}
}
const result = data.data.issueAddLabel
return {
success: result.success,
output: {
success: result.success,
issueId: result.issue?.id || '',
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the label was successfully added',
},
issueId: {
type: 'string',
description: 'The ID of the issue',
},
},
}
@@ -0,0 +1,97 @@
import type {
LinearAddLabelToProjectParams,
LinearAddLabelToProjectResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearAddLabelToProjectTool: ToolConfig<
LinearAddLabelToProjectParams,
LinearAddLabelToProjectResponse
> = {
id: 'linear_add_label_to_project',
name: 'Linear Add Label to Project',
description: 'Add a label to a project in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID',
},
labelId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Label ID to add',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation ProjectAddLabel($id: String!, $labelId: String!) {
projectAddLabel(id: $id, labelId: $labelId) {
success
project {
id
}
}
}
`,
variables: {
id: params.projectId,
labelId: params.labelId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to add label to project',
output: {},
}
}
const result = data.data.projectAddLabel
return {
success: result.success,
output: {
success: result.success,
projectId: result.project?.id,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the label was added successfully',
},
projectId: {
type: 'string',
description: 'The project ID',
},
},
}
+87
View File
@@ -0,0 +1,87 @@
import type { LinearArchiveIssueParams, LinearArchiveIssueResponse } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearArchiveIssueTool: ToolConfig<
LinearArchiveIssueParams,
LinearArchiveIssueResponse
> = {
id: 'linear_archive_issue',
name: 'Linear Archive Issue',
description: 'Archive an issue in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Linear issue ID to archive',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation ArchiveIssue($id: String!) {
issueArchive(id: $id) {
success
entity {
id
}
}
}
`,
variables: {
id: params.issueId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to archive issue',
output: {},
}
}
const result = data.data.issueArchive
return {
success: result.success,
output: {
success: result.success,
issueId: result.entity?.id,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the archive operation was successful',
},
issueId: {
type: 'string',
description: 'The ID of the archived issue',
},
},
}
+83
View File
@@ -0,0 +1,83 @@
import type { LinearArchiveLabelParams, LinearArchiveLabelResponse } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearArchiveLabelTool: ToolConfig<
LinearArchiveLabelParams,
LinearArchiveLabelResponse
> = {
id: 'linear_archive_label',
name: 'Linear Archive Label',
description: 'Archive a label in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
labelId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Label ID to archive',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation ArchiveLabel($id: String!) {
issueLabelArchive(id: $id) {
success
}
}
`,
variables: {
id: params.labelId,
},
}),
},
transformResponse: async (response, params) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to archive label',
output: {},
}
}
return {
success: data.data.issueLabelArchive.success,
output: {
success: data.data.issueLabelArchive.success,
labelId: params?.labelId,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the archive operation was successful',
},
labelId: {
type: 'string',
description: 'The ID of the archived label',
},
},
}
+95
View File
@@ -0,0 +1,95 @@
import type { LinearArchiveProjectParams, LinearArchiveProjectResponse } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearArchiveProjectTool: ToolConfig<
LinearArchiveProjectParams,
LinearArchiveProjectResponse
> = {
id: 'linear_archive_project',
name: 'Linear Archive Project',
description: 'Archive a project in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID to archive',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation ArchiveProject($id: String!) {
projectDelete(id: $id) {
success
entity {
id
}
}
}
`,
variables: {
id: params.projectId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to archive project',
output: {},
}
}
const result = data.data.projectDelete
if (!result.success) {
return {
success: false,
error: 'Project archive was not successful',
output: {},
}
}
return {
success: true,
output: {
success: result.success,
projectId: result.entity?.id || '',
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the archive operation was successful',
},
projectId: {
type: 'string',
description: 'The ID of the archived project',
},
},
}
+139
View File
@@ -0,0 +1,139 @@
import type {
LinearCreateAttachmentParams,
LinearCreateAttachmentResponse,
} from '@/tools/linear/types'
import { ATTACHMENT_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateAttachmentTool: ToolConfig<
LinearCreateAttachmentParams,
LinearCreateAttachmentResponse
> = {
id: 'linear_create_attachment',
name: 'Linear Create Attachment',
description: 'Add an attachment to an issue in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID to attach to',
},
url: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'URL of the attachment',
},
file: {
type: 'file',
required: false,
visibility: 'user-only',
description: 'File to attach',
},
title: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Attachment title',
},
subtitle: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Attachment subtitle/description',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const attachmentUrl = params.url || params.file?.url
if (!attachmentUrl) {
throw new Error('URL or file is required')
}
const input: Record<string, any> = {
issueId: params.issueId,
url: attachmentUrl,
title: params.title,
}
if (params.subtitle != null && params.subtitle !== '') input.subtitle = params.subtitle
return {
query: `
mutation CreateAttachment($input: AttachmentCreateInput!) {
attachmentCreate(input: $input) {
success
attachment {
id
title
subtitle
url
createdAt
updatedAt
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create attachment',
output: {},
}
}
const result = data.data.attachmentCreate
if (!result.success) {
return {
success: false,
error: 'Attachment creation was not successful',
output: {},
}
}
return {
success: true,
output: {
attachment: result.attachment,
},
}
},
outputs: {
attachment: {
type: 'object',
description: 'The created attachment',
properties: ATTACHMENT_OUTPUT_PROPERTIES,
},
},
}
+113
View File
@@ -0,0 +1,113 @@
import type { LinearCreateCommentParams, LinearCreateCommentResponse } from '@/tools/linear/types'
import { COMMENT_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateCommentTool: ToolConfig<
LinearCreateCommentParams,
LinearCreateCommentResponse
> = {
id: 'linear_create_comment',
name: 'Linear Create Comment',
description: 'Add a comment to an issue in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Linear issue ID to comment on',
},
body: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comment text (supports Markdown)',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation CreateComment($input: CommentCreateInput!) {
commentCreate(input: $input) {
success
comment {
id
body
createdAt
updatedAt
user {
id
name
email
}
issue {
id
title
}
}
}
}
`,
variables: {
input: {
issueId: params.issueId,
body: params.body,
},
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create comment',
output: {},
}
}
const result = data.data.commentCreate
if (!result.success) {
return {
success: false,
error: 'Comment creation was not successful',
output: {},
}
}
return {
success: true,
output: {
comment: result.comment,
},
}
},
outputs: {
comment: {
type: 'object',
description: 'The created comment',
properties: COMMENT_OUTPUT_PROPERTIES,
},
},
}
+187
View File
@@ -0,0 +1,187 @@
import type { LinearCreateCustomerParams, LinearCreateCustomerResponse } from '@/tools/linear/types'
import { CUSTOMER_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateCustomerTool: ToolConfig<
LinearCreateCustomerParams,
LinearCreateCustomerResponse
> = {
id: 'linear_create_customer',
name: 'Linear Create Customer',
description: 'Create a new customer in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer name',
},
domains: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Domains associated with this customer',
},
externalIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'External IDs from other systems',
},
logoUrl: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: "Customer's logo URL",
},
ownerId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ID of the user who owns this customer',
},
revenue: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Annual revenue from this customer',
},
size: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Size of the customer organization',
},
statusId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Customer status ID',
},
tierId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Customer tier ID',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
name: params.name,
}
// Optional fields with proper validation
if (params.domains != null && Array.isArray(params.domains) && params.domains.length > 0) {
input.domains = params.domains
}
if (
params.externalIds != null &&
Array.isArray(params.externalIds) &&
params.externalIds.length > 0
) {
input.externalIds = params.externalIds
}
if (params.logoUrl != null && params.logoUrl !== '') {
input.logoUrl = params.logoUrl
}
if (params.ownerId != null && params.ownerId !== '') {
input.ownerId = params.ownerId
}
if (params.revenue != null) {
input.revenue = params.revenue
}
if (params.size != null) {
input.size = params.size
}
if (params.statusId != null && params.statusId !== '') {
input.statusId = params.statusId
}
if (params.tierId != null && params.tierId !== '') {
input.tierId = params.tierId
}
return {
query: `
mutation CustomerCreate($input: CustomerCreateInput!) {
customerCreate(input: $input) {
success
customer {
id
name
domains
externalIds
logoUrl
slugId
approximateNeedCount
revenue
size
createdAt
updatedAt
archivedAt
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create customer',
output: {},
}
}
const result = data.data.customerCreate
if (!result.success) {
return {
success: false,
error: 'Customer creation was not successful',
output: {},
}
}
return {
success: true,
output: {
customer: result.customer,
},
}
},
outputs: {
customer: {
type: 'object',
description: 'The created customer',
properties: CUSTOMER_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,174 @@
import type {
LinearCreateCustomerRequestParams,
LinearCreateCustomerRequestResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateCustomerRequestTool: ToolConfig<
LinearCreateCustomerRequestParams,
LinearCreateCustomerRequestResponse
> = {
id: 'linear_create_customer_request',
name: 'Linear Create Customer Request',
description:
'Create a customer request (need) in Linear. Assign to customer, set urgency (priority: 0 = Not important, 1 = Important), and optionally link to an issue.',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
customerId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer ID to assign this request to',
},
body: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Description of the customer request',
},
priority: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Urgency level: 0 = Not important, 1 = Important (default: 0)',
},
issueId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Issue ID to link this request to',
},
projectId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Project ID to link this request to',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
customerId: params.customerId,
priority: params.priority != null ? params.priority : 0,
}
// Optional fields with proper validation
if (params.body != null && params.body !== '') {
input.body = params.body
}
if (params.issueId != null && params.issueId !== '') {
input.issueId = params.issueId
}
if (params.projectId != null && params.projectId !== '') {
input.projectId = params.projectId
}
return {
query: `
mutation CustomerNeedCreate($input: CustomerNeedCreateInput!) {
customerNeedCreate(input: $input) {
success
need {
id
body
priority
createdAt
updatedAt
archivedAt
customer {
id
name
}
issue {
id
title
}
project {
id
name
}
creator {
id
name
}
url
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create customer request',
output: {},
}
}
const result = data.data.customerNeedCreate
if (!result.success) {
return {
success: false,
error: 'Customer request creation was not successful',
output: {},
}
}
return {
success: true,
output: {
customerNeed: result.need,
},
}
},
outputs: {
customerNeed: {
type: 'object',
description: 'The created customer request',
properties: {
id: { type: 'string', description: 'Customer request ID' },
body: { type: 'string', description: 'Request description' },
priority: {
type: 'number',
description: 'Urgency level (0 = Not important, 1 = Important)',
},
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last update timestamp' },
archivedAt: { type: 'string', description: 'Archive timestamp (null if not archived)' },
customer: { type: 'object', description: 'Assigned customer' },
issue: { type: 'object', description: 'Linked issue (null if not linked)' },
project: { type: 'object', description: 'Linked project (null if not linked)' },
creator: { type: 'object', description: 'User who created the request' },
url: { type: 'string', description: 'URL to the customer request' },
},
},
},
}
@@ -0,0 +1,144 @@
import type {
LinearCreateCustomerStatusParams,
LinearCreateCustomerStatusResponse,
} from '@/tools/linear/types'
import { CUSTOMER_STATUS_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateCustomerStatusTool: ToolConfig<
LinearCreateCustomerStatusParams,
LinearCreateCustomerStatusResponse
> = {
id: 'linear_create_customer_status',
name: 'Linear Create Customer Status',
description: 'Create a new customer status in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer status name',
},
color: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Status color (hex code)',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Status description',
},
displayName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Display name for the status',
},
position: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Position in status list',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
name: params.name,
color: params.color,
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.displayName != null && params.displayName !== '') {
input.displayName = params.displayName
}
if (params.position != null) {
input.position = params.position
}
return {
query: `
mutation CustomerStatusCreate($input: CustomerStatusCreateInput!) {
customerStatusCreate(input: $input) {
success
status {
id
name
description
color
position
type
createdAt
updatedAt
archivedAt
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create customer status',
output: {},
}
}
const result = data.data.customerStatusCreate
if (!result.success) {
return {
success: false,
error: 'Customer status creation was not successful',
output: {},
}
}
return {
success: true,
output: {
customerStatus: result.status,
},
}
},
outputs: {
customerStatus: {
type: 'object',
description: 'The created customer status',
properties: CUSTOMER_STATUS_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,143 @@
import type {
LinearCreateCustomerTierParams,
LinearCreateCustomerTierResponse,
} from '@/tools/linear/types'
import { CUSTOMER_TIER_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateCustomerTierTool: ToolConfig<
LinearCreateCustomerTierParams,
LinearCreateCustomerTierResponse
> = {
id: 'linear_create_customer_tier',
name: 'Linear Create Customer Tier',
description: 'Create a new customer tier in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer tier name',
},
color: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Tier color (hex code)',
},
displayName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Display name for the tier',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Tier description',
},
position: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Position in tier list',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
name: params.name,
color: params.color,
}
if (params.displayName != null && params.displayName !== '') {
input.displayName = params.displayName
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.position != null) {
input.position = params.position
}
return {
query: `
mutation CustomerTierCreate($input: CustomerTierCreateInput!) {
customerTierCreate(input: $input) {
success
customerTier {
id
name
displayName
description
color
position
createdAt
archivedAt
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create customer tier',
output: {},
}
}
const result = data.data.customerTierCreate
if (!result.success) {
return {
success: false,
error: 'Customer tier creation was not successful',
output: {},
}
}
return {
success: true,
output: {
customerTier: result.customerTier,
},
}
},
outputs: {
customerTier: {
type: 'object',
description: 'The created customer tier',
properties: CUSTOMER_TIER_OUTPUT_PROPERTIES,
},
},
}
+129
View File
@@ -0,0 +1,129 @@
import type { LinearCreateCycleParams, LinearCreateCycleResponse } from '@/tools/linear/types'
import { CYCLE_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateCycleTool: ToolConfig<LinearCreateCycleParams, LinearCreateCycleResponse> =
{
id: 'linear_create_cycle',
name: 'Linear Create Cycle',
description: 'Create a new cycle (sprint/iteration) in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
teamId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Team ID to create the cycle in',
},
startsAt: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Cycle start date (ISO format)',
},
endsAt: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Cycle end date (ISO format)',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cycle name (optional, will be auto-generated if not provided)',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
teamId: params.teamId,
startsAt: params.startsAt,
endsAt: params.endsAt,
}
if (params.name != null && params.name !== '') input.name = params.name
return {
query: `
mutation CreateCycle($input: CycleCreateInput!) {
cycleCreate(input: $input) {
success
cycle {
id
number
name
startsAt
endsAt
completedAt
progress
createdAt
team {
id
name
}
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create cycle',
output: {},
}
}
const result = data.data.cycleCreate
if (!result.success) {
return {
success: false,
error: 'Cycle creation was not successful',
output: {},
}
}
return {
success: true,
output: {
cycle: result.cycle,
},
}
},
outputs: {
cycle: {
type: 'object',
description: 'The created cycle',
properties: CYCLE_FULL_OUTPUT_PROPERTIES,
},
},
}
+141
View File
@@ -0,0 +1,141 @@
import type { LinearCreateFavoriteParams, LinearCreateFavoriteResponse } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateFavoriteTool: ToolConfig<
LinearCreateFavoriteParams,
LinearCreateFavoriteResponse
> = {
id: 'linear_create_favorite',
name: 'Linear Create Favorite',
description: 'Bookmark an issue, project, cycle, or label in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Issue ID to favorite',
},
projectId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Project ID to favorite',
},
cycleId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cycle ID to favorite',
},
labelId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Label ID to favorite',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
if (params.issueId != null && params.issueId !== '') input.issueId = params.issueId
if (params.projectId != null && params.projectId !== '') input.projectId = params.projectId
if (params.cycleId != null && params.cycleId !== '') input.cycleId = params.cycleId
if (params.labelId != null && params.labelId !== '') input.labelId = params.labelId
if (Object.keys(input).length === 0) {
throw new Error('At least one ID (issue, project, cycle, or label) must be provided')
}
return {
query: `
mutation CreateFavorite($input: FavoriteCreateInput!) {
favoriteCreate(input: $input) {
success
favorite {
id
type
issue {
id
title
}
project {
id
name
}
cycle {
id
name
}
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create favorite',
output: {},
}
}
const result = data.data.favoriteCreate
if (!result.success) {
return {
success: false,
error: 'Favorite creation was not successful',
output: {},
}
}
return {
success: true,
output: {
favorite: result.favorite,
},
}
},
outputs: {
favorite: {
type: 'object',
description: 'The created favorite',
properties: {
id: { type: 'string', description: 'Favorite ID' },
type: { type: 'string', description: 'Favorite type' },
issue: { type: 'object', description: 'Favorited issue (if applicable)' },
project: { type: 'object', description: 'Favorited project (if applicable)' },
cycle: { type: 'object', description: 'Favorited cycle (if applicable)' },
},
},
},
}
+274
View File
@@ -0,0 +1,274 @@
import type { LinearCreateIssueParams, LinearCreateIssueResponse } from '@/tools/linear/types'
import { ISSUE_EXTENDED_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateIssueTool: ToolConfig<LinearCreateIssueParams, LinearCreateIssueResponse> =
{
id: 'linear_create_issue',
name: 'Linear Issue Writer',
description: 'Create a new issue in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
teamId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Linear team ID (UUID format) where the issue will be created',
},
projectId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Linear project ID (UUID format) to associate with the issue',
},
title: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue title',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Issue description',
},
stateId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Workflow state ID (status)',
},
assigneeId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User ID to assign the issue to',
},
priority: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Priority (0=No priority, 1=Urgent, 2=High, 3=Normal, 4=Low)',
},
estimate: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Estimate in points',
},
labelIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of label IDs to set on the issue',
},
cycleId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cycle ID to assign the issue to',
},
parentId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Parent issue ID (for creating sub-issues)',
},
dueDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Due date in ISO 8601 format (date only: YYYY-MM-DD)',
},
subscriberIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of user IDs to subscribe to the issue',
},
projectMilestoneId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Project milestone ID to associate with the issue',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
if (!params.title || !params.title.trim()) {
throw new Error('Title is required to create a Linear issue')
}
const input: Record<string, any> = {
teamId: params.teamId,
title: params.title,
}
if (params.projectId != null && params.projectId !== '') {
input.projectId = params.projectId
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.stateId != null && params.stateId !== '') {
input.stateId = params.stateId
}
if (params.assigneeId != null && params.assigneeId !== '') {
input.assigneeId = params.assigneeId
}
if (params.priority != null) {
input.priority = Number(params.priority)
}
if (params.estimate != null) {
input.estimate = Number(params.estimate)
}
if (params.labelIds != null && Array.isArray(params.labelIds)) {
input.labelIds = params.labelIds
}
if (params.cycleId != null && params.cycleId !== '') {
input.cycleId = params.cycleId
}
if (params.parentId != null && params.parentId !== '') {
input.parentId = params.parentId
}
if (params.dueDate != null && params.dueDate !== '') {
input.dueDate = params.dueDate
}
if (params.subscriberIds != null && Array.isArray(params.subscriberIds)) {
input.subscriberIds = params.subscriberIds
}
if (params.projectMilestoneId != null && params.projectMilestoneId !== '') {
input.projectMilestoneId = params.projectMilestoneId
}
return {
query: `
mutation CreateIssue($input: IssueCreateInput!) {
issueCreate(input: $input) {
issue {
id
title
description
priority
estimate
url
dueDate
state {
id
name
type
}
assignee {
id
name
email
}
team { id }
project { id }
cycle {
id
number
name
}
parent {
id
title
}
projectMilestone {
id
name
}
labels {
nodes {
id
name
color
}
}
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create issue',
output: {},
}
}
const result = data.data?.issueCreate
if (!result) {
return {
success: false,
error: 'Issue creation was not successful',
output: {},
}
}
const issue = result.issue
return {
success: true,
output: {
issue: {
id: issue.id,
title: issue.title,
description: issue.description,
priority: issue.priority,
estimate: issue.estimate,
url: issue.url,
dueDate: issue.dueDate,
state: issue.state,
assignee: issue.assignee,
teamId: issue.team?.id,
projectId: issue.project?.id,
cycleId: issue.cycle?.id,
cycleNumber: issue.cycle?.number,
cycleName: issue.cycle?.name,
parentId: issue.parent?.id,
parentTitle: issue.parent?.title,
projectMilestoneId: issue.projectMilestone?.id,
projectMilestoneName: issue.projectMilestone?.name,
labels: issue.labels?.nodes || [],
},
},
}
},
outputs: {
issue: {
type: 'object',
description: 'The created issue with all its properties',
properties: ISSUE_EXTENDED_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,125 @@
import type {
LinearCreateIssueRelationParams,
LinearCreateIssueRelationResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateIssueRelationTool: ToolConfig<
LinearCreateIssueRelationParams,
LinearCreateIssueRelationResponse
> = {
id: 'linear_create_issue_relation',
name: 'Linear Create Issue Relation',
description: 'Link two issues together in Linear (blocks, relates to, duplicates)',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Source issue ID',
},
relatedIssueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Target issue ID to link to',
},
type: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Relation type: "blocks", "duplicate", or "related". Note: When creating "blocks" from A to B, the inverse relation (B blocked by A) is automatically created.',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation CreateIssueRelation($input: IssueRelationCreateInput!) {
issueRelationCreate(input: $input) {
success
issueRelation {
id
type
issue {
id
title
}
relatedIssue {
id
title
}
}
}
}
`,
variables: {
input: {
issueId: params.issueId,
relatedIssueId: params.relatedIssueId,
type: params.type,
},
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create issue relation',
output: {},
}
}
const result = data.data.issueRelationCreate
if (!result.success) {
return {
success: false,
error: 'Issue relation creation was not successful',
output: {},
}
}
return {
success: true,
output: {
relation: result.issueRelation,
},
}
},
outputs: {
relation: {
type: 'object',
description: 'The created issue relation',
properties: {
id: { type: 'string', description: 'Relation ID' },
type: { type: 'string', description: 'Relation type' },
issue: { type: 'object', description: 'Source issue' },
relatedIssue: { type: 'object', description: 'Target issue' },
},
},
},
}
+130
View File
@@ -0,0 +1,130 @@
import type { LinearCreateLabelParams, LinearCreateLabelResponse } from '@/tools/linear/types'
import { LABEL_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateLabelTool: ToolConfig<LinearCreateLabelParams, LinearCreateLabelResponse> =
{
id: 'linear_create_label',
name: 'Linear Create Label',
description: 'Create a new label in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Label name',
},
color: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Label color (hex format, e.g., "#ff0000")',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Label description',
},
teamId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Team ID (if omitted, creates workspace label)',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
name: params.name,
}
if (params.color != null && params.color !== '') input.color = params.color
if (params.description != null && params.description !== '')
input.description = params.description
if (params.teamId != null && params.teamId !== '') input.teamId = params.teamId
return {
query: `
mutation CreateLabel($input: IssueLabelCreateInput!) {
issueLabelCreate(input: $input) {
success
issueLabel {
id
name
color
description
isGroup
createdAt
updatedAt
archivedAt
team {
id
name
}
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create label',
output: {},
}
}
const result = data.data.issueLabelCreate
if (!result.success) {
return {
success: false,
error: 'Label creation was not successful',
output: {},
}
}
return {
success: true,
output: {
label: result.issueLabel,
},
}
},
outputs: {
label: {
type: 'object',
description: 'The created label',
properties: LABEL_FULL_OUTPUT_PROPERTIES,
},
},
}
+180
View File
@@ -0,0 +1,180 @@
import type { LinearCreateProjectParams, LinearCreateProjectResponse } from '@/tools/linear/types'
import { PROJECT_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateProjectTool: ToolConfig<
LinearCreateProjectParams,
LinearCreateProjectResponse
> = {
id: 'linear_create_project',
name: 'Linear Create Project',
description: 'Create a new project in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
teamId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Team ID to create the project in',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project name',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Project description',
},
leadId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User ID of the project lead',
},
startDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Project start date (ISO format)',
},
targetDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Project target date (ISO format)',
},
priority: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Project priority (0-4)',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
teamIds: [params.teamId],
name: params.name,
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.leadId != null && params.leadId !== '') {
input.leadId = params.leadId
}
if (params.startDate != null && params.startDate !== '') {
input.startDate = params.startDate
}
if (params.targetDate != null && params.targetDate !== '') {
input.targetDate = params.targetDate
}
if (params.priority != null) {
input.priority = Number(params.priority)
}
return {
query: `
mutation CreateProject($input: ProjectCreateInput!) {
projectCreate(input: $input) {
success
project {
id
name
description
state
priority
startDate
targetDate
url
lead {
id
name
}
teams {
nodes {
id
name
}
}
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create project',
output: {},
}
}
const result = data.data.projectCreate
if (!result.success) {
return {
success: false,
error: 'Project creation was not successful',
output: {},
}
}
const project = result.project
return {
success: true,
output: {
project: {
id: project.id,
name: project.name,
description: project.description,
state: project.state,
priority: project.priority,
startDate: project.startDate,
targetDate: project.targetDate,
url: project.url,
lead: project.lead,
teams: project.teams?.nodes || [],
},
},
}
},
outputs: {
project: {
type: 'object',
description: 'The created project',
properties: PROJECT_FULL_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,145 @@
import type {
LinearCreateProjectLabelParams,
LinearCreateProjectLabelResponse,
} from '@/tools/linear/types'
import { PROJECT_LABEL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateProjectLabelTool: ToolConfig<
LinearCreateProjectLabelParams,
LinearCreateProjectLabelResponse
> = {
id: 'linear_create_project_label',
name: 'Linear Create Project Label',
description: 'Create a new project label in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project label name',
},
color: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Label color (hex code)',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Label description',
},
isGroup: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether this is a label group',
},
parentId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Parent label group ID',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
name: params.name,
}
if (params.color != null && params.color !== '') {
input.color = params.color
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.isGroup != null) {
input.isGroup = params.isGroup
}
if (params.parentId != null && params.parentId !== '') {
input.parentId = params.parentId
}
return {
query: `
mutation ProjectLabelCreate($input: ProjectLabelCreateInput!) {
projectLabelCreate(input: $input) {
success
projectLabel {
id
name
description
color
isGroup
createdAt
updatedAt
archivedAt
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create project label',
output: {},
}
}
const result = data.data.projectLabelCreate
if (!result.success) {
return {
success: false,
error: 'Project label creation was not successful',
output: {},
}
}
return {
success: true,
output: {
projectLabel: result.projectLabel,
},
}
},
outputs: {
projectLabel: {
type: 'object',
description: 'The created project label',
properties: PROJECT_LABEL_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,143 @@
import type {
LinearCreateProjectMilestoneParams,
LinearCreateProjectMilestoneResponse,
} from '@/tools/linear/types'
import { PROJECT_MILESTONE_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateProjectMilestoneTool: ToolConfig<
LinearCreateProjectMilestoneParams,
LinearCreateProjectMilestoneResponse
> = {
id: 'linear_create_project_milestone',
name: 'Linear Create Project Milestone',
description: 'Create a new project milestone in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Milestone name',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Milestone description',
},
targetDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Target date (ISO 8601)',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
projectId: params.projectId,
name: params.name,
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.targetDate != null && params.targetDate !== '') {
input.targetDate = params.targetDate
}
return {
query: `
mutation ProjectMilestoneCreate($input: ProjectMilestoneCreateInput!) {
projectMilestoneCreate(input: $input) {
success
projectMilestone {
id
name
description
targetDate
progress
sortOrder
status
createdAt
archivedAt
project {
id
}
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create project milestone',
output: {},
}
}
const result = data.data.projectMilestoneCreate
if (!result.success) {
return {
success: false,
error: 'Project milestone creation was not successful',
output: {},
}
}
const milestone = result.projectMilestone
return {
success: true,
output: {
projectMilestone: {
...milestone,
projectId: milestone.project?.id ?? null,
project: undefined,
},
},
}
},
outputs: {
projectMilestone: {
type: 'object',
description: 'The created project milestone',
properties: PROJECT_MILESTONE_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,151 @@
import type {
LinearCreateProjectStatusParams,
LinearCreateProjectStatusResponse,
} from '@/tools/linear/types'
import { PROJECT_STATUS_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateProjectStatusTool: ToolConfig<
LinearCreateProjectStatusParams,
LinearCreateProjectStatusResponse
> = {
id: 'linear_create_project_status',
name: 'Linear Create Project Status',
description: 'Create a new project status in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project status name',
},
type: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Status type: "backlog", "planned", "started", "paused", "completed", or "canceled"',
},
color: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Status color (hex code)',
},
position: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Position in status list (e.g. 0, 1, 2...)',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Status description',
},
indefinite: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the status is indefinite',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
name: params.name,
type: params.type,
color: params.color,
position: params.position,
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.indefinite != null) {
input.indefinite = params.indefinite
}
return {
query: `
mutation ProjectStatusCreate($input: ProjectStatusCreateInput!) {
projectStatusCreate(input: $input) {
success
status {
id
name
description
color
indefinite
position
type
createdAt
updatedAt
archivedAt
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create project status',
output: {},
}
}
const result = data.data.projectStatusCreate
if (!result.success) {
return {
success: false,
error: 'Project status creation was not successful',
output: {},
}
}
return {
success: true,
output: {
projectStatus: result.status,
},
}
},
outputs: {
projectStatus: {
type: 'object',
description: 'The created project status',
properties: PROJECT_STATUS_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,128 @@
import type {
LinearCreateProjectUpdateParams,
LinearCreateProjectUpdateResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateProjectUpdateTool: ToolConfig<
LinearCreateProjectUpdateParams,
LinearCreateProjectUpdateResponse
> = {
id: 'linear_create_project_update',
name: 'Linear Create Project Update',
description: 'Post a status update for a project in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID to post update for',
},
body: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Update message (supports Markdown)',
},
health: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Project health: "onTrack", "atRisk", or "offTrack"',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
projectId: params.projectId,
body: params.body,
}
if (params.health != null && params.health !== '') input.health = params.health
return {
query: `
mutation CreateProjectUpdate($input: ProjectUpdateCreateInput!) {
projectUpdateCreate(input: $input) {
success
projectUpdate {
id
body
health
createdAt
user {
id
name
}
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create project update',
output: {},
}
}
const result = data.data.projectUpdateCreate
if (!result.success) {
return {
success: false,
error: 'Project update creation was not successful',
output: {},
}
}
return {
success: true,
output: {
update: result.projectUpdate,
},
}
},
outputs: {
update: {
type: 'object',
description: 'The created project update',
properties: {
id: { type: 'string', description: 'Update ID' },
body: { type: 'string', description: 'Update message' },
health: { type: 'string', description: 'Project health status' },
createdAt: { type: 'string', description: 'Creation timestamp' },
user: { type: 'object', description: 'User who created the update' },
},
},
},
}
@@ -0,0 +1,155 @@
import type {
LinearCreateWorkflowStateParams,
LinearCreateWorkflowStateResponse,
} from '@/tools/linear/types'
import { WORKFLOW_STATE_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearCreateWorkflowStateTool: ToolConfig<
LinearCreateWorkflowStateParams,
LinearCreateWorkflowStateResponse
> = {
id: 'linear_create_workflow_state',
name: 'Linear Create Workflow State',
description: 'Create a new workflow state (status) in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
teamId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Team ID to create the state in',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'State name (e.g., "In Review")',
},
color: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'State color (hex format)',
},
type: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'State type: "backlog", "unstarted", "started", "completed", or "canceled"',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'State description',
},
position: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Position in the workflow',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
teamId: params.teamId,
name: params.name,
type: params.type,
}
if (params.color != null && params.color !== '') {
input.color = params.color
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.position != null) {
input.position = Number(params.position)
}
return {
query: `
mutation CreateWorkflowState($input: WorkflowStateCreateInput!) {
workflowStateCreate(input: $input) {
success
workflowState {
id
name
description
type
color
position
createdAt
updatedAt
archivedAt
team {
id
name
}
}
}
}
`,
variables: {
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to create workflow state',
output: {},
}
}
const result = data.data.workflowStateCreate
if (!result.success) {
return {
success: false,
error: 'Workflow state creation was not successful',
output: {},
}
}
return {
success: true,
output: {
state: result.workflowState,
},
}
},
outputs: {
state: {
type: 'object',
description: 'The created workflow state',
properties: WORKFLOW_STATE_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,81 @@
import type {
LinearDeleteAttachmentParams,
LinearDeleteAttachmentResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearDeleteAttachmentTool: ToolConfig<
LinearDeleteAttachmentParams,
LinearDeleteAttachmentResponse
> = {
id: 'linear_delete_attachment',
name: 'Linear Delete Attachment',
description: 'Delete an attachment from Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
attachmentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Attachment ID to delete',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation DeleteAttachment($id: String!) {
attachmentDelete(id: $id) {
success
}
}
`,
variables: {
id: params.attachmentId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete attachment',
output: {},
}
}
return {
success: data.data.attachmentDelete.success,
output: {
success: data.data.attachmentDelete.success,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the delete operation was successful',
},
},
}
+78
View File
@@ -0,0 +1,78 @@
import type { LinearDeleteCommentParams, LinearDeleteCommentResponse } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearDeleteCommentTool: ToolConfig<
LinearDeleteCommentParams,
LinearDeleteCommentResponse
> = {
id: 'linear_delete_comment',
name: 'Linear Delete Comment',
description: 'Delete a comment from Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
commentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comment ID to delete',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation DeleteComment($id: String!) {
commentDelete(id: $id) {
success
}
}
`,
variables: {
id: params.commentId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete comment',
output: {},
}
}
return {
success: data.data.commentDelete.success,
output: {
success: data.data.commentDelete.success,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the delete operation was successful',
},
},
}
+79
View File
@@ -0,0 +1,79 @@
import type { LinearDeleteCustomerParams, LinearDeleteCustomerResponse } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearDeleteCustomerTool: ToolConfig<
LinearDeleteCustomerParams,
LinearDeleteCustomerResponse
> = {
id: 'linear_delete_customer',
name: 'Linear Delete Customer',
description: 'Delete a customer in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
customerId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer ID to delete',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation CustomerDelete($id: String!) {
customerDelete(id: $id) {
success
}
}
`,
variables: {
id: params.customerId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete customer',
output: {},
}
}
const result = data.data.customerDelete
return {
success: result.success,
output: {
success: result.success,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the deletion was successful',
},
},
}
@@ -0,0 +1,82 @@
import type {
LinearDeleteCustomerStatusParams,
LinearDeleteCustomerStatusResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearDeleteCustomerStatusTool: ToolConfig<
LinearDeleteCustomerStatusParams,
LinearDeleteCustomerStatusResponse
> = {
id: 'linear_delete_customer_status',
name: 'Linear Delete Customer Status',
description: 'Delete a customer status in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
statusId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer status ID to delete',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation CustomerStatusDelete($id: String!) {
customerStatusDelete(id: $id) {
success
}
}
`,
variables: {
id: params.statusId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete customer status',
output: {},
}
}
const result = data.data.customerStatusDelete
return {
success: result.success,
output: {
success: result.success,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the deletion was successful',
},
},
}
@@ -0,0 +1,82 @@
import type {
LinearDeleteCustomerTierParams,
LinearDeleteCustomerTierResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearDeleteCustomerTierTool: ToolConfig<
LinearDeleteCustomerTierParams,
LinearDeleteCustomerTierResponse
> = {
id: 'linear_delete_customer_tier',
name: 'Linear Delete Customer Tier',
description: 'Delete a customer tier in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
tierId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer tier ID to delete',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation CustomerTierDelete($id: String!) {
customerTierDelete(id: $id) {
success
}
}
`,
variables: {
id: params.tierId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete customer tier',
output: {},
}
}
const result = data.data.customerTierDelete
return {
success: result.success,
output: {
success: result.success,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the deletion was successful',
},
},
}
+76
View File
@@ -0,0 +1,76 @@
import type { LinearDeleteIssueParams, LinearDeleteIssueResponse } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearDeleteIssueTool: ToolConfig<LinearDeleteIssueParams, LinearDeleteIssueResponse> =
{
id: 'linear_delete_issue',
name: 'Linear Delete Issue',
description: 'Delete (trash) an issue in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Linear issue ID to delete',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation DeleteIssue($id: String!) {
issueDelete(id: $id) {
success
}
}
`,
variables: {
id: params.issueId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete issue',
output: {},
}
}
return {
success: data.data.issueDelete.success,
output: {
success: data.data.issueDelete.success,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the delete operation was successful',
},
},
}
@@ -0,0 +1,81 @@
import type {
LinearDeleteIssueRelationParams,
LinearDeleteIssueRelationResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearDeleteIssueRelationTool: ToolConfig<
LinearDeleteIssueRelationParams,
LinearDeleteIssueRelationResponse
> = {
id: 'linear_delete_issue_relation',
name: 'Linear Delete Issue Relation',
description: 'Remove a relation between two issues in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
relationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Relation ID to delete',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation DeleteIssueRelation($id: String!) {
issueRelationDelete(id: $id) {
success
}
}
`,
variables: {
id: params.relationId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete issue relation',
output: {},
}
}
return {
success: data.data.issueRelationDelete.success,
output: {
success: data.data.issueRelationDelete.success,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the delete operation was successful',
},
},
}
+79
View File
@@ -0,0 +1,79 @@
import type { LinearDeleteProjectParams, LinearDeleteProjectResponse } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearDeleteProjectTool: ToolConfig<
LinearDeleteProjectParams,
LinearDeleteProjectResponse
> = {
id: 'linear_delete_project',
name: 'Linear Delete Project',
description: 'Delete a project in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID to delete',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation ProjectDelete($id: String!) {
projectDelete(id: $id) {
success
}
}
`,
variables: {
id: params.projectId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete project',
output: {},
}
}
const result = data.data.projectDelete
return {
success: result.success,
output: {
success: result.success,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the deletion was successful',
},
},
}
@@ -0,0 +1,82 @@
import type {
LinearDeleteProjectLabelParams,
LinearDeleteProjectLabelResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearDeleteProjectLabelTool: ToolConfig<
LinearDeleteProjectLabelParams,
LinearDeleteProjectLabelResponse
> = {
id: 'linear_delete_project_label',
name: 'Linear Delete Project Label',
description: 'Delete a project label in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
labelId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project label ID to delete',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation ProjectLabelDelete($id: String!) {
projectLabelDelete(id: $id) {
success
}
}
`,
variables: {
id: params.labelId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete project label',
output: {},
}
}
const result = data.data.projectLabelDelete
return {
success: result.success,
output: {
success: result.success,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the deletion was successful',
},
},
}
@@ -0,0 +1,82 @@
import type {
LinearDeleteProjectMilestoneParams,
LinearDeleteProjectMilestoneResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearDeleteProjectMilestoneTool: ToolConfig<
LinearDeleteProjectMilestoneParams,
LinearDeleteProjectMilestoneResponse
> = {
id: 'linear_delete_project_milestone',
name: 'Linear Delete Project Milestone',
description: 'Delete a project milestone in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
milestoneId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project milestone ID to delete',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation ProjectMilestoneDelete($id: String!) {
projectMilestoneDelete(id: $id) {
success
}
}
`,
variables: {
id: params.milestoneId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete project milestone',
output: {},
}
}
const result = data.data.projectMilestoneDelete
return {
success: result.success,
output: {
success: result.success,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the deletion was successful',
},
},
}
@@ -0,0 +1,82 @@
import type {
LinearDeleteProjectStatusParams,
LinearDeleteProjectStatusResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearDeleteProjectStatusTool: ToolConfig<
LinearDeleteProjectStatusParams,
LinearDeleteProjectStatusResponse
> = {
id: 'linear_delete_project_status',
name: 'Linear Delete Project Status',
description: 'Delete a project status in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
statusId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project status ID to delete',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation ProjectStatusDelete($id: String!) {
projectStatusDelete(id: $id) {
success
}
}
`,
variables: {
id: params.statusId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to delete project status',
output: {},
}
}
const result = data.data.projectStatusDelete
return {
success: result.success,
output: {
success: result.success,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the deletion was successful',
},
},
}
+101
View File
@@ -0,0 +1,101 @@
import type { LinearGetActiveCycleParams, LinearGetActiveCycleResponse } from '@/tools/linear/types'
import { CYCLE_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearGetActiveCycleTool: ToolConfig<
LinearGetActiveCycleParams,
LinearGetActiveCycleResponse
> = {
id: 'linear_get_active_cycle',
name: 'Linear Get Active Cycle',
description: 'Get the currently active cycle for a team',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
teamId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Team ID',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query GetActiveCycle($id: String!) {
team(id: $id) {
activeCycle {
id
number
name
startsAt
endsAt
completedAt
progress
createdAt
team {
id
name
}
}
}
}
`,
variables: {
id: params.teamId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to fetch active cycle',
output: {},
}
}
if (!data.data?.team) {
return {
success: false,
error: 'Team not found',
output: {},
}
}
return {
success: true,
output: {
cycle: data.data.team.activeCycle || null,
},
}
},
outputs: {
cycle: {
type: 'object',
description: 'The active cycle (null if no active cycle)',
properties: CYCLE_FULL_OUTPUT_PROPERTIES,
},
},
}
+89
View File
@@ -0,0 +1,89 @@
import type { LinearGetCustomerParams, LinearGetCustomerResponse } from '@/tools/linear/types'
import { CUSTOMER_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearGetCustomerTool: ToolConfig<LinearGetCustomerParams, LinearGetCustomerResponse> =
{
id: 'linear_get_customer',
name: 'Linear Get Customer',
description: 'Get a single customer by ID in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
customerId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer ID to retrieve',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query GetCustomer($id: String!) {
customer(id: $id) {
id
name
domains
externalIds
logoUrl
slugId
approximateNeedCount
revenue
size
createdAt
updatedAt
archivedAt
}
}
`,
variables: {
id: params.customerId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to get customer',
output: {},
}
}
return {
success: true,
output: {
customer: data.data.customer,
},
}
},
outputs: {
customer: {
type: 'object',
description: 'The customer data',
properties: CUSTOMER_OUTPUT_PROPERTIES,
},
},
}
+88
View File
@@ -0,0 +1,88 @@
import type { LinearGetCycleParams, LinearGetCycleResponse } from '@/tools/linear/types'
import { CYCLE_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearGetCycleTool: ToolConfig<LinearGetCycleParams, LinearGetCycleResponse> = {
id: 'linear_get_cycle',
name: 'Linear Get Cycle',
description: 'Get a single cycle by ID from Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
cycleId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Cycle ID',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query GetCycle($id: String!) {
cycle(id: $id) {
id
number
name
startsAt
endsAt
completedAt
progress
createdAt
team {
id
name
}
}
}
`,
variables: {
id: params.cycleId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to fetch cycle',
output: {},
}
}
return {
success: true,
output: {
cycle: data.data.cycle,
},
}
},
outputs: {
cycle: {
type: 'object',
description: 'The cycle with full details',
properties: CYCLE_FULL_OUTPUT_PROPERTIES,
},
},
}
+130
View File
@@ -0,0 +1,130 @@
import type { LinearGetIssueParams, LinearGetIssueResponse } from '@/tools/linear/types'
import { ISSUE_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearGetIssueTool: ToolConfig<LinearGetIssueParams, LinearGetIssueResponse> = {
id: 'linear_get_issue',
name: 'Linear Get Issue',
description: 'Get a single issue by ID from Linear with full details',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Linear issue ID',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query GetIssue($id: String!) {
issue(id: $id) {
id
title
description
priority
estimate
url
createdAt
updatedAt
completedAt
canceledAt
archivedAt
state {
id
name
type
}
assignee {
id
name
email
}
team {
id
name
}
project {
id
name
}
labels {
nodes {
id
name
color
}
}
}
}
`,
variables: {
id: params.issueId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to fetch issue',
output: {},
}
}
const issue = data.data.issue
return {
success: true,
output: {
issue: {
id: issue.id,
title: issue.title,
description: issue.description,
priority: issue.priority,
estimate: issue.estimate,
url: issue.url,
createdAt: issue.createdAt,
updatedAt: issue.updatedAt,
completedAt: issue.completedAt,
canceledAt: issue.canceledAt,
archivedAt: issue.archivedAt,
state: issue.state,
assignee: issue.assignee,
teamId: issue.team?.id,
projectId: issue.project?.id,
labels: issue.labels?.nodes || [],
},
},
}
},
outputs: {
issue: {
type: 'object',
description: 'The issue with full details',
properties: ISSUE_OUTPUT_PROPERTIES,
},
},
}
+112
View File
@@ -0,0 +1,112 @@
import type { LinearGetProjectParams, LinearGetProjectResponse } from '@/tools/linear/types'
import { PROJECT_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearGetProjectTool: ToolConfig<LinearGetProjectParams, LinearGetProjectResponse> = {
id: 'linear_get_project',
name: 'Linear Get Project',
description: 'Get a single project by ID from Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Linear project ID',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query GetProject($id: String!) {
project(id: $id) {
id
name
description
state
priority
startDate
targetDate
completedAt
canceledAt
archivedAt
url
lead {
id
name
}
teams {
nodes {
id
name
}
}
}
}
`,
variables: {
id: params.projectId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to fetch project',
output: {},
}
}
const project = data.data.project
return {
success: true,
output: {
project: {
id: project.id,
name: project.name,
description: project.description,
state: project.state,
priority: project.priority,
startDate: project.startDate,
targetDate: project.targetDate,
completedAt: project.completedAt,
canceledAt: project.canceledAt,
archivedAt: project.archivedAt,
url: project.url,
lead: project.lead,
teams: project.teams?.nodes || [],
},
},
}
},
outputs: {
project: {
type: 'object',
description: 'The project with full details',
properties: PROJECT_FULL_OUTPUT_PROPERTIES,
},
},
}
+73
View File
@@ -0,0 +1,73 @@
import type { LinearGetViewerParams, LinearGetViewerResponse } from '@/tools/linear/types'
import { USER_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearGetViewerTool: ToolConfig<LinearGetViewerParams, LinearGetViewerResponse> = {
id: 'linear_get_viewer',
name: 'Linear Get Current User',
description: 'Get the currently authenticated user (viewer) information',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: () => ({
query: `
query GetViewer {
viewer {
id
name
email
displayName
active
admin
avatarUrl
}
}
`,
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to get viewer',
output: {},
}
}
return {
success: true,
output: {
user: data.data.viewer,
},
}
},
outputs: {
user: {
type: 'object',
description: 'The currently authenticated user',
properties: USER_FULL_OUTPUT_PROPERTIES,
},
},
}
+159
View File
@@ -0,0 +1,159 @@
import { linearAddLabelToIssueTool } from '@/tools/linear/add_label_to_issue'
import { linearAddLabelToProjectTool } from '@/tools/linear/add_label_to_project'
import { linearArchiveIssueTool } from '@/tools/linear/archive_issue'
import { linearArchiveLabelTool } from '@/tools/linear/archive_label'
import { linearArchiveProjectTool } from '@/tools/linear/archive_project'
import { linearCreateAttachmentTool } from '@/tools/linear/create_attachment'
import { linearCreateCommentTool } from '@/tools/linear/create_comment'
import { linearCreateCustomerTool } from '@/tools/linear/create_customer'
import { linearCreateCustomerRequestTool } from '@/tools/linear/create_customer_request'
import { linearCreateCustomerStatusTool } from '@/tools/linear/create_customer_status'
import { linearCreateCustomerTierTool } from '@/tools/linear/create_customer_tier'
import { linearCreateCycleTool } from '@/tools/linear/create_cycle'
import { linearCreateFavoriteTool } from '@/tools/linear/create_favorite'
import { linearCreateIssueTool } from '@/tools/linear/create_issue'
import { linearCreateIssueRelationTool } from '@/tools/linear/create_issue_relation'
import { linearCreateLabelTool } from '@/tools/linear/create_label'
import { linearCreateProjectTool } from '@/tools/linear/create_project'
import { linearCreateProjectLabelTool } from '@/tools/linear/create_project_label'
import { linearCreateProjectMilestoneTool } from '@/tools/linear/create_project_milestone'
import { linearCreateProjectStatusTool } from '@/tools/linear/create_project_status'
import { linearCreateProjectUpdateTool } from '@/tools/linear/create_project_update'
import { linearCreateWorkflowStateTool } from '@/tools/linear/create_workflow_state'
import { linearDeleteAttachmentTool } from '@/tools/linear/delete_attachment'
import { linearDeleteCommentTool } from '@/tools/linear/delete_comment'
import { linearDeleteCustomerTool } from '@/tools/linear/delete_customer'
import { linearDeleteCustomerStatusTool } from '@/tools/linear/delete_customer_status'
import { linearDeleteCustomerTierTool } from '@/tools/linear/delete_customer_tier'
import { linearDeleteIssueTool } from '@/tools/linear/delete_issue'
import { linearDeleteIssueRelationTool } from '@/tools/linear/delete_issue_relation'
import { linearDeleteProjectTool } from '@/tools/linear/delete_project'
import { linearDeleteProjectLabelTool } from '@/tools/linear/delete_project_label'
import { linearDeleteProjectMilestoneTool } from '@/tools/linear/delete_project_milestone'
import { linearDeleteProjectStatusTool } from '@/tools/linear/delete_project_status'
import { linearGetActiveCycleTool } from '@/tools/linear/get_active_cycle'
import { linearGetCustomerTool } from '@/tools/linear/get_customer'
import { linearGetCycleTool } from '@/tools/linear/get_cycle'
import { linearGetIssueTool } from '@/tools/linear/get_issue'
import { linearGetProjectTool } from '@/tools/linear/get_project'
import { linearGetViewerTool } from '@/tools/linear/get_viewer'
import { linearListAttachmentsTool } from '@/tools/linear/list_attachments'
import { linearListCommentsTool } from '@/tools/linear/list_comments'
import { linearListCustomerRequestsTool } from '@/tools/linear/list_customer_requests'
import { linearListCustomerStatusesTool } from '@/tools/linear/list_customer_statuses'
import { linearListCustomerTiersTool } from '@/tools/linear/list_customer_tiers'
import { linearListCustomersTool } from '@/tools/linear/list_customers'
import { linearListCyclesTool } from '@/tools/linear/list_cycles'
import { linearListFavoritesTool } from '@/tools/linear/list_favorites'
import { linearListIssueRelationsTool } from '@/tools/linear/list_issue_relations'
import { linearListLabelsTool } from '@/tools/linear/list_labels'
import { linearListNotificationsTool } from '@/tools/linear/list_notifications'
import { linearListProjectLabelsTool } from '@/tools/linear/list_project_labels'
import { linearListProjectMilestonesTool } from '@/tools/linear/list_project_milestones'
import { linearListProjectStatusesTool } from '@/tools/linear/list_project_statuses'
import { linearListProjectUpdatesTool } from '@/tools/linear/list_project_updates'
import { linearListProjectsTool } from '@/tools/linear/list_projects'
import { linearListTeamsTool } from '@/tools/linear/list_teams'
import { linearListUsersTool } from '@/tools/linear/list_users'
import { linearListWorkflowStatesTool } from '@/tools/linear/list_workflow_states'
import { linearMergeCustomersTool } from '@/tools/linear/merge_customers'
import { linearReadIssuesTool } from '@/tools/linear/read_issues'
import { linearRemoveLabelFromIssueTool } from '@/tools/linear/remove_label_from_issue'
import { linearRemoveLabelFromProjectTool } from '@/tools/linear/remove_label_from_project'
import { linearSearchIssuesTool } from '@/tools/linear/search_issues'
import { linearUnarchiveIssueTool } from '@/tools/linear/unarchive_issue'
import { linearUpdateAttachmentTool } from '@/tools/linear/update_attachment'
import { linearUpdateCommentTool } from '@/tools/linear/update_comment'
import { linearUpdateCustomerTool } from '@/tools/linear/update_customer'
import { linearUpdateCustomerRequestTool } from '@/tools/linear/update_customer_request'
import { linearUpdateCustomerStatusTool } from '@/tools/linear/update_customer_status'
import { linearUpdateCustomerTierTool } from '@/tools/linear/update_customer_tier'
import { linearUpdateIssueTool } from '@/tools/linear/update_issue'
import { linearUpdateLabelTool } from '@/tools/linear/update_label'
import { linearUpdateNotificationTool } from '@/tools/linear/update_notification'
import { linearUpdateProjectTool } from '@/tools/linear/update_project'
import { linearUpdateProjectLabelTool } from '@/tools/linear/update_project_label'
import { linearUpdateProjectMilestoneTool } from '@/tools/linear/update_project_milestone'
import { linearUpdateProjectStatusTool } from '@/tools/linear/update_project_status'
import { linearUpdateWorkflowStateTool } from '@/tools/linear/update_workflow_state'
export {
linearReadIssuesTool,
linearCreateIssueTool,
linearGetIssueTool,
linearUpdateIssueTool,
linearArchiveIssueTool,
linearUnarchiveIssueTool,
linearDeleteIssueTool,
linearAddLabelToIssueTool,
linearRemoveLabelFromIssueTool,
linearSearchIssuesTool,
linearCreateCommentTool,
linearUpdateCommentTool,
linearDeleteCommentTool,
linearListCommentsTool,
linearListProjectsTool,
linearGetProjectTool,
linearCreateProjectTool,
linearUpdateProjectTool,
linearArchiveProjectTool,
linearDeleteProjectTool,
linearAddLabelToProjectTool,
linearRemoveLabelFromProjectTool,
linearListProjectLabelsTool,
linearCreateProjectLabelTool,
linearDeleteProjectLabelTool,
linearUpdateProjectLabelTool,
linearListProjectMilestonesTool,
linearCreateProjectMilestoneTool,
linearDeleteProjectMilestoneTool,
linearUpdateProjectMilestoneTool,
linearListProjectStatusesTool,
linearCreateProjectStatusTool,
linearDeleteProjectStatusTool,
linearUpdateProjectStatusTool,
linearListUsersTool,
linearListTeamsTool,
linearGetViewerTool,
linearListLabelsTool,
linearCreateLabelTool,
linearUpdateLabelTool,
linearArchiveLabelTool,
linearListWorkflowStatesTool,
linearCreateWorkflowStateTool,
linearUpdateWorkflowStateTool,
linearListCyclesTool,
linearGetCycleTool,
linearCreateCycleTool,
linearGetActiveCycleTool,
linearCreateAttachmentTool,
linearListAttachmentsTool,
linearUpdateAttachmentTool,
linearDeleteAttachmentTool,
linearCreateIssueRelationTool,
linearListIssueRelationsTool,
linearDeleteIssueRelationTool,
linearCreateFavoriteTool,
linearListFavoritesTool,
linearCreateProjectUpdateTool,
linearListProjectUpdatesTool,
linearListNotificationsTool,
linearUpdateNotificationTool,
linearCreateCustomerTool,
linearGetCustomerTool,
linearUpdateCustomerTool,
linearDeleteCustomerTool,
linearListCustomersTool,
linearMergeCustomersTool,
linearListCustomerStatusesTool,
linearCreateCustomerStatusTool,
linearDeleteCustomerStatusTool,
linearUpdateCustomerStatusTool,
linearListCustomerTiersTool,
linearCreateCustomerTierTool,
linearDeleteCustomerTierTool,
linearUpdateCustomerTierTool,
linearCreateCustomerRequestTool,
linearUpdateCustomerRequestTool,
linearListCustomerRequestsTool,
}
+127
View File
@@ -0,0 +1,127 @@
import type {
LinearListAttachmentsParams,
LinearListAttachmentsResponse,
} from '@/tools/linear/types'
import { ATTACHMENT_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListAttachmentsTool: ToolConfig<
LinearListAttachmentsParams,
LinearListAttachmentsResponse
> = {
id: 'linear_list_attachments',
name: 'Linear List Attachments',
description: 'List all attachments on an issue in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of attachments to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query ListAttachments($issueId: String!, $first: Int, $after: String) {
issue(id: $issueId) {
attachments(first: $first, after: $after) {
nodes {
id
title
subtitle
url
createdAt
updatedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`,
variables: {
issueId: params.issueId,
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list attachments',
output: {},
}
}
if (!data.data?.issue) {
return {
success: false,
error: 'Issue not found',
output: {},
}
}
const result = data.data.issue.attachments
return {
success: true,
output: {
attachments: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
attachments: {
type: 'array',
description: 'Array of attachments',
items: {
type: 'object',
properties: ATTACHMENT_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
+127
View File
@@ -0,0 +1,127 @@
import type { LinearListCommentsParams, LinearListCommentsResponse } from '@/tools/linear/types'
import { COMMENT_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListCommentsTool: ToolConfig<
LinearListCommentsParams,
LinearListCommentsResponse
> = {
id: 'linear_list_comments',
name: 'Linear List Comments',
description: 'List all comments on an issue in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Linear issue ID',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of comments to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query ListComments($issueId: String!, $first: Int, $after: String) {
issue(id: $issueId) {
comments(first: $first, after: $after) {
nodes {
id
body
createdAt
updatedAt
user {
id
name
email
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`,
variables: {
issueId: params.issueId,
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list comments',
output: {},
}
}
if (!data.data?.issue) {
return {
success: false,
error: 'Issue not found',
output: {},
}
}
const result = data.data.issue.comments
return {
success: true,
output: {
comments: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
comments: {
type: 'array',
description: 'Array of comments on the issue',
items: {
type: 'object',
properties: COMMENT_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
@@ -0,0 +1,151 @@
import type {
LinearListCustomerRequestsParams,
LinearListCustomerRequestsResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListCustomerRequestsTool: ToolConfig<
LinearListCustomerRequestsParams,
LinearListCustomerRequestsResponse
> = {
id: 'linear_list_customer_requests',
name: 'Linear List Customer Requests',
description: 'List all customer requests (needs) in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of customer requests to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
includeArchived: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include archived customer requests (default: false)',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query ListCustomerNeeds($first: Int, $after: String, $includeArchived: Boolean) {
customerNeeds(first: $first, after: $after, includeArchived: $includeArchived) {
nodes {
id
body
priority
createdAt
updatedAt
archivedAt
customer {
id
name
}
issue {
id
title
}
project {
id
name
}
creator {
id
name
}
url
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
includeArchived: params.includeArchived || false,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list customer requests',
output: {},
}
}
const result = data.data.customerNeeds
return {
success: true,
output: {
customerNeeds: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
customerNeeds: {
type: 'array',
description: 'Array of customer requests',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Customer request ID' },
body: { type: 'string', description: 'Request description' },
priority: {
type: 'number',
description: 'Urgency level (0 = Not important, 1 = Important)',
},
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last update timestamp' },
archivedAt: { type: 'string', description: 'Archive timestamp (null if not archived)' },
customer: { type: 'object', description: 'Assigned customer' },
issue: { type: 'object', description: 'Linked issue (null if not linked)' },
project: { type: 'object', description: 'Linked project (null if not linked)' },
creator: { type: 'object', description: 'User who created the request' },
url: { type: 'string', description: 'URL to the customer request' },
},
},
},
pageInfo: {
type: 'object',
description: 'Pagination information',
},
},
}
@@ -0,0 +1,113 @@
import type {
LinearListCustomerStatusesParams,
LinearListCustomerStatusesResponse,
} from '@/tools/linear/types'
import { CUSTOMER_STATUS_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListCustomerStatusesTool: ToolConfig<
LinearListCustomerStatusesParams,
LinearListCustomerStatusesResponse
> = {
id: 'linear_list_customer_statuses',
name: 'Linear List Customer Statuses',
description: 'List all customer statuses in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of statuses to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query CustomerStatuses($first: Int, $after: String) {
customerStatuses(first: $first, after: $after) {
nodes {
id
name
description
color
position
type
createdAt
updatedAt
archivedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list customer statuses',
output: {},
}
}
const result = data.data.customerStatuses
return {
success: true,
output: {
customerStatuses: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
customerStatuses: {
type: 'array',
description: 'List of customer statuses',
items: {
type: 'object',
properties: CUSTOMER_STATUS_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
@@ -0,0 +1,112 @@
import type {
LinearListCustomerTiersParams,
LinearListCustomerTiersResponse,
} from '@/tools/linear/types'
import { CUSTOMER_TIER_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListCustomerTiersTool: ToolConfig<
LinearListCustomerTiersParams,
LinearListCustomerTiersResponse
> = {
id: 'linear_list_customer_tiers',
name: 'Linear List Customer Tiers',
description: 'List all customer tiers in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of tiers to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query CustomerTiers($first: Int, $after: String) {
customerTiers(first: $first, after: $after) {
nodes {
id
name
displayName
description
color
position
createdAt
archivedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list customer tiers',
output: {},
}
}
const result = data.data.customerTiers
return {
success: true,
output: {
customerTiers: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
customerTiers: {
type: 'array',
description: 'List of customer tiers',
items: {
type: 'object',
properties: CUSTOMER_TIER_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
+120
View File
@@ -0,0 +1,120 @@
import type { LinearListCustomersParams, LinearListCustomersResponse } from '@/tools/linear/types'
import { CUSTOMER_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListCustomersTool: ToolConfig<
LinearListCustomersParams,
LinearListCustomersResponse
> = {
id: 'linear_list_customers',
name: 'Linear List Customers',
description: 'List all customers in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of customers to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
includeArchived: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include archived customers (default: false)',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query ListCustomers($first: Int, $after: String, $includeArchived: Boolean) {
customers(first: $first, after: $after, includeArchived: $includeArchived) {
nodes {
id
name
domains
externalIds
logoUrl
slugId
approximateNeedCount
revenue
size
createdAt
updatedAt
archivedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
includeArchived: params.includeArchived || false,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list customers',
output: {},
}
}
const result = data.data.customers
return {
success: true,
output: {
customers: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
customers: {
type: 'array',
description: 'Array of customers',
items: {
type: 'object',
properties: CUSTOMER_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
+124
View File
@@ -0,0 +1,124 @@
import type { LinearListCyclesParams, LinearListCyclesResponse } from '@/tools/linear/types'
import { CYCLE_FULL_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListCyclesTool: ToolConfig<LinearListCyclesParams, LinearListCyclesResponse> = {
id: 'linear_list_cycles',
name: 'Linear List Cycles',
description: 'List cycles (sprints/iterations) in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
teamId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by team ID',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of cycles to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const filter: Record<string, any> = {}
if (params.teamId) {
filter.team = { id: { eq: params.teamId } }
}
return {
query: `
query ListCycles($filter: CycleFilter, $first: Int, $after: String) {
cycles(filter: $filter, first: $first, after: $after) {
nodes {
id
number
name
startsAt
endsAt
completedAt
progress
createdAt
team {
id
name
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
filter: Object.keys(filter).length > 0 ? filter : undefined,
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list cycles',
output: {},
}
}
const result = data.data.cycles
return {
success: true,
output: {
cycles: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
cycles: {
type: 'array',
description: 'Array of cycles',
items: {
type: 'object',
properties: CYCLE_FULL_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
+123
View File
@@ -0,0 +1,123 @@
import type { LinearListFavoritesParams, LinearListFavoritesResponse } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListFavoritesTool: ToolConfig<
LinearListFavoritesParams,
LinearListFavoritesResponse
> = {
id: 'linear_list_favorites',
name: 'Linear List Favorites',
description: 'List all bookmarked items for the current user in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of favorites to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query ListFavorites($first: Int, $after: String) {
favorites(first: $first, after: $after) {
nodes {
id
type
issue {
id
title
}
project {
id
name
}
cycle {
id
name
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list favorites',
output: {},
}
}
const result = data.data.favorites
return {
success: true,
output: {
favorites: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
favorites: {
type: 'array',
description: 'Array of favorited items',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Favorite ID' },
type: { type: 'string', description: 'Favorite type' },
issue: { type: 'object', description: 'Favorited issue' },
project: { type: 'object', description: 'Favorited project' },
cycle: { type: 'object', description: 'Favorited cycle' },
},
},
},
pageInfo: {
type: 'object',
description: 'Pagination information',
},
},
}
@@ -0,0 +1,138 @@
import type {
LinearListIssueRelationsParams,
LinearListIssueRelationsResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListIssueRelationsTool: ToolConfig<
LinearListIssueRelationsParams,
LinearListIssueRelationsResponse
> = {
id: 'linear_list_issue_relations',
name: 'Linear List Issue Relations',
description: 'List all relations (dependencies) for an issue in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue ID',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of relations to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query ListIssueRelations($issueId: String!, $first: Int, $after: String) {
issue(id: $issueId) {
relations(first: $first, after: $after) {
nodes {
id
type
issue {
id
title
}
relatedIssue {
id
title
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`,
variables: {
issueId: params.issueId,
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list issue relations',
output: {},
}
}
if (!data.data?.issue) {
return {
success: false,
error: 'Issue not found',
output: {},
}
}
const result = data.data.issue.relations
return {
success: true,
output: {
relations: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
relations: {
type: 'array',
description: 'Array of issue relations',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Relation ID' },
type: { type: 'string', description: 'Relation type' },
issue: { type: 'object', description: 'Source issue' },
relatedIssue: { type: 'object', description: 'Target issue' },
},
},
},
pageInfo: {
type: 'object',
description: 'Pagination information',
},
},
}
+124
View File
@@ -0,0 +1,124 @@
import type { LinearListLabelsParams, LinearListLabelsResponse } from '@/tools/linear/types'
import { LABEL_FULL_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListLabelsTool: ToolConfig<LinearListLabelsParams, LinearListLabelsResponse> = {
id: 'linear_list_labels',
name: 'Linear List Labels',
description: 'List all labels in Linear workspace or team',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
teamId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by team ID',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of labels to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const filter: Record<string, any> = {}
if (params.teamId) {
filter.team = { id: { eq: params.teamId } }
}
return {
query: `
query ListLabels($filter: IssueLabelFilter, $first: Int, $after: String) {
issueLabels(filter: $filter, first: $first, after: $after) {
nodes {
id
name
color
description
isGroup
createdAt
updatedAt
archivedAt
team {
id
name
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
filter: Object.keys(filter).length > 0 ? filter : undefined,
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list labels',
output: {},
}
}
const result = data.data.issueLabels
return {
success: true,
output: {
labels: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
labels: {
type: 'array',
description: 'Array of labels',
items: {
type: 'object',
properties: LABEL_FULL_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
+122
View File
@@ -0,0 +1,122 @@
import type {
LinearListNotificationsParams,
LinearListNotificationsResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListNotificationsTool: ToolConfig<
LinearListNotificationsParams,
LinearListNotificationsResponse
> = {
id: 'linear_list_notifications',
name: 'Linear List Notifications',
description: 'List notifications for the current user in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of notifications to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query ListNotifications($first: Int, $after: String) {
notifications(first: $first, after: $after) {
nodes {
id
type
createdAt
readAt
... on IssueNotification {
issue {
id
title
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list notifications',
output: {},
}
}
const result = data.data.notifications
return {
success: true,
output: {
notifications: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
notifications: {
type: 'array',
description: 'Array of notifications',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Notification ID' },
type: { type: 'string', description: 'Notification type' },
createdAt: { type: 'string', description: 'Creation timestamp' },
readAt: { type: 'string', description: 'Read timestamp (null if unread)' },
issue: { type: 'object', description: 'Related issue' },
},
},
},
pageInfo: {
type: 'object',
description: 'Pagination information',
},
},
}
@@ -0,0 +1,168 @@
import type {
LinearListProjectLabelsParams,
LinearListProjectLabelsResponse,
} from '@/tools/linear/types'
import { PAGE_INFO_OUTPUT, PROJECT_LABEL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListProjectLabelsTool: ToolConfig<
LinearListProjectLabelsParams,
LinearListProjectLabelsResponse
> = {
id: 'linear_list_project_labels',
name: 'Linear List Project Labels',
description: 'List all project labels in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
projectId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Optional project ID to filter labels for a specific project',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of labels to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
if (params.projectId?.trim()) {
return {
query: `
query ProjectWithLabels($id: String!, $first: Int, $after: String) {
project(id: $id) {
id
name
labels(first: $first, after: $after) {
nodes {
id
name
description
color
isGroup
createdAt
updatedAt
archivedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`,
variables: {
id: params.projectId.trim(),
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}
}
return {
query: `
query ProjectLabels($first: Int, $after: String) {
projectLabels(first: $first, after: $after) {
nodes {
id
name
description
color
isGroup
createdAt
updatedAt
archivedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list project labels',
output: {},
}
}
if (data.data.project) {
const result = data.data.project.labels
return {
success: true,
output: {
projectLabels: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
}
const result = data.data.projectLabels
return {
success: true,
output: {
projectLabels: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
projectLabels: {
type: 'array',
description: 'List of project labels',
items: {
type: 'object',
properties: PROJECT_LABEL_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
@@ -0,0 +1,130 @@
import type {
LinearListProjectMilestonesParams,
LinearListProjectMilestonesResponse,
} from '@/tools/linear/types'
import { PAGE_INFO_OUTPUT, PROJECT_MILESTONE_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListProjectMilestonesTool: ToolConfig<
LinearListProjectMilestonesParams,
LinearListProjectMilestonesResponse
> = {
id: 'linear_list_project_milestones',
name: 'Linear List Project Milestones',
description: 'List all milestones for a project in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID to list milestones for',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of milestones to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query Project($id: String!, $first: Int, $after: String) {
project(id: $id) {
projectMilestones(first: $first, after: $after) {
nodes {
id
name
description
targetDate
progress
sortOrder
status
createdAt
archivedAt
project {
id
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`,
variables: {
id: params.projectId,
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list project milestones',
output: {},
}
}
const result = data.data.project?.projectMilestones
const milestones = (result?.nodes || []).map((node: Record<string, unknown>) => ({
...node,
projectId: (node.project as Record<string, string>)?.id ?? null,
project: undefined,
}))
return {
success: true,
output: {
projectMilestones: milestones,
pageInfo: {
hasNextPage: result?.pageInfo?.hasNextPage ?? false,
endCursor: result?.pageInfo?.endCursor,
},
},
}
},
outputs: {
projectMilestones: {
type: 'array',
description: 'List of project milestones',
items: {
type: 'object',
properties: PROJECT_MILESTONE_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
@@ -0,0 +1,114 @@
import type {
LinearListProjectStatusesParams,
LinearListProjectStatusesResponse,
} from '@/tools/linear/types'
import { PAGE_INFO_OUTPUT, PROJECT_STATUS_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListProjectStatusesTool: ToolConfig<
LinearListProjectStatusesParams,
LinearListProjectStatusesResponse
> = {
id: 'linear_list_project_statuses',
name: 'Linear List Project Statuses',
description: 'List all project statuses in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of statuses to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query ProjectStatuses($first: Int, $after: String) {
projectStatuses(first: $first, after: $after) {
nodes {
id
name
description
color
indefinite
position
type
createdAt
updatedAt
archivedAt
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list project statuses',
output: {},
}
}
const result = data.data.projectStatuses
return {
success: true,
output: {
projectStatuses: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
projectStatuses: {
type: 'array',
description: 'List of project statuses',
items: {
type: 'object',
properties: PROJECT_STATUS_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
@@ -0,0 +1,137 @@
import type {
LinearListProjectUpdatesParams,
LinearListProjectUpdatesResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListProjectUpdatesTool: ToolConfig<
LinearListProjectUpdatesParams,
LinearListProjectUpdatesResponse
> = {
id: 'linear_list_project_updates',
name: 'Linear List Project Updates',
description: 'List all status updates for a project in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of updates to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query ListProjectUpdates($projectId: String!, $first: Int, $after: String) {
project(id: $projectId) {
projectUpdates(first: $first, after: $after) {
nodes {
id
body
health
createdAt
user {
id
name
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
`,
variables: {
projectId: params.projectId,
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list project updates',
output: {},
}
}
if (!data.data?.project) {
return {
success: false,
error: 'Project not found',
output: {},
}
}
const result = data.data.project.projectUpdates
return {
success: true,
output: {
updates: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
updates: {
type: 'array',
description: 'Array of project updates',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Update ID' },
body: { type: 'string', description: 'Update message' },
health: { type: 'string', description: 'Project health' },
createdAt: { type: 'string', description: 'Creation timestamp' },
user: { type: 'object', description: 'User who created the update' },
},
},
},
pageInfo: {
type: 'object',
description: 'Pagination information',
},
},
}
+162
View File
@@ -0,0 +1,162 @@
import type { LinearListProjectsParams, LinearListProjectsResponse } from '@/tools/linear/types'
import { PAGE_INFO_OUTPUT, PROJECT_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListProjectsTool: ToolConfig<
LinearListProjectsParams,
LinearListProjectsResponse
> = {
id: 'linear_list_projects',
name: 'Linear List Projects',
description: 'List projects in Linear with optional filtering',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
teamId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by team ID',
},
includeArchived: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include archived projects',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of projects to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
// Note: ProjectFilter does not support filtering by team directly
// We need to filter projects client-side by team if teamId is provided
return {
query: `
query ListProjects($first: Int, $after: String, $includeArchived: Boolean) {
projects(first: $first, after: $after, includeArchived: $includeArchived) {
nodes {
id
name
description
state
priority
startDate
targetDate
completedAt
canceledAt
archivedAt
url
lead {
id
name
}
teams {
nodes {
id
name
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
includeArchived: params.includeArchived || false,
},
}
},
},
transformResponse: async (response, params) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list projects',
output: {},
}
}
const result = data.data.projects
let projects = result.nodes.map((project: any) => ({
id: project.id,
name: project.name,
description: project.description,
state: project.state,
priority: project.priority,
startDate: project.startDate,
targetDate: project.targetDate,
completedAt: project.completedAt,
canceledAt: project.canceledAt,
archivedAt: project.archivedAt,
url: project.url,
lead: project.lead,
teams: project.teams?.nodes || [],
}))
// Filter by teamId client-side if provided
if (params?.teamId) {
projects = projects.filter((project: any) =>
project.teams.some((team: any) => team.id === params.teamId)
)
}
return {
success: true,
output: {
projects,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
projects: {
type: 'array',
description: 'Array of projects',
items: {
type: 'object',
properties: PROJECT_FULL_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
+102
View File
@@ -0,0 +1,102 @@
import type { LinearListTeamsParams, LinearListTeamsResponse } from '@/tools/linear/types'
import { PAGE_INFO_OUTPUT, TEAM_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListTeamsTool: ToolConfig<LinearListTeamsParams, LinearListTeamsResponse> = {
id: 'linear_list_teams',
name: 'Linear List Teams',
description: 'List all teams in the Linear workspace',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of teams to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query ListTeams($first: Int, $after: String) {
teams(first: $first, after: $after) {
nodes {
id
name
key
description
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list teams',
output: {},
}
}
const result = data.data.teams
return {
success: true,
output: {
teams: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
teams: {
type: 'array',
description: 'Array of teams',
items: {
type: 'object',
properties: TEAM_FULL_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
+112
View File
@@ -0,0 +1,112 @@
import type { LinearListUsersParams, LinearListUsersResponse } from '@/tools/linear/types'
import { PAGE_INFO_OUTPUT, USER_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListUsersTool: ToolConfig<LinearListUsersParams, LinearListUsersResponse> = {
id: 'linear_list_users',
name: 'Linear List Users',
description: 'List all users in the Linear workspace',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
includeDisabled: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include disabled/inactive users',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of users to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
query ListUsers($includeDisabled: Boolean, $first: Int, $after: String) {
users(includeDisabled: $includeDisabled, first: $first, after: $after) {
nodes {
id
name
email
displayName
active
admin
avatarUrl
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
includeDisabled: params.includeDisabled || false,
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list users',
output: {},
}
}
const result = data.data.users
return {
success: true,
output: {
users: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
users: {
type: 'array',
description: 'Array of workspace users',
items: {
type: 'object',
properties: USER_FULL_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
@@ -0,0 +1,131 @@
import type {
LinearListWorkflowStatesParams,
LinearListWorkflowStatesResponse,
} from '@/tools/linear/types'
import { PAGE_INFO_OUTPUT, WORKFLOW_STATE_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearListWorkflowStatesTool: ToolConfig<
LinearListWorkflowStatesParams,
LinearListWorkflowStatesResponse
> = {
id: 'linear_list_workflow_states',
name: 'Linear List Workflow States',
description: 'List all workflow states (statuses) in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
teamId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by team ID',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of states to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const filter: Record<string, any> = {}
if (params.teamId) {
filter.team = { id: { eq: params.teamId } }
}
return {
query: `
query ListWorkflowStates($filter: WorkflowStateFilter, $first: Int, $after: String) {
workflowStates(filter: $filter, first: $first, after: $after) {
nodes {
id
name
description
type
color
position
createdAt
updatedAt
archivedAt
team {
id
name
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
filter: Object.keys(filter).length > 0 ? filter : undefined,
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to list workflow states',
output: {},
}
}
const result = data.data.workflowStates
return {
success: true,
output: {
states: result.nodes,
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
states: {
type: 'array',
description: 'Array of workflow states',
items: {
type: 'object',
properties: WORKFLOW_STATE_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
+96
View File
@@ -0,0 +1,96 @@
import type { LinearMergeCustomersParams, LinearMergeCustomersResponse } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearMergeCustomersTool: ToolConfig<
LinearMergeCustomersParams,
LinearMergeCustomersResponse
> = {
id: 'linear_merge_customers',
name: 'Linear Merge Customers',
description: 'Merge two customers in Linear by moving all data from source to target',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
sourceCustomerId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Source customer ID (will be deleted after merge)',
},
targetCustomerId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Target customer ID (will receive all data)',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation CustomerMerge($sourceCustomerId: String!, $targetCustomerId: String!) {
customerMerge(sourceCustomerId: $sourceCustomerId, targetCustomerId: $targetCustomerId) {
success
customer {
id
name
domains
externalIds
logoUrl
approximateNeedCount
createdAt
archivedAt
}
}
}
`,
variables: {
sourceCustomerId: params.sourceCustomerId,
targetCustomerId: params.targetCustomerId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to merge customers',
output: {},
}
}
const result = data.data.customerMerge
return {
success: result.success,
output: {
customer: result.customer,
},
}
},
outputs: {
customer: {
type: 'object',
description: 'The merged target customer',
},
},
}
+281
View File
@@ -0,0 +1,281 @@
import type { LinearReadIssuesParams, LinearReadIssuesResponse } from '@/tools/linear/types'
import { ISSUE_LIST_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearReadIssuesTool: ToolConfig<LinearReadIssuesParams, LinearReadIssuesResponse> = {
id: 'linear_read_issues',
name: 'Linear Issue Reader',
description: 'Fetch and filter issues from Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
teamId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Linear team ID (UUID format) to filter issues by team',
},
projectId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Linear project ID (UUID format) to filter issues by project',
},
assigneeId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User ID to filter by assignee',
},
stateId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Workflow state ID to filter by status',
},
priority: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Priority to filter by (0=No priority, 1=Urgent, 2=High, 3=Normal, 4=Low)',
},
labelIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of label IDs to filter by',
},
createdAfter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter issues created after this date (ISO 8601 format)',
},
updatedAfter: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter issues updated after this date (ISO 8601 format)',
},
includeArchived: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include archived issues (default: false)',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of issues to return (default: 50, max: 250)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pagination cursor for next page',
},
orderBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order: "createdAt" or "updatedAt" (default: "updatedAt")',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const filter: Record<string, any> = {}
if (params.teamId != null && params.teamId !== '') {
filter.team = { id: { eq: params.teamId } }
}
if (params.projectId != null && params.projectId !== '') {
filter.project = { id: { eq: params.projectId } }
}
if (params.assigneeId != null && params.assigneeId !== '') {
filter.assignee = { id: { eq: params.assigneeId } }
}
if (params.stateId != null && params.stateId !== '') {
filter.state = { id: { eq: params.stateId } }
}
if (params.priority != null) {
filter.priority = { eq: Number(params.priority) }
}
if (params.labelIds != null && Array.isArray(params.labelIds) && params.labelIds.length > 0) {
filter.labels = { some: { id: { in: params.labelIds } } }
}
if (params.createdAfter != null && params.createdAfter !== '') {
filter.createdAt = { gte: params.createdAfter }
}
if (params.updatedAfter != null && params.updatedAfter !== '') {
filter.updatedAt = { gte: params.updatedAfter }
}
const variables: Record<string, any> = {}
if (Object.keys(filter).length > 0) {
variables.filter = filter
}
if (params.first != null) {
variables.first = Math.min(Number(params.first), 250)
}
if (params.after?.trim()) {
variables.after = params.after.trim()
}
if (params.includeArchived != null) {
variables.includeArchived = params.includeArchived
}
if (params.orderBy != null) {
variables.orderBy = params.orderBy
}
return {
query: `
query Issues(
$filter: IssueFilter
$first: Int
$after: String
$includeArchived: Boolean
$orderBy: PaginationOrderBy
) {
issues(
filter: $filter
first: $first
after: $after
includeArchived: $includeArchived
orderBy: $orderBy
) {
nodes {
id
title
description
priority
estimate
url
dueDate
createdAt
updatedAt
state {
id
name
type
}
assignee {
id
name
email
}
team {
id
name
}
project {
id
name
}
cycle {
id
number
name
}
labels {
nodes {
id
name
color
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables,
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to fetch issues',
output: {},
}
}
if (!data.data?.issues) {
return {
success: false,
error: 'No issues data returned',
output: {},
}
}
const issues = data.data.issues.nodes || []
const pageInfo = data.data.issues.pageInfo || {}
return {
success: true,
output: {
issues: issues.map((issue: any) => ({
id: issue.id,
title: issue.title,
description: issue.description,
priority: issue.priority,
estimate: issue.estimate,
url: issue.url,
dueDate: issue.dueDate,
createdAt: issue.createdAt,
updatedAt: issue.updatedAt,
state: issue.state,
assignee: issue.assignee,
teamId: issue.team?.id,
teamName: issue.team?.name,
projectId: issue.project?.id,
projectName: issue.project?.name,
cycleId: issue.cycle?.id,
cycleNumber: issue.cycle?.number,
cycleName: issue.cycle?.name,
labels: issue.labels?.nodes || [],
})),
hasNextPage: pageInfo.hasNextPage,
endCursor: pageInfo.endCursor,
},
}
},
outputs: {
issues: {
type: 'array',
description: 'Array of filtered issues from Linear',
items: {
type: 'object',
properties: ISSUE_LIST_OUTPUT_PROPERTIES,
},
},
hasNextPage: PAGE_INFO_OUTPUT_PROPERTIES.hasNextPage,
endCursor: PAGE_INFO_OUTPUT_PROPERTIES.endCursor,
},
}
@@ -0,0 +1,97 @@
import type {
LinearRemoveLabelFromIssueParams,
LinearRemoveLabelResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearRemoveLabelFromIssueTool: ToolConfig<
LinearRemoveLabelFromIssueParams,
LinearRemoveLabelResponse
> = {
id: 'linear_remove_label_from_issue',
name: 'Linear Remove Label from Issue',
description: 'Remove a label from an issue in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Linear issue ID',
},
labelId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Label ID to remove from the issue',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation RemoveLabelFromIssue($issueId: String!, $labelId: String!) {
issueRemoveLabel(id: $issueId, labelId: $labelId) {
success
issue {
id
}
}
}
`,
variables: {
issueId: params.issueId,
labelId: params.labelId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to remove label from issue',
output: {},
}
}
const result = data.data.issueRemoveLabel
return {
success: result.success,
output: {
success: result.success,
issueId: result.issue?.id || '',
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the label was successfully removed',
},
issueId: {
type: 'string',
description: 'The ID of the issue',
},
},
}
@@ -0,0 +1,97 @@
import type {
LinearRemoveLabelFromProjectParams,
LinearRemoveLabelFromProjectResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearRemoveLabelFromProjectTool: ToolConfig<
LinearRemoveLabelFromProjectParams,
LinearRemoveLabelFromProjectResponse
> = {
id: 'linear_remove_label_from_project',
name: 'Linear Remove Label from Project',
description: 'Remove a label from a project in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID',
},
labelId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Label ID to remove',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation ProjectRemoveLabel($id: String!, $labelId: String!) {
projectRemoveLabel(id: $id, labelId: $labelId) {
success
project {
id
}
}
}
`,
variables: {
id: params.projectId,
labelId: params.labelId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to remove label from project',
output: {},
}
}
const result = data.data.projectRemoveLabel
return {
success: result.success,
output: {
success: result.success,
projectId: result.project?.id,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the label was removed successfully',
},
projectId: {
type: 'string',
description: 'The project ID',
},
},
}
+178
View File
@@ -0,0 +1,178 @@
import type { LinearSearchIssuesParams, LinearSearchIssuesResponse } from '@/tools/linear/types'
import { ISSUE_OUTPUT_PROPERTIES, PAGE_INFO_OUTPUT } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearSearchIssuesTool: ToolConfig<
LinearSearchIssuesParams,
LinearSearchIssuesResponse
> = {
id: 'linear_search_issues',
name: 'Linear Search Issues',
description: 'Search for issues in Linear using full-text search',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Search query string',
},
teamId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by team ID',
},
includeArchived: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include archived issues in search results',
},
first: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to return (default: 50)',
},
after: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for pagination',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const filter: Record<string, any> = {}
if (params.teamId) {
filter.team = { id: { eq: params.teamId } }
}
return {
query: `
query SearchIssues($term: String!, $filter: IssueFilter, $first: Int, $after: String, $includeArchived: Boolean) {
searchIssues(term: $term, filter: $filter, first: $first, after: $after, includeArchived: $includeArchived) {
nodes {
id
title
description
priority
estimate
url
createdAt
updatedAt
archivedAt
state {
id
name
type
}
assignee {
id
name
email
}
team {
id
name
}
project {
id
name
}
labels {
nodes {
id
name
color
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: {
term: params.query,
filter: Object.keys(filter).length > 0 ? filter : undefined,
first: params.first ? Number(params.first) : 50,
after: params.after?.trim() || undefined,
includeArchived: params.includeArchived || false,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to search issues',
output: {},
}
}
const result = data.data.searchIssues
return {
success: true,
output: {
issues: result.nodes.map((issue: any) => ({
id: issue.id,
title: issue.title,
description: issue.description,
priority: issue.priority,
estimate: issue.estimate,
url: issue.url,
createdAt: issue.createdAt,
updatedAt: issue.updatedAt,
archivedAt: issue.archivedAt,
state: issue.state,
assignee: issue.assignee,
teamId: issue.team?.id,
projectId: issue.project?.id,
labels: issue.labels?.nodes || [],
})),
pageInfo: {
hasNextPage: result.pageInfo.hasNextPage,
endCursor: result.pageInfo.endCursor,
},
},
}
},
outputs: {
issues: {
type: 'array',
description: 'Array of matching issues',
items: {
type: 'object',
properties: ISSUE_OUTPUT_PROPERTIES,
},
},
pageInfo: PAGE_INFO_OUTPUT,
},
}
File diff suppressed because it is too large Load Diff
+87
View File
@@ -0,0 +1,87 @@
import type { LinearUnarchiveIssueParams, LinearUnarchiveIssueResponse } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUnarchiveIssueTool: ToolConfig<
LinearUnarchiveIssueParams,
LinearUnarchiveIssueResponse
> = {
id: 'linear_unarchive_issue',
name: 'Linear Unarchive Issue',
description: 'Unarchive (restore) an archived issue in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Linear issue ID to unarchive',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => ({
query: `
mutation UnarchiveIssue($id: String!) {
issueUnarchive(id: $id) {
success
entity {
id
}
}
}
`,
variables: {
id: params.issueId,
},
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to unarchive issue',
output: {},
}
}
const result = data.data.issueUnarchive
return {
success: result.success,
output: {
success: result.success,
issueId: result.entity?.id,
},
}
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the unarchive operation was successful',
},
issueId: {
type: 'string',
description: 'The ID of the unarchived issue',
},
},
}
+123
View File
@@ -0,0 +1,123 @@
import type {
LinearUpdateAttachmentParams,
LinearUpdateAttachmentResponse,
} from '@/tools/linear/types'
import { ATTACHMENT_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateAttachmentTool: ToolConfig<
LinearUpdateAttachmentParams,
LinearUpdateAttachmentResponse
> = {
id: 'linear_update_attachment',
name: 'Linear Update Attachment',
description: 'Update an attachment metadata in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
attachmentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Attachment ID to update',
},
title: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'New attachment title',
},
subtitle: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New attachment subtitle',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {
title: params.title,
}
if (params.subtitle != null && params.subtitle !== '') {
input.subtitle = params.subtitle
}
return {
query: `
mutation UpdateAttachment($id: String!, $input: AttachmentUpdateInput!) {
attachmentUpdate(id: $id, input: $input) {
success
attachment {
id
title
subtitle
url
createdAt
updatedAt
}
}
}
`,
variables: {
id: params.attachmentId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update attachment',
output: {},
}
}
const result = data.data.attachmentUpdate
if (!result.success) {
return {
success: false,
error: 'Attachment update was not successful',
output: {},
}
}
return {
success: true,
output: {
attachment: result.attachment,
},
}
},
outputs: {
attachment: {
type: 'object',
description: 'The updated attachment',
properties: ATTACHMENT_OUTPUT_PROPERTIES,
},
},
}
+113
View File
@@ -0,0 +1,113 @@
import type { LinearUpdateCommentParams, LinearUpdateCommentResponse } from '@/tools/linear/types'
import { COMMENT_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateCommentTool: ToolConfig<
LinearUpdateCommentParams,
LinearUpdateCommentResponse
> = {
id: 'linear_update_comment',
name: 'Linear Update Comment',
description: 'Edit a comment in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
commentId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comment ID to update',
},
body: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New comment text (supports Markdown)',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
if (params.body != null && params.body !== '') input.body = params.body
return {
query: `
mutation UpdateComment($id: String!, $input: CommentUpdateInput!) {
commentUpdate(id: $id, input: $input) {
success
comment {
id
body
createdAt
updatedAt
user {
id
name
email
}
}
}
}
`,
variables: {
id: params.commentId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update comment',
output: {},
}
}
const result = data.data.commentUpdate
if (!result.success) {
return {
success: false,
error: 'Comment update was not successful',
output: {},
}
}
return {
success: true,
output: {
comment: result.comment,
},
}
},
outputs: {
comment: {
type: 'object',
description: 'The updated comment',
properties: COMMENT_OUTPUT_PROPERTIES,
},
},
}
+186
View File
@@ -0,0 +1,186 @@
import type { LinearUpdateCustomerParams, LinearUpdateCustomerResponse } from '@/tools/linear/types'
import { CUSTOMER_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateCustomerTool: ToolConfig<
LinearUpdateCustomerParams,
LinearUpdateCustomerResponse
> = {
id: 'linear_update_customer',
name: 'Linear Update Customer',
description: 'Update a customer in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
customerId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer ID to update',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated customer name',
},
domains: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Updated domains',
},
externalIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Updated external IDs',
},
logoUrl: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated logo URL',
},
ownerId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated owner user ID',
},
revenue: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Updated annual revenue',
},
size: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Updated organization size',
},
statusId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated customer status ID',
},
tierId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated customer tier ID',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
if (params.name != null && params.name !== '') {
input.name = params.name
}
if (params.domains != null && Array.isArray(params.domains) && params.domains.length > 0) {
input.domains = params.domains
}
if (
params.externalIds != null &&
Array.isArray(params.externalIds) &&
params.externalIds.length > 0
) {
input.externalIds = params.externalIds
}
if (params.logoUrl != null && params.logoUrl !== '') {
input.logoUrl = params.logoUrl
}
if (params.ownerId != null && params.ownerId !== '') {
input.ownerId = params.ownerId
}
if (params.revenue != null) {
input.revenue = params.revenue
}
if (params.size != null) {
input.size = params.size
}
if (params.statusId != null && params.statusId !== '') {
input.statusId = params.statusId
}
if (params.tierId != null && params.tierId !== '') {
input.tierId = params.tierId
}
return {
query: `
mutation CustomerUpdate($id: String!, $input: CustomerUpdateInput!) {
customerUpdate(id: $id, input: $input) {
success
customer {
id
name
domains
externalIds
logoUrl
slugId
approximateNeedCount
revenue
size
createdAt
updatedAt
archivedAt
}
}
}
`,
variables: {
id: params.customerId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update customer',
output: {},
}
}
const result = data.data.customerUpdate
return {
success: result.success,
output: {
customer: result.customer,
},
}
},
outputs: {
customer: {
type: 'object',
description: 'The updated customer',
properties: CUSTOMER_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,176 @@
import type {
LinearUpdateCustomerRequestParams,
LinearUpdateCustomerRequestResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateCustomerRequestTool: ToolConfig<
LinearUpdateCustomerRequestParams,
LinearUpdateCustomerRequestResponse
> = {
id: 'linear_update_customer_request',
name: 'Linear Update Customer Request',
description:
'Update a customer request (need) in Linear. Can change urgency, description, customer assignment, and linked issue.',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
customerNeedId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer request ID to update',
},
body: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated description of the customer request',
},
priority: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Updated urgency level: 0 = Not important, 1 = Important',
},
customerId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New customer ID to assign this request to',
},
issueId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New issue ID to link this request to',
},
projectId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New project ID to link this request to',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
// Optional fields with proper validation
if (params.body != null && params.body !== '') {
input.body = params.body
}
if (params.priority != null) {
input.priority = params.priority
}
if (params.customerId != null && params.customerId !== '') {
input.customerId = params.customerId
}
if (params.issueId != null && params.issueId !== '') {
input.issueId = params.issueId
}
if (params.projectId != null && params.projectId !== '') {
input.projectId = params.projectId
}
return {
query: `
mutation CustomerNeedUpdate($id: String!, $input: CustomerNeedUpdateInput!) {
customerNeedUpdate(id: $id, input: $input) {
success
need {
id
body
priority
createdAt
updatedAt
archivedAt
customer {
id
name
}
issue {
id
title
}
project {
id
name
}
creator {
id
name
}
url
}
}
}
`,
variables: {
id: params.customerNeedId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update customer request',
output: {},
}
}
const result = data.data.customerNeedUpdate
return {
success: result.success,
output: {
customerNeed: result.need,
},
}
},
outputs: {
customerNeed: {
type: 'object',
description: 'The updated customer request',
properties: {
id: { type: 'string', description: 'Customer request ID' },
body: { type: 'string', description: 'Request description' },
priority: {
type: 'number',
description: 'Urgency level (0 = Not important, 1 = Important)',
},
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last update timestamp' },
archivedAt: { type: 'string', description: 'Archive timestamp (null if not archived)' },
customer: { type: 'object', description: 'Assigned customer' },
issue: { type: 'object', description: 'Linked issue (null if not linked)' },
project: { type: 'object', description: 'Linked project (null if not linked)' },
creator: { type: 'object', description: 'User who created the request' },
url: { type: 'string', description: 'URL to the customer request' },
},
},
},
}
@@ -0,0 +1,146 @@
import type {
LinearUpdateCustomerStatusParams,
LinearUpdateCustomerStatusResponse,
} from '@/tools/linear/types'
import { CUSTOMER_STATUS_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateCustomerStatusTool: ToolConfig<
LinearUpdateCustomerStatusParams,
LinearUpdateCustomerStatusResponse
> = {
id: 'linear_update_customer_status',
name: 'Linear Update Customer Status',
description: 'Update a customer status in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
statusId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer status ID to update',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated status name',
},
color: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated status color',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated description',
},
displayName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated display name',
},
position: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Updated position',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
if (params.name != null && params.name !== '') {
input.name = params.name
}
if (params.color != null && params.color !== '') {
input.color = params.color
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.displayName != null && params.displayName !== '') {
input.displayName = params.displayName
}
if (params.position != null) {
input.position = params.position
}
return {
query: `
mutation CustomerStatusUpdate($id: String!, $input: CustomerStatusUpdateInput!) {
customerStatusUpdate(id: $id, input: $input) {
success
customerStatus {
id
name
description
color
position
type
createdAt
updatedAt
archivedAt
}
}
}
`,
variables: {
id: params.statusId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update customer status',
output: {},
}
}
const result = data.data.customerStatusUpdate
return {
success: result.success,
output: {
customerStatus: result.customerStatus,
},
}
},
outputs: {
customerStatus: {
type: 'object',
description: 'The updated customer status',
properties: CUSTOMER_STATUS_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,143 @@
import type {
LinearUpdateCustomerTierParams,
LinearUpdateCustomerTierResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateCustomerTierTool: ToolConfig<
LinearUpdateCustomerTierParams,
LinearUpdateCustomerTierResponse
> = {
id: 'linear_update_customer_tier',
name: 'Linear Update Customer Tier',
description: 'Update a customer tier in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
tierId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Customer tier ID to update',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated tier name',
},
color: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated tier color',
},
displayName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated display name',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated description',
},
position: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Updated position',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
if (params.name != null && params.name !== '') {
input.name = params.name
}
if (params.color != null && params.color !== '') {
input.color = params.color
}
if (params.displayName != null && params.displayName !== '') {
input.displayName = params.displayName
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.position != null) {
input.position = params.position
}
return {
query: `
mutation CustomerTierUpdate($id: String!, $input: CustomerTierUpdateInput!) {
customerTierUpdate(id: $id, input: $input) {
success
customerTier {
id
name
displayName
description
color
position
createdAt
archivedAt
}
}
}
`,
variables: {
id: params.tierId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update customer tier',
output: {},
}
}
const result = data.data.customerTierUpdate
return {
success: result.success,
output: {
customerTier: result.customerTier,
},
}
},
outputs: {
customerTier: {
type: 'object',
description: 'The updated customer tier',
},
},
}
+272
View File
@@ -0,0 +1,272 @@
import type { LinearUpdateIssueParams, LinearUpdateIssueResponse } from '@/tools/linear/types'
import { ISSUE_EXTENDED_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateIssueTool: ToolConfig<LinearUpdateIssueParams, LinearUpdateIssueResponse> =
{
id: 'linear_update_issue',
name: 'Linear Update Issue',
description: 'Update an existing issue in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
issueId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Linear issue ID to update',
},
title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New issue title',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New issue description',
},
stateId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Workflow state ID (status)',
},
assigneeId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User ID to assign the issue to',
},
priority: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Priority (0=No priority, 1=Urgent, 2=High, 3=Normal, 4=Low)',
},
estimate: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Estimate in points',
},
labelIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of label IDs to set on the issue (replaces all existing labels)',
},
projectId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Project ID to move the issue to',
},
cycleId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cycle ID to assign the issue to',
},
parentId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Parent issue ID (for making this a sub-issue)',
},
dueDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Due date in ISO 8601 format (date only: YYYY-MM-DD)',
},
addedLabelIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of label IDs to add to the issue (without replacing existing labels)',
},
removedLabelIds: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of label IDs to remove from the issue',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
if (params.title != null && params.title !== '') {
input.title = params.title
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.stateId != null && params.stateId !== '') {
input.stateId = params.stateId
}
if (params.assigneeId != null && params.assigneeId !== '') {
input.assigneeId = params.assigneeId
}
if (params.priority != null) {
input.priority = Number(params.priority)
}
if (params.estimate != null) {
input.estimate = Number(params.estimate)
}
if (params.labelIds != null && Array.isArray(params.labelIds)) {
input.labelIds = params.labelIds
}
if (params.projectId != null && params.projectId !== '') {
input.projectId = params.projectId
}
if (params.cycleId != null && params.cycleId !== '') {
input.cycleId = params.cycleId
}
if (params.parentId != null && params.parentId !== '') {
input.parentId = params.parentId
}
if (params.dueDate != null && params.dueDate !== '') {
input.dueDate = params.dueDate
}
if (params.addedLabelIds != null && Array.isArray(params.addedLabelIds)) {
input.addedLabelIds = params.addedLabelIds
}
if (params.removedLabelIds != null && Array.isArray(params.removedLabelIds)) {
input.removedLabelIds = params.removedLabelIds
}
return {
query: `
mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
issueUpdate(id: $id, input: $input) {
success
issue {
id
title
description
priority
estimate
url
updatedAt
dueDate
state {
id
name
type
}
assignee {
id
name
email
}
team {
id
}
project {
id
}
cycle {
id
number
name
}
parent {
id
title
}
labels {
nodes {
id
name
color
}
}
}
}
}
`,
variables: {
id: params.issueId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update issue',
output: {},
}
}
const result = data.data.issueUpdate
if (!result.success) {
return {
success: false,
error: 'Issue update was not successful',
output: {},
}
}
const issue = result.issue
return {
success: true,
output: {
issue: {
id: issue.id,
title: issue.title,
description: issue.description,
priority: issue.priority,
estimate: issue.estimate,
url: issue.url,
updatedAt: issue.updatedAt,
dueDate: issue.dueDate,
state: issue.state,
assignee: issue.assignee,
teamId: issue.team?.id,
projectId: issue.project?.id,
cycleId: issue.cycle?.id,
cycleNumber: issue.cycle?.number,
cycleName: issue.cycle?.name,
parentId: issue.parent?.id,
parentTitle: issue.parent?.title,
labels: issue.labels?.nodes || [],
},
},
}
},
outputs: {
issue: {
type: 'object',
description: 'The updated issue',
properties: ISSUE_EXTENDED_OUTPUT_PROPERTIES,
},
},
}
+129
View File
@@ -0,0 +1,129 @@
import type { LinearUpdateLabelParams, LinearUpdateLabelResponse } from '@/tools/linear/types'
import { LABEL_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateLabelTool: ToolConfig<LinearUpdateLabelParams, LinearUpdateLabelResponse> =
{
id: 'linear_update_label',
name: 'Linear Update Label',
description: 'Update an existing label in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
labelId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Label ID to update',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New label name',
},
color: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New label color (hex format)',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New label description',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
if (params.name != null && params.name !== '') input.name = params.name
if (params.color != null && params.color !== '') input.color = params.color
if (params.description != null && params.description !== '')
input.description = params.description
return {
query: `
mutation UpdateLabel($id: String!, $input: IssueLabelUpdateInput!) {
issueLabelUpdate(id: $id, input: $input) {
success
issueLabel {
id
name
color
description
isGroup
createdAt
updatedAt
archivedAt
team {
id
name
}
}
}
}
`,
variables: {
id: params.labelId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update label',
output: {},
}
}
const result = data.data.issueLabelUpdate
if (!result.success) {
return {
success: false,
error: 'Label update was not successful',
output: {},
}
}
return {
success: true,
output: {
label: result.issueLabel,
},
}
},
outputs: {
label: {
type: 'object',
description: 'The updated label',
properties: LABEL_FULL_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,125 @@
import type {
LinearUpdateNotificationParams,
LinearUpdateNotificationResponse,
} from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateNotificationTool: ToolConfig<
LinearUpdateNotificationParams,
LinearUpdateNotificationResponse
> = {
id: 'linear_update_notification',
name: 'Linear Update Notification',
description: 'Mark a notification as read or unread in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
notificationId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Notification ID to update',
},
readAt: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Timestamp to mark as read (ISO format). Pass null or omit to mark as unread',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
// If readAt is provided, use it; if explicitly null, mark as unread; if omitted, mark as read now
if (params.readAt !== undefined) {
input.readAt = params.readAt
} else {
input.readAt = new Date().toISOString()
}
return {
query: `
mutation UpdateNotification($id: String!, $input: NotificationUpdateInput!) {
notificationUpdate(id: $id, input: $input) {
success
notification {
id
type
createdAt
readAt
issue {
id
title
}
}
}
}
`,
variables: {
id: params.notificationId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update notification',
output: {},
}
}
const result = data.data.notificationUpdate
if (!result.success) {
return {
success: false,
error: 'Notification update was not successful',
output: {},
}
}
return {
success: true,
output: {
notification: result.notification,
},
}
},
outputs: {
notification: {
type: 'object',
description: 'The updated notification',
properties: {
id: { type: 'string', description: 'Notification ID' },
type: { type: 'string', description: 'Notification type' },
createdAt: { type: 'string', description: 'Creation timestamp' },
readAt: { type: 'string', description: 'Read timestamp' },
issue: { type: 'object', description: 'Related issue' },
},
},
},
}
+190
View File
@@ -0,0 +1,190 @@
import type { LinearUpdateProjectParams, LinearUpdateProjectResponse } from '@/tools/linear/types'
import { PROJECT_FULL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateProjectTool: ToolConfig<
LinearUpdateProjectParams,
LinearUpdateProjectResponse
> = {
id: 'linear_update_project',
name: 'Linear Update Project',
description: 'Update an existing project in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
projectId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project ID to update',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New project name',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New project description',
},
state: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Project state (planned, started, completed, canceled)',
},
leadId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'User ID of the project lead',
},
startDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Project start date (ISO format: YYYY-MM-DD)',
},
targetDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Project target date (ISO format: YYYY-MM-DD)',
},
priority: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Project priority (0=No priority, 1=Urgent, 2=High, 3=Normal, 4=Low)',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
if (params.name != null && params.name !== '') {
input.name = params.name
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.state != null && params.state !== '') {
input.state = params.state
}
if (params.leadId != null && params.leadId !== '') {
input.leadId = params.leadId
}
if (params.startDate != null && params.startDate !== '') {
input.startDate = params.startDate
}
if (params.targetDate != null && params.targetDate !== '') {
input.targetDate = params.targetDate
}
if (params.priority != null) {
input.priority = Number(params.priority)
}
return {
query: `
mutation UpdateProject($id: String!, $input: ProjectUpdateInput!) {
projectUpdate(id: $id, input: $input) {
success
project {
id
name
description
state
priority
startDate
targetDate
url
lead {
id
name
}
teams {
nodes {
id
name
}
}
}
}
}
`,
variables: {
id: params.projectId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update project',
output: {},
}
}
const result = data.data.projectUpdate
if (!result.success) {
return {
success: false,
error: 'Project update was not successful',
output: {},
}
}
const project = result.project
return {
success: true,
output: {
project: {
id: project.id,
name: project.name,
description: project.description,
state: project.state,
priority: project.priority,
startDate: project.startDate,
targetDate: project.targetDate,
url: project.url,
lead: project.lead,
teams: project.teams?.nodes || [],
},
},
}
},
outputs: {
project: {
type: 'object',
description: 'The updated project',
properties: PROJECT_FULL_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,127 @@
import type {
LinearUpdateProjectLabelParams,
LinearUpdateProjectLabelResponse,
} from '@/tools/linear/types'
import { PROJECT_LABEL_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateProjectLabelTool: ToolConfig<
LinearUpdateProjectLabelParams,
LinearUpdateProjectLabelResponse
> = {
id: 'linear_update_project_label',
name: 'Linear Update Project Label',
description: 'Update a project label in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
labelId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project label ID to update',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated label name',
},
color: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated label color',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated description',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
if (params.name != null && params.name !== '') {
input.name = params.name
}
if (params.color != null && params.color !== '') {
input.color = params.color
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
return {
query: `
mutation ProjectLabelUpdate($id: String!, $input: ProjectLabelUpdateInput!) {
projectLabelUpdate(id: $id, input: $input) {
success
projectLabel {
id
name
description
color
isGroup
createdAt
updatedAt
archivedAt
}
}
}
`,
variables: {
id: params.labelId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update project label',
output: {},
}
}
const result = data.data.projectLabelUpdate
return {
success: result.success,
output: {
projectLabel: result.projectLabel,
},
}
},
outputs: {
projectLabel: {
type: 'object',
description: 'The updated project label',
properties: PROJECT_LABEL_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,144 @@
import type {
LinearUpdateProjectMilestoneParams,
LinearUpdateProjectMilestoneResponse,
} from '@/tools/linear/types'
import { PROJECT_MILESTONE_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateProjectMilestoneTool: ToolConfig<
LinearUpdateProjectMilestoneParams,
LinearUpdateProjectMilestoneResponse
> = {
id: 'linear_update_project_milestone',
name: 'Linear Update Project Milestone',
description: 'Update a project milestone in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
milestoneId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project milestone ID to update',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated milestone name',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated description',
},
targetDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated target date (ISO 8601)',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
if (params.name != null && params.name !== '') {
input.name = params.name
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.targetDate != null && params.targetDate !== '') {
input.targetDate = params.targetDate
}
return {
query: `
mutation ProjectMilestoneUpdate($id: String!, $input: ProjectMilestoneUpdateInput!) {
projectMilestoneUpdate(id: $id, input: $input) {
success
projectMilestone {
id
name
description
targetDate
progress
sortOrder
status
createdAt
archivedAt
project {
id
}
}
}
}
`,
variables: {
id: params.milestoneId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update project milestone',
output: {},
}
}
const result = data.data.projectMilestoneUpdate
if (!result.success) {
return {
success: false,
error: 'Project milestone update was not successful',
output: {},
}
}
const milestone = result.projectMilestone
return {
success: true,
output: {
projectMilestone: {
...milestone,
projectId: milestone.project?.id ?? null,
project: undefined,
},
},
}
},
outputs: {
projectMilestone: {
type: 'object',
description: 'The updated project milestone',
properties: PROJECT_MILESTONE_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,147 @@
import type {
LinearUpdateProjectStatusParams,
LinearUpdateProjectStatusResponse,
} from '@/tools/linear/types'
import { PROJECT_STATUS_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateProjectStatusTool: ToolConfig<
LinearUpdateProjectStatusParams,
LinearUpdateProjectStatusResponse
> = {
id: 'linear_update_project_status',
name: 'Linear Update Project Status',
description: 'Update a project status in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
statusId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project status ID to update',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated status name',
},
color: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated status color',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Updated description',
},
indefinite: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Updated indefinite flag',
},
position: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Updated position',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
if (params.name != null && params.name !== '') {
input.name = params.name
}
if (params.color != null && params.color !== '') {
input.color = params.color
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.indefinite != null) {
input.indefinite = params.indefinite
}
if (params.position != null) {
input.position = params.position
}
return {
query: `
mutation ProjectStatusUpdate($id: String!, $input: ProjectStatusUpdateInput!) {
projectStatusUpdate(id: $id, input: $input) {
success
status {
id
name
description
color
indefinite
position
type
createdAt
updatedAt
archivedAt
}
}
}
`,
variables: {
id: params.statusId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update project status',
output: {},
}
}
const result = data.data.projectStatusUpdate
return {
success: result.success,
output: {
projectStatus: result.status,
},
}
},
outputs: {
projectStatus: {
type: 'object',
description: 'The updated project status',
properties: PROJECT_STATUS_OUTPUT_PROPERTIES,
},
},
}
@@ -0,0 +1,149 @@
import type {
LinearUpdateWorkflowStateParams,
LinearUpdateWorkflowStateResponse,
} from '@/tools/linear/types'
import { WORKFLOW_STATE_OUTPUT_PROPERTIES } from '@/tools/linear/types'
import type { ToolConfig } from '@/tools/types'
export const linearUpdateWorkflowStateTool: ToolConfig<
LinearUpdateWorkflowStateParams,
LinearUpdateWorkflowStateResponse
> = {
id: 'linear_update_workflow_state',
name: 'Linear Update Workflow State',
description: 'Update an existing workflow state in Linear',
version: '1.0.0',
oauth: {
required: true,
provider: 'linear',
},
params: {
stateId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow state ID to update',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New state name',
},
color: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New state color (hex format)',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New state description',
},
position: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'New position in workflow',
},
},
request: {
url: 'https://api.linear.app/graphql',
method: 'POST',
headers: (params) => {
if (!params.accessToken) {
throw new Error('Missing access token for Linear API request')
}
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${params.accessToken}`,
}
},
body: (params) => {
const input: Record<string, any> = {}
if (params.name != null && params.name !== '') {
input.name = params.name
}
if (params.color != null && params.color !== '') {
input.color = params.color
}
if (params.description != null && params.description !== '') {
input.description = params.description
}
if (params.position != null) {
input.position = Number(params.position)
}
return {
query: `
mutation UpdateWorkflowState($id: String!, $input: WorkflowStateUpdateInput!) {
workflowStateUpdate(id: $id, input: $input) {
success
workflowState {
id
name
description
type
color
position
createdAt
updatedAt
archivedAt
team {
id
name
}
}
}
}
`,
variables: {
id: params.stateId,
input,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
error: data.errors[0]?.message || 'Failed to update workflow state',
output: {},
}
}
const result = data.data.workflowStateUpdate
if (!result.success) {
return {
success: false,
error: 'Workflow state update was not successful',
output: {},
}
}
return {
success: true,
output: {
state: result.workflowState,
},
}
},
outputs: {
state: {
type: 'object',
description: 'The updated workflow state',
properties: WORKFLOW_STATE_OUTPUT_PROPERTIES,
},
},
}