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
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:
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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)' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user