feat(tools): added 48 new github tools, 12 triggers (#1821)

* feat(tools): added 10 new github triggers

* feat(tools): added 48 new github tools, 12 triggers

* fix(logging): make logging safe start an upsert to prevent insertions of duplicate execution id records, remove layout from github block
This commit is contained in:
Waleed
2025-11-06 11:11:45 -08:00
committed by GitHub
parent 541bdd3772
commit 8c9ed34d99
73 changed files with 16526 additions and 31 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -133,16 +133,6 @@ async function executeWebhookJobInternal(
const loggingSession = new LoggingSession(payload.workflowId, executionId, 'webhook', requestId)
try {
await loggingSession.safeStart({
userId: payload.userId,
workspaceId: '', // Will be resolved below
variables: {},
triggerData: {
isTest: payload.testMode === true,
executionTarget: payload.executionTarget || 'deployed',
},
})
const workflowData =
payload.executionTarget === 'live'
? await loadWorkflowFromNormalizedTables(payload.workflowId)
@@ -478,6 +468,17 @@ async function executeWebhookJobInternal(
})
try {
// Ensure logging session is started (safe to call multiple times)
await loggingSession.safeStart({
userId: payload.userId,
workspaceId: '', // May not be available for early errors
variables: {},
triggerData: {
isTest: payload.testMode === true,
executionTarget: payload.executionTarget || 'deployed',
},
})
const executionResult = (error?.executionResult as ExecutionResult | undefined) || {
success: false,
output: {},

File diff suppressed because it is too large Load Diff

View File

@@ -56,6 +56,39 @@ export class ExecutionLogger implements IExecutionLoggerService {
logger.debug(`Starting workflow execution ${executionId} for workflow ${workflowId}`)
// Check if execution log already exists (idempotency check)
const existingLog = await db
.select()
.from(workflowExecutionLogs)
.where(eq(workflowExecutionLogs.executionId, executionId))
.limit(1)
if (existingLog.length > 0) {
logger.debug(
`Execution log already exists for ${executionId}, skipping duplicate INSERT (idempotent)`
)
const snapshot = await snapshotService.getSnapshot(existingLog[0].stateSnapshotId)
if (!snapshot) {
throw new Error(`Snapshot ${existingLog[0].stateSnapshotId} not found for existing log`)
}
return {
workflowLog: {
id: existingLog[0].id,
workflowId: existingLog[0].workflowId,
executionId: existingLog[0].executionId,
stateSnapshotId: existingLog[0].stateSnapshotId,
level: existingLog[0].level as 'info' | 'error',
trigger: existingLog[0].trigger as ExecutionTrigger['type'],
startedAt: existingLog[0].startedAt.toISOString(),
endedAt: existingLog[0].endedAt?.toISOString() || existingLog[0].startedAt.toISOString(),
totalDurationMs: existingLog[0].totalDurationMs || 0,
executionData: existingLog[0].executionData as WorkflowExecutionLog['executionData'],
createdAt: existingLog[0].createdAt.toISOString(),
},
snapshot,
}
}
const snapshotResult = await snapshotService.createSnapshotWithDeduplication(
workflowId,
workflowState

View File

@@ -590,6 +590,37 @@ export async function queueWebhookExecution(
return NextResponse.json({ error: 'Workspace billing account required' }, { status: 402 })
}
// GitHub event filtering for event-specific triggers
if (foundWebhook.provider === 'github') {
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
const triggerId = providerConfig.triggerId as string | undefined
if (triggerId && triggerId !== 'github_webhook') {
const eventType = request.headers.get('x-github-event')
const action = body.action
const { isGitHubEventMatch } = await import('@/triggers/github/utils')
if (!isGitHubEventMatch(triggerId, eventType || '', action, body)) {
logger.debug(
`[${options.requestId}] GitHub event mismatch for trigger ${triggerId}. Event: ${eventType}, Action: ${action}. Skipping execution.`,
{
webhookId: foundWebhook.id,
workflowId: foundWorkflow.id,
triggerId,
receivedEvent: eventType,
receivedAction: action,
}
)
// Return 200 OK to prevent GitHub from retrying
return NextResponse.json({
message: 'Event type does not match trigger configuration. Ignoring.',
})
}
}
}
const headers = Object.fromEntries(request.headers.entries())
// For Microsoft Teams Graph notifications, extract unique identifiers for idempotency

View File

@@ -0,0 +1,108 @@
import type { AddAssigneesParams, IssueResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const addAssigneesTool: ToolConfig<AddAssigneesParams, IssueResponse> = {
id: 'github_add_assignees',
name: 'GitHub Add Assignees',
description: 'Add assignees to an issue in a GitHub repository',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
issue_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue number',
},
assignees: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated list of usernames to assign to the issue',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/assignees`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const assigneesArray = params.assignees
.split(',')
.map((a) => a.trim())
.filter((a) => a)
return {
assignees: assigneesArray,
}
},
},
transformResponse: async (response) => {
const issue = await response.json()
const labels = issue.labels?.map((label: any) => label.name) || []
const assignees = issue.assignees?.map((assignee: any) => assignee.login) || []
const content = `Assignees added to issue #${issue.number}: "${issue.title}"
All assignees: ${assignees.join(', ')}
URL: ${issue.html_url}`
return {
success: true,
output: {
content,
metadata: {
number: issue.number,
title: issue.title,
state: issue.state,
html_url: issue.html_url,
labels,
assignees,
created_at: issue.created_at,
updated_at: issue.updated_at,
body: issue.body,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable assignees confirmation' },
metadata: {
type: 'object',
description: 'Updated issue metadata with assignees',
properties: {
number: { type: 'number', description: 'Issue number' },
title: { type: 'string', description: 'Issue title' },
state: { type: 'string', description: 'Issue state (open/closed)' },
html_url: { type: 'string', description: 'GitHub web URL' },
labels: { type: 'array', description: 'Array of label names' },
assignees: { type: 'array', description: 'All assignees on the issue' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
body: { type: 'string', description: 'Issue body/description' },
},
},
},
}

View File

@@ -0,0 +1,96 @@
import type { AddLabelsParams, LabelsResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const addLabelsTool: ToolConfig<AddLabelsParams, LabelsResponse> = {
id: 'github_add_labels',
name: 'GitHub Add Labels',
description: 'Add labels to an issue in a GitHub repository',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
issue_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue number',
},
labels: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated list of label names to add to the issue',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/labels`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const labelsArray = params.labels
.split(',')
.map((l) => l.trim())
.filter((l) => l)
return {
labels: labelsArray,
}
},
},
transformResponse: async (response) => {
const labelsData = await response.json()
const labels = labelsData.map((label: any) => label.name)
const content = `Labels added to issue successfully!
All labels on issue: ${labels.join(', ')}`
return {
success: true,
output: {
content,
metadata: {
labels,
issue_number: 0, // Will be filled from params in actual implementation
html_url: '', // Will be constructed from params
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable labels confirmation' },
metadata: {
type: 'object',
description: 'Labels metadata',
properties: {
labels: { type: 'array', description: 'All labels currently on the issue' },
issue_number: { type: 'number', description: 'Issue number' },
html_url: { type: 'string', description: 'GitHub issue URL' },
},
},
},
}

View File

@@ -0,0 +1,124 @@
import type { CancelWorkflowRunParams, CancelWorkflowRunResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const cancelWorkflowRunTool: ToolConfig<CancelWorkflowRunParams, CancelWorkflowRunResponse> =
{
id: 'github_cancel_workflow_run',
name: 'GitHub Cancel Workflow Run',
description:
'Cancel a workflow run. Returns 202 Accepted if cancellation is initiated, or 409 Conflict if the run cannot be cancelled (already completed).',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
run_id: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Workflow run ID to cancel',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/runs/${params.run_id}/cancel`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response, params) => {
if (!params) {
return {
success: false,
error: 'Missing parameters',
output: {
content: '',
metadata: {
run_id: 0,
status: 'error',
},
},
}
}
if (response.status === 202) {
const content = `Workflow run #${params.run_id} cancellation initiated successfully.
The run will be cancelled shortly.`
return {
success: true,
output: {
content,
metadata: {
run_id: params.run_id,
status: 'cancellation_initiated',
},
},
}
}
if (response.status === 409) {
const content = `Cannot cancel workflow run #${params.run_id}.
The run may have already completed or been cancelled.`
return {
success: false,
output: {
content,
metadata: {
run_id: params.run_id,
status: 'cannot_cancel',
},
},
}
}
const content = `Workflow run #${params.run_id} cancellation request processed.`
return {
success: true,
output: {
content,
metadata: {
run_id: params.run_id,
status: 'processed',
},
},
}
},
outputs: {
content: { type: 'string', description: 'Cancellation status message' },
metadata: {
type: 'object',
description: 'Cancellation metadata',
properties: {
run_id: { type: 'number', description: 'Workflow run ID' },
status: {
type: 'string',
description: 'Cancellation status (cancellation_initiated, cannot_cancel, processed)',
},
},
},
},
}

View File

@@ -0,0 +1,115 @@
import type { CloseIssueParams, IssueResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const closeIssueTool: ToolConfig<CloseIssueParams, IssueResponse> = {
id: 'github_close_issue',
name: 'GitHub Close Issue',
description: 'Close an issue in a GitHub repository',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
issue_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue number',
},
state_reason: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Reason for closing: completed or not_planned',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}`,
method: 'PATCH',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const body: any = {
state: 'closed',
}
if (params.state_reason) {
body.state_reason = params.state_reason
}
return body
},
},
transformResponse: async (response) => {
const issue = await response.json()
const labels = issue.labels?.map((label: any) => label.name) || []
const assignees = issue.assignees?.map((assignee: any) => assignee.login) || []
const content = `Issue #${issue.number} closed: "${issue.title}"
State: ${issue.state}
${issue.state_reason ? `Reason: ${issue.state_reason}` : ''}
Closed at: ${issue.closed_at}
URL: ${issue.html_url}`
return {
success: true,
output: {
content,
metadata: {
number: issue.number,
title: issue.title,
state: issue.state,
html_url: issue.html_url,
labels,
assignees,
created_at: issue.created_at,
updated_at: issue.updated_at,
closed_at: issue.closed_at,
body: issue.body,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable issue close confirmation' },
metadata: {
type: 'object',
description: 'Closed issue metadata',
properties: {
number: { type: 'number', description: 'Issue number' },
title: { type: 'string', description: 'Issue title' },
state: { type: 'string', description: 'Issue state (closed)' },
html_url: { type: 'string', description: 'GitHub web URL' },
labels: { type: 'array', description: 'Array of label names' },
assignees: { type: 'array', description: 'Array of assignee usernames' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
closed_at: { type: 'string', description: 'Closed timestamp' },
body: { type: 'string', description: 'Issue body/description' },
},
},
},
}

View File

@@ -0,0 +1,92 @@
import type { ClosePRParams, PRResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const closePRTool: ToolConfig<ClosePRParams, PRResponse> = {
id: 'github_close_pr',
name: 'GitHub Close Pull Request',
description: 'Close a pull request in a GitHub repository',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
pullNumber: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Pull request number',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}`,
method: 'PATCH',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: () => ({
state: 'closed',
}),
},
transformResponse: async (response) => {
const pr = await response.json()
const content = `PR #${pr.number} closed: "${pr.title}"
URL: ${pr.html_url}`
return {
success: true,
output: {
content,
metadata: {
number: pr.number,
title: pr.title,
state: pr.state,
html_url: pr.html_url,
merged: pr.merged,
draft: pr.draft,
created_at: pr.created_at,
updated_at: pr.updated_at,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable PR close confirmation' },
metadata: {
type: 'object',
description: 'Closed pull request metadata',
properties: {
number: { type: 'number', description: 'Pull request number' },
title: { type: 'string', description: 'PR title' },
state: { type: 'string', description: 'PR state (should be closed)' },
html_url: { type: 'string', description: 'GitHub web URL' },
merged: { type: 'boolean', description: 'Whether PR is merged' },
draft: { type: 'boolean', description: 'Whether PR is draft' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
},
},
},
}

View File

@@ -0,0 +1,93 @@
import type { CreateBranchParams, RefResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const createBranchTool: ToolConfig<CreateBranchParams, RefResponse> = {
id: 'github_create_branch',
name: 'GitHub Create Branch',
description:
'Create a new branch in a GitHub repository by creating a git reference pointing to a specific commit SHA.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
branch: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the branch to create',
},
sha: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Commit SHA to point the branch to',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/git/refs`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
'Content-Type': 'application/json',
}),
body: (params) => ({
ref: `refs/heads/${params.branch}`,
sha: params.sha,
}),
},
transformResponse: async (response) => {
const ref = await response.json()
// Create a human-readable content string
const content = `Branch created successfully:
Branch: ${ref.ref.replace('refs/heads/', '')}
SHA: ${ref.object.sha}
URL: ${ref.url}`
return {
success: true,
output: {
content,
metadata: {
ref: ref.ref,
url: ref.url,
sha: ref.object.sha,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable branch creation confirmation' },
metadata: {
type: 'object',
description: 'Git reference metadata',
properties: {
ref: { type: 'string', description: 'Full reference name (refs/heads/branch)' },
url: { type: 'string', description: 'API URL for the reference' },
sha: { type: 'string', description: 'Commit SHA the branch points to' },
},
},
},
}

View File

@@ -0,0 +1,172 @@
import type { CreateFileParams, FileOperationResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const createFileTool: ToolConfig<CreateFileParams, FileOperationResponse> = {
id: 'github_create_file',
name: 'GitHub Create File',
description:
'Create a new file in a GitHub repository. The file content will be automatically Base64 encoded. Supports files up to 1MB.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
path: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path where the file will be created (e.g., "src/newfile.ts")',
},
message: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Commit message for this file creation',
},
content: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'File content (plain text, will be Base64 encoded automatically)',
},
branch: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Branch to create the file in (defaults to repository default branch)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/contents/${params.path}`,
method: 'PUT',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const base64Content = Buffer.from(params.content).toString('base64')
const body: Record<string, any> = {
message: params.message,
content: base64Content,
}
if (params.branch) {
body.branch = params.branch
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
const content = `File created successfully!
Path: ${data.content.path}
Name: ${data.content.name}
Size: ${data.content.size} bytes
SHA: ${data.content.sha}
Commit:
- SHA: ${data.commit.sha}
- Message: ${data.commit.message}
- Author: ${data.commit.author.name}
- Date: ${data.commit.author.date}
View file: ${data.content.html_url}`
return {
success: true,
output: {
content,
metadata: {
file: {
name: data.content.name,
path: data.content.path,
sha: data.content.sha,
size: data.content.size,
type: data.content.type,
download_url: data.content.download_url,
html_url: data.content.html_url,
},
commit: {
sha: data.commit.sha,
message: data.commit.message,
author: {
name: data.commit.author.name,
email: data.commit.author.email,
date: data.commit.author.date,
},
committer: {
name: data.commit.committer.name,
email: data.commit.committer.email,
date: data.commit.committer.date,
},
html_url: data.commit.html_url,
},
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable file creation confirmation' },
metadata: {
type: 'object',
description: 'File and commit metadata',
properties: {
file: {
type: 'object',
description: 'Created file information',
properties: {
name: { type: 'string', description: 'File name' },
path: { type: 'string', description: 'Full path in repository' },
sha: { type: 'string', description: 'Git blob SHA' },
size: { type: 'number', description: 'File size in bytes' },
type: { type: 'string', description: 'Content type' },
download_url: { type: 'string', description: 'Direct download URL' },
html_url: { type: 'string', description: 'GitHub web UI URL' },
},
},
commit: {
type: 'object',
description: 'Commit information',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
message: { type: 'string', description: 'Commit message' },
author: {
type: 'object',
description: 'Author information',
},
committer: {
type: 'object',
description: 'Committer information',
},
html_url: { type: 'string', description: 'Commit URL' },
},
},
},
},
},
}

View File

@@ -0,0 +1,143 @@
import type { CreateIssueParams, IssueResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const createIssueTool: ToolConfig<CreateIssueParams, IssueResponse> = {
id: 'github_create_issue',
name: 'GitHub Create Issue',
description: 'Create a new issue in a GitHub repository',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
title: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Issue title',
},
body: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Issue description/body',
},
assignees: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of usernames to assign to this issue',
},
labels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of label names to add to this issue',
},
milestone: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Milestone number to associate with this issue',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/issues`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const body: any = {
title: params.title,
}
if (params.body) body.body = params.body
if (params.assignees) {
const assigneesArray = params.assignees
.split(',')
.map((a) => a.trim())
.filter((a) => a)
if (assigneesArray.length > 0) body.assignees = assigneesArray
}
if (params.labels) {
const labelsArray = params.labels
.split(',')
.map((l) => l.trim())
.filter((l) => l)
if (labelsArray.length > 0) body.labels = labelsArray
}
if (params.milestone) body.milestone = params.milestone
return body
},
},
transformResponse: async (response) => {
const issue = await response.json()
const labels = issue.labels?.map((label: any) => label.name) || []
const assignees = issue.assignees?.map((assignee: any) => assignee.login) || []
const content = `Issue #${issue.number} created: "${issue.title}"
State: ${issue.state}
URL: ${issue.html_url}
${labels.length > 0 ? `Labels: ${labels.join(', ')}` : ''}
${assignees.length > 0 ? `Assignees: ${assignees.join(', ')}` : ''}`
return {
success: true,
output: {
content,
metadata: {
number: issue.number,
title: issue.title,
state: issue.state,
html_url: issue.html_url,
labels,
assignees,
created_at: issue.created_at,
updated_at: issue.updated_at,
body: issue.body,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable issue creation confirmation' },
metadata: {
type: 'object',
description: 'Issue metadata',
properties: {
number: { type: 'number', description: 'Issue number' },
title: { type: 'string', description: 'Issue title' },
state: { type: 'string', description: 'Issue state (open/closed)' },
html_url: { type: 'string', description: 'GitHub web URL' },
labels: { type: 'array', description: 'Array of label names' },
assignees: { type: 'array', description: 'Array of assignee usernames' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
body: { type: 'string', description: 'Issue body/description' },
},
},
},
}

View File

@@ -0,0 +1,120 @@
import type { CreatePRParams, PRResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const createPRTool: ToolConfig<CreatePRParams, PRResponse> = {
id: 'github_create_pr',
name: 'GitHub Create Pull Request',
description: 'Create a new pull request in a GitHub repository',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
title: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Pull request title',
},
head: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The name of the branch where your changes are implemented',
},
base: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The name of the branch you want the changes pulled into',
},
body: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Pull request description (Markdown)',
},
draft: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Create as draft pull request',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/pulls`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => ({
title: params.title,
head: params.head,
base: params.base,
body: params.body,
draft: params.draft,
}),
},
transformResponse: async (response) => {
const pr = await response.json()
const content = `PR #${pr.number} created: "${pr.title}" (${pr.state}${pr.draft ? ', draft' : ''})
From: ${pr.head.ref} → To: ${pr.base.ref}
URL: ${pr.html_url}`
return {
success: true,
output: {
content,
metadata: {
number: pr.number,
title: pr.title,
state: pr.state,
html_url: pr.html_url,
merged: pr.merged,
draft: pr.draft,
created_at: pr.created_at,
updated_at: pr.updated_at,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable PR creation confirmation' },
metadata: {
type: 'object',
description: 'Pull request metadata',
properties: {
number: { type: 'number', description: 'Pull request number' },
title: { type: 'string', description: 'PR title' },
state: { type: 'string', description: 'PR state (open/closed)' },
html_url: { type: 'string', description: 'GitHub web URL' },
merged: { type: 'boolean', description: 'Whether PR is merged' },
draft: { type: 'boolean', description: 'Whether PR is draft' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
},
},
},
}

View File

@@ -0,0 +1,148 @@
import type { CreateProjectParams, ProjectResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const createProjectTool: ToolConfig<CreateProjectParams, ProjectResponse> = {
id: 'github_create_project',
name: 'GitHub Create Project',
description:
'Create a new GitHub Project V2. Requires the owner Node ID (not login name). Returns the created project with ID, title, and URL.',
version: '1.0.0',
params: {
owner_id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Owner Node ID (format: PVT_... or MDQ6...). Use GitHub GraphQL API to get this ID from organization or user login.',
},
title: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project title',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token with project write permissions',
},
},
request: {
url: 'https://api.github.com/graphql',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const query = `
mutation($ownerId: ID!, $title: String!) {
createProjectV2(input: {
ownerId: $ownerId
title: $title
}) {
projectV2 {
id
title
number
url
closed
public
shortDescription
}
}
}
`
return {
query,
variables: {
ownerId: params.owner_id,
title: params.title,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
output: {
content: `GraphQL Error: ${data.errors[0].message}`,
metadata: {
id: '',
title: '',
url: '',
},
},
error: data.errors[0].message,
}
}
const project = data.data?.createProjectV2?.projectV2
if (!project) {
return {
success: false,
output: {
content: 'Failed to create project',
metadata: {
id: '',
title: '',
url: '',
},
},
error: 'Failed to create project',
}
}
let content = `Project created successfully!\n`
content += `Title: ${project.title}\n`
content += `ID: ${project.id}\n`
content += `Number: ${project.number}\n`
content += `URL: ${project.url}\n`
content += `Status: ${project.closed ? 'Closed' : 'Open'}\n`
content += `Visibility: ${project.public ? 'Public' : 'Private'}`
return {
success: true,
output: {
content,
metadata: {
id: project.id,
title: project.title,
number: project.number,
url: project.url,
closed: project.closed,
public: project.public,
shortDescription: project.shortDescription || '',
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable confirmation message' },
metadata: {
type: 'object',
description: 'Created project metadata',
properties: {
id: { type: 'string', description: 'Project node ID' },
title: { type: 'string', description: 'Project title' },
number: { type: 'number', description: 'Project number', optional: true },
url: { type: 'string', description: 'Project URL' },
closed: { type: 'boolean', description: 'Whether project is closed', optional: true },
public: { type: 'boolean', description: 'Whether project is public', optional: true },
shortDescription: {
type: 'string',
description: 'Project short description',
optional: true,
},
},
},
},
}

View File

@@ -0,0 +1,157 @@
import type { CreateReleaseParams, ReleaseResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const createReleaseTool: ToolConfig<CreateReleaseParams, ReleaseResponse> = {
id: 'github_create_release',
name: 'GitHub Create Release',
description:
'Create a new release for a GitHub repository. Specify tag name, target commit, title, description, and whether it should be a draft or prerelease.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
tag_name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The name of the tag for this release',
},
target_commitish: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Specifies the commitish value that determines where the Git tag is created from. Can be any branch or commit SHA. Defaults to the repository default branch.',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The name of the release',
},
body: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Text describing the contents of the release (markdown supported)',
},
draft: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'true to create a draft (unpublished) release, false to create a published one',
default: false,
},
prerelease: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description:
'true to identify the release as a prerelease, false to identify as a full release',
default: false,
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/releases`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const body: any = {
tag_name: params.tag_name,
}
if (params.target_commitish) {
body.target_commitish = params.target_commitish
}
if (params.name) {
body.name = params.name
}
if (params.body) {
body.body = params.body
}
if (params.draft !== undefined) {
body.draft = params.draft
}
if (params.prerelease !== undefined) {
body.prerelease = params.prerelease
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
const releaseType = data.draft ? 'Draft' : data.prerelease ? 'Prerelease' : 'Release'
const content = `${releaseType} created: "${data.name || data.tag_name}"
Tag: ${data.tag_name}
URL: ${data.html_url}
Created: ${data.created_at}
${data.published_at ? `Published: ${data.published_at}` : 'Not yet published'}
Download URLs:
- Tarball: ${data.tarball_url}
- Zipball: ${data.zipball_url}`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
tag_name: data.tag_name,
name: data.name || data.tag_name,
html_url: data.html_url,
tarball_url: data.tarball_url,
zipball_url: data.zipball_url,
draft: data.draft,
prerelease: data.prerelease,
created_at: data.created_at,
published_at: data.published_at,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable release creation summary' },
metadata: {
type: 'object',
description: 'Release metadata including download URLs',
properties: {
id: { type: 'number', description: 'Release ID' },
tag_name: { type: 'string', description: 'Git tag name' },
name: { type: 'string', description: 'Release name' },
html_url: { type: 'string', description: 'GitHub web URL for the release' },
tarball_url: { type: 'string', description: 'URL to download release as tarball' },
zipball_url: { type: 'string', description: 'URL to download release as zipball' },
draft: { type: 'boolean', description: 'Whether this is a draft release' },
prerelease: { type: 'boolean', description: 'Whether this is a prerelease' },
created_at: { type: 'string', description: 'Creation timestamp' },
published_at: { type: 'string', description: 'Publication timestamp' },
},
},
},
}

View File

@@ -0,0 +1,89 @@
import type { DeleteBranchParams, DeleteBranchResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const deleteBranchTool: ToolConfig<DeleteBranchParams, DeleteBranchResponse> = {
id: 'github_delete_branch',
name: 'GitHub Delete Branch',
description:
'Delete a branch from a GitHub repository by removing its git reference. Protected branches cannot be deleted.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
branch: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the branch to delete',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/git/refs/heads/${params.branch}`,
method: 'DELETE',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response, params) => {
if (!params) {
return {
success: false,
error: 'Missing parameters',
output: {
content: '',
metadata: {
deleted: false,
branch: '',
},
},
}
}
const content = `Branch "${params.branch}" has been successfully deleted from ${params.owner}/${params.repo}`
return {
success: true,
output: {
content,
metadata: {
deleted: true,
branch: params.branch,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable deletion confirmation' },
metadata: {
type: 'object',
description: 'Deletion metadata',
properties: {
deleted: { type: 'boolean', description: 'Whether the branch was deleted' },
branch: { type: 'string', description: 'Name of the deleted branch' },
},
},
},
}

View File

@@ -0,0 +1,74 @@
import type { DeleteCommentParams, DeleteCommentResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const deleteCommentTool: ToolConfig<DeleteCommentParams, DeleteCommentResponse> = {
id: 'github_delete_comment',
name: 'GitHub Comment Deleter',
description: 'Delete a comment on a GitHub issue or pull request',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
comment_id: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Comment ID',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/comments/${params.comment_id}`,
method: 'DELETE',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const content = `Comment #${response.url.split('/').pop()} successfully deleted`
return {
success: true,
output: {
content,
metadata: {
deleted: true,
comment_id: Number(response.url.split('/').pop()),
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable deletion confirmation' },
metadata: {
type: 'object',
description: 'Deletion result metadata',
properties: {
deleted: { type: 'boolean', description: 'Whether deletion was successful' },
comment_id: { type: 'number', description: 'Deleted comment ID' },
},
},
},
}

View File

@@ -0,0 +1,149 @@
import type { DeleteFileParams, DeleteFileResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const deleteFileTool: ToolConfig<DeleteFileParams, DeleteFileResponse> = {
id: 'github_delete_file',
name: 'GitHub Delete File',
description:
'Delete a file from a GitHub repository. Requires the file SHA. This operation cannot be undone through the API.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
path: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path to the file to delete (e.g., "src/oldfile.ts")',
},
message: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Commit message for this file deletion',
},
sha: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The blob SHA of the file being deleted (get from github_get_file_content)',
},
branch: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Branch to delete the file from (defaults to repository default branch)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/contents/${params.path}`,
method: 'DELETE',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const body: Record<string, any> = {
message: params.message,
sha: params.sha, // Required for delete
}
if (params.branch) {
body.branch = params.branch
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
const content = `File deleted successfully!
Path: ${data.commit.sha ? 'File removed from repository' : 'Unknown'}
Deleted: Yes
Commit:
- SHA: ${data.commit.sha}
- Message: ${data.commit.message || 'N/A'}
- Author: ${data.commit.author?.name || 'N/A'}
- Date: ${data.commit.author?.date || 'N/A'}
View commit: ${data.commit.html_url || 'N/A'}`
return {
success: true,
output: {
content,
metadata: {
deleted: true,
path: data.commit.tree?.sha || 'N/A',
commit: {
sha: data.commit.sha,
message: data.commit.message || '',
author: {
name: data.commit.author?.name || '',
email: data.commit.author?.email || '',
date: data.commit.author?.date || '',
},
committer: {
name: data.commit.committer?.name || '',
email: data.commit.committer?.email || '',
date: data.commit.committer?.date || '',
},
html_url: data.commit.html_url || '',
},
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable file deletion confirmation' },
metadata: {
type: 'object',
description: 'Deletion confirmation and commit metadata',
properties: {
deleted: { type: 'boolean', description: 'Whether the file was deleted' },
path: { type: 'string', description: 'File path that was deleted' },
commit: {
type: 'object',
description: 'Commit information',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
message: { type: 'string', description: 'Commit message' },
author: {
type: 'object',
description: 'Author information',
},
committer: {
type: 'object',
description: 'Committer information',
},
html_url: { type: 'string', description: 'Commit URL' },
},
},
},
},
},
}

View File

@@ -0,0 +1,128 @@
import type { DeleteProjectParams, ProjectResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const deleteProjectTool: ToolConfig<DeleteProjectParams, ProjectResponse> = {
id: 'github_delete_project',
name: 'GitHub Delete Project',
description:
'Delete a GitHub Project V2. This action is permanent and cannot be undone. Requires the project Node ID.',
version: '1.0.0',
params: {
project_id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project Node ID (format: PVT_...)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token with project admin permissions',
},
},
request: {
url: 'https://api.github.com/graphql',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const query = `
mutation($projectId: ID!) {
deleteProjectV2(input: {
projectId: $projectId
}) {
projectV2 {
id
title
number
url
}
}
}
`
return {
query,
variables: {
projectId: params.project_id,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
output: {
content: `GraphQL Error: ${data.errors[0].message}`,
metadata: {
id: '',
title: '',
url: '',
},
},
error: data.errors[0].message,
}
}
// Extract project data
const project = data.data?.deleteProjectV2?.projectV2
if (!project) {
return {
success: false,
output: {
content: 'Failed to delete project',
metadata: {
id: '',
title: '',
url: '',
},
},
error: 'Failed to delete project',
}
}
// Create human-readable content
let content = `Project deleted successfully!\n`
content += `Title: ${project.title}\n`
content += `ID: ${project.id}\n`
if (project.number) {
content += `Number: ${project.number}\n`
}
content += `URL: ${project.url}`
return {
success: true,
output: {
content,
metadata: {
id: project.id,
title: project.title,
number: project.number,
url: project.url,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable confirmation message' },
metadata: {
type: 'object',
description: 'Deleted project metadata',
properties: {
id: { type: 'string', description: 'Project node ID' },
title: { type: 'string', description: 'Project title' },
number: { type: 'number', description: 'Project number', optional: true },
url: { type: 'string', description: 'Project URL' },
},
},
},
}

View File

@@ -0,0 +1,98 @@
import type { DeleteReleaseParams, DeleteReleaseResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const deleteReleaseTool: ToolConfig<DeleteReleaseParams, DeleteReleaseResponse> = {
id: 'github_delete_release',
name: 'GitHub Delete Release',
description:
'Delete a GitHub release by ID. This permanently removes the release but does not delete the associated Git tag.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
release_id: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'The unique identifier of the release to delete',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/releases/${params.release_id}`,
method: 'DELETE',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response, params) => {
if (!params) {
return {
success: false,
error: 'Missing parameters',
output: {
content: '',
metadata: {
deleted: false,
release_id: 0,
},
},
}
}
if (response.status === 204) {
const content = `Release deleted successfully
Release ID: ${params.release_id}
Repository: ${params.owner}/${params.repo}
Note: The associated Git tag has not been deleted and remains in the repository.`
return {
success: true,
output: {
content,
metadata: {
deleted: true,
release_id: params.release_id,
},
},
}
}
const data = await response.text()
throw new Error(`Unexpected response: ${response.status} - ${data}`)
},
outputs: {
content: { type: 'string', description: 'Human-readable deletion confirmation' },
metadata: {
type: 'object',
description: 'Deletion result metadata',
properties: {
deleted: { type: 'boolean', description: 'Whether the release was successfully deleted' },
release_id: { type: 'number', description: 'ID of the deleted release' },
},
},
},
}

View File

@@ -0,0 +1,92 @@
import type { BranchResponse, GetBranchParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const getBranchTool: ToolConfig<GetBranchParams, BranchResponse> = {
id: 'github_get_branch',
name: 'GitHub Get Branch',
description:
'Get detailed information about a specific branch in a GitHub repository, including commit details and protection status.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
branch: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Branch name',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/branches/${params.branch}`,
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const branch = await response.json()
const content = `Branch: ${branch.name}
Commit SHA: ${branch.commit.sha}
Commit URL: ${branch.commit.url}
Protected: ${branch.protected ? 'Yes' : 'No'}`
return {
success: true,
output: {
content,
metadata: {
name: branch.name,
commit: {
sha: branch.commit.sha,
url: branch.commit.url,
},
protected: branch.protected,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable branch details' },
metadata: {
type: 'object',
description: 'Branch metadata',
properties: {
name: { type: 'string', description: 'Branch name' },
commit: {
type: 'object',
description: 'Commit information',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
url: { type: 'string', description: 'Commit API URL' },
},
},
protected: { type: 'boolean', description: 'Whether branch is protected' },
},
},
},
}

View File

@@ -0,0 +1,183 @@
import type { BranchProtectionResponse, GetBranchProtectionParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const getBranchProtectionTool: ToolConfig<
GetBranchProtectionParams,
BranchProtectionResponse
> = {
id: 'github_get_branch_protection',
name: 'GitHub Get Branch Protection',
description:
'Get the branch protection rules for a specific branch, including status checks, review requirements, and restrictions.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
branch: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Branch name',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/branches/${params.branch}/protection`,
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const protection = await response.json()
let content = `Branch Protection for "${protection.url.split('/branches/')[1].split('/protection')[0]}":
Enforce Admins: ${protection.enforce_admins?.enabled ? 'Yes' : 'No'}`
if (protection.required_status_checks) {
content += `\n\nRequired Status Checks:
- Strict: ${protection.required_status_checks.strict}
- Contexts: ${protection.required_status_checks.contexts.length > 0 ? protection.required_status_checks.contexts.join(', ') : 'None'}`
} else {
content += '\n\nRequired Status Checks: None'
}
if (protection.required_pull_request_reviews) {
content += `\n\nRequired Pull Request Reviews:
- Required Approving Reviews: ${protection.required_pull_request_reviews.required_approving_review_count || 0}
- Dismiss Stale Reviews: ${protection.required_pull_request_reviews.dismiss_stale_reviews ? 'Yes' : 'No'}
- Require Code Owner Reviews: ${protection.required_pull_request_reviews.require_code_owner_reviews ? 'Yes' : 'No'}`
} else {
content += '\n\nRequired Pull Request Reviews: None'
}
if (protection.restrictions) {
const users = protection.restrictions.users?.map((u: any) => u.login) || []
const teams = protection.restrictions.teams?.map((t: any) => t.slug) || []
content += `\n\nRestrictions:
- Users: ${users.length > 0 ? users.join(', ') : 'None'}
- Teams: ${teams.length > 0 ? teams.join(', ') : 'None'}`
} else {
content += '\n\nRestrictions: None'
}
return {
success: true,
output: {
content,
metadata: {
required_status_checks: protection.required_status_checks
? {
strict: protection.required_status_checks.strict,
contexts: protection.required_status_checks.contexts,
}
: null,
enforce_admins: {
enabled: protection.enforce_admins?.enabled || false,
},
required_pull_request_reviews: protection.required_pull_request_reviews
? {
required_approving_review_count:
protection.required_pull_request_reviews.required_approving_review_count || 0,
dismiss_stale_reviews:
protection.required_pull_request_reviews.dismiss_stale_reviews || false,
require_code_owner_reviews:
protection.required_pull_request_reviews.require_code_owner_reviews || false,
}
: null,
restrictions: protection.restrictions
? {
users: protection.restrictions.users?.map((u: any) => u.login) || [],
teams: protection.restrictions.teams?.map((t: any) => t.slug) || [],
}
: null,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable branch protection summary' },
metadata: {
type: 'object',
description: 'Branch protection configuration',
properties: {
required_status_checks: {
type: 'object',
description: 'Status check requirements (null if not configured)',
properties: {
strict: { type: 'boolean', description: 'Require branches to be up to date' },
contexts: {
type: 'array',
description: 'Required status check contexts',
items: { type: 'string' },
},
},
},
enforce_admins: {
type: 'object',
description: 'Admin enforcement settings',
properties: {
enabled: { type: 'boolean', description: 'Enforce for administrators' },
},
},
required_pull_request_reviews: {
type: 'object',
description: 'Pull request review requirements (null if not configured)',
properties: {
required_approving_review_count: {
type: 'number',
description: 'Number of approving reviews required',
},
dismiss_stale_reviews: {
type: 'boolean',
description: 'Dismiss stale pull request approvals',
},
require_code_owner_reviews: {
type: 'boolean',
description: 'Require review from code owners',
},
},
},
restrictions: {
type: 'object',
description: 'Push restrictions (null if not configured)',
properties: {
users: {
type: 'array',
description: 'Users who can push',
items: { type: 'string' },
},
teams: {
type: 'array',
description: 'Teams who can push',
items: { type: 'string' },
},
},
},
},
},
},
}

View File

@@ -0,0 +1,138 @@
import type { FileContentResponse, GetFileContentParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const getFileContentTool: ToolConfig<GetFileContentParams, FileContentResponse> = {
id: 'github_get_file_content',
name: 'GitHub Get File Content',
description:
'Get the content of a file from a GitHub repository. Supports files up to 1MB. Content is returned decoded and human-readable.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
path: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path to the file in the repository (e.g., "src/index.ts")',
},
ref: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Branch name, tag, or commit SHA (defaults to repository default branch)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) => {
const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/contents/${params.path}`
return params.ref ? `${baseUrl}?ref=${params.ref}` : baseUrl
},
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (Array.isArray(data)) {
return {
success: false,
error: 'Path points to a directory. Use github_get_tree to list directory contents.',
output: {
content: '',
metadata: {
name: '',
path: '',
sha: '',
size: 0,
type: 'dir',
download_url: '',
html_url: '',
},
},
}
}
let decodedContent = ''
if (data.content) {
try {
decodedContent = Buffer.from(data.content, 'base64').toString('utf-8')
} catch (error) {
decodedContent = '[Binary file - content cannot be displayed as text]'
}
}
const contentPreview =
decodedContent.length > 500
? `${decodedContent.substring(0, 500)}...\n\n[Content truncated. Full content available in metadata]`
: decodedContent
const content = `File: ${data.name}
Path: ${data.path}
Size: ${data.size} bytes
Type: ${data.type}
SHA: ${data.sha}
Content Preview:
${contentPreview}`
return {
success: true,
output: {
content,
metadata: {
name: data.name,
path: data.path,
sha: data.sha,
size: data.size,
type: data.type,
download_url: data.download_url,
html_url: data.html_url,
},
},
}
},
outputs: {
content: {
type: 'string',
description: 'Human-readable file information with content preview',
},
metadata: {
type: 'object',
description: 'File metadata including name, path, SHA, size, and URLs',
properties: {
name: { type: 'string', description: 'File name' },
path: { type: 'string', description: 'Full path in repository' },
sha: { type: 'string', description: 'Git blob SHA' },
size: { type: 'number', description: 'File size in bytes' },
type: { type: 'string', description: 'Content type (file or dir)' },
download_url: { type: 'string', description: 'Direct download URL', optional: true },
html_url: { type: 'string', description: 'GitHub web UI URL', optional: true },
},
},
},
}

View File

@@ -0,0 +1,106 @@
import type { GetIssueParams, IssueResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const getIssueTool: ToolConfig<GetIssueParams, IssueResponse> = {
id: 'github_get_issue',
name: 'GitHub Get Issue',
description: 'Get detailed information about a specific issue in a GitHub repository',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
issue_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue number',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}`,
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const issue = await response.json()
const labels = issue.labels?.map((label: any) => label.name) || []
const assignees = issue.assignees?.map((assignee: any) => assignee.login) || []
const content = `Issue #${issue.number}: "${issue.title}"
State: ${issue.state}
Created: ${issue.created_at}
Updated: ${issue.updated_at}
${issue.closed_at ? `Closed: ${issue.closed_at}` : ''}
URL: ${issue.html_url}
${labels.length > 0 ? `Labels: ${labels.join(', ')}` : 'No labels'}
${assignees.length > 0 ? `Assignees: ${assignees.join(', ')}` : 'No assignees'}
Description:
${issue.body || 'No description provided'}`
return {
success: true,
output: {
content,
metadata: {
number: issue.number,
title: issue.title,
state: issue.state,
html_url: issue.html_url,
labels,
assignees,
created_at: issue.created_at,
updated_at: issue.updated_at,
closed_at: issue.closed_at,
body: issue.body,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable issue details' },
metadata: {
type: 'object',
description: 'Detailed issue metadata',
properties: {
number: { type: 'number', description: 'Issue number' },
title: { type: 'string', description: 'Issue title' },
state: { type: 'string', description: 'Issue state (open/closed)' },
html_url: { type: 'string', description: 'GitHub web URL' },
labels: { type: 'array', description: 'Array of label names' },
assignees: { type: 'array', description: 'Array of assignee usernames' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
closed_at: { type: 'string', description: 'Closed timestamp' },
body: { type: 'string', description: 'Issue body/description' },
},
},
},
}

View File

@@ -0,0 +1,138 @@
import type { GetPRFilesParams, PRFilesListResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const getPRFilesTool: ToolConfig<GetPRFilesParams, PRFilesListResponse> = {
id: 'github_get_pr_files',
name: 'GitHub Get PR Files',
description: 'Get the list of files changed in a pull request',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
pullNumber: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Pull request number',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Results per page (max 100)',
default: 30,
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number',
default: 1,
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => {
const url = new URL(
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/files`
)
if (params.per_page) url.searchParams.append('per_page', params.per_page.toString())
if (params.page) url.searchParams.append('page', params.page.toString())
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const files = await response.json()
const totalAdditions = files.reduce((sum: number, file: any) => sum + file.additions, 0)
const totalDeletions = files.reduce((sum: number, file: any) => sum + file.deletions, 0)
const totalChanges = files.reduce((sum: number, file: any) => sum + file.changes, 0)
const content = `Found ${files.length} file(s) changed in PR
Total additions: ${totalAdditions}, Total deletions: ${totalDeletions}, Total changes: ${totalChanges}
Files:
${files
.map(
(file: any) =>
`- ${file.filename} (${file.status})
+${file.additions} -${file.deletions} (~${file.changes} changes)`
)
.join('\n')}`
return {
success: true,
output: {
content,
metadata: {
files: files.map((file: any) => ({
filename: file.filename,
status: file.status,
additions: file.additions,
deletions: file.deletions,
changes: file.changes,
patch: file.patch,
blob_url: file.blob_url,
raw_url: file.raw_url,
})),
total_count: files.length,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable list of files changed in PR' },
metadata: {
type: 'object',
description: 'PR files metadata',
properties: {
files: {
type: 'array',
description: 'Array of file changes',
items: {
type: 'object',
properties: {
filename: { type: 'string', description: 'File path' },
status: {
type: 'string',
description: 'Change type (added/modified/deleted/renamed)',
},
additions: { type: 'number', description: 'Lines added' },
deletions: { type: 'number', description: 'Lines deleted' },
changes: { type: 'number', description: 'Total changes' },
patch: { type: 'string', description: 'File diff patch' },
blob_url: { type: 'string', description: 'GitHub blob URL' },
raw_url: { type: 'string', description: 'Raw file URL' },
},
},
},
total_count: { type: 'number', description: 'Total number of files changed' },
},
},
},
}

View File

@@ -0,0 +1,163 @@
import type { GetProjectParams, ProjectResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const getProjectTool: ToolConfig<GetProjectParams, ProjectResponse> = {
id: 'github_get_project',
name: 'GitHub Get Project',
description:
'Get detailed information about a specific GitHub Project V2 by its number. Returns project details including ID, title, description, URL, and status.',
version: '1.0.0',
params: {
owner_type: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Owner type: "org" for organization or "user" for user',
},
owner_login: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Organization or user login name',
},
project_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Project number',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token with project read permissions',
},
},
request: {
url: 'https://api.github.com/graphql',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const ownerType = params.owner_type === 'org' ? 'organization' : 'user'
const query = `
query($login: String!, $number: Int!) {
${ownerType}(login: $login) {
projectV2(number: $number) {
id
title
number
url
closed
public
shortDescription
readme
createdAt
updatedAt
}
}
}
`
return {
query,
variables: {
login: params.owner_login,
number: params.project_number,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
output: {
content: `GraphQL Error: ${data.errors[0].message}`,
metadata: {
id: '',
title: '',
url: '',
},
},
error: data.errors[0].message,
}
}
const ownerData = data.data?.organization || data.data?.user
if (!ownerData || !ownerData.projectV2) {
return {
success: false,
output: {
content: 'Project not found',
metadata: {
id: '',
title: '',
url: '',
},
},
error: 'Project not found',
}
}
const project = ownerData.projectV2
let content = `Project: ${project.title} (#${project.number})\n`
content += `ID: ${project.id}\n`
content += `URL: ${project.url}\n`
content += `Status: ${project.closed ? 'Closed' : 'Open'}\n`
content += `Visibility: ${project.public ? 'Public' : 'Private'}\n`
if (project.shortDescription) {
content += `Description: ${project.shortDescription}\n`
}
if (project.createdAt) {
content += `Created: ${project.createdAt}\n`
}
if (project.updatedAt) {
content += `Updated: ${project.updatedAt}\n`
}
return {
success: true,
output: {
content: content.trim(),
metadata: {
id: project.id,
title: project.title,
number: project.number,
url: project.url,
closed: project.closed,
public: project.public,
shortDescription: project.shortDescription || '',
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable project details' },
metadata: {
type: 'object',
description: 'Project metadata',
properties: {
id: { type: 'string', description: 'Project node ID' },
title: { type: 'string', description: 'Project title' },
number: { type: 'number', description: 'Project number', optional: true },
url: { type: 'string', description: 'Project URL' },
closed: { type: 'boolean', description: 'Whether project is closed', optional: true },
public: { type: 'boolean', description: 'Whether project is public', optional: true },
shortDescription: {
type: 'string',
description: 'Project short description',
optional: true,
},
},
},
},
}

View File

@@ -0,0 +1,111 @@
import type { GetReleaseParams, ReleaseResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const getReleaseTool: ToolConfig<GetReleaseParams, ReleaseResponse> = {
id: 'github_get_release',
name: 'GitHub Get Release',
description:
'Get detailed information about a specific GitHub release by ID. Returns release metadata including assets and download URLs.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
release_id: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'The unique identifier of the release',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/releases/${params.release_id}`,
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const data = await response.json()
const releaseType = data.draft ? 'Draft' : data.prerelease ? 'Prerelease' : 'Release'
const assetsInfo =
data.assets && data.assets.length > 0
? `\n\nAssets (${data.assets.length}):\n${data.assets.map((asset: any) => `- ${asset.name} (${asset.size} bytes, downloaded ${asset.download_count} times)`).join('\n')}`
: '\n\nNo assets attached'
const content = `${releaseType}: "${data.name || data.tag_name}"
Tag: ${data.tag_name}
Author: ${data.author?.login || 'Unknown'}
Created: ${data.created_at}
${data.published_at ? `Published: ${data.published_at}` : 'Not yet published'}
URL: ${data.html_url}
Description:
${data.body || 'No description provided'}
Download URLs:
- Tarball: ${data.tarball_url}
- Zipball: ${data.zipball_url}${assetsInfo}`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
tag_name: data.tag_name,
name: data.name || data.tag_name,
html_url: data.html_url,
tarball_url: data.tarball_url,
zipball_url: data.zipball_url,
draft: data.draft,
prerelease: data.prerelease,
created_at: data.created_at,
published_at: data.published_at,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable release details' },
metadata: {
type: 'object',
description: 'Release metadata including download URLs',
properties: {
id: { type: 'number', description: 'Release ID' },
tag_name: { type: 'string', description: 'Git tag name' },
name: { type: 'string', description: 'Release name' },
html_url: { type: 'string', description: 'GitHub web URL for the release' },
tarball_url: { type: 'string', description: 'URL to download release as tarball' },
zipball_url: { type: 'string', description: 'URL to download release as zipball' },
draft: { type: 'boolean', description: 'Whether this is a draft release' },
prerelease: { type: 'boolean', description: 'Whether this is a prerelease' },
created_at: { type: 'string', description: 'Creation timestamp' },
published_at: { type: 'string', description: 'Publication timestamp' },
},
},
},
}

View File

@@ -0,0 +1,163 @@
import type { GetTreeParams, TreeResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const getTreeTool: ToolConfig<GetTreeParams, TreeResponse> = {
id: 'github_get_tree',
name: 'GitHub Get Repository Tree',
description:
'Get the contents of a directory in a GitHub repository. Returns a list of files and subdirectories. Use empty path or omit to get root directory contents.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
path: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Directory path (e.g., "src/components"). Leave empty for root directory.',
default: '',
},
ref: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Branch name, tag, or commit SHA (defaults to repository default branch)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) => {
const path = params.path || ''
const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/contents/${path}`
return params.ref ? `${baseUrl}?ref=${params.ref}` : baseUrl
},
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const data = await response.json()
if (!Array.isArray(data)) {
return {
success: false,
error: 'Path points to a file. Use github_get_file_content to get file contents.',
output: {
content: '',
metadata: {
path: '',
items: [],
total_count: 0,
},
},
}
}
const items = data.map((item: any) => ({
name: item.name,
path: item.path,
sha: item.sha,
size: item.size,
type: item.type,
download_url: item.download_url,
html_url: item.html_url,
}))
const files = items.filter((item) => item.type === 'file')
const dirs = items.filter((item) => item.type === 'dir')
const other = items.filter((item) => item.type !== 'file' && item.type !== 'dir')
let content = `Repository Tree: ${data[0]?.path ? data[0].path.split('/').slice(0, -1).join('/') || '/' : '/'}
Total items: ${items.length}
`
if (dirs.length > 0) {
content += `Directories (${dirs.length}):\n`
dirs.forEach((dir) => {
content += ` - ${dir.name}/\n`
})
content += '\n'
}
if (files.length > 0) {
content += `Files (${files.length}):\n`
files.forEach((file) => {
const sizeKB = (file.size / 1024).toFixed(2)
content += ` - ${file.name} (${sizeKB} KB)\n`
})
content += '\n'
}
if (other.length > 0) {
content += `Other (${other.length}):\n`
other.forEach((item) => {
content += ` - ${item.name} [${item.type}]\n`
})
}
return {
success: true,
output: {
content,
metadata: {
path: data[0]?.path?.split('/').slice(0, -1).join('/') || '/',
items,
total_count: items.length,
},
},
}
},
outputs: {
content: {
type: 'string',
description: 'Human-readable directory tree listing',
},
metadata: {
type: 'object',
description: 'Directory contents metadata',
properties: {
path: { type: 'string', description: 'Directory path' },
items: {
type: 'array',
description: 'Array of files and directories',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'File or directory name' },
path: { type: 'string', description: 'Full path in repository' },
sha: { type: 'string', description: 'Git object SHA' },
size: { type: 'number', description: 'Size in bytes' },
type: { type: 'string', description: 'Type (file, dir, symlink, submodule)' },
download_url: { type: 'string', description: 'Direct download URL (files only)' },
html_url: { type: 'string', description: 'GitHub web UI URL' },
},
},
},
total_count: { type: 'number', description: 'Total number of items' },
},
},
},
}

View File

@@ -0,0 +1,89 @@
import type { GetWorkflowParams, WorkflowResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const getWorkflowTool: ToolConfig<GetWorkflowParams, WorkflowResponse> = {
id: 'github_get_workflow',
name: 'GitHub Get Workflow',
description:
'Get details of a specific GitHub Actions workflow by ID or filename. Returns workflow information including name, path, state, and badge URL.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
workflow_id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow ID (number) or workflow filename (e.g., "main.yaml")',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/workflows/${params.workflow_id}`,
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const data = await response.json()
const content = `Workflow: ${data.name}
State: ${data.state}
Path: ${data.path}
ID: ${data.id}
Badge URL: ${data.badge_url}
Created: ${data.created_at}
Updated: ${data.updated_at}`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
name: data.name,
path: data.path,
state: data.state,
badge_url: data.badge_url,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable workflow details' },
metadata: {
type: 'object',
description: 'Workflow metadata',
properties: {
id: { type: 'number', description: 'Workflow ID' },
name: { type: 'string', description: 'Workflow name' },
path: { type: 'string', description: 'Path to workflow file' },
state: { type: 'string', description: 'Workflow state (active/disabled)' },
badge_url: { type: 'string', description: 'Badge URL for workflow' },
},
},
},
}

View File

@@ -0,0 +1,95 @@
import type { GetWorkflowRunParams, WorkflowRunResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const getWorkflowRunTool: ToolConfig<GetWorkflowRunParams, WorkflowRunResponse> = {
id: 'github_get_workflow_run',
name: 'GitHub Get Workflow Run',
description:
'Get detailed information about a specific workflow run by ID. Returns status, conclusion, timing, and links to the run.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
run_id: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Workflow run ID',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/runs/${params.run_id}`,
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const data = await response.json()
const content = `Workflow Run #${data.run_number}: ${data.name}
Status: ${data.status}${data.conclusion ? ` - ${data.conclusion}` : ''}
Branch: ${data.head_branch}
Commit: ${data.head_sha.substring(0, 7)}
Event: ${data.event}
Triggered by: ${data.triggering_actor?.login || 'Unknown'}
Started: ${data.run_started_at || data.created_at}
${data.updated_at ? `Updated: ${data.updated_at}` : ''}
${data.run_attempt ? `Attempt: ${data.run_attempt}` : ''}
URL: ${data.html_url}
Logs: ${data.logs_url}`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
name: data.name,
status: data.status,
conclusion: data.conclusion,
html_url: data.html_url,
run_number: data.run_number,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable workflow run details' },
metadata: {
type: 'object',
description: 'Workflow run metadata',
properties: {
id: { type: 'number', description: 'Workflow run ID' },
name: { type: 'string', description: 'Workflow name' },
status: { type: 'string', description: 'Run status' },
conclusion: { type: 'string', description: 'Run conclusion' },
html_url: { type: 'string', description: 'GitHub web URL' },
run_number: { type: 'number', description: 'Run number' },
},
},
},
}

View File

@@ -1,9 +1,105 @@
import { addAssigneesTool } from '@/tools/github/add_assignees'
import { addLabelsTool } from '@/tools/github/add_labels'
import { cancelWorkflowRunTool } from '@/tools/github/cancel_workflow_run'
import { closeIssueTool } from '@/tools/github/close_issue'
import { closePRTool } from '@/tools/github/close_pr'
import { commentTool } from '@/tools/github/comment'
import { createBranchTool } from '@/tools/github/create_branch'
import { createFileTool } from '@/tools/github/create_file'
import { createIssueTool } from '@/tools/github/create_issue'
import { createPRTool } from '@/tools/github/create_pr'
import { createProjectTool } from '@/tools/github/create_project'
import { createReleaseTool } from '@/tools/github/create_release'
import { deleteBranchTool } from '@/tools/github/delete_branch'
import { deleteCommentTool } from '@/tools/github/delete_comment'
import { deleteFileTool } from '@/tools/github/delete_file'
import { deleteProjectTool } from '@/tools/github/delete_project'
import { deleteReleaseTool } from '@/tools/github/delete_release'
import { getBranchTool } from '@/tools/github/get_branch'
import { getBranchProtectionTool } from '@/tools/github/get_branch_protection'
import { getFileContentTool } from '@/tools/github/get_file_content'
import { getIssueTool } from '@/tools/github/get_issue'
import { getPRFilesTool } from '@/tools/github/get_pr_files'
import { getProjectTool } from '@/tools/github/get_project'
import { getReleaseTool } from '@/tools/github/get_release'
import { getTreeTool } from '@/tools/github/get_tree'
import { getWorkflowTool } from '@/tools/github/get_workflow'
import { getWorkflowRunTool } from '@/tools/github/get_workflow_run'
import { issueCommentTool } from '@/tools/github/issue_comment'
import { latestCommitTool } from '@/tools/github/latest_commit'
import { listBranchesTool } from '@/tools/github/list_branches'
import { listIssueCommentsTool } from '@/tools/github/list_issue_comments'
import { listIssuesTool } from '@/tools/github/list_issues'
import { listPRCommentsTool } from '@/tools/github/list_pr_comments'
import { listProjectsTool } from '@/tools/github/list_projects'
import { listPRsTool } from '@/tools/github/list_prs'
import { listReleasesTool } from '@/tools/github/list_releases'
import { listWorkflowRunsTool } from '@/tools/github/list_workflow_runs'
import { listWorkflowsTool } from '@/tools/github/list_workflows'
import { mergePRTool } from '@/tools/github/merge_pr'
import { prTool } from '@/tools/github/pr'
import { removeLabelTool } from '@/tools/github/remove_label'
import { repoInfoTool } from '@/tools/github/repo_info'
import { requestReviewersTool } from '@/tools/github/request_reviewers'
import { rerunWorkflowTool } from '@/tools/github/rerun_workflow'
import { triggerWorkflowTool } from '@/tools/github/trigger_workflow'
import { updateBranchProtectionTool } from '@/tools/github/update_branch_protection'
import { updateCommentTool } from '@/tools/github/update_comment'
import { updateFileTool } from '@/tools/github/update_file'
import { updateIssueTool } from '@/tools/github/update_issue'
import { updatePRTool } from '@/tools/github/update_pr'
import { updateProjectTool } from '@/tools/github/update_project'
import { updateReleaseTool } from '@/tools/github/update_release'
export const githubCancelWorkflowRunTool = cancelWorkflowRunTool
export const githubClosePRTool = closePRTool
export const githubCommentTool = commentTool
export const githubCreateBranchTool = createBranchTool
export const githubCreateFileTool = createFileTool
export const githubCreatePRTool = createPRTool
export const githubCreateProjectTool = createProjectTool
export const githubCreateReleaseTool = createReleaseTool
export const githubDeleteBranchTool = deleteBranchTool
export const githubDeleteCommentTool = deleteCommentTool
export const githubDeleteFileTool = deleteFileTool
export const githubDeleteProjectTool = deleteProjectTool
export const githubDeleteReleaseTool = deleteReleaseTool
export const githubGetBranchTool = getBranchTool
export const githubGetBranchProtectionTool = getBranchProtectionTool
export const githubGetFileContentTool = getFileContentTool
export const githubGetPRFilesTool = getPRFilesTool
export const githubGetProjectTool = getProjectTool
export const githubGetReleaseTool = getReleaseTool
export const githubGetTreeTool = getTreeTool
export const githubGetWorkflowTool = getWorkflowTool
export const githubGetWorkflowRunTool = getWorkflowRunTool
export const githubIssueCommentTool = issueCommentTool
export const githubLatestCommitTool = latestCommitTool
export const githubListBranchesTool = listBranchesTool
export const githubListIssueCommentsTool = listIssueCommentsTool
export const githubListPRCommentsTool = listPRCommentsTool
export const githubListPRsTool = listPRsTool
export const githubListProjectsTool = listProjectsTool
export const githubListReleasesTool = listReleasesTool
export const githubListWorkflowRunsTool = listWorkflowRunsTool
export const githubListWorkflowsTool = listWorkflowsTool
export const githubMergePRTool = mergePRTool
export const githubPrTool = prTool
export const githubRepoInfoTool = repoInfoTool
export const githubRequestReviewersTool = requestReviewersTool
export const githubRerunWorkflowTool = rerunWorkflowTool
export const githubTriggerWorkflowTool = triggerWorkflowTool
export const githubUpdateBranchProtectionTool = updateBranchProtectionTool
export const githubUpdateCommentTool = updateCommentTool
export const githubUpdateFileTool = updateFileTool
export const githubUpdatePRTool = updatePRTool
export const githubUpdateProjectTool = updateProjectTool
export const githubUpdateReleaseTool = updateReleaseTool
export const githubAddAssigneesTool = addAssigneesTool
export const githubAddLabelsTool = addLabelsTool
export const githubCloseIssueTool = closeIssueTool
export const githubCreateIssueTool = createIssueTool
export const githubGetIssueTool = getIssueTool
export const githubListIssuesTool = listIssuesTool
export const githubRemoveLabelTool = removeLabelTool
export const githubUpdateIssueTool = updateIssueTool

View File

@@ -0,0 +1,103 @@
import type { CreateIssueCommentParams, IssueCommentResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const issueCommentTool: ToolConfig<CreateIssueCommentParams, IssueCommentResponse> = {
id: 'github_issue_comment',
name: 'GitHub Issue Comment Creator',
description: 'Create a comment on a GitHub issue',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
issue_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue number',
},
body: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comment content',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/comments`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => ({
body: params.body,
}),
},
transformResponse: async (response) => {
const data = await response.json()
const content = `Comment created on issue #${data.issue_url.split('/').pop()}: "${data.body.substring(0, 100)}${data.body.length > 100 ? '...' : ''}"`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
html_url: data.html_url,
body: data.body,
created_at: data.created_at,
updated_at: data.updated_at,
user: {
login: data.user.login,
id: data.user.id,
},
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable comment confirmation' },
metadata: {
type: 'object',
description: 'Comment metadata',
properties: {
id: { type: 'number', description: 'Comment ID' },
html_url: { type: 'string', description: 'GitHub web URL' },
body: { type: 'string', description: 'Comment body' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
user: {
type: 'object',
description: 'User who created the comment',
properties: {
login: { type: 'string', description: 'User login' },
id: { type: 'number', description: 'User ID' },
},
},
},
},
},
}

View File

@@ -0,0 +1,137 @@
import type { BranchListResponse, ListBranchesParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const listBranchesTool: ToolConfig<ListBranchesParams, BranchListResponse> = {
id: 'github_list_branches',
name: 'GitHub List Branches',
description:
'List all branches in a GitHub repository. Optionally filter by protected status and control pagination.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
protected: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Filter branches by protection status',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (max 100, default 30)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for pagination (default 1)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) => {
const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/branches`
const queryParams = new URLSearchParams()
if (params.protected !== undefined) {
queryParams.append('protected', params.protected.toString())
}
if (params.per_page) {
queryParams.append('per_page', params.per_page.toString())
}
if (params.page) {
queryParams.append('page', params.page.toString())
}
const query = queryParams.toString()
return query ? `${baseUrl}?${query}` : baseUrl
},
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const branches = await response.json()
const branchList = branches
.map(
(branch: any) =>
`- ${branch.name} (SHA: ${branch.commit.sha.substring(0, 7)}${branch.protected ? ', Protected' : ''})`
)
.join('\n')
const content = `Found ${branches.length} branch${branches.length !== 1 ? 'es' : ''}:
${branchList}`
return {
success: true,
output: {
content,
metadata: {
branches: branches.map((branch: any) => ({
name: branch.name,
commit: {
sha: branch.commit.sha,
url: branch.commit.url,
},
protected: branch.protected,
})),
total_count: branches.length,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable list of branches' },
metadata: {
type: 'object',
description: 'Branch list metadata',
properties: {
branches: {
type: 'array',
description: 'Array of branch objects',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Branch name' },
commit: {
type: 'object',
description: 'Commit information',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
url: { type: 'string', description: 'Commit API URL' },
},
},
protected: { type: 'boolean', description: 'Whether branch is protected' },
},
},
},
total_count: { type: 'number', description: 'Total number of branches' },
},
},
},
}

View File

@@ -0,0 +1,140 @@
import type { CommentsListResponse, ListIssueCommentsParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const listIssueCommentsTool: ToolConfig<ListIssueCommentsParams, CommentsListResponse> = {
id: 'github_list_issue_comments',
name: 'GitHub Issue Comments Lister',
description: 'List all comments on a GitHub issue',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
issue_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue number',
},
since: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Only show comments updated after this ISO 8601 timestamp',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (max 100)',
default: 30,
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number',
default: 1,
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => {
const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/comments`
const queryParams = new URLSearchParams()
if (params.since) queryParams.append('since', params.since)
if (params.per_page) queryParams.append('per_page', params.per_page.toString())
if (params.page) queryParams.append('page', params.page.toString())
const query = queryParams.toString()
return query ? `${baseUrl}?${query}` : baseUrl
},
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const data = await response.json()
const content = `Found ${data.length} comment${data.length !== 1 ? 's' : ''} on issue #${response.url.split('/').slice(-2, -1)[0]}${
data.length > 0
? `\n\nRecent comments:\n${data
.slice(0, 5)
.map(
(c: any) =>
`- ${c.user.login} (${new Date(c.created_at).toLocaleDateString()}): "${c.body.substring(0, 80)}${c.body.length > 80 ? '...' : ''}"`
)
.join('\n')}`
: ''
}`
return {
success: true,
output: {
content,
metadata: {
comments: data.map((comment: any) => ({
id: comment.id,
body: comment.body,
user: { login: comment.user.login },
created_at: comment.created_at,
html_url: comment.html_url,
})),
total_count: data.length,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable comments summary' },
metadata: {
type: 'object',
description: 'Comments list metadata',
properties: {
comments: {
type: 'array',
description: 'Array of comment objects',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Comment ID' },
body: { type: 'string', description: 'Comment body' },
user: {
type: 'object',
description: 'User who created the comment',
properties: {
login: { type: 'string', description: 'User login' },
},
},
created_at: { type: 'string', description: 'Creation timestamp' },
html_url: { type: 'string', description: 'GitHub web URL' },
},
},
},
total_count: { type: 'number', description: 'Total number of comments' },
},
},
},
}

View File

@@ -0,0 +1,169 @@
import type { IssuesListResponse, ListIssuesParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const listIssuesTool: ToolConfig<ListIssuesParams, IssuesListResponse> = {
id: 'github_list_issues',
name: 'GitHub List Issues',
description:
'List issues in a GitHub repository. Note: This includes pull requests as PRs are considered issues in GitHub',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
state: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by state: open, closed, or all (default: open)',
default: 'open',
},
assignee: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by assignee username',
},
creator: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by creator username',
},
labels: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of label names to filter by',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort by: created, updated, or comments (default: created)',
default: 'created',
},
direction: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort direction: asc or desc (default: desc)',
default: 'desc',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Results per page (max 100, default: 30)',
default: 30,
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number (default: 1)',
default: 1,
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => {
const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/issues`)
if (params.state) url.searchParams.append('state', params.state)
if (params.assignee) url.searchParams.append('assignee', params.assignee)
if (params.creator) url.searchParams.append('creator', params.creator)
if (params.labels) url.searchParams.append('labels', params.labels)
if (params.sort) url.searchParams.append('sort', params.sort)
if (params.direction) url.searchParams.append('direction', params.direction)
if (params.per_page) url.searchParams.append('per_page', params.per_page.toString())
if (params.page) url.searchParams.append('page', params.page.toString())
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const issues = await response.json()
const transformedIssues = issues.map((issue: any) => ({
number: issue.number,
title: issue.title,
state: issue.state,
html_url: issue.html_url,
labels: issue.labels?.map((label: any) => label.name) || [],
assignees: issue.assignees?.map((assignee: any) => assignee.login) || [],
created_at: issue.created_at,
updated_at: issue.updated_at,
}))
const content = `Found ${issues.length} issue(s):
${transformedIssues
.map(
(issue: any) =>
`#${issue.number}: "${issue.title}" (${issue.state}) - ${issue.html_url}
${issue.labels.length > 0 ? `Labels: ${issue.labels.join(', ')}` : 'No labels'}
${issue.assignees.length > 0 ? `Assignees: ${issue.assignees.join(', ')}` : 'No assignees'}`
)
.join('\n\n')}`
return {
success: true,
output: {
content,
metadata: {
issues: transformedIssues,
total_count: issues.length,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable list of issues' },
metadata: {
type: 'object',
description: 'Issues list metadata',
properties: {
issues: {
type: 'array',
description: 'Array of issues',
items: {
type: 'object',
properties: {
number: { type: 'number', description: 'Issue number' },
title: { type: 'string', description: 'Issue title' },
state: { type: 'string', description: 'Issue state' },
html_url: { type: 'string', description: 'GitHub web URL' },
labels: { type: 'array', description: 'Array of label names' },
assignees: { type: 'array', description: 'Array of assignee usernames' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
},
},
},
total_count: { type: 'number', description: 'Total number of issues returned' },
},
},
},
}

View File

@@ -0,0 +1,156 @@
import type { CommentsListResponse, ListPRCommentsParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const listPRCommentsTool: ToolConfig<ListPRCommentsParams, CommentsListResponse> = {
id: 'github_list_pr_comments',
name: 'GitHub PR Review Comments Lister',
description: 'List all review comments on a GitHub pull request',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
pullNumber: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Pull request number',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort by created or updated',
default: 'created',
},
direction: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort direction (asc or desc)',
default: 'desc',
},
since: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Only show comments updated after this ISO 8601 timestamp',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (max 100)',
default: 30,
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number',
default: 1,
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => {
const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/comments`
const queryParams = new URLSearchParams()
if (params.sort) queryParams.append('sort', params.sort)
if (params.direction) queryParams.append('direction', params.direction)
if (params.since) queryParams.append('since', params.since)
if (params.per_page) queryParams.append('per_page', params.per_page.toString())
if (params.page) queryParams.append('page', params.page.toString())
const query = queryParams.toString()
return query ? `${baseUrl}?${query}` : baseUrl
},
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const data = await response.json()
const content = `Found ${data.length} review comment${data.length !== 1 ? 's' : ''} on PR #${response.url.split('/').slice(-2, -1)[0]}${
data.length > 0
? `\n\nRecent review comments:\n${data
.slice(0, 5)
.map(
(c: any) =>
`- ${c.user.login} on ${c.path}${c.line ? `:${c.line}` : ''} (${new Date(c.created_at).toLocaleDateString()}): "${c.body.substring(0, 80)}${c.body.length > 80 ? '...' : ''}"`
)
.join('\n')}`
: ''
}`
return {
success: true,
output: {
content,
metadata: {
comments: data.map((comment: any) => ({
id: comment.id,
body: comment.body,
user: { login: comment.user.login },
created_at: comment.created_at,
html_url: comment.html_url,
})),
total_count: data.length,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable review comments summary' },
metadata: {
type: 'object',
description: 'Review comments list metadata',
properties: {
comments: {
type: 'array',
description: 'Array of review comment objects',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Comment ID' },
body: { type: 'string', description: 'Comment body' },
user: {
type: 'object',
description: 'User who created the comment',
properties: {
login: { type: 'string', description: 'User login' },
},
},
created_at: { type: 'string', description: 'Creation timestamp' },
html_url: { type: 'string', description: 'GitHub web URL' },
},
},
},
total_count: { type: 'number', description: 'Total number of review comments' },
},
},
},
}

View File

@@ -0,0 +1,165 @@
import type { ListProjectsParams, ListProjectsResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const listProjectsTool: ToolConfig<ListProjectsParams, ListProjectsResponse> = {
id: 'github_list_projects',
name: 'GitHub List Projects',
description:
'List GitHub Projects V2 for an organization or user. Returns up to 20 projects with their details including ID, title, number, URL, and status.',
version: '1.0.0',
params: {
owner_type: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Owner type: "org" for organization or "user" for user',
},
owner_login: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Organization or user login name',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token with project read permissions',
},
},
request: {
url: 'https://api.github.com/graphql',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const ownerType = params.owner_type === 'org' ? 'organization' : 'user'
const query = `
query($login: String!) {
${ownerType}(login: $login) {
projectsV2(first: 20) {
nodes {
id
title
number
url
closed
public
shortDescription
}
totalCount
}
}
}
`
return {
query,
variables: {
login: params.owner_login,
},
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
output: {
content: `GraphQL Error: ${data.errors[0].message}`,
metadata: {
projects: [],
totalCount: 0,
},
},
error: data.errors[0].message,
}
}
const ownerData = data.data?.organization || data.data?.user
if (!ownerData) {
return {
success: false,
output: {
content: 'No organization or user found',
metadata: {
projects: [],
totalCount: 0,
},
},
error: 'No organization or user found',
}
}
const projectsData = ownerData.projectsV2
const projects = projectsData.nodes.map((project: any) => ({
id: project.id,
title: project.title,
number: project.number,
url: project.url,
closed: project.closed,
public: project.public,
shortDescription: project.shortDescription || '',
}))
let content = `Found ${projectsData.totalCount} project(s):\n\n`
projects.forEach((project: any, index: number) => {
content += `${index + 1}. ${project.title} (#${project.number})\n`
content += ` ID: ${project.id}\n`
content += ` URL: ${project.url}\n`
content += ` Status: ${project.closed ? 'Closed' : 'Open'}\n`
content += ` Visibility: ${project.public ? 'Public' : 'Private'}\n`
if (project.shortDescription) {
content += ` Description: ${project.shortDescription}\n`
}
content += '\n'
})
return {
success: true,
output: {
content: content.trim(),
metadata: {
projects,
totalCount: projectsData.totalCount,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable list of projects' },
metadata: {
type: 'object',
description: 'Projects metadata',
properties: {
projects: {
type: 'array',
description: 'Array of project objects',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Project node ID' },
title: { type: 'string', description: 'Project title' },
number: { type: 'number', description: 'Project number' },
url: { type: 'string', description: 'Project URL' },
closed: { type: 'boolean', description: 'Whether project is closed' },
public: { type: 'boolean', description: 'Whether project is public' },
shortDescription: {
type: 'string',
description: 'Project short description',
},
},
},
},
totalCount: { type: 'number', description: 'Total number of projects' },
},
},
},
}

View File

@@ -0,0 +1,153 @@
import type { ListPRsParams, PRListResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const listPRsTool: ToolConfig<ListPRsParams, PRListResponse> = {
id: 'github_list_prs',
name: 'GitHub List Pull Requests',
description: 'List pull requests in a GitHub repository',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
state: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by state: open, closed, or all',
default: 'open',
},
head: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Filter by head user or branch name (format: user:ref-name or organization:ref-name)',
},
base: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by base branch name',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort by: created, updated, popularity, or long-running',
default: 'created',
},
direction: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort direction: asc or desc',
default: 'desc',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Results per page (max 100)',
default: 30,
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number',
default: 1,
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => {
const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/pulls`)
if (params.state) url.searchParams.append('state', params.state)
if (params.head) url.searchParams.append('head', params.head)
if (params.base) url.searchParams.append('base', params.base)
if (params.sort) url.searchParams.append('sort', params.sort)
if (params.direction) url.searchParams.append('direction', params.direction)
if (params.per_page) url.searchParams.append('per_page', params.per_page.toString())
if (params.page) url.searchParams.append('page', params.page.toString())
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const prs = await response.json()
const openCount = prs.filter((pr: any) => pr.state === 'open').length
const closedCount = prs.filter((pr: any) => pr.state === 'closed').length
const content = `Found ${prs.length} pull request(s)
Open: ${openCount}, Closed: ${closedCount}
${prs
.map(
(pr: any) =>
`#${pr.number}: ${pr.title} (${pr.state})
URL: ${pr.html_url}`
)
.join('\n\n')}`
return {
success: true,
output: {
content,
metadata: {
prs: prs.map((pr: any) => ({
number: pr.number,
title: pr.title,
state: pr.state,
html_url: pr.html_url,
created_at: pr.created_at,
updated_at: pr.updated_at,
})),
total_count: prs.length,
open_count: openCount,
closed_count: closedCount,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable list of pull requests' },
metadata: {
type: 'object',
description: 'Pull requests list metadata',
properties: {
prs: {
type: 'array',
description: 'Array of pull request summaries',
},
total_count: { type: 'number', description: 'Total number of PRs returned' },
open_count: { type: 'number', description: 'Number of open PRs' },
closed_count: { type: 'number', description: 'Number of closed PRs' },
},
},
},
}

View File

@@ -0,0 +1,137 @@
import type { ListReleasesParams, ListReleasesResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const listReleasesTool: ToolConfig<ListReleasesParams, ListReleasesResponse> = {
id: 'github_list_releases',
name: 'GitHub List Releases',
description:
'List all releases for a GitHub repository. Returns release information including tags, names, and download URLs.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (max 100)',
default: 30,
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number of the results to fetch',
default: 1,
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) => {
const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/releases`)
if (params.per_page) {
url.searchParams.append('per_page', params.per_page.toString())
}
if (params.page) {
url.searchParams.append('page', params.page.toString())
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const releases = await response.json()
const totalReleases = releases.length
const releasesList = releases
.map(
(release: any, index: number) =>
`${index + 1}. ${release.name || release.tag_name} (${release.tag_name})
${release.draft ? '[DRAFT] ' : ''}${release.prerelease ? '[PRERELEASE] ' : ''}
Published: ${release.published_at || 'Not published'}
URL: ${release.html_url}`
)
.join('\n\n')
const content = `Total releases: ${totalReleases}
${releasesList}
Summary of tags: ${releases.map((r: any) => r.tag_name).join(', ')}`
return {
success: true,
output: {
content,
metadata: {
total_count: totalReleases,
releases: releases.map((release: any) => ({
id: release.id,
tag_name: release.tag_name,
name: release.name || release.tag_name,
html_url: release.html_url,
tarball_url: release.tarball_url,
zipball_url: release.zipball_url,
draft: release.draft,
prerelease: release.prerelease,
created_at: release.created_at,
published_at: release.published_at,
})),
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable list of releases with summary' },
metadata: {
type: 'object',
description: 'Releases metadata',
properties: {
total_count: { type: 'number', description: 'Total number of releases returned' },
releases: {
type: 'array',
description: 'Array of release objects',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Release ID' },
tag_name: { type: 'string', description: 'Git tag name' },
name: { type: 'string', description: 'Release name' },
html_url: { type: 'string', description: 'GitHub web URL' },
tarball_url: { type: 'string', description: 'Tarball download URL' },
zipball_url: { type: 'string', description: 'Zipball download URL' },
draft: { type: 'boolean', description: 'Is draft release' },
prerelease: { type: 'boolean', description: 'Is prerelease' },
created_at: { type: 'string', description: 'Creation timestamp' },
published_at: { type: 'string', description: 'Publication timestamp' },
},
},
},
},
},
},
}

View File

@@ -0,0 +1,186 @@
import type { ListWorkflowRunsParams, ListWorkflowRunsResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const listWorkflowRunsTool: ToolConfig<ListWorkflowRunsParams, ListWorkflowRunsResponse> = {
id: 'github_list_workflow_runs',
name: 'GitHub List Workflow Runs',
description:
'List workflow runs for a repository. Supports filtering by actor, branch, event, and status. Returns run details including status, conclusion, and links.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
actor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by user who triggered the workflow',
},
branch: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by branch name',
},
event: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by event type (e.g., push, pull_request, workflow_dispatch)',
},
status: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter by status (queued, in_progress, completed, waiting, requested, pending)',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (default: 30, max: 100)',
default: 30,
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number of results to fetch (default: 1)',
default: 1,
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) => {
const url = new URL(
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/runs`
)
if (params.actor) {
url.searchParams.append('actor', params.actor)
}
if (params.branch) {
url.searchParams.append('branch', params.branch)
}
if (params.event) {
url.searchParams.append('event', params.event)
}
if (params.status) {
url.searchParams.append('status', params.status)
}
if (params.per_page) {
url.searchParams.append('per_page', params.per_page.toString())
}
if (params.page) {
url.searchParams.append('page', params.page.toString())
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const data = await response.json()
const statusCounts = data.workflow_runs.reduce((acc: Record<string, number>, run: any) => {
acc[run.status] = (acc[run.status] || 0) + 1
return acc
}, {})
const conclusionCounts = data.workflow_runs.reduce((acc: Record<string, number>, run: any) => {
if (run.conclusion) {
acc[run.conclusion] = (acc[run.conclusion] || 0) + 1
}
return acc
}, {})
const statusSummary = Object.entries(statusCounts)
.map(([status, count]) => `${count} ${status}`)
.join(', ')
const conclusionSummary = Object.entries(conclusionCounts)
.map(([conclusion, count]) => `${count} ${conclusion}`)
.join(', ')
const content = `Found ${data.total_count} workflow run(s)
Status: ${statusSummary}
${conclusionSummary ? `Conclusion: ${conclusionSummary}` : ''}
Recent runs:
${data.workflow_runs
.slice(0, 10)
.map(
(run: any) =>
`- Run #${run.run_number}: ${run.name} (${run.status}${run.conclusion ? ` - ${run.conclusion}` : ''})
Branch: ${run.head_branch}
Triggered by: ${run.triggering_actor?.login || 'Unknown'}
URL: ${run.html_url}`
)
.join('\n')}`
return {
success: true,
output: {
content,
metadata: {
total_count: data.total_count,
workflow_runs: data.workflow_runs.map((run: any) => ({
id: run.id,
name: run.name,
status: run.status,
conclusion: run.conclusion,
html_url: run.html_url,
run_number: run.run_number,
})),
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable workflow runs summary' },
metadata: {
type: 'object',
description: 'Workflow runs metadata',
properties: {
total_count: { type: 'number', description: 'Total number of workflow runs' },
workflow_runs: {
type: 'array',
description: 'Array of workflow run objects',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Workflow run ID' },
name: { type: 'string', description: 'Workflow name' },
status: { type: 'string', description: 'Run status' },
conclusion: { type: 'string', description: 'Run conclusion' },
html_url: { type: 'string', description: 'GitHub web URL' },
run_number: { type: 'number', description: 'Run number' },
},
},
},
},
},
},
}

View File

@@ -0,0 +1,135 @@
import type { ListWorkflowsParams, ListWorkflowsResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const listWorkflowsTool: ToolConfig<ListWorkflowsParams, ListWorkflowsResponse> = {
id: 'github_list_workflows',
name: 'GitHub List Workflows',
description:
'List all workflows in a GitHub repository. Returns workflow details including ID, name, path, state, and badge URL.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results per page (default: 30, max: 100)',
default: 30,
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number of results to fetch (default: 1)',
default: 1,
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) => {
const url = new URL(
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/workflows`
)
if (params.per_page) {
url.searchParams.append('per_page', params.per_page.toString())
}
if (params.page) {
url.searchParams.append('page', params.page.toString())
}
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const data = await response.json()
const stateCounts = data.workflows.reduce((acc: Record<string, number>, workflow: any) => {
acc[workflow.state] = (acc[workflow.state] || 0) + 1
return acc
}, {})
const statesSummary = Object.entries(stateCounts)
.map(([state, count]) => `${count} ${state}`)
.join(', ')
const content = `Found ${data.total_count} workflow(s) in ${data.workflows[0]?.path.split('/')[0] || '.github/workflows'}
States: ${statesSummary}
Workflows:
${data.workflows
.map(
(w: any) =>
`- ${w.name} (${w.state})
Path: ${w.path}
ID: ${w.id}
Badge: ${w.badge_url}`
)
.join('\n')}`
return {
success: true,
output: {
content,
metadata: {
total_count: data.total_count,
workflows: data.workflows.map((workflow: any) => ({
id: workflow.id,
name: workflow.name,
path: workflow.path,
state: workflow.state,
badge_url: workflow.badge_url,
})),
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable workflows summary' },
metadata: {
type: 'object',
description: 'Workflows metadata',
properties: {
total_count: { type: 'number', description: 'Total number of workflows' },
workflows: {
type: 'array',
description: 'Array of workflow objects',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Workflow ID' },
name: { type: 'string', description: 'Workflow name' },
path: { type: 'string', description: 'Path to workflow file' },
state: { type: 'string', description: 'Workflow state (active/disabled)' },
badge_url: { type: 'string', description: 'Badge URL for workflow' },
},
},
},
},
},
},
}

View File

@@ -0,0 +1,122 @@
import type { MergePRParams, MergeResultResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const mergePRTool: ToolConfig<MergePRParams, MergeResultResponse> = {
id: 'github_merge_pr',
name: 'GitHub Merge Pull Request',
description: 'Merge a pull request in a GitHub repository',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
pullNumber: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Pull request number',
},
commit_title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Title for the merge commit',
},
commit_message: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Extra detail to append to merge commit message',
},
merge_method: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Merge method: merge, squash, or rebase',
default: 'merge',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/merge`,
method: 'PUT',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const body: Record<string, any> = {}
if (params.commit_title !== undefined) body.commit_title = params.commit_title
if (params.commit_message !== undefined) body.commit_message = params.commit_message
if (params.merge_method !== undefined) body.merge_method = params.merge_method
return body
},
},
transformResponse: async (response) => {
if (response.status === 405) {
const error = await response.json()
return {
success: false,
error: error.message || 'Pull request is not mergeable',
output: {
content: '',
metadata: {
sha: '',
merged: false,
message: error.message || 'Pull request is not mergeable',
},
},
}
}
const result = await response.json()
const content = `PR merged successfully!
SHA: ${result.sha}
Message: ${result.message}`
return {
success: true,
output: {
content,
metadata: {
sha: result.sha,
merged: result.merged,
message: result.message,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable merge confirmation' },
metadata: {
type: 'object',
description: 'Merge result metadata',
properties: {
sha: { type: 'string', description: 'Merge commit SHA' },
merged: { type: 'boolean', description: 'Whether merge was successful' },
message: { type: 'string', description: 'Response message' },
},
},
},
}

View File

@@ -47,17 +47,14 @@ export const prTool: ToolConfig<PROperationParams, PullRequestResponse> = {
transformResponse: async (response) => {
const pr = await response.json()
// Fetch the PR diff
const diffResponse = await fetch(pr.diff_url)
const _diff = await diffResponse.text()
// Fetch files changed
const filesResponse = await fetch(
`https://api.github.com/repos/${pr.base.repo.owner.login}/${pr.base.repo.name}/pulls/${pr.number}/files`
)
const files = await filesResponse.json()
// Create a human-readable content string
const content = `PR #${pr.number}: "${pr.title}" (${pr.state}) - Created: ${pr.created_at}, Updated: ${pr.updated_at}
Description: ${pr.body || 'No description'}
Files changed: ${files.length}

View File

@@ -0,0 +1,102 @@
import type { LabelsResponse, RemoveLabelParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const removeLabelTool: ToolConfig<RemoveLabelParams, LabelsResponse> = {
id: 'github_remove_label',
name: 'GitHub Remove Label',
description: 'Remove a label from an issue in a GitHub repository',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
issue_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue number',
},
name: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Label name to remove',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/labels/${encodeURIComponent(params.name)}`,
method: 'DELETE',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response, params) => {
if (!params) {
return {
success: false,
error: 'Missing parameters',
output: {
content: '',
metadata: {
labels: [],
issue_number: 0,
html_url: '',
},
},
}
}
const labelsData = await response.json()
const labels = labelsData.map((label: any) => label.name)
const content = `Label "${params.name}" removed from issue #${params.issue_number}
${labels.length > 0 ? `Remaining labels: ${labels.join(', ')}` : 'No labels remaining on this issue'}`
return {
success: true,
output: {
content,
metadata: {
labels,
issue_number: params.issue_number,
html_url: `https://github.com/${params.owner}/${params.repo}/issues/${params.issue_number}`,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable label removal confirmation' },
metadata: {
type: 'object',
description: 'Remaining labels metadata',
properties: {
labels: { type: 'array', description: 'Labels remaining on the issue after removal' },
issue_number: { type: 'number', description: 'Issue number' },
html_url: { type: 'string', description: 'GitHub issue URL' },
},
},
},
}

View File

@@ -42,7 +42,6 @@ export const repoInfoTool: ToolConfig<BaseGitHubParams, RepoInfoResponse> = {
transformResponse: async (response) => {
const data = await response.json()
// Create a human-readable content string
const content = `Repository: ${data.name}
Description: ${data.description || 'No description'}
Language: ${data.language || 'Not specified'}

View File

@@ -0,0 +1,147 @@
import type { RequestReviewersParams, ReviewersResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const requestReviewersTool: ToolConfig<RequestReviewersParams, ReviewersResponse> = {
id: 'github_request_reviewers',
name: 'GitHub Request Reviewers',
description: 'Request reviewers for a pull request',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
pullNumber: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Pull request number',
},
reviewers: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comma-separated list of user logins to request reviews from',
},
team_reviewers: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated list of team slugs to request reviews from',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/requested_reviewers`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const reviewersArray = params.reviewers
.split(',')
.map((r) => r.trim())
.filter((r) => r)
const body: Record<string, any> = {
reviewers: reviewersArray,
}
if (params.team_reviewers) {
const teamReviewersArray = params.team_reviewers
.split(',')
.map((t) => t.trim())
.filter((t) => t)
if (teamReviewersArray.length > 0) {
body.team_reviewers = teamReviewersArray
}
}
return body
},
},
transformResponse: async (response) => {
const pr = await response.json()
const reviewers = pr.requested_reviewers || []
const teams = pr.requested_teams || []
const reviewersList = reviewers.map((r: any) => r.login).join(', ')
const teamsList = teams.map((t: any) => t.name).join(', ')
let content = `Review requested for PR #${pr.number}
Reviewers: ${reviewersList || 'None'}`
if (teamsList) {
content += `
Team Reviewers: ${teamsList}`
}
return {
success: true,
output: {
content,
metadata: {
requested_reviewers: reviewers.map((r: any) => ({
login: r.login,
id: r.id,
})),
requested_teams: teams.length
? teams.map((t: any) => ({
name: t.name,
id: t.id,
}))
: undefined,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable reviewer request confirmation' },
metadata: {
type: 'object',
description: 'Requested reviewers metadata',
properties: {
requested_reviewers: {
type: 'array',
description: 'Array of requested reviewer users',
items: {
type: 'object',
properties: {
login: { type: 'string', description: 'User login' },
id: { type: 'number', description: 'User ID' },
},
},
},
requested_teams: {
type: 'array',
description: 'Array of requested reviewer teams',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Team name' },
id: { type: 'number', description: 'Team ID' },
},
},
},
},
},
},
}

View File

@@ -0,0 +1,102 @@
import type { RerunWorkflowParams, RerunWorkflowResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const rerunWorkflowTool: ToolConfig<RerunWorkflowParams, RerunWorkflowResponse> = {
id: 'github_rerun_workflow',
name: 'GitHub Rerun Workflow',
description:
'Rerun a workflow run. Optionally enable debug logging for the rerun. Returns 201 Created on success.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
run_id: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Workflow run ID to rerun',
},
enable_debug_logging: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Enable debug logging for the rerun (default: false)',
default: false,
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/runs/${params.run_id}/rerun`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => ({
...(params.enable_debug_logging !== undefined && {
enable_debug_logging: params.enable_debug_logging,
}),
}),
},
transformResponse: async (response, params) => {
if (!params) {
return {
success: false,
error: 'Missing parameters',
output: {
content: '',
metadata: {
run_id: 0,
status: 'error',
},
},
}
}
const content = `Workflow run #${params.run_id} has been queued for rerun.${params.enable_debug_logging ? '\nDebug logging is enabled for this rerun.' : ''}
The rerun should start shortly.`
return {
success: true,
output: {
content,
metadata: {
run_id: params.run_id,
status: 'rerun_initiated',
},
},
}
},
outputs: {
content: { type: 'string', description: 'Rerun confirmation message' },
metadata: {
type: 'object',
description: 'Rerun metadata',
properties: {
run_id: { type: 'number', description: 'Workflow run ID' },
status: { type: 'string', description: 'Rerun status (rerun_initiated)' },
},
},
},
}

View File

@@ -0,0 +1,85 @@
import type { TriggerWorkflowParams, TriggerWorkflowResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const triggerWorkflowTool: ToolConfig<TriggerWorkflowParams, TriggerWorkflowResponse> = {
id: 'github_trigger_workflow',
name: 'GitHub Trigger Workflow',
description:
'Trigger a workflow dispatch event for a GitHub Actions workflow. The workflow must have a workflow_dispatch trigger configured. Returns 204 No Content on success.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
workflow_id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow ID (number) or workflow filename (e.g., "main.yaml")',
},
ref: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Git reference (branch or tag name) to run the workflow on',
},
inputs: {
type: 'object',
required: false,
visibility: 'user-or-llm',
description: 'Input keys and values configured in the workflow file',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/actions/workflows/${params.workflow_id}/dispatches`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => ({
ref: params.ref,
...(params.inputs && { inputs: params.inputs }),
}),
},
transformResponse: async (response) => {
const content = `Workflow dispatched successfully on ref: ${response.url.includes('ref') ? 'requested ref' : 'default branch'}
The workflow run should start shortly.`
return {
success: true,
output: {
content,
metadata: {},
},
}
},
outputs: {
content: { type: 'string', description: 'Confirmation message' },
metadata: {
type: 'object',
description: 'Empty metadata object (204 No Content response)',
},
},
}

View File

@@ -28,6 +28,62 @@ export interface LatestCommitParams extends BaseGitHubParams {
branch?: string
}
// Create PR parameters
export interface CreatePRParams extends BaseGitHubParams {
title: string
head: string
base: string
body?: string
draft?: boolean
}
// Update PR parameters
export interface UpdatePRParams extends BaseGitHubParams {
pullNumber: number
title?: string
body?: string
state?: 'open' | 'closed'
base?: string
}
// Merge PR parameters
export interface MergePRParams extends BaseGitHubParams {
pullNumber: number
commit_title?: string
commit_message?: string
merge_method?: 'merge' | 'squash' | 'rebase'
}
// List PRs parameters
export interface ListPRsParams extends BaseGitHubParams {
state?: 'open' | 'closed' | 'all'
head?: string
base?: string
sort?: 'created' | 'updated' | 'popularity' | 'long-running'
direction?: 'asc' | 'desc'
per_page?: number
page?: number
}
// Get PR files parameters
export interface GetPRFilesParams extends BaseGitHubParams {
pullNumber: number
per_page?: number
page?: number
}
// Close PR parameters
export interface ClosePRParams extends BaseGitHubParams {
pullNumber: number
}
// Request reviewers parameters
export interface RequestReviewersParams extends BaseGitHubParams {
pullNumber: number
reviewers: string
team_reviewers?: string
}
// Response metadata interfaces
interface BasePRMetadata {
number: number
@@ -119,6 +175,64 @@ interface RepoMetadata {
language: string
}
// PR operation response metadata
interface PRMetadata {
number: number
title: string
state: string
html_url: string
merged: boolean
draft: boolean
created_at?: string
updated_at?: string
merge_commit_sha?: string
}
interface MergeResultMetadata {
sha: string
merged: boolean
message: string
}
interface PRListMetadata {
prs: Array<{
number: number
title: string
state: string
html_url: string
created_at: string
updated_at: string
}>
total_count: number
open_count?: number
closed_count?: number
}
interface PRFilesListMetadata {
files: Array<{
filename: string
status: string
additions: number
deletions: number
changes: number
patch?: string
blob_url: string
raw_url: string
}>
total_count: number
}
interface ReviewersMetadata {
requested_reviewers: Array<{
login: string
id: number
}>
requested_teams?: Array<{
name: string
id: number
}>
}
// Response types
export interface PullRequestResponse extends ToolResponse {
output: {
@@ -148,8 +262,771 @@ export interface RepoInfoResponse extends ToolResponse {
}
}
// Issue comment operation parameters
export interface CreateIssueCommentParams extends BaseGitHubParams {
issue_number: number
body: string
}
export interface ListIssueCommentsParams extends BaseGitHubParams {
issue_number: number
since?: string
per_page?: number
page?: number
}
export interface UpdateCommentParams extends BaseGitHubParams {
comment_id: number
body: string
}
export interface DeleteCommentParams extends BaseGitHubParams {
comment_id: number
}
export interface ListPRCommentsParams extends BaseGitHubParams {
pullNumber: number
sort?: 'created' | 'updated'
direction?: 'asc' | 'desc'
since?: string
per_page?: number
page?: number
}
// Branch operation parameters
export interface ListBranchesParams extends BaseGitHubParams {
protected?: boolean
per_page?: number
page?: number
}
export interface GetBranchParams extends BaseGitHubParams {
branch: string
}
export interface CreateBranchParams extends BaseGitHubParams {
branch: string
sha: string // commit SHA to point to
}
export interface DeleteBranchParams extends BaseGitHubParams {
branch: string
}
export interface GetBranchProtectionParams extends BaseGitHubParams {
branch: string
}
export interface UpdateBranchProtectionParams extends BaseGitHubParams {
branch: string
required_status_checks: {
strict: boolean
contexts: string[]
} | null
enforce_admins: boolean
required_pull_request_reviews: {
required_approving_review_count?: number
dismiss_stale_reviews?: boolean
require_code_owner_reviews?: boolean
} | null
restrictions: {
users: string[]
teams: string[]
} | null
}
// Issue comment response metadata
interface IssueCommentMetadata {
id: number
html_url: string
body: string
created_at: string
updated_at: string
user: {
login: string
id: number
}
}
interface CommentsListMetadata {
comments: Array<{
id: number
body: string
user: { login: string }
created_at: string
html_url: string
}>
total_count: number
}
// Response types for new tools
export interface IssueCommentResponse extends ToolResponse {
output: {
content: string
metadata: IssueCommentMetadata
}
}
export interface CommentsListResponse extends ToolResponse {
output: {
content: string
metadata: CommentsListMetadata
}
}
export interface DeleteCommentResponse extends ToolResponse {
output: {
content: string
metadata: {
deleted: boolean
comment_id: number
}
}
}
// New PR operation response types
export interface PRResponse extends ToolResponse {
output: {
content: string
metadata: PRMetadata
}
}
export interface MergeResultResponse extends ToolResponse {
output: {
content: string
metadata: MergeResultMetadata
}
}
export interface PRListResponse extends ToolResponse {
output: {
content: string
metadata: PRListMetadata
}
}
export interface PRFilesListResponse extends ToolResponse {
output: {
content: string
metadata: PRFilesListMetadata
}
}
export interface ReviewersResponse extends ToolResponse {
output: {
content: string
metadata: ReviewersMetadata
}
}
// Branch response metadata
interface BranchMetadata {
name: string
commit: {
sha: string
url: string
}
protected: boolean
}
interface BranchListMetadata {
branches: Array<{
name: string
commit: {
sha: string
url: string
}
protected: boolean
}>
total_count: number
}
interface BranchProtectionMetadata {
required_status_checks: {
strict: boolean
contexts: string[]
} | null
enforce_admins: {
enabled: boolean
}
required_pull_request_reviews: {
required_approving_review_count: number
dismiss_stale_reviews: boolean
require_code_owner_reviews: boolean
} | null
restrictions: {
users: string[]
teams: string[]
} | null
}
interface RefMetadata {
ref: string
url: string
sha: string
}
interface DeleteBranchMetadata {
deleted: boolean
branch: string
}
// Branch response types
export interface BranchResponse extends ToolResponse {
output: {
content: string
metadata: BranchMetadata
}
}
export interface BranchListResponse extends ToolResponse {
output: {
content: string
metadata: BranchListMetadata
}
}
export interface BranchProtectionResponse extends ToolResponse {
output: {
content: string
metadata: BranchProtectionMetadata
}
}
export interface RefResponse extends ToolResponse {
output: {
content: string
metadata: RefMetadata
}
}
export interface DeleteBranchResponse extends ToolResponse {
output: {
content: string
metadata: DeleteBranchMetadata
}
}
// GitHub Projects V2 parameters
export interface ListProjectsParams {
owner_type: 'org' | 'user'
owner_login: string
apiKey: string
}
export interface GetProjectParams {
owner_type: 'org' | 'user'
owner_login: string
project_number: number
apiKey: string
}
export interface CreateProjectParams {
owner_id: string // Node ID
title: string
apiKey: string
}
export interface UpdateProjectParams {
project_id: string // Node ID
title?: string
shortDescription?: string
project_public?: boolean
closed?: boolean
apiKey: string
}
export interface DeleteProjectParams {
project_id: string
apiKey: string
}
// GitHub Projects V2 response metadata
interface ProjectMetadata {
id: string
title: string
number?: number
url: string
closed?: boolean
public?: boolean
shortDescription?: string
}
// GitHub Projects V2 response types
export interface ListProjectsResponse extends ToolResponse {
output: {
content: string
metadata: {
projects: ProjectMetadata[]
totalCount: number
}
}
}
export interface ProjectResponse extends ToolResponse {
output: {
content: string
metadata: ProjectMetadata
}
}
// Workflow operation parameters
export interface ListWorkflowsParams extends BaseGitHubParams {
per_page?: number
page?: number
}
export interface GetWorkflowParams extends BaseGitHubParams {
workflow_id: number | string
}
export interface TriggerWorkflowParams extends BaseGitHubParams {
workflow_id: number | string
ref: string // branch or tag name
inputs?: Record<string, string>
}
export interface ListWorkflowRunsParams extends BaseGitHubParams {
actor?: string
branch?: string
event?: string
status?: string
per_page?: number
page?: number
}
export interface GetWorkflowRunParams extends BaseGitHubParams {
run_id: number
}
export interface CancelWorkflowRunParams extends BaseGitHubParams {
run_id: number
}
export interface RerunWorkflowParams extends BaseGitHubParams {
run_id: number
enable_debug_logging?: boolean
}
// Workflow response metadata interfaces
interface WorkflowMetadata {
id: number
name: string
path: string
state: string
badge_url: string
}
interface WorkflowRunMetadata {
id: number
name: string
status: string
conclusion: string
html_url: string
run_number: number
}
interface ListWorkflowsMetadata {
total_count: number
workflows: Array<{
id: number
name: string
path: string
state: string
badge_url: string
}>
}
interface ListWorkflowRunsMetadata {
total_count: number
workflow_runs: Array<{
id: number
name: string
status: string
conclusion: string
html_url: string
run_number: number
}>
}
// Workflow response types
export interface WorkflowResponse extends ToolResponse {
output: {
content: string
metadata: WorkflowMetadata
}
}
export interface WorkflowRunResponse extends ToolResponse {
output: {
content: string
metadata: WorkflowRunMetadata
}
}
export interface ListWorkflowsResponse extends ToolResponse {
output: {
content: string
metadata: ListWorkflowsMetadata
}
}
export interface ListWorkflowRunsResponse extends ToolResponse {
output: {
content: string
metadata: ListWorkflowRunsMetadata
}
}
export interface TriggerWorkflowResponse extends ToolResponse {
output: {
content: string
metadata: Record<string, never>
}
}
export interface CancelWorkflowRunResponse extends ToolResponse {
output: {
content: string
metadata: {
run_id: number
status: string
}
}
}
export interface RerunWorkflowResponse extends ToolResponse {
output: {
content: string
metadata: {
run_id: number
status: string
}
}
}
export type GitHubResponse =
| PullRequestResponse
| CreateCommentResponse
| LatestCommitResponse
| RepoInfoResponse
| IssueCommentResponse
| CommentsListResponse
| DeleteCommentResponse
| PRResponse
| MergeResultResponse
| PRListResponse
| PRFilesListResponse
| ReviewersResponse
| ListProjectsResponse
| ProjectResponse
| BranchResponse
| BranchListResponse
| BranchProtectionResponse
| RefResponse
| DeleteBranchResponse
| WorkflowResponse
| WorkflowRunResponse
| ListWorkflowsResponse
| ListWorkflowRunsResponse
| TriggerWorkflowResponse
| CancelWorkflowRunResponse
| RerunWorkflowResponse
| IssueResponse
| IssuesListResponse
| LabelsResponse
// Release operation parameters
export interface CreateReleaseParams extends BaseGitHubParams {
tag_name: string
target_commitish?: string
name?: string
body?: string
draft?: boolean
prerelease?: boolean
}
export interface UpdateReleaseParams extends BaseGitHubParams {
release_id: number
tag_name?: string
target_commitish?: string
name?: string
body?: string
draft?: boolean
prerelease?: boolean
}
export interface ListReleasesParams extends BaseGitHubParams {
per_page?: number
page?: number
}
export interface GetReleaseParams extends BaseGitHubParams {
release_id: number
}
export interface DeleteReleaseParams extends BaseGitHubParams {
release_id: number
}
// Release metadata interface
interface ReleaseMetadata {
id: number
tag_name: string
name: string
html_url: string
tarball_url: string
zipball_url: string
draft: boolean
prerelease: boolean
created_at: string
published_at: string
}
// Response types for releases
export interface ReleaseResponse extends ToolResponse {
output: {
content: string
metadata: ReleaseMetadata
}
}
export interface ListReleasesResponse extends ToolResponse {
output: {
content: string
metadata: {
total_count: number
releases: Array<ReleaseMetadata>
}
}
}
export interface DeleteReleaseResponse extends ToolResponse {
output: {
content: string
metadata: {
deleted: boolean
release_id: number
}
}
}
// Issue operation parameters
export interface CreateIssueParams extends BaseGitHubParams {
title: string
body?: string
assignees?: string
labels?: string
milestone?: number
}
export interface UpdateIssueParams extends BaseGitHubParams {
issue_number: number
title?: string
body?: string
state?: 'open' | 'closed'
labels?: string[]
assignees?: string[]
}
export interface ListIssuesParams extends BaseGitHubParams {
state?: 'open' | 'closed' | 'all'
assignee?: string
creator?: string
labels?: string
sort?: 'created' | 'updated' | 'comments'
direction?: 'asc' | 'desc'
per_page?: number
page?: number
}
export interface GetIssueParams extends BaseGitHubParams {
issue_number: number
}
export interface CloseIssueParams extends BaseGitHubParams {
issue_number: number
state_reason?: 'completed' | 'not_planned'
}
export interface AddLabelsParams extends BaseGitHubParams {
issue_number: number
labels: string
}
export interface RemoveLabelParams extends BaseGitHubParams {
issue_number: number
name: string
}
export interface AddAssigneesParams extends BaseGitHubParams {
issue_number: number
assignees: string
}
// Issue response metadata
interface IssueMetadata {
number: number
title: string
state: string
html_url: string
labels: string[]
assignees: string[]
created_at?: string
updated_at?: string
closed_at?: string
body?: string
}
interface IssuesListMetadata {
issues: Array<{
number: number
title: string
state: string
html_url: string
labels: string[]
assignees: string[]
created_at: string
updated_at: string
}>
total_count: number
page?: number
}
interface LabelsMetadata {
labels: string[]
issue_number: number
html_url: string
}
// Issue response types
export interface IssueResponse extends ToolResponse {
output: {
content: string
metadata: IssueMetadata
}
}
export interface IssuesListResponse extends ToolResponse {
output: {
content: string
metadata: IssuesListMetadata
}
}
export interface LabelsResponse extends ToolResponse {
output: {
content: string
metadata: LabelsMetadata
}
}
export interface GetFileContentParams extends BaseGitHubParams {
path: string
ref?: string // branch, tag, or commit SHA
}
export interface CreateFileParams extends BaseGitHubParams {
path: string
message: string
content: string // Plain text (will be Base64 encoded internally)
branch?: string
}
export interface UpdateFileParams extends BaseGitHubParams {
path: string
message: string
content: string // Plain text (will be Base64 encoded internally)
sha: string // Required for update
branch?: string
}
export interface DeleteFileParams extends BaseGitHubParams {
path: string
message: string
sha: string // Required
branch?: string
}
export interface GetTreeParams extends BaseGitHubParams {
path?: string
ref?: string
}
// File/Content metadata interfaces
export interface FileContentMetadata {
name: string
path: string
sha: string
size: number
type: 'file' | 'dir'
download_url?: string
html_url?: string
}
export interface FileCommitMetadata {
sha: string
message: string
author: {
name: string
email: string
date: string
}
committer: {
name: string
email: string
date: string
}
html_url: string
}
export interface TreeItemMetadata {
name: string
path: string
sha: string
size: number
type: 'file' | 'dir' | 'symlink' | 'submodule'
download_url?: string
html_url?: string
}
// Response types
export interface FileContentResponse extends ToolResponse {
output: {
content: string
metadata: FileContentMetadata
}
}
export interface FileOperationResponse extends ToolResponse {
output: {
content: string
metadata: {
file: FileContentMetadata
commit: FileCommitMetadata
}
}
}
export interface DeleteFileResponse extends ToolResponse {
output: {
content: string
metadata: {
deleted: boolean
path: string
commit: FileCommitMetadata
}
}
}
export interface TreeResponse extends ToolResponse {
output: {
content: string
metadata: {
path: string
items: TreeItemMetadata[]
total_count: number
}
}
}

View File

@@ -0,0 +1,221 @@
import type { BranchProtectionResponse, UpdateBranchProtectionParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const updateBranchProtectionTool: ToolConfig<
UpdateBranchProtectionParams,
BranchProtectionResponse
> = {
id: 'github_update_branch_protection',
name: 'GitHub Update Branch Protection',
description:
'Update branch protection rules for a specific branch, including status checks, review requirements, admin enforcement, and push restrictions.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
branch: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Branch name',
},
required_status_checks: {
type: 'object',
required: true,
visibility: 'user-or-llm',
description:
'Required status check configuration (null to disable). Object with strict (boolean) and contexts (string array)',
},
enforce_admins: {
type: 'boolean',
required: true,
visibility: 'user-or-llm',
description: 'Whether to enforce restrictions for administrators',
},
required_pull_request_reviews: {
type: 'object',
required: true,
visibility: 'user-or-llm',
description:
'PR review requirements (null to disable). Object with optional required_approving_review_count, dismiss_stale_reviews, require_code_owner_reviews',
},
restrictions: {
type: 'object',
required: true,
visibility: 'user-or-llm',
description:
'Push restrictions (null to disable). Object with users (string array) and teams (string array)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/branches/${params.branch}/protection`,
method: 'PUT',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
'Content-Type': 'application/json',
}),
body: (params) => {
const body: any = {
required_status_checks: params.required_status_checks,
enforce_admins: params.enforce_admins,
required_pull_request_reviews: params.required_pull_request_reviews,
restrictions: params.restrictions,
}
return body
},
},
transformResponse: async (response) => {
const protection = await response.json()
let content = `Branch Protection updated successfully for "${protection.url.split('/branches/')[1].split('/protection')[0]}":
Enforce Admins: ${protection.enforce_admins?.enabled ? 'Yes' : 'No'}`
if (protection.required_status_checks) {
content += `\n\nRequired Status Checks:
- Strict: ${protection.required_status_checks.strict}
- Contexts: ${protection.required_status_checks.contexts.length > 0 ? protection.required_status_checks.contexts.join(', ') : 'None'}`
} else {
content += '\n\nRequired Status Checks: Disabled'
}
if (protection.required_pull_request_reviews) {
content += `\n\nRequired Pull Request Reviews:
- Required Approving Reviews: ${protection.required_pull_request_reviews.required_approving_review_count || 0}
- Dismiss Stale Reviews: ${protection.required_pull_request_reviews.dismiss_stale_reviews ? 'Yes' : 'No'}
- Require Code Owner Reviews: ${protection.required_pull_request_reviews.require_code_owner_reviews ? 'Yes' : 'No'}`
} else {
content += '\n\nRequired Pull Request Reviews: Disabled'
}
if (protection.restrictions) {
const users = protection.restrictions.users?.map((u: any) => u.login) || []
const teams = protection.restrictions.teams?.map((t: any) => t.slug) || []
content += `\n\nRestrictions:
- Users: ${users.length > 0 ? users.join(', ') : 'None'}
- Teams: ${teams.length > 0 ? teams.join(', ') : 'None'}`
} else {
content += '\n\nRestrictions: Disabled'
}
return {
success: true,
output: {
content,
metadata: {
required_status_checks: protection.required_status_checks
? {
strict: protection.required_status_checks.strict,
contexts: protection.required_status_checks.contexts,
}
: null,
enforce_admins: {
enabled: protection.enforce_admins?.enabled || false,
},
required_pull_request_reviews: protection.required_pull_request_reviews
? {
required_approving_review_count:
protection.required_pull_request_reviews.required_approving_review_count || 0,
dismiss_stale_reviews:
protection.required_pull_request_reviews.dismiss_stale_reviews || false,
require_code_owner_reviews:
protection.required_pull_request_reviews.require_code_owner_reviews || false,
}
: null,
restrictions: protection.restrictions
? {
users: protection.restrictions.users?.map((u: any) => u.login) || [],
teams: protection.restrictions.teams?.map((t: any) => t.slug) || [],
}
: null,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable branch protection update summary' },
metadata: {
type: 'object',
description: 'Updated branch protection configuration',
properties: {
required_status_checks: {
type: 'object',
description: 'Status check requirements (null if disabled)',
properties: {
strict: { type: 'boolean', description: 'Require branches to be up to date' },
contexts: {
type: 'array',
description: 'Required status check contexts',
items: { type: 'string' },
},
},
},
enforce_admins: {
type: 'object',
description: 'Admin enforcement settings',
properties: {
enabled: { type: 'boolean', description: 'Enforce for administrators' },
},
},
required_pull_request_reviews: {
type: 'object',
description: 'Pull request review requirements (null if disabled)',
properties: {
required_approving_review_count: {
type: 'number',
description: 'Number of approving reviews required',
},
dismiss_stale_reviews: {
type: 'boolean',
description: 'Dismiss stale pull request approvals',
},
require_code_owner_reviews: {
type: 'boolean',
description: 'Require review from code owners',
},
},
},
restrictions: {
type: 'object',
description: 'Push restrictions (null if disabled)',
properties: {
users: {
type: 'array',
description: 'Users who can push',
items: { type: 'string' },
},
teams: {
type: 'array',
description: 'Teams who can push',
items: { type: 'string' },
},
},
},
},
},
},
}

View File

@@ -0,0 +1,103 @@
import type { IssueCommentResponse, UpdateCommentParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const updateCommentTool: ToolConfig<UpdateCommentParams, IssueCommentResponse> = {
id: 'github_update_comment',
name: 'GitHub Comment Updater',
description: 'Update an existing comment on a GitHub issue or pull request',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
comment_id: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Comment ID',
},
body: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Updated comment content',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/comments/${params.comment_id}`,
method: 'PATCH',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => ({
body: params.body,
}),
},
transformResponse: async (response) => {
const data = await response.json()
const content = `Comment #${data.id} updated: "${data.body.substring(0, 100)}${data.body.length > 100 ? '...' : ''}"`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
html_url: data.html_url,
body: data.body,
created_at: data.created_at,
updated_at: data.updated_at,
user: {
login: data.user.login,
id: data.user.id,
},
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable update confirmation' },
metadata: {
type: 'object',
description: 'Updated comment metadata',
properties: {
id: { type: 'number', description: 'Comment ID' },
html_url: { type: 'string', description: 'GitHub web URL' },
body: { type: 'string', description: 'Updated comment body' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
user: {
type: 'object',
description: 'User who created the comment',
properties: {
login: { type: 'string', description: 'User login' },
id: { type: 'number', description: 'User ID' },
},
},
},
},
},
}

View File

@@ -0,0 +1,179 @@
import type { FileOperationResponse, UpdateFileParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const updateFileTool: ToolConfig<UpdateFileParams, FileOperationResponse> = {
id: 'github_update_file',
name: 'GitHub Update File',
description:
'Update an existing file in a GitHub repository. Requires the file SHA. Content will be automatically Base64 encoded. Supports files up to 1MB.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
path: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Path to the file to update (e.g., "src/index.ts")',
},
message: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Commit message for this file update',
},
content: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'New file content (plain text, will be Base64 encoded automatically)',
},
sha: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The blob SHA of the file being replaced (get from github_get_file_content)',
},
branch: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Branch to update the file in (defaults to repository default branch)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/contents/${params.path}`,
method: 'PUT',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const base64Content = Buffer.from(params.content).toString('base64')
const body: Record<string, any> = {
message: params.message,
content: base64Content,
sha: params.sha, // Required for update
}
if (params.branch) {
body.branch = params.branch
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
const content = `File updated successfully!
Path: ${data.content.path}
Name: ${data.content.name}
Size: ${data.content.size} bytes
New SHA: ${data.content.sha}
Commit:
- SHA: ${data.commit.sha}
- Message: ${data.commit.message}
- Author: ${data.commit.author.name}
- Date: ${data.commit.author.date}
View file: ${data.content.html_url}`
return {
success: true,
output: {
content,
metadata: {
file: {
name: data.content.name,
path: data.content.path,
sha: data.content.sha,
size: data.content.size,
type: data.content.type,
download_url: data.content.download_url,
html_url: data.content.html_url,
},
commit: {
sha: data.commit.sha,
message: data.commit.message,
author: {
name: data.commit.author.name,
email: data.commit.author.email,
date: data.commit.author.date,
},
committer: {
name: data.commit.committer.name,
email: data.commit.committer.email,
date: data.commit.committer.date,
},
html_url: data.commit.html_url,
},
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable file update confirmation' },
metadata: {
type: 'object',
description: 'Updated file and commit metadata',
properties: {
file: {
type: 'object',
description: 'Updated file information',
properties: {
name: { type: 'string', description: 'File name' },
path: { type: 'string', description: 'Full path in repository' },
sha: { type: 'string', description: 'New git blob SHA' },
size: { type: 'number', description: 'File size in bytes' },
type: { type: 'string', description: 'Content type' },
download_url: { type: 'string', description: 'Direct download URL' },
html_url: { type: 'string', description: 'GitHub web UI URL' },
},
},
commit: {
type: 'object',
description: 'Commit information',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
message: { type: 'string', description: 'Commit message' },
author: {
type: 'object',
description: 'Author information',
},
committer: {
type: 'object',
description: 'Committer information',
},
html_url: { type: 'string', description: 'Commit URL' },
},
},
},
},
},
}

View File

@@ -0,0 +1,139 @@
import type { IssueResponse, UpdateIssueParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const updateIssueTool: ToolConfig<UpdateIssueParams, IssueResponse> = {
id: 'github_update_issue',
name: 'GitHub Update Issue',
description: 'Update an existing issue in a GitHub repository',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
issue_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Issue number',
},
title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New issue title',
},
body: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New issue description/body',
},
state: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Issue state (open or closed)',
},
labels: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of label names (replaces all existing labels)',
},
assignees: {
type: 'array',
required: false,
visibility: 'user-or-llm',
description: 'Array of usernames (replaces all existing assignees)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}`,
method: 'PATCH',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const body: any = {}
if (params.title !== undefined) body.title = params.title
if (params.body !== undefined) body.body = params.body
if (params.state !== undefined) body.state = params.state
if (params.labels !== undefined) body.labels = params.labels
if (params.assignees !== undefined) body.assignees = params.assignees
return body
},
},
transformResponse: async (response) => {
const issue = await response.json()
const labels = issue.labels?.map((label: any) => label.name) || []
const assignees = issue.assignees?.map((assignee: any) => assignee.login) || []
const content = `Issue #${issue.number} updated: "${issue.title}"
State: ${issue.state}
URL: ${issue.html_url}
${labels.length > 0 ? `Labels: ${labels.join(', ')}` : ''}
${assignees.length > 0 ? `Assignees: ${assignees.join(', ')}` : ''}`
return {
success: true,
output: {
content,
metadata: {
number: issue.number,
title: issue.title,
state: issue.state,
html_url: issue.html_url,
labels,
assignees,
created_at: issue.created_at,
updated_at: issue.updated_at,
closed_at: issue.closed_at,
body: issue.body,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable issue update confirmation' },
metadata: {
type: 'object',
description: 'Updated issue metadata',
properties: {
number: { type: 'number', description: 'Issue number' },
title: { type: 'string', description: 'Issue title' },
state: { type: 'string', description: 'Issue state (open/closed)' },
html_url: { type: 'string', description: 'GitHub web URL' },
labels: { type: 'array', description: 'Array of label names' },
assignees: { type: 'array', description: 'Array of assignee usernames' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
closed_at: { type: 'string', description: 'Closed timestamp' },
body: { type: 'string', description: 'Issue body/description' },
},
},
},
}

View File

@@ -0,0 +1,121 @@
import type { PRResponse, UpdatePRParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const updatePRTool: ToolConfig<UpdatePRParams, PRResponse> = {
id: 'github_update_pr',
name: 'GitHub Update Pull Request',
description: 'Update an existing pull request in a GitHub repository',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
pullNumber: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Pull request number',
},
title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New pull request title',
},
body: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New pull request description (Markdown)',
},
state: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New state (open or closed)',
},
base: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New base branch name',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}`,
method: 'PATCH',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const body: Record<string, any> = {}
if (params.title !== undefined) body.title = params.title
if (params.body !== undefined) body.body = params.body
if (params.state !== undefined) body.state = params.state
if (params.base !== undefined) body.base = params.base
return body
},
},
transformResponse: async (response) => {
const pr = await response.json()
const content = `PR #${pr.number} updated: "${pr.title}" (${pr.state})
URL: ${pr.html_url}`
return {
success: true,
output: {
content,
metadata: {
number: pr.number,
title: pr.title,
state: pr.state,
html_url: pr.html_url,
merged: pr.merged,
draft: pr.draft,
created_at: pr.created_at,
updated_at: pr.updated_at,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable PR update confirmation' },
metadata: {
type: 'object',
description: 'Updated pull request metadata',
properties: {
number: { type: 'number', description: 'Pull request number' },
title: { type: 'string', description: 'PR title' },
state: { type: 'string', description: 'PR state (open/closed)' },
html_url: { type: 'string', description: 'GitHub web URL' },
merged: { type: 'boolean', description: 'Whether PR is merged' },
draft: { type: 'boolean', description: 'Whether PR is draft' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
},
},
},
}

View File

@@ -0,0 +1,192 @@
import type { ProjectResponse, UpdateProjectParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const updateProjectTool: ToolConfig<UpdateProjectParams, ProjectResponse> = {
id: 'github_update_project',
name: 'GitHub Update Project',
description:
'Update an existing GitHub Project V2. Can update title, description, visibility (public), or status (closed). Requires the project Node ID.',
version: '1.0.0',
params: {
project_id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Project Node ID (format: PVT_...)',
},
title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New project title',
},
shortDescription: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New project short description',
},
project_public: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Set project visibility (true = public, false = private)',
},
closed: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Set project status (true = closed, false = open)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token with project write permissions',
},
},
request: {
url: 'https://api.github.com/graphql',
method: 'POST',
headers: (params) => ({
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
}),
body: (params) => {
const inputFields: string[] = ['projectId: $projectId']
const variables: Record<string, any> = {
projectId: params.project_id,
}
if (params.title !== undefined) {
inputFields.push('title: $title')
variables.title = params.title
}
if (params.shortDescription !== undefined) {
inputFields.push('shortDescription: $shortDescription')
variables.shortDescription = params.shortDescription
}
if (params.project_public !== undefined) {
inputFields.push('public: $project_public')
variables.project_public = params.project_public
}
if (params.closed !== undefined) {
inputFields.push('closed: $closed')
variables.closed = params.closed
}
const variableDefs = ['$projectId: ID!']
if (params.title !== undefined) variableDefs.push('$title: String')
if (params.shortDescription !== undefined) variableDefs.push('$shortDescription: String')
if (params.project_public !== undefined) variableDefs.push('$project_public: Boolean')
if (params.closed !== undefined) variableDefs.push('$closed: Boolean')
const query = `
mutation(${variableDefs.join(', ')}) {
updateProjectV2(input: {
${inputFields.join('\n ')}
}) {
projectV2 {
id
title
number
url
closed
public
shortDescription
}
}
}
`
return {
query,
variables,
}
},
},
transformResponse: async (response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
output: {
content: `GraphQL Error: ${data.errors[0].message}`,
metadata: {
id: '',
title: '',
url: '',
},
},
error: data.errors[0].message,
}
}
const project = data.data?.updateProjectV2?.projectV2
if (!project) {
return {
success: false,
output: {
content: 'Failed to update project',
metadata: {
id: '',
title: '',
url: '',
},
},
error: 'Failed to update project',
}
}
let content = `Project updated successfully!\n`
content += `Title: ${project.title}\n`
content += `ID: ${project.id}\n`
content += `Number: ${project.number}\n`
content += `URL: ${project.url}\n`
content += `Status: ${project.closed ? 'Closed' : 'Open'}\n`
content += `Visibility: ${project.public ? 'Public' : 'Private'}\n`
if (project.shortDescription) {
content += `Description: ${project.shortDescription}`
}
return {
success: true,
output: {
content: content.trim(),
metadata: {
id: project.id,
title: project.title,
number: project.number,
url: project.url,
closed: project.closed,
public: project.public,
shortDescription: project.shortDescription || '',
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable confirmation message' },
metadata: {
type: 'object',
description: 'Updated project metadata',
properties: {
id: { type: 'string', description: 'Project node ID' },
title: { type: 'string', description: 'Project title' },
number: { type: 'number', description: 'Project number', optional: true },
url: { type: 'string', description: 'Project URL' },
closed: { type: 'boolean', description: 'Whether project is closed', optional: true },
public: { type: 'boolean', description: 'Whether project is public', optional: true },
shortDescription: {
type: 'string',
description: 'Project short description',
optional: true,
},
},
},
},
}

View File

@@ -0,0 +1,161 @@
import type { ReleaseResponse, UpdateReleaseParams } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const updateReleaseTool: ToolConfig<UpdateReleaseParams, ReleaseResponse> = {
id: 'github_update_release',
name: 'GitHub Update Release',
description:
'Update an existing GitHub release. Modify tag name, target commit, title, description, draft status, or prerelease status.',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner (user or organization)',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name',
},
release_id: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'The unique identifier of the release',
},
tag_name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The name of the tag',
},
target_commitish: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Specifies the commitish value for where the tag is created from',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The name of the release',
},
body: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Text describing the contents of the release (markdown supported)',
},
draft: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'true to set as draft, false to publish',
},
prerelease: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'true to identify as a prerelease, false for a full release',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub Personal Access Token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/releases/${params.release_id}`,
method: 'PATCH',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const body: any = {}
if (params.tag_name) {
body.tag_name = params.tag_name
}
if (params.target_commitish) {
body.target_commitish = params.target_commitish
}
if (params.name !== undefined) {
body.name = params.name
}
if (params.body !== undefined) {
body.body = params.body
}
if (params.draft !== undefined) {
body.draft = params.draft
}
if (params.prerelease !== undefined) {
body.prerelease = params.prerelease
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
const releaseType = data.draft ? 'Draft' : data.prerelease ? 'Prerelease' : 'Release'
const content = `${releaseType} updated: "${data.name || data.tag_name}"
Tag: ${data.tag_name}
URL: ${data.html_url}
Last updated: ${data.updated_at || data.created_at}
${data.published_at ? `Published: ${data.published_at}` : 'Not yet published'}
Download URLs:
- Tarball: ${data.tarball_url}
- Zipball: ${data.zipball_url}`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
tag_name: data.tag_name,
name: data.name || data.tag_name,
html_url: data.html_url,
tarball_url: data.tarball_url,
zipball_url: data.zipball_url,
draft: data.draft,
prerelease: data.prerelease,
created_at: data.created_at,
published_at: data.published_at,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable release update summary' },
metadata: {
type: 'object',
description: 'Updated release metadata including download URLs',
properties: {
id: { type: 'number', description: 'Release ID' },
tag_name: { type: 'string', description: 'Git tag name' },
name: { type: 'string', description: 'Release name' },
html_url: { type: 'string', description: 'GitHub web URL for the release' },
tarball_url: { type: 'string', description: 'URL to download release as tarball' },
zipball_url: { type: 'string', description: 'URL to download release as zipball' },
draft: { type: 'boolean', description: 'Whether this is a draft release' },
prerelease: { type: 'boolean', description: 'Whether this is a prerelease' },
created_at: { type: 'string', description: 'Creation timestamp' },
published_at: { type: 'string', description: 'Publication timestamp' },
},
},
},
}

View File

@@ -27,10 +27,58 @@ import { fileParseTool } from '@/tools/file'
import { crawlTool, scrapeTool, searchTool } from '@/tools/firecrawl'
import { functionExecuteTool } from '@/tools/function'
import {
githubAddAssigneesTool,
githubAddLabelsTool,
githubCancelWorkflowRunTool,
githubCloseIssueTool,
githubClosePRTool,
githubCommentTool,
githubCreateBranchTool,
githubCreateFileTool,
githubCreateIssueTool,
githubCreatePRTool,
githubCreateProjectTool,
githubCreateReleaseTool,
githubDeleteBranchTool,
githubDeleteCommentTool,
githubDeleteFileTool,
githubDeleteProjectTool,
githubDeleteReleaseTool,
githubGetBranchProtectionTool,
githubGetBranchTool,
githubGetFileContentTool,
githubGetIssueTool,
githubGetPRFilesTool,
githubGetProjectTool,
githubGetReleaseTool,
githubGetTreeTool,
githubGetWorkflowRunTool,
githubGetWorkflowTool,
githubIssueCommentTool,
githubLatestCommitTool,
githubListBranchesTool,
githubListIssueCommentsTool,
githubListIssuesTool,
githubListPRCommentsTool,
githubListPRsTool,
githubListProjectsTool,
githubListReleasesTool,
githubListWorkflowRunsTool,
githubListWorkflowsTool,
githubMergePRTool,
githubPrTool,
githubRemoveLabelTool,
githubRepoInfoTool,
githubRequestReviewersTool,
githubRerunWorkflowTool,
githubTriggerWorkflowTool,
githubUpdateBranchProtectionTool,
githubUpdateCommentTool,
githubUpdateFileTool,
githubUpdateIssueTool,
githubUpdatePRTool,
githubUpdateProjectTool,
githubUpdateReleaseTool,
} from '@/tools/github'
import {
gmailAddLabelTool,
@@ -391,6 +439,54 @@ export const tools: Record<string, ToolConfig> = {
mysql_execute: mysqlExecuteTool,
github_pr: githubPrTool,
github_comment: githubCommentTool,
github_issue_comment: githubIssueCommentTool,
github_list_issue_comments: githubListIssueCommentsTool,
github_update_comment: githubUpdateCommentTool,
github_delete_comment: githubDeleteCommentTool,
github_list_pr_comments: githubListPRCommentsTool,
github_create_pr: githubCreatePRTool,
github_update_pr: githubUpdatePRTool,
github_merge_pr: githubMergePRTool,
github_list_prs: githubListPRsTool,
github_get_pr_files: githubGetPRFilesTool,
github_close_pr: githubClosePRTool,
github_request_reviewers: githubRequestReviewersTool,
github_get_file_content: githubGetFileContentTool,
github_create_file: githubCreateFileTool,
github_update_file: githubUpdateFileTool,
github_delete_file: githubDeleteFileTool,
github_get_tree: githubGetTreeTool,
github_list_branches: githubListBranchesTool,
github_get_branch: githubGetBranchTool,
github_create_branch: githubCreateBranchTool,
github_delete_branch: githubDeleteBranchTool,
github_get_branch_protection: githubGetBranchProtectionTool,
github_update_branch_protection: githubUpdateBranchProtectionTool,
github_create_issue: githubCreateIssueTool,
github_update_issue: githubUpdateIssueTool,
github_list_issues: githubListIssuesTool,
github_get_issue: githubGetIssueTool,
github_close_issue: githubCloseIssueTool,
github_add_labels: githubAddLabelsTool,
github_remove_label: githubRemoveLabelTool,
github_add_assignees: githubAddAssigneesTool,
github_create_release: githubCreateReleaseTool,
github_update_release: githubUpdateReleaseTool,
github_list_releases: githubListReleasesTool,
github_get_release: githubGetReleaseTool,
github_delete_release: githubDeleteReleaseTool,
github_list_workflows: githubListWorkflowsTool,
github_get_workflow: githubGetWorkflowTool,
github_trigger_workflow: githubTriggerWorkflowTool,
github_list_workflow_runs: githubListWorkflowRunsTool,
github_get_workflow_run: githubGetWorkflowRunTool,
github_cancel_workflow_run: githubCancelWorkflowRunTool,
github_rerun_workflow: githubRerunWorkflowTool,
github_list_projects: githubListProjectsTool,
github_get_project: githubGetProjectTool,
github_create_project: githubCreateProjectTool,
github_update_project: githubUpdateProjectTool,
github_delete_project: githubDeleteProjectTool,
exa_search: exaSearchTool,
exa_get_contents: exaGetContentsTool,
exa_find_similar_links: exaFindSimilarLinksTool,

View File

@@ -1 +1,12 @@
export { githubIssueClosedTrigger } from './issue_closed'
export { githubIssueCommentTrigger } from './issue_comment'
export { githubIssueOpenedTrigger } from './issue_opened'
export { githubPRClosedTrigger } from './pr_closed'
export { githubPRCommentTrigger } from './pr_comment'
export { githubPRMergedTrigger } from './pr_merged'
export { githubPROpenedTrigger } from './pr_opened'
export { githubPRReviewedTrigger } from './pr_reviewed'
export { githubPushTrigger } from './push'
export { githubReleasePublishedTrigger } from './release_published'
export { githubWebhookTrigger } from './webhook'
export { githubWorkflowRunTrigger } from './workflow_run'

View File

@@ -0,0 +1,401 @@
import { GithubIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
export const githubIssueClosedTrigger: TriggerConfig = {
id: 'github_issue_closed',
name: 'GitHub Issue Closed',
provider: 'github',
description: 'Trigger workflow when an issue is closed in a GitHub repository',
version: '1.0.0',
icon: GithubIcon,
subBlocks: [
{
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_closed',
},
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'application/json', id: 'application/json' },
{
label: 'application/x-www-form-urlencoded',
id: 'application/x-www-form-urlencoded',
},
],
defaultValue: 'application/json',
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_closed',
},
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_closed',
},
},
{
id: 'sslVerification',
title: 'SSL Verification',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'enabled' },
{ label: 'Disabled', id: 'disabled' },
],
defaultValue: 'enabled',
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_closed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
type: 'text',
defaultValue: [
'Go to your GitHub Repository > Settings > Webhooks.',
'Click "Add webhook".',
'Paste the <strong>Webhook URL</strong> above into the "Payload URL" field.',
'Select your chosen Content Type from the dropdown.',
'Enter the <strong>Webhook Secret</strong> into the "Secret" field if you\'ve configured one.',
'Set SSL verification according to your selection.',
'Select "Let me select individual events" and check <strong>issues</strong> (<strong>closed</strong> action).',
'Ensure "Active" is checked and click "Add webhook".',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_closed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'github_issue_closed',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_closed',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',
type: 'code',
language: 'json',
defaultValue: JSON.stringify(
{
action: 'closed',
issue: {
id: 1234567890,
number: 123,
title: 'Bug: Application crashes on startup',
body: 'When I try to start the application, it immediately crashes with error code 500.',
state: 'closed',
state_reason: 'completed',
html_url: 'https://github.com/owner/repo/issues/123',
user: {
login: 'octocat',
id: 1,
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
html_url: 'https://github.com/octocat',
user_type: 'User',
},
labels: [
{
name: 'bug',
color: 'd73a4a',
},
],
assignees: [],
created_at: '2025-01-15T10:30:00Z',
updated_at: '2025-01-15T14:20:00Z',
closed_at: '2025-01-15T14:20:00Z',
},
repository: {
id: 123456,
name: 'repo-name',
full_name: 'owner/repo-name',
html_url: 'https://github.com/owner/repo-name',
repo_description: 'A sample repository',
private: false,
owner: {
login: 'owner',
id: 7890,
avatar_url: 'https://github.com/images/error/owner.gif',
owner_type: 'User',
},
},
sender: {
login: 'maintainer',
id: 2,
avatar_url: 'https://github.com/images/error/maintainer.gif',
user_type: 'User',
},
},
null,
2
),
readOnly: true,
collapsible: true,
defaultCollapsed: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_closed',
},
},
],
outputs: {
action: {
type: 'string',
description: 'Action performed (opened, closed, reopened, edited, etc.)',
},
issue: {
id: {
type: 'number',
description: 'Issue ID',
},
node_id: {
type: 'string',
description: 'Issue node ID',
},
number: {
type: 'number',
description: 'Issue number',
},
title: {
type: 'string',
description: 'Issue title',
},
body: {
type: 'string',
description: 'Issue body/description',
},
state: {
type: 'string',
description: 'Issue state (open, closed)',
},
state_reason: {
type: 'string',
description: 'Reason for state (completed, not_planned, reopened)',
},
html_url: {
type: 'string',
description: 'Issue HTML URL',
},
user: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
labels: {
type: 'array',
description: 'Array of label objects',
},
assignees: {
type: 'array',
description: 'Array of assigned users',
},
milestone: {
type: 'object',
description: 'Milestone object if assigned',
},
created_at: {
type: 'string',
description: 'Issue creation timestamp',
},
updated_at: {
type: 'string',
description: 'Issue last update timestamp',
},
closed_at: {
type: 'string',
description: 'Issue closed timestamp',
},
},
repository: {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name (owner/repo)',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
html_url: {
type: 'string',
description: 'Repository HTML URL',
},
repo_description: {
type: 'string',
description: 'Repository description',
},
fork: {
type: 'boolean',
description: 'Whether the repository is a fork',
},
url: {
type: 'string',
description: 'Repository API URL',
},
homepage: {
type: 'string',
description: 'Repository homepage URL',
},
size: {
type: 'number',
description: 'Repository size in KB',
},
stargazers_count: {
type: 'number',
description: 'Number of stars',
},
watchers_count: {
type: 'number',
description: 'Number of watchers',
},
language: {
type: 'string',
description: 'Primary programming language',
},
forks_count: {
type: 'number',
description: 'Number of forks',
},
open_issues_count: {
type: 'number',
description: 'Number of open issues',
},
default_branch: {
type: 'string',
description: 'Default branch name',
},
owner: {
login: {
type: 'string',
description: 'Owner username',
},
id: {
type: 'number',
description: 'Owner ID',
},
avatar_url: {
type: 'string',
description: 'Owner avatar URL',
},
html_url: {
type: 'string',
description: 'Owner profile URL',
},
owner_type: {
type: 'string',
description: 'Owner type (User, Organization)',
},
},
},
sender: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
},
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GitHub-Event': 'issues',
'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'X-Hub-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,409 @@
import { GithubIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
export const githubIssueCommentTrigger: TriggerConfig = {
id: 'github_issue_comment',
name: 'GitHub Issue Comment',
provider: 'github',
description: 'Trigger workflow when a comment is added to an issue (not pull requests)',
version: '1.0.0',
icon: GithubIcon,
subBlocks: [
{
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_comment',
},
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'application/json', id: 'application/json' },
{
label: 'application/x-www-form-urlencoded',
id: 'application/x-www-form-urlencoded',
},
],
defaultValue: 'application/json',
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_comment',
},
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_comment',
},
},
{
id: 'sslVerification',
title: 'SSL Verification',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'enabled' },
{ label: 'Disabled', id: 'disabled' },
],
defaultValue: 'enabled',
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_comment',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
type: 'text',
defaultValue: [
'Go to your GitHub Repository > Settings > Webhooks.',
'Click "Add webhook".',
'Paste the <strong>Webhook URL</strong> above into the "Payload URL" field.',
'Select your chosen Content Type from the dropdown.',
'Enter the <strong>Webhook Secret</strong> into the "Secret" field if you\'ve configured one.',
'Set SSL verification according to your selection.',
'Select "Let me select individual events" and check <strong>issue_comment</strong> (<strong>created, edited, deleted</strong> actions).',
'Ensure "Active" is checked and click "Add webhook".',
'<strong>Note:</strong> This trigger filters for issue comments only. For PR comments, use the "GitHub PR Comment" trigger.',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_comment',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'github_issue_comment',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_comment',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',
type: 'code',
language: 'json',
defaultValue: JSON.stringify(
{
action: 'created',
issue: {
number: 123,
title: 'Bug: Application crashes on startup',
state: 'open',
html_url: 'https://github.com/owner/repo/issues/123',
user: {
login: 'octocat',
id: 1,
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
user_type: 'User',
},
},
comment: {
id: 987654321,
body: 'I can confirm this bug. It happens on my machine too.',
html_url: 'https://github.com/owner/repo/issues/123#issuecomment-987654321',
user: {
login: 'commenter',
id: 3,
avatar_url: 'https://github.com/images/error/commenter.gif',
user_type: 'User',
},
created_at: '2025-01-15T11:00:00Z',
updated_at: '2025-01-15T11:00:00Z',
},
repository: {
id: 123456,
name: 'repo-name',
full_name: 'owner/repo-name',
html_url: 'https://github.com/owner/repo-name',
owner: {
login: 'owner',
id: 7890,
owner_type: 'User',
},
},
sender: {
login: 'commenter',
id: 3,
user_type: 'User',
},
},
null,
2
),
readOnly: true,
collapsible: true,
defaultCollapsed: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_comment',
},
},
],
outputs: {
action: {
type: 'string',
description: 'Action performed (created, edited, deleted)',
},
issue: {
number: {
type: 'number',
description: 'Issue number',
},
title: {
type: 'string',
description: 'Issue title',
},
state: {
type: 'string',
description: 'Issue state (open, closed)',
},
html_url: {
type: 'string',
description: 'Issue HTML URL',
},
user: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
},
comment: {
id: {
type: 'number',
description: 'Comment ID',
},
node_id: {
type: 'string',
description: 'Comment node ID',
},
body: {
type: 'string',
description: 'Comment text',
},
html_url: {
type: 'string',
description: 'Comment HTML URL',
},
user: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
created_at: {
type: 'string',
description: 'Comment creation timestamp',
},
updated_at: {
type: 'string',
description: 'Comment last update timestamp',
},
},
repository: {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name (owner/repo)',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
html_url: {
type: 'string',
description: 'Repository HTML URL',
},
repo_description: {
type: 'string',
description: 'Repository description',
},
fork: {
type: 'boolean',
description: 'Whether the repository is a fork',
},
url: {
type: 'string',
description: 'Repository API URL',
},
homepage: {
type: 'string',
description: 'Repository homepage URL',
},
size: {
type: 'number',
description: 'Repository size in KB',
},
stargazers_count: {
type: 'number',
description: 'Number of stars',
},
watchers_count: {
type: 'number',
description: 'Number of watchers',
},
language: {
type: 'string',
description: 'Primary programming language',
},
forks_count: {
type: 'number',
description: 'Number of forks',
},
open_issues_count: {
type: 'number',
description: 'Number of open issues',
},
default_branch: {
type: 'string',
description: 'Default branch name',
},
owner: {
login: {
type: 'string',
description: 'Owner username',
},
id: {
type: 'number',
description: 'Owner ID',
},
avatar_url: {
type: 'string',
description: 'Owner avatar URL',
},
html_url: {
type: 'string',
description: 'Owner profile URL',
},
owner_type: {
type: 'string',
description: 'Owner type (User, Organization)',
},
},
},
sender: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
},
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GitHub-Event': 'issue_comment',
'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'X-Hub-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,420 @@
import { GithubIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
export const githubIssueOpenedTrigger: TriggerConfig = {
id: 'github_issue_opened',
name: 'GitHub Issue Opened',
provider: 'github',
description: 'Trigger workflow when a new issue is opened in a GitHub repository',
version: '1.0.0',
icon: GithubIcon,
subBlocks: [
{
id: 'selectedTriggerId',
title: 'Trigger Type',
type: 'dropdown',
mode: 'trigger',
options: [
{ label: 'Issue Opened', id: 'github_issue_opened' },
{ label: 'Issue Closed', id: 'github_issue_closed' },
{ label: 'Issue Comment', id: 'github_issue_comment' },
{ label: 'PR Opened', id: 'github_pr_opened' },
{ label: 'PR Closed', id: 'github_pr_closed' },
{ label: 'PR Merged', id: 'github_pr_merged' },
{ label: 'PR Comment', id: 'github_pr_comment' },
{ label: 'PR Reviewed', id: 'github_pr_reviewed' },
{ label: 'Code Push', id: 'github_push' },
{ label: 'Release Published', id: 'github_release_published' },
{ label: 'Actions Workflow Run', id: 'github_workflow_run' },
],
value: () => 'github_issue_opened',
required: true,
},
{
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_opened',
},
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'application/json', id: 'application/json' },
{
label: 'application/x-www-form-urlencoded',
id: 'application/x-www-form-urlencoded',
},
],
defaultValue: 'application/json',
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_opened',
},
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_opened',
},
},
{
id: 'sslVerification',
title: 'SSL Verification',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'enabled' },
{ label: 'Disabled', id: 'disabled' },
],
defaultValue: 'enabled',
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_opened',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
type: 'text',
defaultValue: [
'Go to your GitHub Repository > Settings > Webhooks.',
'Click "Add webhook".',
'Paste the <strong>Webhook URL</strong> above into the "Payload URL" field.',
'Select your chosen Content Type from the dropdown.',
'Enter the <strong>Webhook Secret</strong> into the "Secret" field if you\'ve configured one.',
'Set SSL verification according to your selection.',
'Select "Let me select individual events" and check <strong>issues</strong> (<strong>opened</strong> action).',
'Ensure "Active" is checked and click "Add webhook".',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_opened',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'github_issue_opened',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_opened',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',
type: 'code',
language: 'json',
defaultValue: JSON.stringify(
{
action: 'opened',
issue: {
id: 1234567890,
number: 123,
title: 'Bug: Application crashes on startup',
body: 'When I try to start the application, it immediately crashes with error code 500.',
state: 'open',
html_url: 'https://github.com/owner/repo/issues/123',
user: {
login: 'octocat',
id: 1,
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
html_url: 'https://github.com/octocat',
user_type: 'User',
},
labels: [
{
name: 'bug',
color: 'd73a4a',
},
],
assignees: [],
created_at: '2025-01-15T10:30:00Z',
updated_at: '2025-01-15T10:30:00Z',
},
repository: {
id: 123456,
name: 'repo-name',
full_name: 'owner/repo-name',
html_url: 'https://github.com/owner/repo-name',
repo_description: 'A sample repository',
private: false,
owner: {
login: 'owner',
id: 7890,
avatar_url: 'https://github.com/images/error/owner.gif',
owner_type: 'User',
},
},
sender: {
login: 'octocat',
id: 1,
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
user_type: 'User',
},
},
null,
2
),
readOnly: true,
collapsible: true,
defaultCollapsed: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_issue_opened',
},
},
],
outputs: {
action: {
type: 'string',
description: 'Action performed (opened, closed, reopened, edited, etc.)',
},
issue: {
id: {
type: 'number',
description: 'Issue ID',
},
node_id: {
type: 'string',
description: 'Issue node ID',
},
number: {
type: 'number',
description: 'Issue number',
},
title: {
type: 'string',
description: 'Issue title',
},
body: {
type: 'string',
description: 'Issue body/description',
},
state: {
type: 'string',
description: 'Issue state (open, closed)',
},
state_reason: {
type: 'string',
description: 'Reason for state (completed, not_planned, reopened)',
},
html_url: {
type: 'string',
description: 'Issue HTML URL',
},
user: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
labels: {
type: 'array',
description: 'Array of label objects',
},
assignees: {
type: 'array',
description: 'Array of assigned users',
},
milestone: {
type: 'object',
description: 'Milestone object if assigned',
},
created_at: {
type: 'string',
description: 'Issue creation timestamp',
},
updated_at: {
type: 'string',
description: 'Issue last update timestamp',
},
closed_at: {
type: 'string',
description: 'Issue closed timestamp',
},
},
repository: {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name (owner/repo)',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
html_url: {
type: 'string',
description: 'Repository HTML URL',
},
repo_description: {
type: 'string',
description: 'Repository description',
},
fork: {
type: 'boolean',
description: 'Whether the repository is a fork',
},
url: {
type: 'string',
description: 'Repository API URL',
},
homepage: {
type: 'string',
description: 'Repository homepage URL',
},
size: {
type: 'number',
description: 'Repository size in KB',
},
stargazers_count: {
type: 'number',
description: 'Number of stars',
},
watchers_count: {
type: 'number',
description: 'Number of watchers',
},
language: {
type: 'string',
description: 'Primary programming language',
},
forks_count: {
type: 'number',
description: 'Number of forks',
},
open_issues_count: {
type: 'number',
description: 'Number of open issues',
},
default_branch: {
type: 'string',
description: 'Default branch name',
},
owner: {
login: {
type: 'string',
description: 'Owner username',
},
id: {
type: 'number',
description: 'Owner ID',
},
avatar_url: {
type: 'string',
description: 'Owner avatar URL',
},
html_url: {
type: 'string',
description: 'Owner profile URL',
},
owner_type: {
type: 'string',
description: 'Owner type (User, Organization)',
},
},
},
sender: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
},
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GitHub-Event': 'issues',
'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'X-Hub-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,518 @@
import { GithubIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
export const githubPRClosedTrigger: TriggerConfig = {
id: 'github_pr_closed',
name: 'GitHub PR Closed',
provider: 'github',
description:
'Trigger workflow when a pull request is closed without being merged (e.g., abandoned) in a GitHub repository',
version: '1.0.0',
icon: GithubIcon,
subBlocks: [
{
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_closed',
},
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'application/json', id: 'application/json' },
{
label: 'application/x-www-form-urlencoded',
id: 'application/x-www-form-urlencoded',
},
],
defaultValue: 'application/json',
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_closed',
},
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_closed',
},
},
{
id: 'sslVerification',
title: 'SSL Verification',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'enabled' },
{ label: 'Disabled', id: 'disabled' },
],
defaultValue: 'enabled',
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_closed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
type: 'text',
defaultValue: [
'Go to your GitHub Repository > Settings > Webhooks.',
'Click "Add webhook".',
'Paste the <strong>Webhook URL</strong> above into the "Payload URL" field.',
'Select your chosen Content Type from the dropdown.',
'Enter the <strong>Webhook Secret</strong> into the "Secret" field if you\'ve configured one.',
'Set SSL verification according to your selection.',
'Select "Let me select individual events" and check <strong>pull_request</strong> (<strong>closed</strong> action).',
'Ensure "Active" is checked and click "Add webhook".',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_closed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'github_pr_closed',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_closed',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',
type: 'code',
language: 'json',
defaultValue: JSON.stringify(
{
action: 'closed',
number: 42,
pull_request: {
id: 1234567890,
number: 42,
title: 'Add new feature',
body: 'This PR adds a new feature that improves performance.',
state: 'closed',
merged: false,
draft: false,
html_url: 'https://github.com/owner/repo/pull/42',
diff_url: 'https://github.com/owner/repo/pull/42.diff',
patch_url: 'https://github.com/owner/repo/pull/42.patch',
user: {
login: 'developer',
id: 5,
avatar_url: 'https://github.com/images/error/developer.gif',
html_url: 'https://github.com/developer',
user_type: 'User',
},
head: {
ref: 'feature-branch',
sha: 'abc123def456',
repo: {
name: 'repo-name',
full_name: 'owner/repo-name',
},
},
base: {
ref: 'main',
sha: '789ghi012jkl',
repo: {
name: 'repo-name',
full_name: 'owner/repo-name',
},
},
additions: 245,
deletions: 67,
changed_files: 12,
labels: [],
assignees: [],
requested_reviewers: [
{
login: 'reviewer1',
id: 6,
},
],
created_at: '2025-01-15T12:00:00Z',
updated_at: '2025-01-15T14:30:00Z',
closed_at: '2025-01-15T14:30:00Z',
},
repository: {
id: 123456,
name: 'repo-name',
full_name: 'owner/repo-name',
html_url: 'https://github.com/owner/repo-name',
repo_description: 'A sample repository',
private: false,
owner: {
login: 'owner',
id: 7890,
owner_type: 'User',
},
},
sender: {
login: 'developer',
id: 5,
user_type: 'User',
},
},
null,
2
),
readOnly: true,
collapsible: true,
defaultCollapsed: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_closed',
},
},
],
outputs: {
action: {
type: 'string',
description: 'Action performed (opened, closed, synchronize, reopened, edited, etc.)',
},
number: {
type: 'number',
description: 'Pull request number',
},
pull_request: {
id: {
type: 'number',
description: 'Pull request ID',
},
node_id: {
type: 'string',
description: 'Pull request node ID',
},
number: {
type: 'number',
description: 'Pull request number',
},
title: {
type: 'string',
description: 'Pull request title',
},
body: {
type: 'string',
description: 'Pull request description',
},
state: {
type: 'string',
description: 'Pull request state (open, closed)',
},
merged: {
type: 'boolean',
description: 'Whether the PR was merged',
},
merged_at: {
type: 'string',
description: 'Timestamp when PR was merged',
},
merged_by: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
draft: {
type: 'boolean',
description: 'Whether the PR is a draft',
},
html_url: {
type: 'string',
description: 'Pull request HTML URL',
},
diff_url: {
type: 'string',
description: 'Pull request diff URL',
},
patch_url: {
type: 'string',
description: 'Pull request patch URL',
},
user: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
head: {
ref: {
type: 'string',
description: 'Source branch name',
},
sha: {
type: 'string',
description: 'Source branch commit SHA',
},
repo: {
name: {
type: 'string',
description: 'Source repository name',
},
full_name: {
type: 'string',
description: 'Source repository full name',
},
},
},
base: {
ref: {
type: 'string',
description: 'Target branch name',
},
sha: {
type: 'string',
description: 'Target branch commit SHA',
},
repo: {
name: {
type: 'string',
description: 'Target repository name',
},
full_name: {
type: 'string',
description: 'Target repository full name',
},
},
},
additions: {
type: 'number',
description: 'Number of lines added',
},
deletions: {
type: 'number',
description: 'Number of lines deleted',
},
changed_files: {
type: 'number',
description: 'Number of files changed',
},
labels: {
type: 'array',
description: 'Array of label objects',
},
assignees: {
type: 'array',
description: 'Array of assigned users',
},
requested_reviewers: {
type: 'array',
description: 'Array of requested reviewers',
},
created_at: {
type: 'string',
description: 'Pull request creation timestamp',
},
updated_at: {
type: 'string',
description: 'Pull request last update timestamp',
},
closed_at: {
type: 'string',
description: 'Pull request closed timestamp',
},
},
repository: {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name (owner/repo)',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
html_url: {
type: 'string',
description: 'Repository HTML URL',
},
repo_description: {
type: 'string',
description: 'Repository description',
},
fork: {
type: 'boolean',
description: 'Whether the repository is a fork',
},
url: {
type: 'string',
description: 'Repository API URL',
},
homepage: {
type: 'string',
description: 'Repository homepage URL',
},
size: {
type: 'number',
description: 'Repository size in KB',
},
stargazers_count: {
type: 'number',
description: 'Number of stars',
},
watchers_count: {
type: 'number',
description: 'Number of watchers',
},
language: {
type: 'string',
description: 'Primary programming language',
},
forks_count: {
type: 'number',
description: 'Number of forks',
},
open_issues_count: {
type: 'number',
description: 'Number of open issues',
},
default_branch: {
type: 'string',
description: 'Default branch name',
},
owner: {
login: {
type: 'string',
description: 'Owner username',
},
id: {
type: 'number',
description: 'Owner ID',
},
avatar_url: {
type: 'string',
description: 'Owner avatar URL',
},
html_url: {
type: 'string',
description: 'Owner profile URL',
},
},
},
sender: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
},
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GitHub-Event': 'pull_request',
'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'X-Hub-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,450 @@
import { GithubIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
export const githubPRCommentTrigger: TriggerConfig = {
id: 'github_pr_comment',
name: 'GitHub PR Comment',
provider: 'github',
description: 'Trigger workflow when a comment is added to a pull request in a GitHub repository',
version: '1.0.0',
icon: GithubIcon,
subBlocks: [
{
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_comment',
},
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'application/json', id: 'application/json' },
{
label: 'application/x-www-form-urlencoded',
id: 'application/x-www-form-urlencoded',
},
],
defaultValue: 'application/json',
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_comment',
},
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_comment',
},
},
{
id: 'sslVerification',
title: 'SSL Verification',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'enabled' },
{ label: 'Disabled', id: 'disabled' },
],
defaultValue: 'enabled',
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_comment',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
type: 'text',
defaultValue: [
'Go to your GitHub Repository > Settings > Webhooks.',
'Click "Add webhook".',
'Paste the <strong>Webhook URL</strong> above into the "Payload URL" field.',
'Select your chosen Content Type from the dropdown.',
'Enter the <strong>Webhook Secret</strong> into the "Secret" field if you\'ve configured one.',
'Set SSL verification according to your selection.',
'Select "Let me select individual events" and check <strong>Issue comments</strong>.',
'Ensure "Active" is checked and click "Add webhook".',
'Note: Pull request comments use the issue_comment event. You can filter for PR comments by checking if issue.pull_request exists.',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_comment',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'github_pr_comment',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_comment',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',
type: 'code',
language: 'json',
defaultValue: JSON.stringify(
{
action: 'created',
issue: {
id: 1234567890,
number: 42,
title: 'Add new feature',
body: 'This PR adds a new feature that improves performance.',
state: 'open',
html_url: 'https://github.com/owner/repo/issues/42',
user: {
login: 'developer',
id: 5,
node_id: 'MDQ6VXNlcjU=',
avatar_url: 'https://github.com/images/error/developer.gif',
html_url: 'https://github.com/developer',
type: 'User',
},
labels: [],
assignees: [],
pull_request: {
url: 'https://api.github.com/repos/owner/repo/pulls/42',
html_url: 'https://github.com/owner/repo/pull/42',
diff_url: 'https://github.com/owner/repo/pull/42.diff',
patch_url: 'https://github.com/owner/repo/pull/42.patch',
},
created_at: '2025-01-15T12:00:00Z',
updated_at: '2025-01-15T12:15:00Z',
},
comment: {
id: 987654321,
node_id: 'MDEyOklzc3VlQ29tbWVudDk4NzY1NDMyMQ==',
url: 'https://api.github.com/repos/owner/repo/issues/comments/987654321',
html_url: 'https://github.com/owner/repo/issues/42#issuecomment-987654321',
body: 'Great work! This looks good to me.',
user: {
login: 'reviewer',
id: 6,
node_id: 'MDQ6VXNlcjY=',
avatar_url: 'https://github.com/images/error/reviewer.gif',
html_url: 'https://github.com/reviewer',
type: 'User',
},
created_at: '2025-01-15T12:15:00Z',
updated_at: '2025-01-15T12:15:00Z',
},
repository: {
id: 123456,
node_id: 'MDEwOlJlcG9zaXRvcnkxMjM0NTY=',
name: 'repo-name',
full_name: 'owner/repo-name',
html_url: 'https://github.com/owner/repo-name',
description: 'A sample repository',
private: false,
owner: {
login: 'owner',
id: 7890,
node_id: 'MDQ6VXNlcjc4OTA=',
avatar_url: 'https://github.com/images/error/owner.gif',
html_url: 'https://github.com/owner',
type: 'User',
},
},
sender: {
login: 'reviewer',
id: 6,
node_id: 'MDQ6VXNlcjY=',
avatar_url: 'https://github.com/images/error/reviewer.gif',
html_url: 'https://github.com/reviewer',
type: 'User',
},
},
null,
2
),
readOnly: true,
collapsible: true,
defaultCollapsed: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_comment',
},
},
],
outputs: {
action: {
type: 'string',
description: 'Action performed (created, edited, deleted)',
},
issue: {
id: {
type: 'number',
description: 'Issue ID',
},
node_id: {
type: 'string',
description: 'Issue node ID',
},
number: {
type: 'number',
description: 'Issue/PR number',
},
title: {
type: 'string',
description: 'Issue/PR title',
},
body: {
type: 'string',
description: 'Issue/PR description',
},
state: {
type: 'string',
description: 'Issue/PR state (open, closed)',
},
html_url: {
type: 'string',
description: 'Issue/PR HTML URL',
},
user: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
labels: {
type: 'array',
description: 'Array of label objects',
},
assignees: {
type: 'array',
description: 'Array of assigned users',
},
pull_request: {
url: {
type: 'string',
description: 'Pull request API URL (present only for PR comments)',
},
html_url: {
type: 'string',
description: 'Pull request HTML URL',
},
diff_url: {
type: 'string',
description: 'Pull request diff URL',
},
patch_url: {
type: 'string',
description: 'Pull request patch URL',
},
},
created_at: {
type: 'string',
description: 'Issue/PR creation timestamp',
},
updated_at: {
type: 'string',
description: 'Issue/PR last update timestamp',
},
},
comment: {
id: {
type: 'number',
description: 'Comment ID',
},
node_id: {
type: 'string',
description: 'Comment node ID',
},
url: {
type: 'string',
description: 'Comment API URL',
},
html_url: {
type: 'string',
description: 'Comment HTML URL',
},
body: {
type: 'string',
description: 'Comment text',
},
user: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
created_at: {
type: 'string',
description: 'Comment creation timestamp',
},
updated_at: {
type: 'string',
description: 'Comment last update timestamp',
},
},
repository: {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name (owner/repo)',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
html_url: {
type: 'string',
description: 'Repository HTML URL',
},
repo_description: {
type: 'string',
description: 'Repository description',
},
owner: {
login: {
type: 'string',
description: 'Owner username',
},
id: {
type: 'number',
description: 'Owner ID',
},
node_id: {
type: 'string',
description: 'Owner node ID',
},
avatar_url: {
type: 'string',
description: 'Owner avatar URL',
},
html_url: {
type: 'string',
description: 'Owner profile URL',
},
owner_type: {
type: 'string',
description: 'Owner type (User, Organization)',
},
},
},
sender: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
},
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GitHub-Event': 'issue_comment',
'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'X-Hub-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,525 @@
import { GithubIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
export const githubPRMergedTrigger: TriggerConfig = {
id: 'github_pr_merged',
name: 'GitHub PR Merged',
provider: 'github',
description: 'Trigger workflow when a pull request is successfully merged in a GitHub repository',
version: '1.0.0',
icon: GithubIcon,
subBlocks: [
{
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_merged',
},
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'application/json', id: 'application/json' },
{
label: 'application/x-www-form-urlencoded',
id: 'application/x-www-form-urlencoded',
},
],
defaultValue: 'application/json',
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_merged',
},
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_merged',
},
},
{
id: 'sslVerification',
title: 'SSL Verification',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'enabled' },
{ label: 'Disabled', id: 'disabled' },
],
defaultValue: 'enabled',
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_merged',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
type: 'text',
defaultValue: [
'Go to your GitHub Repository > Settings > Webhooks.',
'Click "Add webhook".',
'Paste the <strong>Webhook URL</strong> above into the "Payload URL" field.',
'Select your chosen Content Type from the dropdown.',
'Enter the <strong>Webhook Secret</strong> into the "Secret" field if you\'ve configured one.',
'Set SSL verification according to your selection.',
'Select "Let me select individual events" and check <strong>pull_request</strong>. Note: Merged PRs have <strong>action=\'closed\'</strong> AND <strong>merged=true</strong>.',
'Ensure "Active" is checked and click "Add webhook".',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_merged',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'github_pr_merged',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_merged',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',
type: 'code',
language: 'json',
defaultValue: JSON.stringify(
{
action: 'closed',
number: 42,
pull_request: {
id: 1234567890,
number: 42,
title: 'Add new feature',
body: 'This PR adds a new feature that improves performance.',
state: 'closed',
merged: true,
merge_commit_sha: 'mno345pqr678',
merged_at: '2025-01-15T13:30:00Z',
merged_by: {
login: 'maintainer',
id: 8,
avatar_url: 'https://github.com/images/error/maintainer.gif',
html_url: 'https://github.com/maintainer',
user_type: 'User',
},
draft: false,
html_url: 'https://github.com/owner/repo/pull/42',
diff_url: 'https://github.com/owner/repo/pull/42.diff',
patch_url: 'https://github.com/owner/repo/pull/42.patch',
user: {
login: 'developer',
id: 5,
avatar_url: 'https://github.com/images/error/developer.gif',
html_url: 'https://github.com/developer',
user_type: 'User',
},
head: {
ref: 'feature-branch',
sha: 'abc123def456',
repo: {
name: 'repo-name',
full_name: 'owner/repo-name',
},
},
base: {
ref: 'main',
sha: '789ghi012jkl',
repo: {
name: 'repo-name',
full_name: 'owner/repo-name',
},
},
additions: 245,
deletions: 67,
changed_files: 12,
labels: [],
assignees: [],
requested_reviewers: [
{
login: 'reviewer1',
id: 6,
},
],
created_at: '2025-01-15T12:00:00Z',
updated_at: '2025-01-15T13:30:00Z',
},
repository: {
id: 123456,
name: 'repo-name',
full_name: 'owner/repo-name',
html_url: 'https://github.com/owner/repo-name',
repo_description: 'A sample repository',
private: false,
owner: {
login: 'owner',
id: 7890,
owner_type: 'User',
},
},
sender: {
login: 'developer',
id: 5,
user_type: 'User',
},
},
null,
2
),
readOnly: true,
collapsible: true,
defaultCollapsed: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_merged',
},
},
],
outputs: {
action: {
type: 'string',
description: 'Action performed (opened, closed, synchronize, reopened, edited, etc.)',
},
number: {
type: 'number',
description: 'Pull request number',
},
pull_request: {
id: {
type: 'number',
description: 'Pull request ID',
},
node_id: {
type: 'string',
description: 'Pull request node ID',
},
number: {
type: 'number',
description: 'Pull request number',
},
title: {
type: 'string',
description: 'Pull request title',
},
body: {
type: 'string',
description: 'Pull request description',
},
state: {
type: 'string',
description: 'Pull request state (open, closed)',
},
merged: {
type: 'boolean',
description: 'Whether the PR was merged',
},
merged_at: {
type: 'string',
description: 'Timestamp when PR was merged',
},
merged_by: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
draft: {
type: 'boolean',
description: 'Whether the PR is a draft',
},
html_url: {
type: 'string',
description: 'Pull request HTML URL',
},
diff_url: {
type: 'string',
description: 'Pull request diff URL',
},
patch_url: {
type: 'string',
description: 'Pull request patch URL',
},
user: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
head: {
ref: {
type: 'string',
description: 'Source branch name',
},
sha: {
type: 'string',
description: 'Source branch commit SHA',
},
repo: {
name: {
type: 'string',
description: 'Source repository name',
},
full_name: {
type: 'string',
description: 'Source repository full name',
},
},
},
base: {
ref: {
type: 'string',
description: 'Target branch name',
},
sha: {
type: 'string',
description: 'Target branch commit SHA',
},
repo: {
name: {
type: 'string',
description: 'Target repository name',
},
full_name: {
type: 'string',
description: 'Target repository full name',
},
},
},
additions: {
type: 'number',
description: 'Number of lines added',
},
deletions: {
type: 'number',
description: 'Number of lines deleted',
},
changed_files: {
type: 'number',
description: 'Number of files changed',
},
labels: {
type: 'array',
description: 'Array of label objects',
},
assignees: {
type: 'array',
description: 'Array of assigned users',
},
requested_reviewers: {
type: 'array',
description: 'Array of requested reviewers',
},
created_at: {
type: 'string',
description: 'Pull request creation timestamp',
},
updated_at: {
type: 'string',
description: 'Pull request last update timestamp',
},
closed_at: {
type: 'string',
description: 'Pull request closed timestamp',
},
},
repository: {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name (owner/repo)',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
html_url: {
type: 'string',
description: 'Repository HTML URL',
},
repo_description: {
type: 'string',
description: 'Repository description',
},
fork: {
type: 'boolean',
description: 'Whether the repository is a fork',
},
url: {
type: 'string',
description: 'Repository API URL',
},
homepage: {
type: 'string',
description: 'Repository homepage URL',
},
size: {
type: 'number',
description: 'Repository size in KB',
},
stargazers_count: {
type: 'number',
description: 'Number of stars',
},
watchers_count: {
type: 'number',
description: 'Number of watchers',
},
language: {
type: 'string',
description: 'Primary programming language',
},
forks_count: {
type: 'number',
description: 'Number of forks',
},
open_issues_count: {
type: 'number',
description: 'Number of open issues',
},
default_branch: {
type: 'string',
description: 'Default branch name',
},
owner: {
login: {
type: 'string',
description: 'Owner username',
},
id: {
type: 'number',
description: 'Owner ID',
},
avatar_url: {
type: 'string',
description: 'Owner avatar URL',
},
html_url: {
type: 'string',
description: 'Owner profile URL',
},
},
},
sender: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
},
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GitHub-Event': 'pull_request',
'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'X-Hub-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,520 @@
import { GithubIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
export const githubPROpenedTrigger: TriggerConfig = {
id: 'github_pr_opened',
name: 'GitHub PR Opened',
provider: 'github',
description: 'Trigger workflow when a new pull request is opened in a GitHub repository',
version: '1.0.0',
icon: GithubIcon,
subBlocks: [
{
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_opened',
},
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'application/json', id: 'application/json' },
{
label: 'application/x-www-form-urlencoded',
id: 'application/x-www-form-urlencoded',
},
],
defaultValue: 'application/json',
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_opened',
},
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_opened',
},
},
{
id: 'sslVerification',
title: 'SSL Verification',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'enabled' },
{ label: 'Disabled', id: 'disabled' },
],
defaultValue: 'enabled',
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_opened',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
type: 'text',
defaultValue: [
'Go to your GitHub Repository > Settings > Webhooks.',
'Click "Add webhook".',
'Paste the <strong>Webhook URL</strong> above into the "Payload URL" field.',
'Select your chosen Content Type from the dropdown.',
'Enter the <strong>Webhook Secret</strong> into the "Secret" field if you\'ve configured one.',
'Set SSL verification according to your selection.',
'Select "Let me select individual events" and check <strong>pull_request</strong> (<strong>opened</strong> action).',
'Ensure "Active" is checked and click "Add webhook".',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_opened',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'github_pr_opened',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_opened',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',
type: 'code',
language: 'json',
defaultValue: JSON.stringify(
{
action: 'opened',
number: 42,
pull_request: {
id: 1234567890,
number: 42,
title: 'Add new feature',
body: 'This PR adds a new feature that improves performance.',
state: 'open',
merged: false,
draft: false,
html_url: 'https://github.com/owner/repo/pull/42',
diff_url: 'https://github.com/owner/repo/pull/42.diff',
patch_url: 'https://github.com/owner/repo/pull/42.patch',
user: {
login: 'developer',
id: 5,
avatar_url: 'https://github.com/images/error/developer.gif',
html_url: 'https://github.com/developer',
user_type: 'User',
},
head: {
ref: 'feature-branch',
sha: 'abc123def456',
repo: {
name: 'repo-name',
full_name: 'owner/repo-name',
},
},
base: {
ref: 'main',
sha: '789ghi012jkl',
repo: {
name: 'repo-name',
full_name: 'owner/repo-name',
},
},
additions: 245,
deletions: 67,
changed_files: 12,
labels: [],
assignees: [],
requested_reviewers: [
{
login: 'reviewer1',
id: 6,
},
],
created_at: '2025-01-15T12:00:00Z',
updated_at: '2025-01-15T12:00:00Z',
},
repository: {
id: 123456,
name: 'repo-name',
full_name: 'owner/repo-name',
html_url: 'https://github.com/owner/repo-name',
repo_description: 'A sample repository',
private: false,
owner: {
login: 'owner',
id: 7890,
owner_type: 'User',
},
},
sender: {
login: 'developer',
id: 5,
user_type: 'User',
},
},
null,
2
),
readOnly: true,
collapsible: true,
defaultCollapsed: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_opened',
},
},
],
outputs: {
action: {
type: 'string',
description: 'Action performed (opened, closed, synchronize, reopened, edited, etc.)',
},
number: {
type: 'number',
description: 'Pull request number',
},
pull_request: {
id: {
type: 'number',
description: 'Pull request ID',
},
node_id: {
type: 'string',
description: 'Pull request node ID',
},
number: {
type: 'number',
description: 'Pull request number',
},
title: {
type: 'string',
description: 'Pull request title',
},
body: {
type: 'string',
description: 'Pull request description',
},
state: {
type: 'string',
description: 'Pull request state (open, closed)',
},
merged: {
type: 'boolean',
description: 'Whether the PR was merged',
},
merged_at: {
type: 'string',
description: 'Timestamp when PR was merged',
},
merged_by: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
draft: {
type: 'boolean',
description: 'Whether the PR is a draft',
},
html_url: {
type: 'string',
description: 'Pull request HTML URL',
},
diff_url: {
type: 'string',
description: 'Pull request diff URL',
},
patch_url: {
type: 'string',
description: 'Pull request patch URL',
},
user: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
head: {
ref: {
type: 'string',
description: 'Source branch name',
},
sha: {
type: 'string',
description: 'Source branch commit SHA',
},
repo: {
name: {
type: 'string',
description: 'Source repository name',
},
full_name: {
type: 'string',
description: 'Source repository full name',
},
},
},
base: {
ref: {
type: 'string',
description: 'Target branch name',
},
sha: {
type: 'string',
description: 'Target branch commit SHA',
},
repo: {
name: {
type: 'string',
description: 'Target repository name',
},
full_name: {
type: 'string',
description: 'Target repository full name',
},
},
},
additions: {
type: 'number',
description: 'Number of lines added',
},
deletions: {
type: 'number',
description: 'Number of lines deleted',
},
changed_files: {
type: 'number',
description: 'Number of files changed',
},
labels: {
type: 'array',
description: 'Array of label objects',
},
assignees: {
type: 'array',
description: 'Array of assigned users',
},
requested_reviewers: {
type: 'array',
description: 'Array of requested reviewers',
},
created_at: {
type: 'string',
description: 'Pull request creation timestamp',
},
updated_at: {
type: 'string',
description: 'Pull request last update timestamp',
},
closed_at: {
type: 'string',
description: 'Pull request closed timestamp',
},
},
repository: {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name (owner/repo)',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
html_url: {
type: 'string',
description: 'Repository HTML URL',
},
repo_description: {
type: 'string',
description: 'Repository description',
},
fork: {
type: 'boolean',
description: 'Whether the repository is a fork',
},
url: {
type: 'string',
description: 'Repository API URL',
},
homepage: {
type: 'string',
description: 'Repository homepage URL',
},
size: {
type: 'number',
description: 'Repository size in KB',
},
stargazers_count: {
type: 'number',
description: 'Number of stars',
},
watchers_count: {
type: 'number',
description: 'Number of watchers',
},
language: {
type: 'string',
description: 'Primary programming language',
},
forks_count: {
type: 'number',
description: 'Number of forks',
},
open_issues_count: {
type: 'number',
description: 'Number of open issues',
},
default_branch: {
type: 'string',
description: 'Default branch name',
},
owner: {
login: {
type: 'string',
description: 'Owner username',
},
id: {
type: 'number',
description: 'Owner ID',
},
avatar_url: {
type: 'string',
description: 'Owner avatar URL',
},
html_url: {
type: 'string',
description: 'Owner profile URL',
},
owner_type: {
type: 'string',
description: 'Owner type (User, Organization)',
},
},
},
sender: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
},
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GitHub-Event': 'pull_request',
'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'X-Hub-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,498 @@
import { GithubIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
export const githubPRReviewedTrigger: TriggerConfig = {
id: 'github_pr_reviewed',
name: 'GitHub PR Reviewed',
provider: 'github',
description:
'Trigger workflow when a pull request review is submitted, edited, or dismissed in a GitHub repository',
version: '1.0.0',
icon: GithubIcon,
subBlocks: [
{
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_reviewed',
},
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'application/json', id: 'application/json' },
{
label: 'application/x-www-form-urlencoded',
id: 'application/x-www-form-urlencoded',
},
],
defaultValue: 'application/json',
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_reviewed',
},
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_reviewed',
},
},
{
id: 'sslVerification',
title: 'SSL Verification',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'enabled' },
{ label: 'Disabled', id: 'disabled' },
],
defaultValue: 'enabled',
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_reviewed',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
type: 'text',
defaultValue: [
'Go to your GitHub Repository > Settings > Webhooks.',
'Click "Add webhook".',
'Paste the <strong>Webhook URL</strong> above into the "Payload URL" field.',
'Select your chosen Content Type from the dropdown.',
'Enter the <strong>Webhook Secret</strong> into the "Secret" field if you\'ve configured one.',
'Set SSL verification according to your selection.',
'Select "Let me select individual events" and check <strong>Pull request reviews</strong>.',
'Ensure "Active" is checked and click "Add webhook".',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_reviewed',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'github_pr_reviewed',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_reviewed',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',
type: 'code',
language: 'json',
defaultValue: JSON.stringify(
{
action: 'submitted',
review: {
id: 80,
node_id: 'MDE3OlB1bGxSZXF1ZXN0UmV2aWV3ODA=',
user: {
login: 'reviewer',
id: 6,
node_id: 'MDQ6VXNlcjY=',
avatar_url: 'https://github.com/images/error/reviewer.gif',
html_url: 'https://github.com/reviewer',
type: 'User',
},
body: 'This looks great! Nice work.',
state: 'approved',
html_url: 'https://github.com/owner/repo/pull/42#pullrequestreview-80',
submitted_at: '2025-01-15T14:00:00Z',
commit_id: 'abc123def456',
author_association: 'COLLABORATOR',
},
pull_request: {
id: 1234567890,
number: 42,
node_id: 'MDExOlB1bGxSZXF1ZXN0MQ==',
title: 'Add new feature',
body: 'This PR adds a new feature that improves performance.',
state: 'open',
merged: false,
draft: false,
html_url: 'https://github.com/owner/repo/pull/42',
diff_url: 'https://github.com/owner/repo/pull/42.diff',
patch_url: 'https://github.com/owner/repo/pull/42.patch',
user: {
login: 'developer',
id: 5,
node_id: 'MDQ6VXNlcjU=',
avatar_url: 'https://github.com/images/error/developer.gif',
html_url: 'https://github.com/developer',
type: 'User',
},
head: {
ref: 'feature-branch',
sha: 'abc123def456',
repo: {
name: 'repo-name',
full_name: 'owner/repo-name',
},
},
base: {
ref: 'main',
sha: '789ghi012jkl',
repo: {
name: 'repo-name',
full_name: 'owner/repo-name',
},
},
created_at: '2025-01-15T12:00:00Z',
updated_at: '2025-01-15T14:00:00Z',
},
repository: {
id: 123456,
node_id: 'MDEwOlJlcG9zaXRvcnkxMjM0NTY=',
name: 'repo-name',
full_name: 'owner/repo-name',
html_url: 'https://github.com/owner/repo-name',
description: 'A sample repository',
private: false,
owner: {
login: 'owner',
id: 7890,
node_id: 'MDQ6VXNlcjc4OTA=',
avatar_url: 'https://github.com/images/error/owner.gif',
html_url: 'https://github.com/owner',
type: 'User',
},
},
sender: {
login: 'reviewer',
id: 6,
node_id: 'MDQ6VXNlcjY=',
avatar_url: 'https://github.com/images/error/reviewer.gif',
html_url: 'https://github.com/reviewer',
type: 'User',
},
},
null,
2
),
readOnly: true,
collapsible: true,
defaultCollapsed: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_pr_reviewed',
},
},
],
outputs: {
action: {
type: 'string',
description: 'Action performed (submitted, edited, dismissed)',
},
review: {
id: {
type: 'number',
description: 'Review ID',
},
node_id: {
type: 'string',
description: 'Review node ID',
},
user: {
login: {
type: 'string',
description: 'Reviewer username',
},
id: {
type: 'number',
description: 'Reviewer user ID',
},
node_id: {
type: 'string',
description: 'Reviewer node ID',
},
avatar_url: {
type: 'string',
description: 'Reviewer avatar URL',
},
html_url: {
type: 'string',
description: 'Reviewer profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
body: {
type: 'string',
description: 'Review comment text',
},
state: {
type: 'string',
description: 'Review state (approved, changes_requested, commented, dismissed)',
},
html_url: {
type: 'string',
description: 'Review HTML URL',
},
submitted_at: {
type: 'string',
description: 'Review submission timestamp',
},
commit_id: {
type: 'string',
description: 'Commit SHA that was reviewed',
},
author_association: {
type: 'string',
description: 'Author association (OWNER, MEMBER, COLLABORATOR, CONTRIBUTOR, etc.)',
},
},
pull_request: {
id: {
type: 'number',
description: 'Pull request ID',
},
node_id: {
type: 'string',
description: 'Pull request node ID',
},
number: {
type: 'number',
description: 'Pull request number',
},
title: {
type: 'string',
description: 'Pull request title',
},
body: {
type: 'string',
description: 'Pull request description',
},
state: {
type: 'string',
description: 'Pull request state (open, closed)',
},
merged: {
type: 'boolean',
description: 'Whether the PR was merged',
},
draft: {
type: 'boolean',
description: 'Whether the PR is a draft',
},
html_url: {
type: 'string',
description: 'Pull request HTML URL',
},
diff_url: {
type: 'string',
description: 'Pull request diff URL',
},
patch_url: {
type: 'string',
description: 'Pull request patch URL',
},
user: {
login: {
type: 'string',
description: 'PR author username',
},
id: {
type: 'number',
description: 'PR author user ID',
},
node_id: {
type: 'string',
description: 'PR author node ID',
},
avatar_url: {
type: 'string',
description: 'PR author avatar URL',
},
html_url: {
type: 'string',
description: 'PR author profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
head: {
ref: {
type: 'string',
description: 'Source branch name',
},
sha: {
type: 'string',
description: 'Source branch commit SHA',
},
repo: {
name: {
type: 'string',
description: 'Source repository name',
},
full_name: {
type: 'string',
description: 'Source repository full name',
},
},
},
base: {
ref: {
type: 'string',
description: 'Target branch name',
},
sha: {
type: 'string',
description: 'Target branch commit SHA',
},
repo: {
name: {
type: 'string',
description: 'Target repository name',
},
full_name: {
type: 'string',
description: 'Target repository full name',
},
},
},
created_at: {
type: 'string',
description: 'Pull request creation timestamp',
},
updated_at: {
type: 'string',
description: 'Pull request last update timestamp',
},
},
repository: {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name (owner/repo)',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
html_url: {
type: 'string',
description: 'Repository HTML URL',
},
repo_description: {
type: 'string',
description: 'Repository description',
},
owner: {
login: {
type: 'string',
description: 'Owner username',
},
id: {
type: 'number',
description: 'Owner ID',
},
node_id: {
type: 'string',
description: 'Owner node ID',
},
avatar_url: {
type: 'string',
description: 'Owner avatar URL',
},
html_url: {
type: 'string',
description: 'Owner profile URL',
},
owner_type: {
type: 'string',
description: 'Owner type (User, Organization)',
},
},
},
sender: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
},
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GitHub-Event': 'pull_request_review',
'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'X-Hub-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,560 @@
import { GithubIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
export const githubPushTrigger: TriggerConfig = {
id: 'github_push',
name: 'GitHub Push',
provider: 'github',
description: 'Trigger workflow when code is pushed to a repository',
version: '1.0.0',
icon: GithubIcon,
subBlocks: [
{
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_push',
},
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'application/json', id: 'application/json' },
{
label: 'application/x-www-form-urlencoded',
id: 'application/x-www-form-urlencoded',
},
],
defaultValue: 'application/json',
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_push',
},
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_push',
},
},
{
id: 'sslVerification',
title: 'SSL Verification',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'enabled' },
{ label: 'Disabled', id: 'disabled' },
],
defaultValue: 'enabled',
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_push',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
type: 'text',
defaultValue: [
'Go to your GitHub Repository > Settings > Webhooks.',
'Click "Add webhook".',
'Paste the <strong>Webhook URL</strong> above into the "Payload URL" field.',
'Select your chosen Content Type from the dropdown.',
'Enter the <strong>Webhook Secret</strong> into the "Secret" field if you\'ve configured one.',
'Set SSL verification according to your selection.',
'Select "Let me select individual events" and check <strong>Pushes</strong>.',
'Ensure "Active" is checked and click "Add webhook".',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_push',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'github_push',
condition: {
field: 'selectedTriggerId',
value: 'github_push',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',
type: 'code',
language: 'json',
defaultValue: JSON.stringify(
{
ref: 'refs/heads/main',
before: '0000000000000000000000000000000000000000',
after: 'abc123def456789ghi012jkl345mno678pqr901',
created: true,
deleted: false,
forced: false,
base_ref: null,
compare: 'https://github.com/owner/repo-name/compare/0000000000000000...abc123def456',
commits: [
{
id: 'abc123def456789ghi012jkl345mno678pqr901',
tree_id: 'tree123abc456def789ghi012jkl345mno678',
distinct: true,
message: 'Add new feature to improve performance',
timestamp: '2025-01-15T12:00:00Z',
url: 'https://github.com/owner/repo-name/commit/abc123def456789ghi012jkl345mno678pqr901',
author: {
name: 'Developer Name',
email: 'developer@example.com',
username: 'developer',
},
committer: {
name: 'Developer Name',
email: 'developer@example.com',
username: 'developer',
},
added: ['src/features/new-feature.ts'],
removed: [],
modified: ['src/index.ts', 'README.md'],
},
{
id: 'def456ghi789jkl012mno345pqr678stu901vwx',
tree_id: 'tree456def789ghi012jkl345mno678pqr901',
distinct: true,
message: 'Update documentation',
timestamp: '2025-01-15T12:15:00Z',
url: 'https://github.com/owner/repo-name/commit/def456ghi789jkl012mno345pqr678stu901vwx',
author: {
name: 'Developer Name',
email: 'developer@example.com',
username: 'developer',
},
committer: {
name: 'Developer Name',
email: 'developer@example.com',
username: 'developer',
},
added: [],
removed: [],
modified: ['docs/API.md'],
},
],
head_commit: {
id: 'def456ghi789jkl012mno345pqr678stu901vwx',
tree_id: 'tree456def789ghi012jkl345mno678pqr901',
distinct: true,
message: 'Update documentation',
timestamp: '2025-01-15T12:15:00Z',
url: 'https://github.com/owner/repo-name/commit/def456ghi789jkl012mno345pqr678stu901vwx',
author: {
name: 'Developer Name',
email: 'developer@example.com',
username: 'developer',
},
committer: {
name: 'Developer Name',
email: 'developer@example.com',
username: 'developer',
},
added: [],
removed: [],
modified: ['docs/API.md'],
},
pusher: {
name: 'developer',
email: 'developer@example.com',
},
repository: {
id: 123456,
node_id: 'MDEwOlJlcG9zaXRvcnkxMjM0NTY=',
name: 'repo-name',
full_name: 'owner/repo-name',
private: false,
html_url: 'https://github.com/owner/repo-name',
repo_description: 'A sample repository for demonstrating push events',
fork: false,
url: 'https://api.github.com/repos/owner/repo-name',
homepage: 'https://example.com',
size: 1024,
stargazers_count: 42,
watchers_count: 42,
language: 'TypeScript',
forks_count: 5,
open_issues_count: 3,
default_branch: 'main',
owner: {
login: 'owner',
id: 7890,
node_id: 'MDQ6VXNlcjc4OTA=',
avatar_url: 'https://github.com/images/error/owner.gif',
html_url: 'https://github.com/owner',
owner_type: 'User',
},
},
sender: {
login: 'developer',
id: 5,
node_id: 'MDQ6VXNlcjU=',
avatar_url: 'https://github.com/images/error/developer.gif',
html_url: 'https://github.com/developer',
user_type: 'User',
},
},
null,
2
),
readOnly: true,
collapsible: true,
defaultCollapsed: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_push',
},
},
],
outputs: {
ref: {
type: 'string',
description: 'Git reference that was pushed (e.g., refs/heads/main)',
},
before: {
type: 'string',
description: 'SHA of the commit before the push',
},
after: {
type: 'string',
description: 'SHA of the commit after the push',
},
created: {
type: 'boolean',
description: 'Whether this push created a new branch or tag',
},
deleted: {
type: 'boolean',
description: 'Whether this push deleted a branch or tag',
},
forced: {
type: 'boolean',
description: 'Whether this was a force push',
},
base_ref: {
type: 'string',
description: 'Base reference for the push',
},
compare: {
type: 'string',
description: 'URL to compare the changes',
},
commits: {
type: 'array',
description: 'Array of commit objects included in this push',
items: {
id: {
type: 'string',
description: 'Commit SHA',
},
tree_id: {
type: 'string',
description: 'Git tree SHA',
},
distinct: {
type: 'boolean',
description: 'Whether this commit is distinct from others in the push',
},
message: {
type: 'string',
description: 'Commit message',
},
timestamp: {
type: 'string',
description: 'Commit timestamp',
},
url: {
type: 'string',
description: 'Commit URL',
},
author: {
name: {
type: 'string',
description: 'Author name',
},
email: {
type: 'string',
description: 'Author email',
},
username: {
type: 'string',
description: 'Author GitHub username',
},
},
committer: {
name: {
type: 'string',
description: 'Committer name',
},
email: {
type: 'string',
description: 'Committer email',
},
username: {
type: 'string',
description: 'Committer GitHub username',
},
},
added: {
type: 'array',
description: 'Array of file paths added in this commit',
},
removed: {
type: 'array',
description: 'Array of file paths removed in this commit',
},
modified: {
type: 'array',
description: 'Array of file paths modified in this commit',
},
},
},
head_commit: {
id: {
type: 'string',
description: 'Commit SHA of the most recent commit',
},
tree_id: {
type: 'string',
description: 'Git tree SHA',
},
distinct: {
type: 'boolean',
description: 'Whether this commit is distinct',
},
message: {
type: 'string',
description: 'Commit message',
},
timestamp: {
type: 'string',
description: 'Commit timestamp',
},
url: {
type: 'string',
description: 'Commit URL',
},
author: {
name: {
type: 'string',
description: 'Author name',
},
email: {
type: 'string',
description: 'Author email',
},
username: {
type: 'string',
description: 'Author GitHub username',
},
},
committer: {
name: {
type: 'string',
description: 'Committer name',
},
email: {
type: 'string',
description: 'Committer email',
},
username: {
type: 'string',
description: 'Committer GitHub username',
},
},
added: {
type: 'array',
description: 'Array of file paths added in this commit',
},
removed: {
type: 'array',
description: 'Array of file paths removed in this commit',
},
modified: {
type: 'array',
description: 'Array of file paths modified in this commit',
},
},
pusher: {
name: {
type: 'string',
description: 'Pusher name',
},
email: {
type: 'string',
description: 'Pusher email',
},
},
repository: {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name (owner/repo)',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
html_url: {
type: 'string',
description: 'Repository HTML URL',
},
repo_description: {
type: 'string',
description: 'Repository description',
},
fork: {
type: 'boolean',
description: 'Whether the repository is a fork',
},
url: {
type: 'string',
description: 'Repository API URL',
},
homepage: {
type: 'string',
description: 'Repository homepage URL',
},
size: {
type: 'number',
description: 'Repository size in KB',
},
stargazers_count: {
type: 'number',
description: 'Number of stars',
},
watchers_count: {
type: 'number',
description: 'Number of watchers',
},
language: {
type: 'string',
description: 'Primary programming language',
},
forks_count: {
type: 'number',
description: 'Number of forks',
},
open_issues_count: {
type: 'number',
description: 'Number of open issues',
},
default_branch: {
type: 'string',
description: 'Default branch name',
},
owner: {
login: {
type: 'string',
description: 'Owner username',
},
id: {
type: 'number',
description: 'Owner ID',
},
node_id: {
type: 'string',
description: 'Owner node ID',
},
avatar_url: {
type: 'string',
description: 'Owner avatar URL',
},
html_url: {
type: 'string',
description: 'Owner profile URL',
},
owner_type: {
type: 'string',
description: 'Owner type (User, Organization)',
},
},
},
sender: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
},
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GitHub-Event': 'push',
'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'X-Hub-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,969 @@
import { GithubIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
export const githubReleasePublishedTrigger: TriggerConfig = {
id: 'github_release_published',
name: 'GitHub Release Published',
provider: 'github',
description: 'Trigger workflow when a new release is published in a GitHub repository',
version: '1.0.0',
icon: GithubIcon,
subBlocks: [
{
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_release_published',
},
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'application/json', id: 'application/json' },
{
label: 'application/x-www-form-urlencoded',
id: 'application/x-www-form-urlencoded',
},
],
defaultValue: 'application/json',
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_release_published',
},
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_release_published',
},
},
{
id: 'sslVerification',
title: 'SSL Verification',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'enabled' },
{ label: 'Disabled', id: 'disabled' },
],
defaultValue: 'enabled',
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_release_published',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
type: 'text',
defaultValue: [
'Go to your GitHub Repository > Settings > Webhooks.',
'Click "Add webhook".',
'Paste the <strong>Webhook URL</strong> above into the "Payload URL" field.',
'Select your chosen Content Type from the dropdown.',
'Enter the <strong>Webhook Secret</strong> into the "Secret" field if you\'ve configured one.',
'Set SSL verification according to your selection.',
'Select "Let me select individual events" and check <strong>Releases</strong> event.',
'Ensure "Active" is checked and click "Add webhook".',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_release_published',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'github_release_published',
condition: {
field: 'selectedTriggerId',
value: 'github_release_published',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',
type: 'code',
language: 'json',
defaultValue: JSON.stringify(
{
action: 'published',
release: {
id: 123456789,
node_id: 'RE_kwDOABCDEF4HFGijkl',
tag_name: 'v1.0.0',
target_commitish: 'main',
name: 'v1.0.0 - Initial Release',
body: 'This is the first stable release of our project.\n\n## Features\n- Feature A\n- Feature B\n- Feature C\n\n## Bug Fixes\n- Fixed issue #123\n- Fixed issue #456',
draft: false,
prerelease: false,
created_at: '2025-01-15T10:00:00Z',
published_at: '2025-01-15T12:00:00Z',
url: 'https://api.github.com/repos/owner/repo-name/releases/123456789',
html_url: 'https://github.com/owner/repo-name/releases/tag/v1.0.0',
assets_url: 'https://api.github.com/repos/owner/repo-name/releases/123456789/assets',
upload_url:
'https://uploads.github.com/repos/owner/repo-name/releases/123456789/assets{?name,label}',
tarball_url: 'https://api.github.com/repos/owner/repo-name/tarball/v1.0.0',
zipball_url: 'https://api.github.com/repos/owner/repo-name/zipball/v1.0.0',
discussion_url: 'https://github.com/owner/repo-name/discussions/100',
author: {
login: 'releasemanager',
id: 12345,
node_id: 'MDQ6VXNlcjEyMzQ1',
avatar_url: 'https://avatars.githubusercontent.com/u/12345?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/releasemanager',
html_url: 'https://github.com/releasemanager',
followers_url: 'https://api.github.com/users/releasemanager/followers',
following_url: 'https://api.github.com/users/releasemanager/following{/other_user}',
gists_url: 'https://api.github.com/users/releasemanager/gists{/gist_id}',
starred_url: 'https://api.github.com/users/releasemanager/starred{/owner}{/repo}',
subscriptions_url: 'https://api.github.com/users/releasemanager/subscriptions',
organizations_url: 'https://api.github.com/users/releasemanager/orgs',
repos_url: 'https://api.github.com/users/releasemanager/repos',
events_url: 'https://api.github.com/users/releasemanager/events{/privacy}',
received_events_url: 'https://api.github.com/users/releasemanager/received_events',
user_type: 'User',
site_admin: false,
},
assets: [
{
id: 987654321,
node_id: 'RA_kwDOABCDEF4DcXYZ',
name: 'release-v1.0.0-linux-amd64.tar.gz',
label: 'Linux AMD64 Binary',
content_type: 'application/gzip',
state: 'uploaded',
size: 15728640,
download_count: 42,
created_at: '2025-01-15T11:30:00Z',
updated_at: '2025-01-15T11:30:00Z',
browser_download_url:
'https://github.com/owner/repo-name/releases/download/v1.0.0/release-v1.0.0-linux-amd64.tar.gz',
url: 'https://api.github.com/repos/owner/repo-name/releases/assets/987654321',
uploader: {
login: 'releasemanager',
id: 12345,
node_id: 'MDQ6VXNlcjEyMzQ1',
avatar_url: 'https://avatars.githubusercontent.com/u/12345?v=4',
html_url: 'https://github.com/releasemanager',
user_type: 'User',
},
},
{
id: 987654322,
node_id: 'RA_kwDOABCDEF4DcXYa',
name: 'release-v1.0.0-darwin-amd64.tar.gz',
label: 'macOS AMD64 Binary',
content_type: 'application/gzip',
state: 'uploaded',
size: 14680064,
download_count: 28,
created_at: '2025-01-15T11:30:00Z',
updated_at: '2025-01-15T11:30:00Z',
browser_download_url:
'https://github.com/owner/repo-name/releases/download/v1.0.0/release-v1.0.0-darwin-amd64.tar.gz',
url: 'https://api.github.com/repos/owner/repo-name/releases/assets/987654322',
uploader: {
login: 'releasemanager',
id: 12345,
node_id: 'MDQ6VXNlcjEyMzQ1',
avatar_url: 'https://avatars.githubusercontent.com/u/12345?v=4',
html_url: 'https://github.com/releasemanager',
user_type: 'User',
},
},
],
},
repository: {
id: 123456,
node_id: 'R_kgDOABCDEF',
name: 'repo-name',
full_name: 'owner/repo-name',
private: false,
html_url: 'https://github.com/owner/repo-name',
repo_description: 'A sample repository for demonstrating GitHub release webhooks',
fork: false,
url: 'https://api.github.com/repos/owner/repo-name',
archive_url: 'https://api.github.com/repos/owner/repo-name/{archive_format}{/ref}',
assignees_url: 'https://api.github.com/repos/owner/repo-name/assignees{/user}',
blobs_url: 'https://api.github.com/repos/owner/repo-name/git/blobs{/sha}',
branches_url: 'https://api.github.com/repos/owner/repo-name/branches{/branch}',
collaborators_url:
'https://api.github.com/repos/owner/repo-name/collaborators{/collaborator}',
comments_url: 'https://api.github.com/repos/owner/repo-name/comments{/number}',
commits_url: 'https://api.github.com/repos/owner/repo-name/commits{/sha}',
compare_url: 'https://api.github.com/repos/owner/repo-name/compare/{base}...{head}',
contents_url: 'https://api.github.com/repos/owner/repo-name/contents/{+path}',
contributors_url: 'https://api.github.com/repos/owner/repo-name/contributors',
deployments_url: 'https://api.github.com/repos/owner/repo-name/deployments',
downloads_url: 'https://api.github.com/repos/owner/repo-name/downloads',
events_url: 'https://api.github.com/repos/owner/repo-name/events',
forks_url: 'https://api.github.com/repos/owner/repo-name/forks',
git_commits_url: 'https://api.github.com/repos/owner/repo-name/git/commits{/sha}',
git_refs_url: 'https://api.github.com/repos/owner/repo-name/git/refs{/sha}',
git_tags_url: 'https://api.github.com/repos/owner/repo-name/git/tags{/sha}',
hooks_url: 'https://api.github.com/repos/owner/repo-name/hooks',
issue_comment_url:
'https://api.github.com/repos/owner/repo-name/issues/comments{/number}',
issue_events_url: 'https://api.github.com/repos/owner/repo-name/issues/events{/number}',
issues_url: 'https://api.github.com/repos/owner/repo-name/issues{/number}',
keys_url: 'https://api.github.com/repos/owner/repo-name/keys{/key_id}',
labels_url: 'https://api.github.com/repos/owner/repo-name/labels{/name}',
languages_url: 'https://api.github.com/repos/owner/repo-name/languages',
merges_url: 'https://api.github.com/repos/owner/repo-name/merges',
milestones_url: 'https://api.github.com/repos/owner/repo-name/milestones{/number}',
notifications_url:
'https://api.github.com/repos/owner/repo-name/notifications{?since,all,participating}',
pulls_url: 'https://api.github.com/repos/owner/repo-name/pulls{/number}',
releases_url: 'https://api.github.com/repos/owner/repo-name/releases{/id}',
stargazers_url: 'https://api.github.com/repos/owner/repo-name/stargazers',
statuses_url: 'https://api.github.com/repos/owner/repo-name/statuses/{sha}',
subscribers_url: 'https://api.github.com/repos/owner/repo-name/subscribers',
subscription_url: 'https://api.github.com/repos/owner/repo-name/subscription',
tags_url: 'https://api.github.com/repos/owner/repo-name/tags',
teams_url: 'https://api.github.com/repos/owner/repo-name/teams',
trees_url: 'https://api.github.com/repos/owner/repo-name/git/trees{/sha}',
homepage: 'https://example.com',
size: 1024,
stargazers_count: 350,
watchers_count: 350,
language: 'TypeScript',
has_issues: true,
has_projects: true,
has_downloads: true,
has_wiki: true,
has_pages: false,
forks_count: 42,
mirror_url: null,
archived: false,
disabled: false,
open_issues_count: 12,
license: {
key: 'mit',
name: 'MIT License',
spdx_id: 'MIT',
url: 'https://api.github.com/licenses/mit',
node_id: 'MDc6TGljZW5zZTEz',
},
allow_forking: true,
is_template: false,
topics: ['javascript', 'typescript', 'nodejs'],
visibility: 'public',
forks: 42,
open_issues: 12,
watchers: 350,
default_branch: 'main',
created_at: '2020-01-01T00:00:00Z',
updated_at: '2025-01-15T12:00:00Z',
pushed_at: '2025-01-15T11:45:00Z',
owner: {
login: 'owner',
id: 7890,
node_id: 'MDQ6VXNlcjc4OTA=',
avatar_url: 'https://avatars.githubusercontent.com/u/7890?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/owner',
html_url: 'https://github.com/owner',
followers_url: 'https://api.github.com/users/owner/followers',
following_url: 'https://api.github.com/users/owner/following{/other_user}',
gists_url: 'https://api.github.com/users/owner/gists{/gist_id}',
starred_url: 'https://api.github.com/users/owner/starred{/owner}{/repo}',
subscriptions_url: 'https://api.github.com/users/owner/subscriptions',
organizations_url: 'https://api.github.com/users/owner/orgs',
repos_url: 'https://api.github.com/users/owner/repos',
events_url: 'https://api.github.com/users/owner/events{/privacy}',
received_events_url: 'https://api.github.com/users/owner/received_events',
owner_type: 'User',
site_admin: false,
},
},
sender: {
login: 'releasemanager',
id: 12345,
node_id: 'MDQ6VXNlcjEyMzQ1',
avatar_url: 'https://avatars.githubusercontent.com/u/12345?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/releasemanager',
html_url: 'https://github.com/releasemanager',
followers_url: 'https://api.github.com/users/releasemanager/followers',
following_url: 'https://api.github.com/users/releasemanager/following{/other_user}',
gists_url: 'https://api.github.com/users/releasemanager/gists{/gist_id}',
starred_url: 'https://api.github.com/users/releasemanager/starred{/owner}{/repo}',
subscriptions_url: 'https://api.github.com/users/releasemanager/subscriptions',
organizations_url: 'https://api.github.com/users/releasemanager/orgs',
repos_url: 'https://api.github.com/users/releasemanager/repos',
events_url: 'https://api.github.com/users/releasemanager/events{/privacy}',
received_events_url: 'https://api.github.com/users/releasemanager/received_events',
user_type: 'User',
site_admin: false,
},
},
null,
2
),
readOnly: true,
collapsible: true,
defaultCollapsed: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_release_published',
},
},
],
outputs: {
action: {
type: 'string',
description:
'Action performed (published, unpublished, created, edited, deleted, prereleased, released)',
},
release: {
id: {
type: 'number',
description: 'Release ID',
},
node_id: {
type: 'string',
description: 'Release node ID',
},
tag_name: {
type: 'string',
description: 'Git tag name for the release',
},
target_commitish: {
type: 'string',
description: 'Target branch or commit SHA',
},
name: {
type: 'string',
description: 'Release name/title',
},
body: {
type: 'string',
description: 'Release description/notes in markdown format',
},
draft: {
type: 'boolean',
description: 'Whether the release is a draft',
},
prerelease: {
type: 'boolean',
description: 'Whether the release is a pre-release',
},
created_at: {
type: 'string',
description: 'Release creation timestamp',
},
published_at: {
type: 'string',
description: 'Release publication timestamp',
},
url: {
type: 'string',
description: 'Release API URL',
},
html_url: {
type: 'string',
description: 'Release HTML URL',
},
assets_url: {
type: 'string',
description: 'Release assets API URL',
},
upload_url: {
type: 'string',
description: 'URL for uploading release assets',
},
tarball_url: {
type: 'string',
description: 'Source code tarball download URL',
},
zipball_url: {
type: 'string',
description: 'Source code zipball download URL',
},
discussion_url: {
type: 'string',
description: 'Discussion URL if available',
},
author: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
gravatar_id: {
type: 'string',
description: 'Gravatar ID',
},
url: {
type: 'string',
description: 'User API URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
followers_url: {
type: 'string',
description: 'Followers API URL',
},
following_url: {
type: 'string',
description: 'Following API URL',
},
gists_url: {
type: 'string',
description: 'Gists API URL',
},
starred_url: {
type: 'string',
description: 'Starred repositories API URL',
},
subscriptions_url: {
type: 'string',
description: 'Subscriptions API URL',
},
organizations_url: {
type: 'string',
description: 'Organizations API URL',
},
repos_url: {
type: 'string',
description: 'Repositories API URL',
},
events_url: {
type: 'string',
description: 'Events API URL',
},
received_events_url: {
type: 'string',
description: 'Received events API URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
site_admin: {
type: 'boolean',
description: 'Whether user is a site administrator',
},
},
assets: {
type: 'array',
description: 'Array of release asset objects with download URLs',
},
},
repository: {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name (owner/repo)',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
html_url: {
type: 'string',
description: 'Repository HTML URL',
},
repo_description: {
type: 'string',
description: 'Repository description',
},
fork: {
type: 'boolean',
description: 'Whether the repository is a fork',
},
url: {
type: 'string',
description: 'Repository API URL',
},
archive_url: {
type: 'string',
description: 'Archive API URL',
},
assignees_url: {
type: 'string',
description: 'Assignees API URL',
},
blobs_url: {
type: 'string',
description: 'Blobs API URL',
},
branches_url: {
type: 'string',
description: 'Branches API URL',
},
collaborators_url: {
type: 'string',
description: 'Collaborators API URL',
},
comments_url: {
type: 'string',
description: 'Comments API URL',
},
commits_url: {
type: 'string',
description: 'Commits API URL',
},
compare_url: {
type: 'string',
description: 'Compare API URL',
},
contents_url: {
type: 'string',
description: 'Contents API URL',
},
contributors_url: {
type: 'string',
description: 'Contributors API URL',
},
deployments_url: {
type: 'string',
description: 'Deployments API URL',
},
downloads_url: {
type: 'string',
description: 'Downloads API URL',
},
events_url: {
type: 'string',
description: 'Events API URL',
},
forks_url: {
type: 'string',
description: 'Forks API URL',
},
git_commits_url: {
type: 'string',
description: 'Git commits API URL',
},
git_refs_url: {
type: 'string',
description: 'Git refs API URL',
},
git_tags_url: {
type: 'string',
description: 'Git tags API URL',
},
hooks_url: {
type: 'string',
description: 'Hooks API URL',
},
issue_comment_url: {
type: 'string',
description: 'Issue comment API URL',
},
issue_events_url: {
type: 'string',
description: 'Issue events API URL',
},
issues_url: {
type: 'string',
description: 'Issues API URL',
},
keys_url: {
type: 'string',
description: 'Keys API URL',
},
labels_url: {
type: 'string',
description: 'Labels API URL',
},
languages_url: {
type: 'string',
description: 'Languages API URL',
},
merges_url: {
type: 'string',
description: 'Merges API URL',
},
milestones_url: {
type: 'string',
description: 'Milestones API URL',
},
notifications_url: {
type: 'string',
description: 'Notifications API URL',
},
pulls_url: {
type: 'string',
description: 'Pull requests API URL',
},
releases_url: {
type: 'string',
description: 'Releases API URL',
},
stargazers_url: {
type: 'string',
description: 'Stargazers API URL',
},
statuses_url: {
type: 'string',
description: 'Statuses API URL',
},
subscribers_url: {
type: 'string',
description: 'Subscribers API URL',
},
subscription_url: {
type: 'string',
description: 'Subscription API URL',
},
tags_url: {
type: 'string',
description: 'Tags API URL',
},
teams_url: {
type: 'string',
description: 'Teams API URL',
},
trees_url: {
type: 'string',
description: 'Trees API URL',
},
homepage: {
type: 'string',
description: 'Repository homepage URL',
},
size: {
type: 'number',
description: 'Repository size in KB',
},
stargazers_count: {
type: 'number',
description: 'Number of stars',
},
watchers_count: {
type: 'number',
description: 'Number of watchers',
},
language: {
type: 'string',
description: 'Primary programming language',
},
has_issues: {
type: 'boolean',
description: 'Whether issues are enabled',
},
has_projects: {
type: 'boolean',
description: 'Whether projects are enabled',
},
has_downloads: {
type: 'boolean',
description: 'Whether downloads are enabled',
},
has_wiki: {
type: 'boolean',
description: 'Whether wiki is enabled',
},
has_pages: {
type: 'boolean',
description: 'Whether GitHub Pages is enabled',
},
forks_count: {
type: 'number',
description: 'Number of forks',
},
mirror_url: {
type: 'string',
description: 'Mirror URL if repository is a mirror',
},
archived: {
type: 'boolean',
description: 'Whether the repository is archived',
},
disabled: {
type: 'boolean',
description: 'Whether the repository is disabled',
},
open_issues_count: {
type: 'number',
description: 'Number of open issues',
},
license: {
key: {
type: 'string',
description: 'License key',
},
name: {
type: 'string',
description: 'License name',
},
spdx_id: {
type: 'string',
description: 'SPDX license identifier',
},
url: {
type: 'string',
description: 'License API URL',
},
node_id: {
type: 'string',
description: 'License node ID',
},
},
allow_forking: {
type: 'boolean',
description: 'Whether forking is allowed',
},
is_template: {
type: 'boolean',
description: 'Whether repository is a template',
},
topics: {
type: 'array',
description: 'Array of repository topics',
},
visibility: {
type: 'string',
description: 'Repository visibility (public, private, internal)',
},
forks: {
type: 'number',
description: 'Number of forks',
},
open_issues: {
type: 'number',
description: 'Number of open issues',
},
watchers: {
type: 'number',
description: 'Number of watchers',
},
default_branch: {
type: 'string',
description: 'Default branch name',
},
created_at: {
type: 'string',
description: 'Repository creation timestamp',
},
updated_at: {
type: 'string',
description: 'Repository last update timestamp',
},
pushed_at: {
type: 'string',
description: 'Repository last push timestamp',
},
owner: {
login: {
type: 'string',
description: 'Owner username',
},
id: {
type: 'number',
description: 'Owner ID',
},
node_id: {
type: 'string',
description: 'Owner node ID',
},
avatar_url: {
type: 'string',
description: 'Owner avatar URL',
},
gravatar_id: {
type: 'string',
description: 'Owner gravatar ID',
},
url: {
type: 'string',
description: 'Owner API URL',
},
html_url: {
type: 'string',
description: 'Owner profile URL',
},
followers_url: {
type: 'string',
description: 'Followers API URL',
},
following_url: {
type: 'string',
description: 'Following API URL',
},
gists_url: {
type: 'string',
description: 'Gists API URL',
},
starred_url: {
type: 'string',
description: 'Starred repositories API URL',
},
subscriptions_url: {
type: 'string',
description: 'Subscriptions API URL',
},
organizations_url: {
type: 'string',
description: 'Organizations API URL',
},
repos_url: {
type: 'string',
description: 'Repositories API URL',
},
events_url: {
type: 'string',
description: 'Events API URL',
},
received_events_url: {
type: 'string',
description: 'Received events API URL',
},
owner_type: {
type: 'string',
description: 'Owner type (User, Organization)',
},
site_admin: {
type: 'boolean',
description: 'Whether owner is a site administrator',
},
},
},
sender: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
gravatar_id: {
type: 'string',
description: 'Gravatar ID',
},
url: {
type: 'string',
description: 'User API URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
followers_url: {
type: 'string',
description: 'Followers API URL',
},
following_url: {
type: 'string',
description: 'Following API URL',
},
gists_url: {
type: 'string',
description: 'Gists API URL',
},
starred_url: {
type: 'string',
description: 'Starred repositories API URL',
},
subscriptions_url: {
type: 'string',
description: 'Subscriptions API URL',
},
organizations_url: {
type: 'string',
description: 'Organizations API URL',
},
repos_url: {
type: 'string',
description: 'Repositories API URL',
},
events_url: {
type: 'string',
description: 'Events API URL',
},
received_events_url: {
type: 'string',
description: 'Received events API URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
site_admin: {
type: 'boolean',
description: 'Whether user is a site administrator',
},
},
},
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GitHub-Event': 'release',
'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'X-Hub-Signature-256': 'sha256=...',
},
},
}

View File

@@ -0,0 +1,893 @@
import type { SubBlockConfig } from '@/blocks/types'
import type { TriggerOutput } from '@/triggers/types'
/**
* Shared sub-blocks configuration for all GitHub webhook triggers
*/
export const githubWebhookSubBlocks: SubBlockConfig[] = [
{
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'application/json', id: 'application/json' },
{
label: 'application/x-www-form-urlencoded',
id: 'application/x-www-form-urlencoded',
},
],
defaultValue: 'application/json',
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
},
{
id: 'sslVerification',
title: 'SSL Verification',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'enabled' },
{ label: 'Disabled', id: 'disabled' },
],
defaultValue: 'enabled',
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
},
]
/**
* Generate setup instructions for a specific GitHub event type
*/
export function githubSetupInstructions(
eventType: string,
actions?: string[],
additionalNotes?: string
): string {
const actionText = actions
? ` (<strong>${actions.join(', ')}</strong> ${actions.length === 1 ? 'action' : 'actions'})`
: ''
const instructions = [
'Go to your GitHub Repository > Settings > Webhooks.',
'Click "Add webhook".',
'Paste the <strong>Webhook URL</strong> above into the "Payload URL" field.',
'Select your chosen Content Type from the dropdown.',
'Enter the <strong>Webhook Secret</strong> into the "Secret" field if you\'ve configured one.',
'Set SSL verification according to your selection.',
`Select "Let me select individual events" and check <strong>${eventType}</strong>${actionText}.`,
'Ensure "Active" is checked and click "Add webhook".',
]
if (additionalNotes) {
instructions.push(additionalNotes)
}
return instructions
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join('')
}
/**
* Shared repository output schema
*/
export const repositoryOutputs = {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name (owner/repo)',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
html_url: {
type: 'string',
description: 'Repository HTML URL',
},
description: {
type: 'string',
description: 'Repository description',
},
fork: {
type: 'boolean',
description: 'Whether the repository is a fork',
},
url: {
type: 'string',
description: 'Repository API URL',
},
homepage: {
type: 'string',
description: 'Repository homepage URL',
},
size: {
type: 'number',
description: 'Repository size in KB',
},
stargazers_count: {
type: 'number',
description: 'Number of stars',
},
watchers_count: {
type: 'number',
description: 'Number of watchers',
},
language: {
type: 'string',
description: 'Primary programming language',
},
forks_count: {
type: 'number',
description: 'Number of forks',
},
open_issues_count: {
type: 'number',
description: 'Number of open issues',
},
default_branch: {
type: 'string',
description: 'Default branch name',
},
owner: {
login: {
type: 'string',
description: 'Owner username',
},
id: {
type: 'number',
description: 'Owner ID',
},
avatar_url: {
type: 'string',
description: 'Owner avatar URL',
},
html_url: {
type: 'string',
description: 'Owner profile URL',
},
},
} as const
/**
* Shared sender/user output schema
*/
export const userOutputs = {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
} as const
/**
* Build output schema for issue events
*/
export function buildIssueOutputs(): Record<string, TriggerOutput> {
return {
action: {
type: 'string',
description: 'Action performed (opened, closed, reopened, edited, etc.)',
},
issue: {
id: {
type: 'number',
description: 'Issue ID',
},
node_id: {
type: 'string',
description: 'Issue node ID',
},
number: {
type: 'number',
description: 'Issue number',
},
title: {
type: 'string',
description: 'Issue title',
},
body: {
type: 'string',
description: 'Issue body/description',
},
state: {
type: 'string',
description: 'Issue state (open, closed)',
},
state_reason: {
type: 'string',
description: 'Reason for state (completed, not_planned, reopened)',
},
html_url: {
type: 'string',
description: 'Issue HTML URL',
},
user: userOutputs,
labels: {
type: 'array',
description: 'Array of label objects',
},
assignees: {
type: 'array',
description: 'Array of assigned users',
},
milestone: {
type: 'object',
description: 'Milestone object if assigned',
},
created_at: {
type: 'string',
description: 'Issue creation timestamp',
},
updated_at: {
type: 'string',
description: 'Issue last update timestamp',
},
closed_at: {
type: 'string',
description: 'Issue closed timestamp',
},
},
repository: repositoryOutputs,
sender: userOutputs,
} as any
}
/**
* Build output schema for issue comment events
*/
export function buildIssueCommentOutputs(): Record<string, TriggerOutput> {
return {
action: {
type: 'string',
description: 'Action performed (created, edited, deleted)',
},
issue: {
number: {
type: 'number',
description: 'Issue number',
},
title: {
type: 'string',
description: 'Issue title',
},
state: {
type: 'string',
description: 'Issue state (open, closed)',
},
html_url: {
type: 'string',
description: 'Issue HTML URL',
},
user: userOutputs,
},
comment: {
id: {
type: 'number',
description: 'Comment ID',
},
node_id: {
type: 'string',
description: 'Comment node ID',
},
body: {
type: 'string',
description: 'Comment text',
},
html_url: {
type: 'string',
description: 'Comment HTML URL',
},
user: userOutputs,
created_at: {
type: 'string',
description: 'Comment creation timestamp',
},
updated_at: {
type: 'string',
description: 'Comment last update timestamp',
},
},
repository: repositoryOutputs,
sender: userOutputs,
} as any
}
/**
* Build output schema for pull request events
*/
export function buildPullRequestOutputs(): Record<string, TriggerOutput> {
return {
action: {
type: 'string',
description: 'Action performed (opened, closed, synchronize, reopened, edited, etc.)',
},
number: {
type: 'number',
description: 'Pull request number',
},
pull_request: {
id: {
type: 'number',
description: 'Pull request ID',
},
node_id: {
type: 'string',
description: 'Pull request node ID',
},
number: {
type: 'number',
description: 'Pull request number',
},
title: {
type: 'string',
description: 'Pull request title',
},
body: {
type: 'string',
description: 'Pull request description',
},
state: {
type: 'string',
description: 'Pull request state (open, closed)',
},
merged: {
type: 'boolean',
description: 'Whether the PR was merged',
},
merged_at: {
type: 'string',
description: 'Timestamp when PR was merged',
},
merged_by: userOutputs,
draft: {
type: 'boolean',
description: 'Whether the PR is a draft',
},
html_url: {
type: 'string',
description: 'Pull request HTML URL',
},
diff_url: {
type: 'string',
description: 'Pull request diff URL',
},
patch_url: {
type: 'string',
description: 'Pull request patch URL',
},
user: userOutputs,
head: {
ref: {
type: 'string',
description: 'Source branch name',
},
sha: {
type: 'string',
description: 'Source branch commit SHA',
},
repo: {
name: {
type: 'string',
description: 'Source repository name',
},
full_name: {
type: 'string',
description: 'Source repository full name',
},
},
},
base: {
ref: {
type: 'string',
description: 'Target branch name',
},
sha: {
type: 'string',
description: 'Target branch commit SHA',
},
repo: {
name: {
type: 'string',
description: 'Target repository name',
},
full_name: {
type: 'string',
description: 'Target repository full name',
},
},
},
additions: {
type: 'number',
description: 'Number of lines added',
},
deletions: {
type: 'number',
description: 'Number of lines deleted',
},
changed_files: {
type: 'number',
description: 'Number of files changed',
},
labels: {
type: 'array',
description: 'Array of label objects',
},
assignees: {
type: 'array',
description: 'Array of assigned users',
},
requested_reviewers: {
type: 'array',
description: 'Array of requested reviewers',
},
created_at: {
type: 'string',
description: 'Pull request creation timestamp',
},
updated_at: {
type: 'string',
description: 'Pull request last update timestamp',
},
closed_at: {
type: 'string',
description: 'Pull request closed timestamp',
},
},
repository: repositoryOutputs,
sender: userOutputs,
} as any
}
/**
* Build output schema for PR comment events
*/
export function buildPRCommentOutputs(): Record<string, TriggerOutput> {
return {
action: {
type: 'string',
description: 'Action performed (created, edited, deleted)',
},
issue: {
number: {
type: 'number',
description: 'Pull request number',
},
title: {
type: 'string',
description: 'Pull request title',
},
state: {
type: 'string',
description: 'Pull request state (open, closed)',
},
html_url: {
type: 'string',
description: 'Pull request HTML URL',
},
user: userOutputs,
pull_request: {
url: {
type: 'string',
description: 'Pull request API URL',
},
html_url: {
type: 'string',
description: 'Pull request HTML URL',
},
diff_url: {
type: 'string',
description: 'Pull request diff URL',
},
},
},
comment: {
id: {
type: 'number',
description: 'Comment ID',
},
node_id: {
type: 'string',
description: 'Comment node ID',
},
body: {
type: 'string',
description: 'Comment text',
},
html_url: {
type: 'string',
description: 'Comment HTML URL',
},
user: userOutputs,
created_at: {
type: 'string',
description: 'Comment creation timestamp',
},
updated_at: {
type: 'string',
description: 'Comment last update timestamp',
},
},
repository: repositoryOutputs,
sender: userOutputs,
} as any
}
/**
* Build output schema for PR review events
*/
export function buildPRReviewOutputs(): Record<string, TriggerOutput> {
return {
action: {
type: 'string',
description: 'Action performed (submitted, edited, dismissed)',
},
review: {
id: {
type: 'number',
description: 'Review ID',
},
node_id: {
type: 'string',
description: 'Review node ID',
},
body: {
type: 'string',
description: 'Review comment body',
},
state: {
type: 'string',
description: 'Review state (approved, changes_requested, commented, dismissed)',
},
html_url: {
type: 'string',
description: 'Review HTML URL',
},
user: userOutputs,
submitted_at: {
type: 'string',
description: 'Review submission timestamp',
},
},
pull_request: {
number: {
type: 'number',
description: 'Pull request number',
},
title: {
type: 'string',
description: 'Pull request title',
},
state: {
type: 'string',
description: 'Pull request state (open, closed)',
},
html_url: {
type: 'string',
description: 'Pull request HTML URL',
},
user: userOutputs,
head: {
ref: {
type: 'string',
description: 'Source branch name',
},
},
base: {
ref: {
type: 'string',
description: 'Target branch name',
},
},
},
repository: repositoryOutputs,
sender: userOutputs,
} as any
}
/**
* Build output schema for push events
*/
export function buildPushOutputs(): Record<string, TriggerOutput> {
return {
ref: {
type: 'string',
description: 'Git reference (e.g., refs/heads/main)',
},
before: {
type: 'string',
description: 'SHA of the commit before the push',
},
after: {
type: 'string',
description: 'SHA of the commit after the push',
},
created: {
type: 'boolean',
description: 'Whether the push created the reference',
},
deleted: {
type: 'boolean',
description: 'Whether the push deleted the reference',
},
forced: {
type: 'boolean',
description: 'Whether the push was forced',
},
compare: {
type: 'string',
description: 'URL to compare the changes',
},
commits: {
type: 'array',
description: 'Array of commit objects',
id: {
type: 'string',
description: 'Commit SHA',
},
message: {
type: 'string',
description: 'Commit message',
},
timestamp: {
type: 'string',
description: 'Commit timestamp',
},
url: {
type: 'string',
description: 'Commit URL',
},
author: {
name: {
type: 'string',
description: 'Author name',
},
email: {
type: 'string',
description: 'Author email',
},
},
added: {
type: 'array',
description: 'Array of added files',
},
removed: {
type: 'array',
description: 'Array of removed files',
},
modified: {
type: 'array',
description: 'Array of modified files',
},
},
head_commit: {
id: {
type: 'string',
description: 'Commit SHA',
},
message: {
type: 'string',
description: 'Commit message',
},
timestamp: {
type: 'string',
description: 'Commit timestamp',
},
author: {
name: {
type: 'string',
description: 'Author name',
},
email: {
type: 'string',
description: 'Author email',
},
},
},
pusher: {
name: {
type: 'string',
description: 'Pusher name',
},
email: {
type: 'string',
description: 'Pusher email',
},
},
branch: {
type: 'string',
description: 'Branch name extracted from ref',
},
repository: repositoryOutputs,
sender: userOutputs,
} as any
}
/**
* Build output schema for release events
*/
export function buildReleaseOutputs(): Record<string, TriggerOutput> {
return {
action: {
type: 'string',
description: 'Action performed (published, created, edited, deleted, prereleased, released)',
},
release: {
id: {
type: 'number',
description: 'Release ID',
},
node_id: {
type: 'string',
description: 'Release node ID',
},
tag_name: {
type: 'string',
description: 'Git tag name',
},
target_commitish: {
type: 'string',
description: 'Target branch or commit',
},
name: {
type: 'string',
description: 'Release name/title',
},
body: {
type: 'string',
description: 'Release notes/description',
},
draft: {
type: 'boolean',
description: 'Whether the release is a draft',
},
prerelease: {
type: 'boolean',
description: 'Whether the release is a pre-release',
},
html_url: {
type: 'string',
description: 'Release HTML URL',
},
tarball_url: {
type: 'string',
description: 'Tarball download URL',
},
zipball_url: {
type: 'string',
description: 'Zipball download URL',
},
author: userOutputs,
assets: {
type: 'array',
description: 'Array of release asset objects',
},
created_at: {
type: 'string',
description: 'Release creation timestamp',
},
published_at: {
type: 'string',
description: 'Release publication timestamp',
},
},
repository: repositoryOutputs,
sender: userOutputs,
} as any
}
/**
* Check if a GitHub event matches the expected trigger configuration
* This is used for event filtering in the webhook processor
*/
export function isGitHubEventMatch(
triggerId: string,
eventType: string,
action?: string,
payload?: any
): boolean {
const eventMap: Record<
string,
{ event: string; actions?: string[]; validator?: (payload: any) => boolean }
> = {
github_issue_opened: { event: 'issues', actions: ['opened'] },
github_issue_closed: { event: 'issues', actions: ['closed'] },
github_issue_comment: {
event: 'issue_comment',
validator: (p) => !p.issue?.pull_request, // Only issues, not PRs
},
github_pr_opened: { event: 'pull_request', actions: ['opened'] },
github_pr_closed: {
event: 'pull_request',
actions: ['closed'],
validator: (p) => p.pull_request?.merged === false, // Not merged
},
github_pr_merged: {
event: 'pull_request',
actions: ['closed'],
validator: (p) => p.pull_request?.merged === true, // Merged
},
github_pr_comment: {
event: 'issue_comment',
validator: (p) => !!p.issue?.pull_request, // Only PRs, not issues
},
github_pr_reviewed: { event: 'pull_request_review', actions: ['submitted'] },
github_push: { event: 'push' },
github_release_published: { event: 'release', actions: ['published'] },
}
const config = eventMap[triggerId]
if (!config) {
return true // Unknown trigger, allow through
}
// Check event type
if (config.event !== eventType) {
return false
}
// Check action if specified
if (config.actions && action && !config.actions.includes(action)) {
return false
}
// Run custom validator if provided
if (config.validator && payload) {
return config.validator(payload)
}
return true
}

View File

@@ -19,6 +19,10 @@ export const githubWebhookTrigger: TriggerConfig = {
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_webhook',
},
},
{
id: 'contentType',
@@ -32,16 +36,24 @@ export const githubWebhookTrigger: TriggerConfig = {
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_webhook',
},
},
{
id: 'webhookSecret',
title: 'Webhook Secret (Recommended)',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_webhook',
},
},
{
id: 'sslVerification',
@@ -55,6 +67,10 @@ export const githubWebhookTrigger: TriggerConfig = {
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_webhook',
},
},
{
id: 'triggerInstructions',
@@ -76,6 +92,10 @@ export const githubWebhookTrigger: TriggerConfig = {
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_webhook',
},
},
{
id: 'triggerSave',
@@ -83,6 +103,10 @@ export const githubWebhookTrigger: TriggerConfig = {
type: 'trigger-save',
mode: 'trigger',
triggerId: 'github_webhook',
condition: {
field: 'selectedTriggerId',
value: 'github_webhook',
},
},
{
id: 'samplePayload',
@@ -133,6 +157,10 @@ export const githubWebhookTrigger: TriggerConfig = {
collapsible: true,
defaultCollapsed: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_webhook',
},
},
],

View File

@@ -0,0 +1,609 @@
import { GithubIcon } from '@/components/icons'
import type { TriggerConfig } from '@/triggers/types'
export const githubWorkflowRunTrigger: TriggerConfig = {
id: 'github_workflow_run',
name: 'GitHub Actions Workflow Run',
provider: 'github',
description:
'Trigger workflow when a GitHub Actions workflow run is requested, in progress, or completed',
version: '1.0.0',
icon: GithubIcon,
subBlocks: [
{
id: 'webhookUrlDisplay',
title: 'Webhook URL',
type: 'short-input',
readOnly: true,
showCopyButton: true,
useWebhookUrl: true,
placeholder: 'Webhook URL will be generated',
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_workflow_run',
},
},
{
id: 'contentType',
title: 'Content Type',
type: 'dropdown',
options: [
{ label: 'application/json', id: 'application/json' },
{
label: 'application/x-www-form-urlencoded',
id: 'application/x-www-form-urlencoded',
},
],
defaultValue: 'application/json',
description: 'Format GitHub will use when sending the webhook payload.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_workflow_run',
},
},
{
id: 'webhookSecret',
title: 'Webhook Secret',
type: 'short-input',
placeholder: 'Generate or enter a strong secret',
description: 'Validates that webhook deliveries originate from GitHub.',
password: true,
required: false,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_workflow_run',
},
},
{
id: 'sslVerification',
title: 'SSL Verification',
type: 'dropdown',
options: [
{ label: 'Enabled', id: 'enabled' },
{ label: 'Disabled', id: 'disabled' },
],
defaultValue: 'enabled',
description: 'GitHub verifies SSL certificates when delivering webhooks.',
required: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_workflow_run',
},
},
{
id: 'triggerInstructions',
title: 'Setup Instructions',
type: 'text',
defaultValue: [
'Go to your GitHub Repository > Settings > Webhooks.',
'Click "Add webhook".',
'Paste the <strong>Webhook URL</strong> above into the "Payload URL" field.',
'Select your chosen Content Type from the dropdown.',
'Enter the <strong>Webhook Secret</strong> into the "Secret" field if you\'ve configured one.',
'Set SSL verification according to your selection.',
'Select "Let me select individual events" and check <strong>Workflow runs</strong>.',
'Ensure "Active" is checked and click "Add webhook".',
]
.map(
(instruction, index) =>
`<div class="mb-3"><strong>${index + 1}.</strong> ${instruction}</div>`
)
.join(''),
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_workflow_run',
},
},
{
id: 'triggerSave',
title: '',
type: 'trigger-save',
mode: 'trigger',
triggerId: 'github_workflow_run',
condition: {
field: 'selectedTriggerId',
value: 'github_workflow_run',
},
},
{
id: 'samplePayload',
title: 'Event Payload Example',
type: 'code',
language: 'json',
defaultValue: JSON.stringify(
{
action: 'completed',
workflow_run: {
id: 30433642,
node_id: 'MDEyOldvcmtmbG93IFJ1bjI2OTI4OQ==',
name: 'Build',
workflow_id: 159038,
run_number: 562,
run_attempt: 1,
event: 'push',
status: 'completed',
conclusion: 'success',
head_branch: 'master',
head_sha: 'acb5820ced9479c074f688cc328bf03f341a511d',
path: '.github/workflows/build.yml',
display_title: 'Update README',
run_started_at: '2020-01-22T19:33:08Z',
created_at: '2020-01-22T19:33:08Z',
updated_at: '2020-01-22T19:33:08Z',
html_url: 'https://github.com/octo-org/octo-repo/actions/runs/30433642',
check_suite_id: 42,
check_suite_node_id: 'MDEwOkNoZWNrU3VpdGU0Mg==',
url: 'https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642',
actor: {
login: 'octocat',
id: 1,
node_id: 'MDQ6VXNlcjE=',
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
html_url: 'https://github.com/octocat',
type: 'User',
},
triggering_actor: {
login: 'octocat',
id: 1,
node_id: 'MDQ6VXNlcjE=',
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
html_url: 'https://github.com/octocat',
type: 'User',
},
repository: {
id: 1296269,
node_id: 'MDEwOlJlcG9zaXRvcnkxMjk2MjY5',
name: 'Hello-World',
full_name: 'octocat/Hello-World',
private: false,
},
head_repository: {
id: 1296269,
node_id: 'MDEwOlJlcG9zaXRvcnkxMjk2MjY5',
name: 'Hello-World',
full_name: 'octocat/Hello-World',
private: false,
},
head_commit: {
id: 'acb5820ced9479c074f688cc328bf03f341a511d',
tree_id: 'd23f6eedb1e1b34603681f77168dc1c4',
message: 'Update README.md',
timestamp: '2020-01-22T19:33:05Z',
author: {
name: 'Octo Cat',
email: 'octocat@github.com',
},
committer: {
name: 'GitHub',
email: 'noreply@github.com',
},
},
pull_requests: [],
referenced_workflows: [],
},
workflow: {
id: 159038,
node_id: 'MDg6V29ya2Zsb3cxNTkwMzg=',
name: 'Build',
path: '.github/workflows/build.yml',
state: 'active',
created_at: '2020-01-08T23:48:37.000-08:00',
updated_at: '2020-01-08T23:50:21.000-08:00',
url: 'https://api.github.com/repos/octo-org/octo-repo/actions/workflows/159038',
html_url:
'https://github.com/octo-org/octo-repo/blob/master/.github/workflows/build.yml',
badge_url: 'https://github.com/octo-org/octo-repo/workflows/Build/badge.svg',
},
repository: {
id: 1296269,
node_id: 'MDEwOlJlcG9zaXRvcnkxMjk2MjY5',
name: 'Hello-World',
full_name: 'octocat/Hello-World',
html_url: 'https://github.com/octocat/Hello-World',
description: 'This your first repo!',
private: false,
owner: {
login: 'octocat',
id: 1,
node_id: 'MDQ6VXNlcjE=',
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
html_url: 'https://github.com/octocat',
type: 'User',
},
},
sender: {
login: 'octocat',
id: 1,
node_id: 'MDQ6VXNlcjE=',
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
html_url: 'https://github.com/octocat',
type: 'User',
},
},
null,
2
),
readOnly: true,
collapsible: true,
defaultCollapsed: true,
mode: 'trigger',
condition: {
field: 'selectedTriggerId',
value: 'github_workflow_run',
},
},
],
outputs: {
action: {
type: 'string',
description: 'Action performed (requested, in_progress, completed)',
},
workflow_run: {
id: {
type: 'number',
description: 'Workflow run ID',
},
node_id: {
type: 'string',
description: 'Workflow run node ID',
},
name: {
type: 'string',
description: 'Workflow name',
},
workflow_id: {
type: 'number',
description: 'Workflow ID',
},
run_number: {
type: 'number',
description: 'Run number for this workflow',
},
run_attempt: {
type: 'number',
description: 'Attempt number for this run',
},
event: {
type: 'string',
description: 'Event that triggered the workflow (push, pull_request, etc.)',
},
status: {
type: 'string',
description: 'Current status (queued, in_progress, completed)',
},
conclusion: {
type: 'string',
description:
'Conclusion (success, failure, cancelled, skipped, timed_out, action_required)',
},
head_branch: {
type: 'string',
description: 'Branch name',
},
head_sha: {
type: 'string',
description: 'Commit SHA that triggered the workflow',
},
path: {
type: 'string',
description: 'Path to the workflow file',
},
display_title: {
type: 'string',
description: 'Display title for the run',
},
run_started_at: {
type: 'string',
description: 'Timestamp when the run started',
},
created_at: {
type: 'string',
description: 'Workflow run creation timestamp',
},
updated_at: {
type: 'string',
description: 'Workflow run last update timestamp',
},
html_url: {
type: 'string',
description: 'Workflow run HTML URL',
},
check_suite_id: {
type: 'number',
description: 'Associated check suite ID',
},
check_suite_node_id: {
type: 'string',
description: 'Associated check suite node ID',
},
url: {
type: 'string',
description: 'Workflow run API URL',
},
actor: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
triggering_actor: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
repository: {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name',
},
private: {
type: 'boolean',
description: 'Whether repository is private',
},
},
head_repository: {
id: {
type: 'number',
description: 'Head repository ID',
},
node_id: {
type: 'string',
description: 'Head repository node ID',
},
name: {
type: 'string',
description: 'Head repository name',
},
full_name: {
type: 'string',
description: 'Head repository full name',
},
private: {
type: 'boolean',
description: 'Whether repository is private',
},
},
head_commit: {
id: {
type: 'string',
description: 'Commit SHA',
},
tree_id: {
type: 'string',
description: 'Tree ID',
},
message: {
type: 'string',
description: 'Commit message',
},
timestamp: {
type: 'string',
description: 'Commit timestamp',
},
author: {
name: {
type: 'string',
description: 'Author name',
},
email: {
type: 'string',
description: 'Author email',
},
},
committer: {
name: {
type: 'string',
description: 'Committer name',
},
email: {
type: 'string',
description: 'Committer email',
},
},
},
pull_requests: {
type: 'array',
description: 'Array of associated pull requests',
},
referenced_workflows: {
type: 'array',
description: 'Array of referenced workflow runs',
},
},
workflow: {
id: {
type: 'number',
description: 'Workflow ID',
},
node_id: {
type: 'string',
description: 'Workflow node ID',
},
name: {
type: 'string',
description: 'Workflow name',
},
path: {
type: 'string',
description: 'Path to workflow file',
},
state: {
type: 'string',
description: 'Workflow state (active, deleted, disabled_fork, etc.)',
},
created_at: {
type: 'string',
description: 'Workflow creation timestamp',
},
updated_at: {
type: 'string',
description: 'Workflow last update timestamp',
},
url: {
type: 'string',
description: 'Workflow API URL',
},
html_url: {
type: 'string',
description: 'Workflow HTML URL',
},
badge_url: {
type: 'string',
description: 'Workflow badge URL',
},
},
repository: {
id: {
type: 'number',
description: 'Repository ID',
},
node_id: {
type: 'string',
description: 'Repository node ID',
},
name: {
type: 'string',
description: 'Repository name',
},
full_name: {
type: 'string',
description: 'Repository full name (owner/repo)',
},
private: {
type: 'boolean',
description: 'Whether the repository is private',
},
html_url: {
type: 'string',
description: 'Repository HTML URL',
},
repo_description: {
type: 'string',
description: 'Repository description',
},
owner: {
login: {
type: 'string',
description: 'Owner username',
},
id: {
type: 'number',
description: 'Owner ID',
},
node_id: {
type: 'string',
description: 'Owner node ID',
},
avatar_url: {
type: 'string',
description: 'Owner avatar URL',
},
html_url: {
type: 'string',
description: 'Owner profile URL',
},
owner_type: {
type: 'string',
description: 'Owner type (User, Organization)',
},
},
},
sender: {
login: {
type: 'string',
description: 'Username',
},
id: {
type: 'number',
description: 'User ID',
},
node_id: {
type: 'string',
description: 'User node ID',
},
avatar_url: {
type: 'string',
description: 'Avatar URL',
},
html_url: {
type: 'string',
description: 'Profile URL',
},
user_type: {
type: 'string',
description: 'User type (User, Bot, Organization)',
},
},
},
webhook: {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-GitHub-Event': 'workflow_run',
'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'X-Hub-Signature-256': 'sha256=...',
},
},
}

View File

@@ -1,6 +1,19 @@
import { airtableWebhookTrigger } from '@/triggers/airtable'
import { genericWebhookTrigger } from '@/triggers/generic'
import { githubWebhookTrigger } from '@/triggers/github'
import {
githubIssueClosedTrigger,
githubIssueCommentTrigger,
githubIssueOpenedTrigger,
githubPRClosedTrigger,
githubPRCommentTrigger,
githubPRMergedTrigger,
githubPROpenedTrigger,
githubPRReviewedTrigger,
githubPushTrigger,
githubReleasePublishedTrigger,
githubWebhookTrigger,
githubWorkflowRunTrigger,
} from '@/triggers/github'
import { gmailPollingTrigger } from '@/triggers/gmail'
import { googleFormsWebhookTrigger } from '@/triggers/googleforms'
import {
@@ -27,6 +40,17 @@ export const TRIGGER_REGISTRY: TriggerRegistry = {
airtable_webhook: airtableWebhookTrigger,
generic_webhook: genericWebhookTrigger,
github_webhook: githubWebhookTrigger,
github_issue_opened: githubIssueOpenedTrigger,
github_issue_closed: githubIssueClosedTrigger,
github_issue_comment: githubIssueCommentTrigger,
github_pr_opened: githubPROpenedTrigger,
github_pr_closed: githubPRClosedTrigger,
github_pr_merged: githubPRMergedTrigger,
github_pr_comment: githubPRCommentTrigger,
github_pr_reviewed: githubPRReviewedTrigger,
github_push: githubPushTrigger,
github_release_published: githubReleasePublishedTrigger,
github_workflow_run: githubWorkflowRunTrigger,
gmail_poller: gmailPollingTrigger,
microsoftteams_webhook: microsoftTeamsWebhookTrigger,
microsoftteams_chat_subscription: microsoftTeamsChatSubscriptionTrigger,