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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
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' },
},
},
},
}
export const addAssigneesV2Tool: ToolConfig<AddAssigneesParams, any> = {
id: 'github_add_assignees_v2',
name: addAssigneesTool.name,
description: addAssigneesTool.description,
version: '2.0.0',
params: addAssigneesTool.params,
request: addAssigneesTool.request,
transformResponse: async (response: Response) => {
const issue = await response.json()
return {
success: true,
output: {
id: issue.id,
number: issue.number,
title: issue.title,
state: issue.state,
html_url: issue.html_url,
body: issue.body ?? null,
user: issue.user,
labels: issue.labels ?? [],
assignees: issue.assignees ?? [],
created_at: issue.created_at,
updated_at: issue.updated_at,
},
}
},
outputs: {
id: { type: 'number', description: 'Issue ID' },
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' },
body: { type: 'string', description: 'Issue body', optional: true },
user: { type: 'json', description: 'Issue creator' },
labels: { type: 'array', description: 'Array of label objects' },
assignees: { type: 'array', description: 'Array of assignee objects' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
},
}
+138
View File
@@ -0,0 +1,138 @@
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, params) => {
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: params?.issue_number ?? 0,
html_url: params
? `https://github.com/${params.owner}/${params.repo}/issues/${params.issue_number}`
: '',
},
},
}
},
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' },
},
},
},
}
export const addLabelsV2Tool: ToolConfig<AddLabelsParams, any> = {
id: 'github_add_labels_v2',
name: addLabelsTool.name,
description: addLabelsTool.description,
version: '2.0.0',
params: addLabelsTool.params,
request: addLabelsTool.request,
transformResponse: async (response: Response) => {
const labels = await response.json()
return {
success: true,
output: {
items: labels.map((label: any) => ({
...label,
description: label.description ?? null,
})),
count: labels.length,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of label objects on the issue',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Label ID' },
name: { type: 'string', description: 'Label name' },
color: { type: 'string', description: 'Label color' },
description: { type: 'string', description: 'Label description', optional: true },
},
},
},
count: { type: 'number', description: 'Number of labels' },
},
}
@@ -0,0 +1,147 @@
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)',
},
},
},
},
}
export const cancelWorkflowRunV2Tool: ToolConfig = {
id: 'github_cancel_workflow_run_v2',
name: cancelWorkflowRunTool.name,
description: cancelWorkflowRunTool.description,
version: '2.0.0',
params: cancelWorkflowRunTool.params,
request: cancelWorkflowRunTool.request,
oauth: cancelWorkflowRunTool.oauth,
transformResponse: async (response: Response, params) => {
return {
success: true,
output: {
cancelled: response.status === 202,
run_id: params?.run_id ?? null,
},
}
},
outputs: {
cancelled: { type: 'boolean', description: 'Whether cancellation was initiated' },
run_id: { type: 'number', description: 'Workflow run ID', optional: true },
},
}
+115
View File
@@ -0,0 +1,115 @@
import type { ToolConfig } from '@/tools/types'
interface CheckStarParams {
owner: string
repo: string
apiKey: string
}
interface CheckStarResponse {
success: boolean
output: {
content: string
metadata: {
starred: boolean
owner: string
repo: string
}
}
}
export const checkStarTool: ToolConfig<CheckStarParams, CheckStarResponse> = {
id: 'github_check_star',
name: 'GitHub Check Star',
description: 'Check if you have starred a 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',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/user/starred/${params.owner}/${params.repo}`,
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response, params) => {
const starred = response.status === 204
return {
success: true,
output: {
content: starred
? `You have starred ${params?.owner}/${params?.repo}`
: `You have not starred ${params?.owner}/${params?.repo}`,
metadata: {
starred,
owner: params?.owner ?? '',
repo: params?.repo ?? '',
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Check star metadata',
properties: {
starred: { type: 'boolean', description: 'Whether you have starred the repo' },
owner: { type: 'string', description: 'Repository owner' },
repo: { type: 'string', description: 'Repository name' },
},
},
},
}
export const checkStarV2Tool: ToolConfig<CheckStarParams, any> = {
id: 'github_check_star_v2',
name: checkStarTool.name,
description: checkStarTool.description,
version: '2.0.0',
params: checkStarTool.params,
request: checkStarTool.request,
transformResponse: async (response: Response, params) => {
const starred = response.status === 204
return {
success: true,
output: {
starred,
owner: params?.owner ?? '',
repo: params?.repo ?? '',
},
}
},
outputs: {
starred: { type: 'boolean', description: 'Whether you have starred the repo' },
owner: { type: 'string', description: 'Repository owner' },
repo: { type: 'string', description: 'Repository name' },
},
}
+163
View File
@@ -0,0 +1,163 @@
import type { CloseIssueParams, IssueResponse } from '@/tools/github/types'
import { ISSUE_OUTPUT_PROPERTIES, LABEL_OUTPUT, USER_OUTPUT } 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' },
},
},
},
}
export const closeIssueV2Tool: ToolConfig<CloseIssueParams, any> = {
id: 'github_close_issue_v2',
name: closeIssueTool.name,
description: closeIssueTool.description,
version: '2.0.0',
params: closeIssueTool.params,
request: closeIssueTool.request,
transformResponse: async (response: Response) => {
const issue = await response.json()
return {
success: true,
output: {
id: issue.id,
number: issue.number,
title: issue.title,
state: issue.state,
state_reason: issue.state_reason ?? null,
html_url: issue.html_url,
body: issue.body ?? null,
user: issue.user,
labels: issue.labels ?? [],
assignees: issue.assignees ?? [],
closed_at: issue.closed_at ?? null,
created_at: issue.created_at,
updated_at: issue.updated_at,
},
}
},
outputs: {
...ISSUE_OUTPUT_PROPERTIES,
state_reason: { type: 'string', description: 'Reason for closing', optional: true },
user: USER_OUTPUT,
labels: {
type: 'array',
description: 'Array of label objects',
items: LABEL_OUTPUT,
},
assignees: {
type: 'array',
description: 'Array of assignee objects',
items: USER_OUTPUT,
},
},
}
+141
View File
@@ -0,0 +1,141 @@
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' },
},
},
},
}
export const closePRV2Tool: ToolConfig<ClosePRParams, any> = {
id: 'github_close_pr_v2',
name: closePRTool.name,
description: closePRTool.description,
version: '2.0.0',
params: closePRTool.params,
request: closePRTool.request,
transformResponse: async (response: Response) => {
const pr = await response.json()
return {
success: true,
output: {
id: pr.id,
number: pr.number,
title: pr.title,
state: pr.state,
html_url: pr.html_url,
body: pr.body ?? null,
user: pr.user,
head: pr.head,
base: pr.base,
draft: pr.draft,
merged: pr.merged,
closed_at: pr.closed_at ?? null,
created_at: pr.created_at,
updated_at: pr.updated_at,
},
}
},
outputs: {
id: { type: 'number', description: 'PR ID' },
number: { type: 'number', description: 'PR number' },
title: { type: 'string', description: 'PR title' },
state: { type: 'string', description: 'PR state (closed)' },
html_url: { type: 'string', description: 'GitHub web URL' },
body: { type: 'string', description: 'PR description', optional: true },
user: { type: 'json', description: 'User who created the PR' },
head: { type: 'json', description: 'Head branch info' },
base: { type: 'json', description: 'Base branch info' },
draft: { type: 'boolean', description: 'Whether PR is a draft' },
merged: { type: 'boolean', description: 'Whether PR is merged' },
closed_at: { type: 'string', description: 'Close timestamp', optional: true },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
},
}
+177
View File
@@ -0,0 +1,177 @@
import type { CreateCommentParams, CreateCommentResponse } from '@/tools/github/types'
import { COMMENT_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const commentTool: ToolConfig<CreateCommentParams, CreateCommentResponse> = {
id: 'github_comment',
name: 'GitHub PR Commenter',
description: 'Create comments on GitHub PRs',
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',
},
body: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Comment content',
},
pullNumber: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Pull request number',
},
path: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'File path for review comment',
},
position: {
type: 'number',
required: false,
visibility: 'hidden',
description: 'Line number for review comment',
},
commentType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Type of comment (pr_comment or file_comment)',
},
line: {
type: 'number',
required: false,
visibility: 'hidden',
description: 'Line number for review comment',
},
side: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'Side of the diff (LEFT or RIGHT)',
default: 'RIGHT',
},
commitId: {
type: 'string',
required: false,
visibility: 'hidden',
description: 'The SHA of the commit to comment on',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => {
if (params.path) {
return `https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/comments`
}
return `https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/reviews`
},
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
if (params.commentType === 'file_comment') {
return {
body: params.body,
commit_id: params.commitId,
path: params.path,
line: params.line || params.position,
side: params.side || 'RIGHT',
}
}
return {
body: params.body,
event: 'COMMENT',
}
},
},
transformResponse: async (response) => {
const data = await response.json()
// Create a human-readable content string
const content = `Comment created: "${data.body}"`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
html_url: data.html_url,
created_at: data.created_at,
updated_at: data.updated_at,
path: data.path,
line: data.line || data.position,
side: data.side,
commit_id: data.commit_id,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable comment confirmation' },
metadata: {
type: 'object',
description: 'Comment metadata',
},
},
}
export const commentV2Tool: ToolConfig = {
id: 'github_comment_v2',
name: commentTool.name,
description: commentTool.description,
version: '2.0.0',
params: commentTool.params,
request: commentTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
id: data.id,
body: data.body,
html_url: data.html_url,
user: data.user,
path: data.path ?? null,
line: data.line ?? data.position ?? null,
side: data.side ?? null,
commit_id: data.commit_id ?? null,
created_at: data.created_at,
updated_at: data.updated_at,
},
}
},
outputs: {
...COMMENT_OUTPUT_PROPERTIES,
user: USER_OUTPUT,
path: { type: 'string', description: 'File path (if file comment)', optional: true },
line: { type: 'number', description: 'Line number', optional: true },
side: { type: 'string', description: 'Diff side', optional: true },
commit_id: { type: 'string', description: 'Commit ID', optional: true },
},
}
+303
View File
@@ -0,0 +1,303 @@
import {
COMMIT_DATA_OUTPUT,
COMMIT_FILE_OUTPUT_PROPERTIES,
USER_FULL_OUTPUT,
} from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface CompareCommitsParams {
owner: string
repo: string
base: string
head: string
per_page?: number
page?: number
apiKey: string
}
interface CompareCommitsResponse {
success: boolean
output: {
content: string
metadata: {
status: string
ahead_by: number
behind_by: number
total_commits: number
html_url: string
diff_url: string
patch_url: string
base_commit: { sha: string; html_url: string }
merge_base_commit: { sha: string; html_url: string }
commits: Array<{
sha: string
html_url: string
message: string
author: { login?: string; name: string }
}>
files: Array<{
filename: string
status: string
additions: number
deletions: number
changes: number
}>
}
}
}
export const compareCommitsTool: ToolConfig<CompareCommitsParams, CompareCommitsResponse> = {
id: 'github_compare_commits',
name: 'GitHub Compare Commits',
description:
'Compare two commits or branches to see the diff, commits between them, and changed files',
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',
},
base: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Base branch/tag/SHA for comparison',
},
head: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Head branch/tag/SHA for comparison',
},
per_page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Results per page for files (max 100, default: 30)',
default: 30,
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number for files (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}/compare/${params.base}...${params.head}`
)
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
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 data = await response.json()
const commits = (data.commits ?? []).map((c: any) => ({
sha: c.sha,
html_url: c.html_url,
message: c.commit.message,
author: {
login: c.author?.login,
name: c.commit.author.name,
},
}))
const files = (data.files ?? []).map((f: any) => ({
filename: f.filename,
status: f.status,
additions: f.additions,
deletions: f.deletions,
changes: f.changes,
}))
const metadata = {
status: data.status,
ahead_by: data.ahead_by,
behind_by: data.behind_by,
total_commits: data.total_commits,
html_url: data.html_url,
diff_url: data.diff_url,
patch_url: data.patch_url,
base_commit: {
sha: data.base_commit.sha,
html_url: data.base_commit.html_url,
},
merge_base_commit: {
sha: data.merge_base_commit.sha,
html_url: data.merge_base_commit.html_url,
},
commits,
files,
}
const content = `Comparing ${data.base_commit.sha.substring(0, 7)}...${data.commits?.length > 0 ? data.commits[data.commits.length - 1].sha.substring(0, 7) : 'HEAD'}
Status: ${data.status} | Ahead: ${data.ahead_by} | Behind: ${data.behind_by}
Total commits: ${data.total_commits} | Files changed: ${files.length}
${data.html_url}
Commits:
${commits.map((c: any) => ` ${c.sha.substring(0, 7)} - ${c.message.split('\n')[0]}`).join('\n')}
Files changed:
${files.map((f: any) => ` ${f.status}: ${f.filename} (+${f.additions} -${f.deletions})`).join('\n')}`
return {
success: true,
output: {
content,
metadata,
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable comparison' },
metadata: {
type: 'object',
description: 'Comparison metadata',
properties: {
status: { type: 'string', description: 'ahead, behind, identical, or diverged' },
ahead_by: { type: 'number', description: 'Commits ahead' },
behind_by: { type: 'number', description: 'Commits behind' },
total_commits: { type: 'number', description: 'Total commits between' },
html_url: { type: 'string', description: 'GitHub web URL' },
diff_url: { type: 'string', description: 'Diff URL' },
patch_url: { type: 'string', description: 'Patch URL' },
base_commit: { type: 'object', description: 'Base commit info' },
merge_base_commit: { type: 'object', description: 'Merge base commit info' },
commits: {
type: 'array',
description: 'Commits between base and head',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'Web URL' },
message: { type: 'string', description: 'Commit message' },
author: { type: 'object', description: 'Author info' },
},
},
},
files: {
type: 'array',
description: 'Changed files',
items: {
type: 'object',
properties: {
filename: { type: 'string', description: 'File path' },
status: { type: 'string', description: 'Change type' },
additions: { type: 'number', description: 'Lines added' },
deletions: { type: 'number', description: 'Lines deleted' },
changes: { type: 'number', description: 'Total changes' },
},
},
},
},
},
},
}
export const compareCommitsV2Tool: ToolConfig<CompareCommitsParams, any> = {
id: 'github_compare_commits_v2',
name: compareCommitsTool.name,
description: compareCommitsTool.description,
version: '2.0.0',
params: compareCommitsTool.params,
request: compareCommitsTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
...data,
commits: data.commits ?? [],
files: data.files ?? [],
},
}
},
outputs: {
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'GitHub web URL' },
permalink_url: { type: 'string', description: 'Permanent link URL' },
diff_url: { type: 'string', description: 'Diff download URL' },
patch_url: { type: 'string', description: 'Patch download URL' },
status: {
type: 'string',
description: 'Comparison status (ahead, behind, identical, diverged)',
},
ahead_by: { type: 'number', description: 'Commits head is ahead of base' },
behind_by: { type: 'number', description: 'Commits head is behind base' },
total_commits: { type: 'number', description: 'Total commits in comparison' },
base_commit: {
type: 'object',
description: 'Base commit object',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'Web URL' },
commit: COMMIT_DATA_OUTPUT,
author: USER_FULL_OUTPUT,
committer: USER_FULL_OUTPUT,
},
},
merge_base_commit: {
type: 'object',
description: 'Merge base commit object',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'Web URL' },
},
},
commits: {
type: 'array',
description: 'Commits between base and head',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'Web URL' },
commit: COMMIT_DATA_OUTPUT,
author: USER_FULL_OUTPUT,
committer: USER_FULL_OUTPUT,
},
},
},
files: {
type: 'array',
description: 'Changed files (diff entries)',
items: {
type: 'object',
properties: COMMIT_FILE_OUTPUT_PROPERTIES,
},
},
},
}
+118
View File
@@ -0,0 +1,118 @@
import type { CreateBranchParams, RefResponse } from '@/tools/github/types'
import { GIT_REF_OUTPUT_PROPERTIES } 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' },
},
},
},
}
export const createBranchV2Tool: ToolConfig<CreateBranchParams, any> = {
id: 'github_create_branch_v2',
name: createBranchTool.name,
description: createBranchTool.description,
version: '2.0.0',
params: createBranchTool.params,
request: createBranchTool.request,
transformResponse: async (response: Response) => {
const ref = await response.json()
return {
success: true,
output: {
ref: ref.ref,
node_id: ref.node_id,
url: ref.url,
object: ref.object,
},
}
},
outputs: GIT_REF_OUTPUT_PROPERTIES,
}
@@ -0,0 +1,137 @@
import { REACTION_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface CreateCommentReactionParams {
owner: string
repo: string
comment_id: number
content: '+1' | '-1' | 'laugh' | 'confused' | 'heart' | 'hooray' | 'rocket' | 'eyes'
apiKey: string
}
interface CreateCommentReactionResponse {
success: boolean
output: {
content: string
metadata: {
id: number
user: { login: string }
content: string
created_at: string
}
}
}
export const createCommentReactionTool: ToolConfig<
CreateCommentReactionParams,
CreateCommentReactionResponse
> = {
id: 'github_create_comment_reaction',
name: 'GitHub Create Comment Reaction',
description: 'Add a reaction to an issue comment',
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',
},
content: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Reaction type: +1 (thumbs up), -1 (thumbs down), laugh, confused, heart, hooray, rocket, eyes',
},
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}/reactions`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.squirrel-girl-preview+json',
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => ({
content: params.content,
}),
},
transformResponse: async (response) => {
const data = await response.json()
const content = `Added ${data.content} reaction to comment by ${data.user?.login ?? 'unknown'}`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
user: { login: data.user?.login ?? 'unknown' },
content: data.content,
created_at: data.created_at,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Reaction metadata',
properties: {
id: { type: 'number', description: 'Reaction ID' },
user: { type: 'object', description: 'User who reacted' },
content: { type: 'string', description: 'Reaction type' },
created_at: { type: 'string', description: 'Creation date' },
},
},
},
}
export const createCommentReactionV2Tool: ToolConfig<CreateCommentReactionParams, any> = {
id: 'github_create_comment_reaction_v2',
name: createCommentReactionTool.name,
description: createCommentReactionTool.description,
version: '2.0.0',
params: createCommentReactionTool.params,
request: createCommentReactionTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
...REACTION_OUTPUT_PROPERTIES,
user: { ...USER_OUTPUT, optional: true },
},
}
+196
View File
@@ -0,0 +1,196 @@
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' },
},
},
},
},
},
}
export const createFileV2Tool: ToolConfig = {
id: 'github_create_file_v2',
name: createFileTool.name,
description: createFileTool.description,
version: '2.0.0',
params: createFileTool.params,
request: createFileTool.request,
oauth: createFileTool.oauth,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
content: data.content,
commit: data.commit,
},
}
},
outputs: {
content: { type: 'json', description: 'Created file content info' },
commit: { type: 'json', description: 'Commit information' },
},
}
+208
View File
@@ -0,0 +1,208 @@
import type { ToolConfig } from '@/tools/types'
interface CreateGistParams {
description?: string
files: string
public?: boolean
apiKey: string
}
interface CreateGistResponse {
success: boolean
output: {
content: string
metadata: {
id: string
html_url: string
git_pull_url: string
git_push_url: string
description: string | null
public: boolean
created_at: string
updated_at: string
files: Record<
string,
{ filename: string; type: string; language: string | null; size: number }
>
owner: { login: string }
}
}
}
export const createGistTool: ToolConfig<CreateGistParams, CreateGistResponse> = {
id: 'github_create_gist',
name: 'GitHub Create Gist',
description: 'Create a new gist with one or more files',
version: '1.0.0',
params: {
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Description of the gist',
},
files: {
type: 'json',
required: true,
visibility: 'user-or-llm',
description:
'JSON object with filenames as keys and content as values. Example: {"file.txt": {"content": "Hello"}}',
},
public: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Whether the gist is public (default: false)',
default: false,
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: () => 'https://api.github.com/gists',
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const filesObj = typeof params.files === 'string' ? JSON.parse(params.files) : params.files
return {
description: params.description,
public: params.public ?? false,
files: filesObj,
}
},
},
transformResponse: async (response) => {
const data = await response.json()
const files: Record<
string,
{ filename: string; type: string; language: string | null; size: number }
> = {}
for (const [key, value] of Object.entries(data.files ?? {})) {
const file = value as any
files[key] = {
filename: file.filename,
type: file.type,
language: file.language ?? null,
size: file.size,
}
}
const metadata = {
id: data.id,
html_url: data.html_url,
git_pull_url: data.git_pull_url,
git_push_url: data.git_push_url,
description: data.description ?? null,
public: data.public,
created_at: data.created_at,
updated_at: data.updated_at,
files,
owner: { login: data.owner?.login ?? 'unknown' },
}
const content = `Created gist: ${data.html_url}
Description: ${data.description ?? 'No description'}
Public: ${data.public}
Files: ${Object.keys(files).join(', ')}`
return {
success: true,
output: {
content,
metadata,
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Gist metadata',
properties: {
id: { type: 'string', description: 'Gist ID' },
html_url: { type: 'string', description: 'Web URL' },
git_pull_url: { type: 'string', description: 'Git pull URL' },
git_push_url: { type: 'string', description: 'Git push URL' },
description: { type: 'string', description: 'Description', optional: true },
public: { type: 'boolean', description: 'Is public' },
created_at: { type: 'string', description: 'Creation date' },
updated_at: { type: 'string', description: 'Update date' },
files: { type: 'object', description: 'Files in gist' },
owner: { type: 'object', description: 'Owner info' },
},
},
},
}
export const createGistV2Tool: ToolConfig<CreateGistParams, any> = {
id: 'github_create_gist_v2',
name: createGistTool.name,
description: createGistTool.description,
version: '2.0.0',
params: createGistTool.params,
request: createGistTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
...data,
description: data.description ?? null,
files: data.files ?? {},
},
}
},
outputs: {
id: { type: 'string', description: 'Gist ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Web URL' },
forks_url: { type: 'string', description: 'Forks API URL' },
commits_url: { type: 'string', description: 'Commits API URL' },
git_pull_url: { type: 'string', description: 'Git pull URL' },
git_push_url: { type: 'string', description: 'Git push URL' },
description: { type: 'string', description: 'Gist description', optional: true },
public: { type: 'boolean', description: 'Whether gist is public' },
truncated: { type: 'boolean', description: 'Whether files are truncated' },
comments: { type: 'number', description: 'Number of comments' },
comments_url: { type: 'string', description: 'Comments API URL' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
files: {
type: 'object',
description:
'Files in the gist (object with filenames as keys, each containing filename, type, language, raw_url, size, truncated, content)',
},
owner: {
type: 'object',
description: 'Gist owner',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
}
+196
View File
@@ -0,0 +1,196 @@
import type { CreateIssueParams, IssueResponse } from '@/tools/github/types'
import {
ISSUE_OUTPUT_PROPERTIES,
LABEL_OUTPUT,
MILESTONE_OUTPUT,
USER_OUTPUT,
} 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' },
},
},
},
}
export const createIssueV2Tool: ToolConfig<CreateIssueParams, any> = {
id: 'github_create_issue_v2',
name: createIssueTool.name,
description: createIssueTool.description,
version: '2.0.0',
params: createIssueTool.params,
request: createIssueTool.request,
transformResponse: async (response: Response) => {
const issue = await response.json()
return {
success: true,
output: {
id: issue.id,
number: issue.number,
title: issue.title,
state: issue.state,
html_url: issue.html_url,
body: issue.body ?? null,
user: issue.user,
labels: issue.labels ?? [],
assignees: issue.assignees ?? [],
milestone: issue.milestone ?? null,
created_at: issue.created_at,
updated_at: issue.updated_at,
closed_at: issue.closed_at ?? null,
},
}
},
outputs: {
...ISSUE_OUTPUT_PROPERTIES,
user: USER_OUTPUT,
labels: {
type: 'array',
description: 'Array of label objects',
items: LABEL_OUTPUT,
},
assignees: {
type: 'array',
description: 'Array of assignee objects',
items: USER_OUTPUT,
},
milestone: { ...MILESTONE_OUTPUT, optional: true },
},
}
@@ -0,0 +1,137 @@
import { REACTION_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface CreateIssueReactionParams {
owner: string
repo: string
issue_number: number
content: '+1' | '-1' | 'laugh' | 'confused' | 'heart' | 'hooray' | 'rocket' | 'eyes'
apiKey: string
}
interface CreateIssueReactionResponse {
success: boolean
output: {
content: string
metadata: {
id: number
user: { login: string }
content: string
created_at: string
}
}
}
export const createIssueReactionTool: ToolConfig<
CreateIssueReactionParams,
CreateIssueReactionResponse
> = {
id: 'github_create_issue_reaction',
name: 'GitHub Create Issue Reaction',
description: 'Add a reaction to an 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',
},
content: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Reaction type: +1 (thumbs up), -1 (thumbs down), laugh, confused, heart, hooray, rocket, eyes',
},
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}/reactions`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.squirrel-girl-preview+json',
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => ({
content: params.content,
}),
},
transformResponse: async (response) => {
const data = await response.json()
const content = `Added ${data.content} reaction to issue by ${data.user?.login ?? 'unknown'}`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
user: { login: data.user?.login ?? 'unknown' },
content: data.content,
created_at: data.created_at,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Reaction metadata',
properties: {
id: { type: 'number', description: 'Reaction ID' },
user: { type: 'object', description: 'User who reacted' },
content: { type: 'string', description: 'Reaction type' },
created_at: { type: 'string', description: 'Creation date' },
},
},
},
}
export const createIssueReactionV2Tool: ToolConfig<CreateIssueReactionParams, any> = {
id: 'github_create_issue_reaction_v2',
name: createIssueReactionTool.name,
description: createIssueReactionTool.description,
version: '2.0.0',
params: createIssueReactionTool.params,
request: createIssueReactionTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: data,
}
},
outputs: {
...REACTION_OUTPUT_PROPERTIES,
user: { ...USER_OUTPUT, optional: true },
},
}
+176
View File
@@ -0,0 +1,176 @@
import { MILESTONE_CREATOR_OUTPUT, MILESTONE_V2_OUTPUT_PROPERTIES } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface CreateMilestoneParams {
owner: string
repo: string
title: string
state?: 'open' | 'closed'
description?: string
due_on?: string
apiKey: string
}
interface CreateMilestoneResponse {
success: boolean
output: {
content: string
metadata: {
number: number
title: string
description: string | null
state: string
html_url: string
due_on: string | null
open_issues: number
closed_issues: number
created_at: string
creator: { login: string }
}
}
}
export const createMilestoneTool: ToolConfig<CreateMilestoneParams, CreateMilestoneResponse> = {
id: 'github_create_milestone',
name: 'GitHub Create Milestone',
description: 'Create a milestone in a 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: 'Milestone title',
},
state: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'State: open or closed (default: open)',
default: 'open',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Milestone description',
},
due_on: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Due date (ISO 8601 format, e.g., 2024-12-31T23:59:59Z)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/milestones`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => ({
title: params.title,
state: params.state ?? 'open',
description: params.description,
due_on: params.due_on,
}),
},
transformResponse: async (response) => {
const data = await response.json()
const content = `Created milestone: ${data.title}
Number: ${data.number}
State: ${data.state}
Due: ${data.due_on ?? 'No due date'}
${data.html_url}`
return {
success: true,
output: {
content,
metadata: {
number: data.number,
title: data.title,
description: data.description ?? null,
state: data.state,
html_url: data.html_url,
due_on: data.due_on ?? null,
open_issues: data.open_issues,
closed_issues: data.closed_issues,
created_at: data.created_at,
creator: { login: data.creator?.login ?? 'unknown' },
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Milestone metadata',
properties: {
number: { type: 'number', description: 'Milestone number' },
title: { type: 'string', description: 'Title' },
description: { type: 'string', description: 'Description', optional: true },
state: { type: 'string', description: 'State' },
html_url: { type: 'string', description: 'Web URL' },
due_on: { type: 'string', description: 'Due date', optional: true },
open_issues: { type: 'number', description: 'Open issues count' },
closed_issues: { type: 'number', description: 'Closed issues count' },
created_at: { type: 'string', description: 'Creation date' },
creator: { type: 'object', description: 'Creator info' },
},
},
},
}
export const createMilestoneV2Tool: ToolConfig<CreateMilestoneParams, any> = {
id: 'github_create_milestone_v2',
name: createMilestoneTool.name,
description: createMilestoneTool.description,
version: '2.0.0',
params: createMilestoneTool.params,
request: createMilestoneTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
...data,
description: data.description ?? null,
due_on: data.due_on ?? null,
},
}
},
outputs: {
...MILESTONE_V2_OUTPUT_PROPERTIES,
creator: MILESTONE_CREATOR_OUTPUT,
},
}
+169
View File
@@ -0,0 +1,169 @@
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' },
},
},
},
}
export const createPRV2Tool: ToolConfig<CreatePRParams, any> = {
id: 'github_create_pr_v2',
name: createPRTool.name,
description: createPRTool.description,
version: '2.0.0',
params: createPRTool.params,
request: createPRTool.request,
transformResponse: async (response: Response) => {
const pr = await response.json()
return {
success: true,
output: {
id: pr.id,
number: pr.number,
title: pr.title,
state: pr.state,
html_url: pr.html_url,
body: pr.body ?? null,
user: pr.user,
head: pr.head,
base: pr.base,
draft: pr.draft,
merged: pr.merged,
mergeable: pr.mergeable ?? null,
created_at: pr.created_at,
updated_at: pr.updated_at,
},
}
},
outputs: {
id: { type: 'number', description: 'Pull request ID' },
number: { type: 'number', description: 'Pull request number' },
title: { type: 'string', description: 'PR title' },
state: { type: 'string', description: 'PR state' },
html_url: { type: 'string', description: 'GitHub web URL' },
body: { type: 'string', description: 'PR description', optional: true },
user: { type: 'json', description: 'User who created the PR' },
head: { type: 'json', description: 'Head branch info' },
base: { type: 'json', description: 'Base branch info' },
draft: { type: 'boolean', description: 'Whether PR is a draft' },
merged: { type: 'boolean', description: 'Whether PR is merged' },
mergeable: { type: 'boolean', description: 'Whether PR is mergeable', optional: true },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
},
}
+182
View File
@@ -0,0 +1,182 @@
import type { CreatePRReviewParams, PRReviewResponse } from '@/tools/github/types'
import { USER_OUTPUT } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const createPRReviewTool: ToolConfig<CreatePRReviewParams, PRReviewResponse> = {
id: 'github_create_pr_review',
name: 'GitHub Create PR Review',
description:
'Submit a review for a pull request. Use APPROVE, REQUEST_CHANGES, or COMMENT. A body is required for REQUEST_CHANGES and COMMENT reviews.',
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',
},
event: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The review action to perform: APPROVE, REQUEST_CHANGES, or COMMENT',
},
body: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The body text of the review (required for REQUEST_CHANGES and COMMENT)',
},
commit_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'The SHA of the commit that needs a review (defaults to the most recent commit)',
},
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}/reviews`,
method: 'POST',
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> = {
event: params.event,
}
if (params.body) body.body = params.body
if (params.commit_id) body.commit_id = params.commit_id
return body
},
},
transformResponse: async (response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({}))
return {
success: false,
error: error.message || `Failed to submit PR review (HTTP ${response.status})`,
output: {
content: '',
metadata: { id: 0, state: '', body: '', html_url: '', commit_id: '' },
},
}
}
const review = await response.json()
const content = `Review submitted for PR #${review.pull_request_url?.split('/').pop() ?? ''}
State: ${review.state}
URL: ${review.html_url}`
return {
success: true,
output: {
content,
metadata: {
id: review.id,
state: review.state,
body: review.body ?? '',
html_url: review.html_url,
commit_id: review.commit_id,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable review confirmation' },
metadata: {
type: 'object',
description: 'Review metadata',
properties: {
id: { type: 'number', description: 'Review ID' },
state: {
type: 'string',
description: 'Review state (APPROVED/CHANGES_REQUESTED/COMMENTED)',
},
body: { type: 'string', description: 'Review body text' },
html_url: { type: 'string', description: 'GitHub web URL for the review' },
commit_id: { type: 'string', description: 'SHA of the reviewed commit' },
},
},
},
}
export const createPRReviewV2Tool: ToolConfig<CreatePRReviewParams, any> = {
id: 'github_create_pr_review_v2',
name: createPRReviewTool.name,
description: createPRReviewTool.description,
version: '2.0.0',
params: createPRReviewTool.params,
request: createPRReviewTool.request,
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({}))
return {
success: false,
error: error.message || `Failed to submit PR review (HTTP ${response.status})`,
output: {
id: 0,
user: null,
body: null,
state: '',
html_url: '',
pull_request_url: '',
commit_id: '',
submitted_at: null,
},
}
}
const review = await response.json()
return {
success: true,
output: {
id: review.id,
user: review.user ?? null,
body: review.body ?? null,
state: review.state,
html_url: review.html_url,
pull_request_url: review.pull_request_url,
commit_id: review.commit_id,
submitted_at: review.submitted_at ?? null,
},
}
},
outputs: {
id: { type: 'number', description: 'Review ID' },
user: { ...USER_OUTPUT, optional: true },
body: { type: 'string', description: 'Review body text' },
state: { type: 'string', description: 'Review state (APPROVED/CHANGES_REQUESTED/COMMENTED)' },
html_url: { type: 'string', description: 'GitHub web URL for the review' },
pull_request_url: { type: 'string', description: 'API URL of the reviewed pull request' },
commit_id: { type: 'string', description: 'SHA of the reviewed commit' },
submitted_at: { type: 'string', description: 'Review submission timestamp' },
},
}
+190
View File
@@ -0,0 +1,190 @@
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,
},
},
},
},
}
export const createProjectV2Tool: ToolConfig = {
id: 'github_create_project_v2',
name: createProjectTool.name,
description: createProjectTool.description,
version: '2.0.0',
params: createProjectTool.params,
request: createProjectTool.request,
oauth: createProjectTool.oauth,
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
output: { id: '', title: '', number: 0, url: '' },
error: data.errors[0].message,
}
}
const project = data.data?.createProjectV2?.projectV2 || {}
return {
success: true,
output: {
id: project.id,
title: project.title,
number: project.number,
url: project.url,
closed: project.closed,
public: project.public,
shortDescription: project.shortDescription ?? null,
},
}
},
outputs: {
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: 'Short description', optional: true },
},
}
+209
View File
@@ -0,0 +1,209 @@
import type { CreateReleaseParams, ReleaseResponse } from '@/tools/github/types'
import {
RELEASE_ASSET_OUTPUT_PROPERTIES,
RELEASE_OUTPUT_PROPERTIES,
USER_OUTPUT,
} 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' },
},
},
},
}
export const createReleaseV2Tool: ToolConfig = {
id: 'github_create_release_v2',
name: createReleaseTool.name,
description: createReleaseTool.description,
version: '2.0.0',
params: createReleaseTool.params,
request: createReleaseTool.request,
oauth: createReleaseTool.oauth,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
id: data.id,
tag_name: data.tag_name,
name: data.name,
body: data.body ?? null,
html_url: data.html_url,
tarball_url: data.tarball_url,
zipball_url: data.zipball_url,
draft: data.draft,
prerelease: data.prerelease,
author: data.author,
assets: data.assets,
target_commitish: data.target_commitish,
created_at: data.created_at,
published_at: data.published_at ?? null,
},
}
},
outputs: {
...RELEASE_OUTPUT_PROPERTIES,
author: USER_OUTPUT,
assets: {
type: 'array',
description: 'Release assets',
items: {
type: 'object',
properties: {
...RELEASE_ASSET_OUTPUT_PROPERTIES,
uploader: USER_OUTPUT,
},
},
},
},
}
+112
View File
@@ -0,0 +1,112 @@
import type { DeleteBranchParams, DeleteBranchResponse } from '@/tools/github/types'
import { DELETE_BRANCH_OUTPUT_PROPERTIES } 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' },
},
},
},
}
export const deleteBranchV2Tool: ToolConfig<DeleteBranchParams, any> = {
id: 'github_delete_branch_v2',
name: deleteBranchTool.name,
description: deleteBranchTool.description,
version: '2.0.0',
params: deleteBranchTool.params,
request: deleteBranchTool.request,
transformResponse: async (response: Response, params) => {
// DELETE returns 204 No Content on success
return {
success: true,
output: {
deleted: response.status === 204,
branch: params?.branch || '',
},
}
},
outputs: DELETE_BRANCH_OUTPUT_PROPERTIES,
}
+103
View File
@@ -0,0 +1,103 @@
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, params) => {
const deleted = response.status === 204
const commentId = params?.comment_id ?? Number(response.url.split('/').pop())
const content = deleted
? `Comment #${commentId} successfully deleted`
: `Failed to delete comment #${commentId}`
return {
success: deleted,
output: {
content,
metadata: {
deleted,
comment_id: commentId,
},
},
}
},
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' },
},
},
},
}
export const deleteCommentV2Tool: ToolConfig<DeleteCommentParams, any> = {
id: 'github_delete_comment_v2',
name: deleteCommentTool.name,
description: deleteCommentTool.description,
version: '2.0.0',
params: deleteCommentTool.params,
request: deleteCommentTool.request,
transformResponse: async (response: Response, params) => {
const deleted = response.status === 204
return {
success: deleted,
output: {
deleted,
comment_id: params?.comment_id || 0,
},
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether deletion was successful' },
comment_id: { type: 'number', description: 'Deleted comment ID' },
},
}
@@ -0,0 +1,128 @@
import type { ToolConfig } from '@/tools/types'
interface DeleteCommentReactionParams {
owner: string
repo: string
comment_id: number
reaction_id: number
apiKey: string
}
interface DeleteCommentReactionResponse {
success: boolean
output: {
content: string
metadata: {
deleted: boolean
reaction_id: number
}
}
}
export const deleteCommentReactionTool: ToolConfig<
DeleteCommentReactionParams,
DeleteCommentReactionResponse
> = {
id: 'github_delete_comment_reaction',
name: 'GitHub Delete Comment Reaction',
description: 'Remove a reaction from an issue comment',
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',
},
reaction_id: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Reaction ID to delete',
},
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}/reactions/${params.reaction_id}`,
method: 'DELETE',
headers: (params) => ({
Accept: 'application/vnd.github.squirrel-girl-preview+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response, params) => {
const deleted = response.status === 204
return {
success: deleted,
output: {
content: deleted
? `Successfully deleted reaction ${params?.reaction_id}`
: `Failed to delete reaction ${params?.reaction_id}`,
metadata: {
deleted,
reaction_id: params?.reaction_id ?? 0,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Delete operation metadata',
properties: {
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
reaction_id: { type: 'number', description: 'The deleted reaction ID' },
},
},
},
}
export const deleteCommentReactionV2Tool: ToolConfig<DeleteCommentReactionParams, any> = {
id: 'github_delete_comment_reaction_v2',
name: deleteCommentReactionTool.name,
description: deleteCommentReactionTool.description,
version: '2.0.0',
params: deleteCommentReactionTool.params,
request: deleteCommentReactionTool.request,
transformResponse: async (response: Response, params) => {
const deleted = response.status === 204
return {
success: deleted,
output: {
deleted,
reaction_id: params?.reaction_id ?? 0,
},
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
reaction_id: { type: 'number', description: 'The deleted reaction ID' },
},
}
+174
View File
@@ -0,0 +1,174 @@
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, params) => {
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: params?.path ?? '',
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' },
},
},
},
},
},
}
export const deleteFileV2Tool: ToolConfig<DeleteFileParams, any> = {
id: 'github_delete_file_v2',
name: deleteFileTool.name,
description: deleteFileTool.description,
version: '2.0.0',
params: deleteFileTool.params,
request: deleteFileTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
content: data.content ?? null, // null when file is deleted
commit: data.commit,
},
}
},
outputs: {
content: { type: 'json', description: 'File content info (null for delete)', optional: true },
commit: { type: 'json', description: 'Commit information' },
},
}
+103
View File
@@ -0,0 +1,103 @@
import type { ToolConfig } from '@/tools/types'
interface DeleteGistParams {
gist_id: string
apiKey: string
}
interface DeleteGistResponse {
success: boolean
output: {
content: string
metadata: {
deleted: boolean
gist_id: string
}
}
}
export const deleteGistTool: ToolConfig<DeleteGistParams, DeleteGistResponse> = {
id: 'github_delete_gist',
name: 'GitHub Delete Gist',
description: 'Delete a gist by ID',
version: '1.0.0',
params: {
gist_id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The gist ID to delete',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/gists/${params.gist_id?.trim()}`,
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) => {
const deleted = response.status === 204
return {
success: deleted,
output: {
content: deleted
? `Successfully deleted gist ${params?.gist_id}`
: `Failed to delete gist ${params?.gist_id}`,
metadata: {
deleted,
gist_id: params?.gist_id ?? '',
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Delete operation metadata',
properties: {
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
gist_id: { type: 'string', description: 'The deleted gist ID' },
},
},
},
}
export const deleteGistV2Tool: ToolConfig<DeleteGistParams, any> = {
id: 'github_delete_gist_v2',
name: deleteGistTool.name,
description: deleteGistTool.description,
version: '2.0.0',
params: deleteGistTool.params,
request: deleteGistTool.request,
transformResponse: async (response: Response, params) => {
const deleted = response.status === 204
return {
success: deleted,
output: {
deleted,
gist_id: params?.gist_id ?? '',
},
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
gist_id: { type: 'string', description: 'The deleted gist ID' },
},
}
@@ -0,0 +1,128 @@
import type { ToolConfig } from '@/tools/types'
interface DeleteIssueReactionParams {
owner: string
repo: string
issue_number: number
reaction_id: number
apiKey: string
}
interface DeleteIssueReactionResponse {
success: boolean
output: {
content: string
metadata: {
deleted: boolean
reaction_id: number
}
}
}
export const deleteIssueReactionTool: ToolConfig<
DeleteIssueReactionParams,
DeleteIssueReactionResponse
> = {
id: 'github_delete_issue_reaction',
name: 'GitHub Delete Issue Reaction',
description: 'Remove a reaction from an 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',
},
reaction_id: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Reaction ID to delete',
},
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}/reactions/${params.reaction_id}`,
method: 'DELETE',
headers: (params) => ({
Accept: 'application/vnd.github.squirrel-girl-preview+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response, params) => {
const deleted = response.status === 204
return {
success: deleted,
output: {
content: deleted
? `Successfully deleted reaction ${params?.reaction_id}`
: `Failed to delete reaction ${params?.reaction_id}`,
metadata: {
deleted,
reaction_id: params?.reaction_id ?? 0,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Delete operation metadata',
properties: {
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
reaction_id: { type: 'number', description: 'The deleted reaction ID' },
},
},
},
}
export const deleteIssueReactionV2Tool: ToolConfig<DeleteIssueReactionParams, any> = {
id: 'github_delete_issue_reaction_v2',
name: deleteIssueReactionTool.name,
description: deleteIssueReactionTool.description,
version: '2.0.0',
params: deleteIssueReactionTool.params,
request: deleteIssueReactionTool.request,
transformResponse: async (response: Response, params) => {
const deleted = response.status === 204
return {
success: deleted,
output: {
deleted,
reaction_id: params?.reaction_id ?? 0,
},
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
reaction_id: { type: 'number', description: 'The deleted reaction ID' },
},
}
+118
View File
@@ -0,0 +1,118 @@
import type { ToolConfig } from '@/tools/types'
interface DeleteMilestoneParams {
owner: string
repo: string
milestone_number: number
apiKey: string
}
interface DeleteMilestoneResponse {
success: boolean
output: {
content: string
metadata: {
deleted: boolean
milestone_number: number
}
}
}
export const deleteMilestoneTool: ToolConfig<DeleteMilestoneParams, DeleteMilestoneResponse> = {
id: 'github_delete_milestone',
name: 'GitHub Delete Milestone',
description: 'Delete a milestone from a 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',
},
milestone_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Milestone number to delete',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/milestones/${params.milestone_number}`,
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) => {
const deleted = response.status === 204
return {
success: deleted,
output: {
content: deleted
? `Successfully deleted milestone #${params?.milestone_number}`
: `Failed to delete milestone #${params?.milestone_number}`,
metadata: {
deleted,
milestone_number: params?.milestone_number ?? 0,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Delete operation metadata',
properties: {
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
milestone_number: { type: 'number', description: 'The deleted milestone number' },
},
},
},
}
export const deleteMilestoneV2Tool: ToolConfig<DeleteMilestoneParams, any> = {
id: 'github_delete_milestone_v2',
name: deleteMilestoneTool.name,
description: deleteMilestoneTool.description,
version: '2.0.0',
params: deleteMilestoneTool.params,
request: deleteMilestoneTool.request,
transformResponse: async (response: Response, params) => {
const deleted = response.status === 204
return {
success: deleted,
output: {
deleted,
milestone_number: params?.milestone_number ?? 0,
},
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether deletion succeeded' },
milestone_number: { type: 'number', description: 'The deleted milestone number' },
},
}
+167
View File
@@ -0,0 +1,167 @@
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' },
},
},
},
}
export const deleteProjectV2Tool: ToolConfig<DeleteProjectParams, any> = {
id: 'github_delete_project_v2',
name: deleteProjectTool.name,
description: deleteProjectTool.description,
version: '2.0.0',
params: deleteProjectTool.params,
request: deleteProjectTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
output: { id: '', title: '', number: 0, url: '' },
error: data.errors[0].message,
}
}
const project = data.data?.deleteProjectV2?.projectV2 || {}
return {
success: true,
output: {
id: project.id,
title: project.title,
number: project.number,
url: project.url,
},
}
},
outputs: {
id: { type: 'string', description: 'Deleted project node ID' },
title: { type: 'string', description: 'Deleted project title' },
number: { type: 'number', description: 'Deleted project number' },
url: { type: 'string', description: 'Deleted project URL' },
},
}
+122
View File
@@ -0,0 +1,122 @@
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' },
},
},
},
}
export const deleteReleaseV2Tool: ToolConfig<DeleteReleaseParams, any> = {
id: 'github_delete_release_v2',
name: deleteReleaseTool.name,
description: deleteReleaseTool.description,
version: '2.0.0',
params: deleteReleaseTool.params,
request: deleteReleaseTool.request,
transformResponse: async (response: Response, params) => {
return {
success: true,
output: {
deleted: response.status === 204,
release_id: params?.release_id || 0,
},
}
},
outputs: {
deleted: { type: 'boolean', description: 'Whether the release was deleted' },
release_id: { type: 'number', description: 'ID of the deleted release' },
},
}
+131
View File
@@ -0,0 +1,131 @@
import type { ToolConfig } from '@/tools/types'
interface ForkGistParams {
gist_id: string
apiKey: string
}
interface ForkGistResponse {
success: boolean
output: {
content: string
metadata: {
id: string
html_url: string
git_pull_url: string
description: string | null
public: boolean
created_at: string
owner: { login: string }
files: string[]
}
}
}
export const forkGistTool: ToolConfig<ForkGistParams, ForkGistResponse> = {
id: 'github_fork_gist',
name: 'GitHub Fork Gist',
description: 'Fork a gist to create your own copy',
version: '1.0.0',
params: {
gist_id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The gist ID to fork',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/gists/${params.gist_id?.trim()}/forks`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const data = await response.json()
const files = Object.keys(data.files ?? {})
const content = `Forked gist: ${data.html_url}
Description: ${data.description ?? 'No description'}
Files: ${files.join(', ')}`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
html_url: data.html_url,
git_pull_url: data.git_pull_url,
description: data.description ?? null,
public: data.public,
created_at: data.created_at,
owner: { login: data.owner?.login ?? 'unknown' },
files,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Forked gist metadata',
properties: {
id: { type: 'string', description: 'New gist ID' },
html_url: { type: 'string', description: 'Web URL' },
git_pull_url: { type: 'string', description: 'Git pull URL' },
description: { type: 'string', description: 'Description', optional: true },
public: { type: 'boolean', description: 'Is public' },
created_at: { type: 'string', description: 'Creation date' },
owner: { type: 'object', description: 'Owner info' },
files: { type: 'array', description: 'File names' },
},
},
},
}
export const forkGistV2Tool: ToolConfig<ForkGistParams, any> = {
id: 'github_fork_gist_v2',
name: forkGistTool.name,
description: forkGistTool.description,
version: '2.0.0',
params: forkGistTool.params,
request: forkGistTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
...data,
description: data.description ?? null,
files: data.files ?? {},
},
}
},
outputs: {
id: { type: 'string', description: 'New gist ID' },
html_url: { type: 'string', description: 'Web URL' },
description: { type: 'string', description: 'Description', optional: true },
public: { type: 'boolean', description: 'Is public' },
created_at: { type: 'string', description: 'Creation date' },
owner: { type: 'object', description: 'Owner info' },
files: { type: 'object', description: 'Files' },
},
}
+216
View File
@@ -0,0 +1,216 @@
import {
PARENT_OWNER_OUTPUT_PROPERTIES,
PARENT_REPO_OUTPUT_PROPERTIES,
SOURCE_REPO_OUTPUT_PROPERTIES,
USER_FULL_OUTPUT_PROPERTIES,
} from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface ForkRepoParams {
owner: string
repo: string
organization?: string
name?: string
default_branch_only?: boolean
apiKey: string
}
interface ForkRepoResponse {
success: boolean
output: {
content: string
metadata: {
id: number
full_name: string
html_url: string
clone_url: string
ssh_url: string
default_branch: string
fork: boolean
parent: { full_name: string; html_url: string }
owner: { login: string }
created_at: string
}
}
}
export const forkRepoTool: ToolConfig<ForkRepoParams, ForkRepoResponse> = {
id: 'github_fork_repo',
name: 'GitHub Fork Repository',
description: 'Fork a repository to your account or an organization',
version: '1.0.0',
params: {
owner: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository owner to fork from',
},
repo: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name to fork',
},
organization: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Organization to fork into (omit to fork to your account)',
},
name: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Custom name for the forked repository',
},
default_branch_only: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Only fork the default branch (default: false)',
default: false,
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/repos/${params.owner}/${params.repo}/forks`,
method: 'POST',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const body: Record<string, any> = {}
if (params.organization) body.organization = params.organization
if (params.name) body.name = params.name
if (params.default_branch_only !== undefined)
body.default_branch_only = params.default_branch_only
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
const content = `Forked repository: ${data.html_url}
Forked from: ${data.parent?.full_name ?? 'unknown'}
Clone URL: ${data.clone_url}
Default branch: ${data.default_branch}`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
full_name: data.full_name,
html_url: data.html_url,
clone_url: data.clone_url,
ssh_url: data.ssh_url,
default_branch: data.default_branch,
fork: data.fork,
parent: {
full_name: data.parent?.full_name ?? '',
html_url: data.parent?.html_url ?? '',
},
owner: { login: data.owner?.login ?? 'unknown' },
created_at: data.created_at,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Forked repository metadata',
properties: {
id: { type: 'number', description: 'Repository ID' },
full_name: { type: 'string', description: 'Full name (owner/repo)' },
html_url: { type: 'string', description: 'Web URL' },
clone_url: { type: 'string', description: 'HTTPS clone URL' },
ssh_url: { type: 'string', description: 'SSH clone URL' },
default_branch: { type: 'string', description: 'Default branch' },
fork: { type: 'boolean', description: 'Is a fork' },
parent: { type: 'object', description: 'Parent repository' },
owner: { type: 'object', description: 'Owner info' },
created_at: { type: 'string', description: 'Creation date' },
},
},
},
}
export const forkRepoV2Tool: ToolConfig<ForkRepoParams, any> = {
id: 'github_fork_repo_v2',
name: forkRepoTool.name,
description: forkRepoTool.description,
version: '2.0.0',
params: forkRepoTool.params,
request: forkRepoTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
...data,
parent: data.parent ?? null,
source: data.source ?? null,
},
}
},
outputs: {
id: { type: 'number', description: 'Repository ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
name: { type: 'string', description: 'Repository name' },
full_name: { type: 'string', description: 'Full name (owner/repo)' },
private: { type: 'boolean', description: 'Whether repository is private' },
description: { type: 'string', description: 'Repository description', optional: true },
html_url: { type: 'string', description: 'GitHub web URL' },
url: { type: 'string', description: 'API URL' },
clone_url: { type: 'string', description: 'HTTPS clone URL' },
ssh_url: { type: 'string', description: 'SSH clone URL' },
git_url: { type: 'string', description: 'Git protocol URL' },
default_branch: { type: 'string', description: 'Default branch name' },
fork: { type: 'boolean', description: 'Whether this is a fork' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
pushed_at: { type: 'string', description: 'Last push timestamp', optional: true },
owner: {
type: 'object',
description: 'Fork owner',
properties: USER_FULL_OUTPUT_PROPERTIES,
},
parent: {
type: 'object',
description: 'Parent repository (source of the fork)',
optional: true,
properties: {
...PARENT_REPO_OUTPUT_PROPERTIES,
owner: {
type: 'object',
description: 'Parent owner',
properties: PARENT_OWNER_OUTPUT_PROPERTIES,
},
},
},
source: {
type: 'object',
description: 'Source repository (ultimate origin)',
optional: true,
properties: SOURCE_REPO_OUTPUT_PROPERTIES,
},
},
}
+122
View File
@@ -0,0 +1,122 @@
import type { BranchResponse, GetBranchParams } from '@/tools/github/types'
import { BRANCH_OUTPUT_PROPERTIES } 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' },
},
},
},
}
export const getBranchV2Tool: ToolConfig<GetBranchParams, any> = {
id: 'github_get_branch_v2',
name: getBranchTool.name,
description: getBranchTool.description,
version: '2.0.0',
params: getBranchTool.params,
request: getBranchTool.request,
transformResponse: async (response: Response) => {
const branch = await response.json()
return {
success: true,
output: {
name: branch.name,
commit: branch.commit,
protected: branch.protected,
protection: branch.protection,
protection_url: branch.protection_url,
},
}
},
outputs: {
...BRANCH_OUTPUT_PROPERTIES,
protection: { type: 'json', description: 'Protection settings object' },
protection_url: { type: 'string', description: 'URL to protection settings' },
},
}
@@ -0,0 +1,215 @@
import type { BranchProtectionResponse, GetBranchProtectionParams } from '@/tools/github/types'
import { BRANCH_PROTECTION_OUTPUT_PROPERTIES } 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' },
},
},
},
},
},
},
}
export const getBranchProtectionV2Tool: ToolConfig<GetBranchProtectionParams, any> = {
id: 'github_get_branch_protection_v2',
name: getBranchProtectionTool.name,
description: getBranchProtectionTool.description,
version: '2.0.0',
params: getBranchProtectionTool.params,
request: getBranchProtectionTool.request,
transformResponse: async (response: Response) => {
const protection = await response.json()
return {
success: true,
output: {
url: protection.url,
required_status_checks: protection.required_status_checks ?? null,
enforce_admins: protection.enforce_admins,
required_pull_request_reviews: protection.required_pull_request_reviews ?? null,
restrictions: protection.restrictions ?? null,
required_linear_history: protection.required_linear_history ?? null,
allow_force_pushes: protection.allow_force_pushes ?? null,
allow_deletions: protection.allow_deletions ?? null,
block_creations: protection.block_creations ?? null,
required_conversation_resolution: protection.required_conversation_resolution ?? null,
required_signatures: protection.required_signatures ?? null,
},
}
},
outputs: BRANCH_PROTECTION_OUTPUT_PROPERTIES,
}
+233
View File
@@ -0,0 +1,233 @@
import {
COMMIT_DATA_OUTPUT,
COMMIT_FILE_OUTPUT_PROPERTIES,
COMMIT_PARENT_OUTPUT_PROPERTIES,
COMMIT_STATS_OUTPUT,
COMMIT_SUMMARY_OUTPUT_PROPERTIES,
USER_FULL_OUTPUT,
} from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface GetCommitParams {
owner: string
repo: string
ref: string
apiKey: string
}
interface GetCommitResponse {
success: boolean
output: {
content: string
metadata: {
sha: string
html_url: string
message: string
author: { name: string; email: string; date: string; login?: string }
committer: { name: string; email: string; date: string; login?: string }
stats: { additions: number; deletions: number; total: number }
files: Array<{
filename: string
status: string
additions: number
deletions: number
changes: number
patch?: string
}>
parents: Array<{ sha: string; html_url: string }>
}
}
}
export const getCommitTool: ToolConfig<GetCommitParams, GetCommitResponse> = {
id: 'github_get_commit',
name: 'GitHub Get Commit',
description: 'Get detailed information about a specific commit including files changed and stats',
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',
},
ref: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Commit SHA, branch name, or tag 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}/commits/${params.ref}`,
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 data = await response.json()
const files = (data.files ?? []).map((f: any) => ({
filename: f.filename,
status: f.status,
additions: f.additions,
deletions: f.deletions,
changes: f.changes,
patch: f.patch,
}))
const metadata = {
sha: data.sha,
html_url: data.html_url,
message: data.commit.message,
author: {
name: data.commit.author.name,
email: data.commit.author.email,
date: data.commit.author.date,
login: data.author?.login,
},
committer: {
name: data.commit.committer.name,
email: data.commit.committer.email,
date: data.commit.committer.date,
login: data.committer?.login,
},
stats: data.stats ?? { additions: 0, deletions: 0, total: 0 },
files,
parents: data.parents.map((p: any) => ({ sha: p.sha, html_url: p.html_url })),
}
const content = `Commit ${data.sha.substring(0, 7)}
Message: ${data.commit.message.split('\n')[0]}
Author: ${metadata.author.login ?? metadata.author.name} (${metadata.author.date})
Stats: +${metadata.stats.additions} -${metadata.stats.deletions} (${files.length} files)
${data.html_url}
Files changed:
${files.map((f: any) => ` ${f.status}: ${f.filename} (+${f.additions} -${f.deletions})`).join('\n')}`
return {
success: true,
output: {
content,
metadata,
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable commit details' },
metadata: {
type: 'object',
description: 'Commit metadata',
properties: {
sha: { type: 'string', description: 'Full commit SHA' },
html_url: { type: 'string', description: 'GitHub web URL' },
message: { type: 'string', description: 'Commit message' },
author: { type: 'object', description: 'Author info' },
committer: { type: 'object', description: 'Committer info' },
stats: {
type: 'object',
description: 'Change stats',
properties: {
additions: { type: 'number', description: 'Lines added' },
deletions: { type: 'number', description: 'Lines deleted' },
total: { type: 'number', description: 'Total changes' },
},
},
files: {
type: 'array',
description: 'Changed files',
items: {
type: 'object',
properties: {
filename: { type: 'string', description: 'File path' },
status: { type: 'string', description: 'Change type' },
additions: { type: 'number', description: 'Lines added' },
deletions: { type: 'number', description: 'Lines deleted' },
changes: { type: 'number', description: 'Total changes' },
patch: { type: 'string', description: 'Diff patch', optional: true },
},
},
},
parents: {
type: 'array',
description: 'Parent commits',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Parent commit SHA' },
html_url: { type: 'string', description: 'Parent commit URL' },
},
},
},
},
},
},
}
export const getCommitV2Tool: ToolConfig<GetCommitParams, any> = {
id: 'github_get_commit_v2',
name: getCommitTool.name,
description: getCommitTool.description,
version: '2.0.0',
params: getCommitTool.params,
request: getCommitTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
...data,
author: data.author ?? null,
committer: data.committer ?? null,
stats: data.stats ?? null,
files: data.files ?? [],
},
}
},
outputs: {
...COMMIT_SUMMARY_OUTPUT_PROPERTIES,
commit: COMMIT_DATA_OUTPUT,
author: USER_FULL_OUTPUT,
committer: USER_FULL_OUTPUT,
stats: COMMIT_STATS_OUTPUT,
files: {
type: 'array',
description: 'Changed files (diff entries)',
items: {
type: 'object',
properties: COMMIT_FILE_OUTPUT_PROPERTIES,
},
},
parents: {
type: 'array',
description: 'Parent commits',
items: {
type: 'object',
properties: COMMIT_PARENT_OUTPUT_PROPERTIES,
},
},
},
}
+243
View File
@@ -0,0 +1,243 @@
import { getFileExtension, getMimeTypeFromExtension } from '@/lib/uploads/utils/file-utils'
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 = ''
let file:
| {
name: string
mimeType: string
data: string
size: number
}
| undefined
if (data.content) {
try {
decodedContent = Buffer.from(data.content, 'base64').toString('utf-8')
} catch (error) {
decodedContent = '[Binary file - content cannot be displayed as text]'
}
}
if (data.content && data.encoding === 'base64' && data.name) {
const base64Data = String(data.content).replace(/\n/g, '')
const extension = getFileExtension(data.name)
const mimeType = getMimeTypeFromExtension(extension)
file = {
name: data.name,
mimeType,
data: base64Data,
size: data.size || 0,
}
}
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,
file,
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',
},
file: {
type: 'file',
description: 'Downloaded file stored in execution files',
optional: true,
},
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 },
},
},
},
}
export const getFileContentV2Tool: ToolConfig<GetFileContentParams, any> = {
id: 'github_get_file_content_v2',
name: getFileContentTool.name,
description: getFileContentTool.description,
version: '2.0.0',
params: getFileContentTool.params,
request: getFileContentTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
// Decode base64 content if present
let decodedContent = ''
let file:
| {
name: string
mimeType: string
data: string
size: number
}
| undefined
if (data.content && data.encoding === 'base64') {
try {
decodedContent = Buffer.from(data.content, 'base64').toString('utf-8')
} catch {
decodedContent = data.content
}
}
if (data.content && data.encoding === 'base64' && data.name) {
const base64Data = String(data.content).replace(/\n/g, '')
const extension = getFileExtension(data.name)
const mimeType = getMimeTypeFromExtension(extension)
file = {
name: data.name,
mimeType,
data: base64Data,
size: data.size || 0,
}
}
return {
success: true,
output: {
name: data.name,
path: data.path,
sha: data.sha,
size: data.size,
type: data.type,
content: (decodedContent || data.content) ?? null,
encoding: data.encoding,
html_url: data.html_url,
download_url: data.download_url ?? null,
git_url: data.git_url,
_links: data._links,
file,
},
}
},
outputs: {
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/dir/symlink/submodule)' },
content: { type: 'string', description: 'Decoded file content', optional: true },
encoding: { type: 'string', description: 'Content encoding' },
html_url: { type: 'string', description: 'GitHub web URL' },
download_url: { type: 'string', description: 'Direct download URL', optional: true },
git_url: { type: 'string', description: 'Git blob API URL' },
_links: { type: 'json', description: 'Related links' },
file: {
type: 'file',
description: 'Downloaded file stored in execution files',
optional: true,
},
},
}
+172
View File
@@ -0,0 +1,172 @@
import { GIST_FILES_OUTPUT, GIST_OUTPUT_PROPERTIES, GIST_OWNER_OUTPUT } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface GetGistParams {
gist_id: string
apiKey: string
}
interface GetGistResponse {
success: boolean
output: {
content: string
metadata: {
id: string
html_url: string
git_pull_url: string
git_push_url: string
description: string | null
public: boolean
created_at: string
updated_at: string
files: Record<
string,
{ filename: string; type: string; language: string | null; size: number; content: string }
>
owner: { login: string }
comments: number
forks_url: string
commits_url: string
}
}
}
export const getGistTool: ToolConfig<GetGistParams, GetGistResponse> = {
id: 'github_get_gist',
name: 'GitHub Get Gist',
description: 'Get a gist by ID including its file contents',
version: '1.0.0',
params: {
gist_id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The gist ID',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/gists/${params.gist_id?.trim()}`,
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 data = await response.json()
const files: Record<
string,
{ filename: string; type: string; language: string | null; size: number; content: string }
> = {}
for (const [key, value] of Object.entries(data.files ?? {})) {
const file = value as any
files[key] = {
filename: file.filename,
type: file.type,
language: file.language ?? null,
size: file.size,
content: file.content ?? '',
}
}
const metadata = {
id: data.id,
html_url: data.html_url,
git_pull_url: data.git_pull_url,
git_push_url: data.git_push_url,
description: data.description ?? null,
public: data.public,
created_at: data.created_at,
updated_at: data.updated_at,
files,
owner: { login: data.owner?.login ?? 'unknown' },
comments: data.comments ?? 0,
forks_url: data.forks_url,
commits_url: data.commits_url,
}
const fileList = Object.entries(files)
.map(([name, f]) => `${name} (${f.language ?? 'unknown'}, ${f.size} bytes)`)
.join(', ')
const content = `Gist: ${data.html_url}
Description: ${data.description ?? 'No description'}
Public: ${data.public} | Comments: ${data.comments ?? 0}
Owner: ${data.owner?.login ?? 'unknown'}
Files: ${fileList}
${Object.entries(files)
.map(([name, f]) => `--- ${name} ---\n${f.content}`)
.join('\n\n')}`
return {
success: true,
output: {
content,
metadata,
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable gist with file contents' },
metadata: {
type: 'object',
description: 'Gist metadata',
properties: {
id: { type: 'string', description: 'Gist ID' },
html_url: { type: 'string', description: 'Web URL' },
git_pull_url: { type: 'string', description: 'Git pull URL' },
git_push_url: { type: 'string', description: 'Git push URL' },
description: { type: 'string', description: 'Description', optional: true },
public: { type: 'boolean', description: 'Is public' },
created_at: { type: 'string', description: 'Creation date' },
updated_at: { type: 'string', description: 'Update date' },
files: { type: 'object', description: 'Files with content' },
owner: { type: 'object', description: 'Owner info' },
comments: { type: 'number', description: 'Comment count' },
forks_url: { type: 'string', description: 'Forks URL' },
commits_url: { type: 'string', description: 'Commits URL' },
},
},
},
}
export const getGistV2Tool: ToolConfig<GetGistParams, any> = {
id: 'github_get_gist_v2',
name: getGistTool.name,
description: getGistTool.description,
version: '2.0.0',
params: getGistTool.params,
request: getGistTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
...data,
description: data.description ?? null,
files: data.files ?? {},
comments: data.comments ?? 0,
},
}
},
outputs: {
...GIST_OUTPUT_PROPERTIES,
files: GIST_FILES_OUTPUT,
owner: GIST_OWNER_OUTPUT,
},
}
+162
View File
@@ -0,0 +1,162 @@
import type { GetIssueParams, IssueResponse } from '@/tools/github/types'
import {
ISSUE_OUTPUT_PROPERTIES,
LABEL_OUTPUT,
MILESTONE_OUTPUT,
USER_OUTPUT,
} 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' },
},
},
},
}
export const getIssueV2Tool: ToolConfig<GetIssueParams, any> = {
id: 'github_get_issue_v2',
name: getIssueTool.name,
description: getIssueTool.description,
version: '2.0.0',
params: getIssueTool.params,
request: getIssueTool.request,
transformResponse: async (response: Response) => {
const issue = await response.json()
return {
success: true,
output: {
id: issue.id,
number: issue.number,
title: issue.title,
state: issue.state,
html_url: issue.html_url,
body: issue.body ?? null,
user: issue.user,
labels: issue.labels ?? [],
assignees: issue.assignees ?? [],
milestone: issue.milestone ?? null,
comments: issue.comments,
created_at: issue.created_at,
updated_at: issue.updated_at,
closed_at: issue.closed_at ?? null,
closed_by: issue.closed_by ?? null,
},
}
},
outputs: {
...ISSUE_OUTPUT_PROPERTIES,
user: USER_OUTPUT,
labels: {
type: 'array',
description: 'Array of label objects',
items: LABEL_OUTPUT,
},
assignees: {
type: 'array',
description: 'Array of assignee objects',
items: USER_OUTPUT,
},
milestone: { ...MILESTONE_OUTPUT, optional: true },
closed_by: { ...USER_OUTPUT, description: 'User who closed the issue', optional: true },
},
}
+203
View File
@@ -0,0 +1,203 @@
import type { GetLatestReleaseParams, ReleaseResponse } from '@/tools/github/types'
import {
RELEASE_ASSET_OUTPUT_PROPERTIES,
RELEASE_OUTPUT_PROPERTIES,
USER_OUTPUT,
} from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const getLatestReleaseTool: ToolConfig<GetLatestReleaseParams, ReleaseResponse> = {
id: 'github_get_latest_release',
name: 'GitHub Get Latest Release',
description:
'Get the latest published, non-draft, non-prerelease release for a GitHub repository. 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',
},
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/latest`,
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({}))
return {
success: false,
error: error.message || `Failed to get latest release (HTTP ${response.status})`,
output: {
content: '',
metadata: {
id: 0,
tag_name: '',
name: '',
html_url: '',
tarball_url: '',
zipball_url: '',
draft: false,
prerelease: false,
created_at: '',
published_at: '',
},
},
}
}
const data = await response.json()
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 = `Latest release: "${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' },
},
},
},
}
export const getLatestReleaseV2Tool: ToolConfig<GetLatestReleaseParams, any> = {
id: 'github_get_latest_release_v2',
name: getLatestReleaseTool.name,
description: getLatestReleaseTool.description,
version: '2.0.0',
params: getLatestReleaseTool.params,
request: getLatestReleaseTool.request,
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({}))
return {
success: false,
error: error.message || `Failed to get latest release (HTTP ${response.status})`,
output: {
id: 0,
tag_name: '',
name: '',
body: null,
html_url: '',
tarball_url: '',
zipball_url: '',
draft: false,
prerelease: false,
author: null,
assets: [],
created_at: '',
published_at: null,
target_commitish: '',
},
}
}
const data = await response.json()
return {
success: true,
output: {
id: data.id,
tag_name: data.tag_name,
name: data.name,
body: data.body ?? null,
html_url: data.html_url,
tarball_url: data.tarball_url,
zipball_url: data.zipball_url,
draft: data.draft,
prerelease: data.prerelease,
author: data.author,
assets: data.assets,
created_at: data.created_at,
published_at: data.published_at ?? null,
target_commitish: data.target_commitish,
},
}
},
outputs: {
...RELEASE_OUTPUT_PROPERTIES,
author: USER_OUTPUT,
assets: {
type: 'array',
description: 'Release assets',
items: {
type: 'object',
properties: {
...RELEASE_ASSET_OUTPUT_PROPERTIES,
uploader: USER_OUTPUT,
},
},
},
},
}
+160
View File
@@ -0,0 +1,160 @@
import { MILESTONE_CREATOR_OUTPUT, MILESTONE_V2_OUTPUT_PROPERTIES } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface GetMilestoneParams {
owner: string
repo: string
milestone_number: number
apiKey: string
}
interface GetMilestoneResponse {
success: boolean
output: {
content: string
metadata: {
number: number
title: string
description: string | null
state: string
html_url: string
due_on: string | null
open_issues: number
closed_issues: number
created_at: string
updated_at: string
closed_at: string | null
creator: { login: string }
}
}
}
export const getMilestoneTool: ToolConfig<GetMilestoneParams, GetMilestoneResponse> = {
id: 'github_get_milestone',
name: 'GitHub Get Milestone',
description: 'Get a specific milestone by number',
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',
},
milestone_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Milestone 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}/milestones/${params.milestone_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 data = await response.json()
const progress =
data.open_issues + data.closed_issues > 0
? Math.round((data.closed_issues / (data.open_issues + data.closed_issues)) * 100)
: 0
const content = `Milestone: ${data.title} (#${data.number})
State: ${data.state} | Progress: ${progress}% (${data.closed_issues}/${data.open_issues + data.closed_issues} issues)
Due: ${data.due_on ?? 'No due date'}
Description: ${data.description ?? 'No description'}
${data.html_url}`
return {
success: true,
output: {
content,
metadata: {
number: data.number,
title: data.title,
description: data.description ?? null,
state: data.state,
html_url: data.html_url,
due_on: data.due_on ?? null,
open_issues: data.open_issues,
closed_issues: data.closed_issues,
created_at: data.created_at,
updated_at: data.updated_at,
closed_at: data.closed_at ?? null,
creator: { login: data.creator?.login ?? 'unknown' },
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable milestone details' },
metadata: {
type: 'object',
description: 'Milestone metadata',
properties: {
number: { type: 'number', description: 'Milestone number' },
title: { type: 'string', description: 'Title' },
description: { type: 'string', description: 'Description', optional: true },
state: { type: 'string', description: 'State' },
html_url: { type: 'string', description: 'Web URL' },
due_on: { type: 'string', description: 'Due date', optional: true },
open_issues: { type: 'number', description: 'Open issues count' },
closed_issues: { type: 'number', description: 'Closed issues count' },
created_at: { type: 'string', description: 'Creation date' },
updated_at: { type: 'string', description: 'Update date' },
closed_at: { type: 'string', description: 'Close date', optional: true },
creator: { type: 'object', description: 'Creator info' },
},
},
},
}
export const getMilestoneV2Tool: ToolConfig<GetMilestoneParams, any> = {
id: 'github_get_milestone_v2',
name: getMilestoneTool.name,
description: getMilestoneTool.description,
version: '2.0.0',
params: getMilestoneTool.params,
request: getMilestoneTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
...data,
description: data.description ?? null,
due_on: data.due_on ?? null,
closed_at: data.closed_at ?? null,
},
}
},
outputs: {
...MILESTONE_V2_OUTPUT_PROPERTIES,
creator: MILESTONE_CREATOR_OUTPUT,
},
}
+171
View File
@@ -0,0 +1,171 @@
import type { GetPRFilesParams, PRFilesListResponse } from '@/tools/github/types'
import { PR_FILE_OUTPUT_PROPERTIES } 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', Number(params.per_page).toString())
if (params.page) url.searchParams.append('page', Number(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' },
},
},
},
}
export const getPRFilesV2Tool: ToolConfig<GetPRFilesParams, any> = {
id: 'github_get_pr_files_v2',
name: getPRFilesTool.name,
description: getPRFilesTool.description,
version: '2.0.0',
params: getPRFilesTool.params,
request: getPRFilesTool.request,
transformResponse: async (response: Response) => {
const files = await response.json()
return {
success: true,
output: {
items: files ?? [],
count: files?.length ?? 0,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of changed file objects',
items: {
type: 'object',
properties: PR_FILE_OUTPUT_PROPERTIES,
},
},
count: { type: 'number', description: 'Total number of files' },
},
}
+246
View File
@@ -0,0 +1,246 @@
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,
},
},
},
},
}
export const getProjectV2Tool: ToolConfig<GetProjectParams, any> = {
id: 'github_get_project_v2',
name: getProjectTool.name,
description: getProjectTool.description,
version: '2.0.0',
params: getProjectTool.params,
request: getProjectTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
output: {
id: '',
title: '',
number: 0,
url: '',
closed: false,
public: false,
shortDescription: '',
readme: '',
createdAt: '',
updatedAt: '',
},
error: data.errors[0].message,
}
}
const ownerData = data.data?.organization || data.data?.user
const project = ownerData?.projectV2
if (!project) {
return {
success: false,
output: {
id: '',
title: '',
number: 0,
url: '',
closed: false,
public: false,
shortDescription: '',
readme: '',
createdAt: '',
updatedAt: '',
},
error: 'Project not found',
}
}
return {
success: true,
output: {
id: project.id,
title: project.title,
number: project.number,
url: project.url,
closed: project.closed,
public: project.public,
shortDescription: project.shortDescription ?? null,
readme: project.readme ?? null,
createdAt: project.createdAt,
updatedAt: project.updatedAt,
},
}
},
outputs: {
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: 'Short description', optional: true },
readme: { type: 'string', description: 'Project readme', optional: true },
createdAt: { type: 'string', description: 'Creation timestamp' },
updatedAt: { type: 'string', description: 'Last update timestamp' },
},
}
+178
View File
@@ -0,0 +1,178 @@
import type { GetReadmeParams, ReadmeResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const getReadmeTool: ToolConfig<GetReadmeParams, ReadmeResponse> = {
id: 'github_get_readme',
name: 'GitHub Get README',
description:
'Get the preferred README for a GitHub repository, with its content decoded to plain text.',
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',
},
ref: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'The name of the commit/branch/tag to read the README from (defaults to the 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}/readme`
return params.ref ? `${baseUrl}?ref=${encodeURIComponent(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) => {
if (!response.ok) {
const error = await response.json().catch(() => ({}))
return {
success: false,
error: error.message || `Failed to get README (HTTP ${response.status})`,
output: {
content: '',
metadata: { name: '', path: '', sha: '', size: 0, html_url: '', download_url: '' },
},
}
}
const data = await response.json()
let decodedContent = ''
if (data.content) {
try {
decodedContent = Buffer.from(data.content, 'base64').toString('utf-8')
} catch {
decodedContent = '[Binary file - content cannot be displayed as text]'
}
}
const content = `README: ${data.name}
Path: ${data.path}
Size: ${data.size} bytes
${decodedContent}`
return {
success: true,
output: {
content,
metadata: {
name: data.name,
path: data.path,
sha: data.sha,
size: data.size,
html_url: data.html_url,
download_url: data.download_url,
},
},
}
},
outputs: {
content: { type: 'string', description: 'README name, path, and decoded text content' },
metadata: {
type: 'object',
description: 'README file metadata',
properties: {
name: { type: 'string', description: 'README file name' },
path: { type: 'string', description: 'README file path' },
sha: { type: 'string', description: 'Blob SHA of the README' },
size: { type: 'number', description: 'File size in bytes' },
html_url: { type: 'string', description: 'GitHub web URL for the README' },
download_url: { type: 'string', description: 'Raw download URL for the README' },
},
},
},
}
export const getReadmeV2Tool: ToolConfig<GetReadmeParams, any> = {
id: 'github_get_readme_v2',
name: getReadmeTool.name,
description: getReadmeTool.description,
version: '2.0.0',
params: getReadmeTool.params,
request: getReadmeTool.request,
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({}))
return {
success: false,
error: error.message || `Failed to get README (HTTP ${response.status})`,
output: {
name: '',
path: '',
sha: '',
size: 0,
encoding: '',
html_url: '',
download_url: null,
content: '',
},
}
}
const data = await response.json()
let decodedContent = ''
if (data.content) {
try {
decodedContent = Buffer.from(data.content, 'base64').toString('utf-8')
} catch {
decodedContent = ''
}
}
return {
success: true,
output: {
name: data.name,
path: data.path,
sha: data.sha,
size: data.size,
encoding: data.encoding,
html_url: data.html_url,
download_url: data.download_url ?? null,
content: decodedContent,
},
}
},
outputs: {
name: { type: 'string', description: 'README file name' },
path: { type: 'string', description: 'README file path' },
sha: { type: 'string', description: 'Blob SHA of the README' },
size: { type: 'number', description: 'File size in bytes' },
encoding: { type: 'string', description: 'Original content encoding from the API' },
html_url: { type: 'string', description: 'GitHub web URL for the README' },
download_url: { type: 'string', description: 'Raw download URL for the README' },
content: { type: 'string', description: 'Decoded README text content' },
},
}
+164
View File
@@ -0,0 +1,164 @@
import type { GetReleaseParams, ReleaseResponse } from '@/tools/github/types'
import {
RELEASE_ASSET_OUTPUT_PROPERTIES,
RELEASE_OUTPUT_PROPERTIES,
USER_OUTPUT,
} 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' },
},
},
},
}
export const getReleaseV2Tool: ToolConfig<GetReleaseParams, any> = {
id: 'github_get_release_v2',
name: getReleaseTool.name,
description: getReleaseTool.description,
version: '2.0.0',
params: getReleaseTool.params,
request: getReleaseTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
id: data.id,
tag_name: data.tag_name,
name: data.name,
body: data.body ?? null,
html_url: data.html_url,
tarball_url: data.tarball_url,
zipball_url: data.zipball_url,
draft: data.draft,
prerelease: data.prerelease,
author: data.author,
assets: data.assets,
created_at: data.created_at,
published_at: data.published_at ?? null,
target_commitish: data.target_commitish,
},
}
},
outputs: {
...RELEASE_OUTPUT_PROPERTIES,
author: USER_OUTPUT,
assets: {
type: 'array',
description: 'Release assets',
items: {
type: 'object',
properties: {
...RELEASE_ASSET_OUTPUT_PROPERTIES,
uploader: USER_OUTPUT,
},
},
},
},
}
+227
View File
@@ -0,0 +1,227 @@
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' },
},
},
},
}
export const getTreeV2Tool: ToolConfig<GetTreeParams, any> = {
id: 'github_get_tree_v2',
name: getTreeTool.name,
description: getTreeTool.description,
version: '2.0.0',
params: getTreeTool.params,
request: getTreeTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
// API returns array for directory, object for file
if (!Array.isArray(data)) {
return {
success: false,
output: { items: [], count: 0 },
error: 'Path points to a file. Use github_get_file_content to get file contents.',
}
}
return {
success: true,
output: {
items: data.map((item: any) => ({
name: item.name,
path: item.path,
sha: item.sha,
size: item.size,
type: item.type,
html_url: item.html_url,
download_url: item.download_url ?? null,
git_url: item.git_url,
url: item.url,
_links: item._links,
})),
count: data.length,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of file/directory objects',
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)' },
html_url: { type: 'string', description: 'GitHub web URL' },
download_url: { type: 'string', description: 'Direct download URL', optional: true },
git_url: { type: 'string', description: 'Git blob API URL' },
url: { type: 'string', description: 'API URL for this item' },
_links: { type: 'json', description: 'Related links' },
},
},
},
count: { type: 'number', description: 'Total number of items' },
},
}
+123
View File
@@ -0,0 +1,123 @@
import type { GetWorkflowParams, WorkflowResponse } from '@/tools/github/types'
import { WORKFLOW_OUTPUT_PROPERTIES } 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' },
},
},
},
}
export const getWorkflowV2Tool: ToolConfig<GetWorkflowParams, any> = {
id: 'github_get_workflow_v2',
name: getWorkflowTool.name,
description: getWorkflowTool.description,
version: '2.0.0',
params: getWorkflowTool.params,
request: getWorkflowTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
id: data.id,
node_id: data.node_id,
name: data.name,
path: data.path,
state: data.state,
html_url: data.html_url,
badge_url: data.badge_url,
url: data.url,
created_at: data.created_at,
updated_at: data.updated_at,
deleted_at: data.deleted_at ?? null,
},
}
},
outputs: {
...WORKFLOW_OUTPUT_PROPERTIES,
},
}
+157
View File
@@ -0,0 +1,157 @@
import type { GetWorkflowRunParams, WorkflowRunResponse } from '@/tools/github/types'
import {
HEAD_COMMIT_OUTPUT,
PR_REFERENCE_OUTPUT,
REFERENCED_WORKFLOW_OUTPUT,
USER_OUTPUT,
WORKFLOW_RUN_OUTPUT_PROPERTIES,
} 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' },
},
},
},
}
export const getWorkflowRunV2Tool: ToolConfig<GetWorkflowRunParams, any> = {
id: 'github_get_workflow_run_v2',
name: getWorkflowRunTool.name,
description: getWorkflowRunTool.description,
version: '2.0.0',
params: getWorkflowRunTool.params,
request: getWorkflowRunTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
id: data.id,
name: data.name,
head_branch: data.head_branch,
head_sha: data.head_sha,
run_number: data.run_number,
run_attempt: data.run_attempt,
event: data.event,
status: data.status,
conclusion: data.conclusion ?? null,
workflow_id: data.workflow_id,
html_url: data.html_url,
logs_url: data.logs_url,
jobs_url: data.jobs_url,
artifacts_url: data.artifacts_url,
triggering_actor: data.triggering_actor,
pull_requests: data.pull_requests ?? [],
referenced_workflows: data.referenced_workflows ?? [],
head_commit: data.head_commit,
run_started_at: data.run_started_at,
created_at: data.created_at,
updated_at: data.updated_at,
},
}
},
outputs: {
...WORKFLOW_RUN_OUTPUT_PROPERTIES,
triggering_actor: USER_OUTPUT,
pull_requests: {
type: 'array',
description: 'Associated pull requests',
items: PR_REFERENCE_OUTPUT,
},
referenced_workflows: {
type: 'array',
description: 'Referenced workflows',
items: REFERENCED_WORKFLOW_OUTPUT,
},
head_commit: HEAD_COMMIT_OUTPUT,
},
}
+301
View File
@@ -0,0 +1,301 @@
import { addAssigneesTool, addAssigneesV2Tool } from '@/tools/github/add_assignees'
import { addLabelsTool, addLabelsV2Tool } from '@/tools/github/add_labels'
import { cancelWorkflowRunTool, cancelWorkflowRunV2Tool } from '@/tools/github/cancel_workflow_run'
import { checkStarTool, checkStarV2Tool } from '@/tools/github/check_star'
import { closeIssueTool, closeIssueV2Tool } from '@/tools/github/close_issue'
import { closePRTool, closePRV2Tool } from '@/tools/github/close_pr'
import { commentTool, commentV2Tool } from '@/tools/github/comment'
import { compareCommitsTool, compareCommitsV2Tool } from '@/tools/github/compare_commits'
import { createBranchTool, createBranchV2Tool } from '@/tools/github/create_branch'
import {
createCommentReactionTool,
createCommentReactionV2Tool,
} from '@/tools/github/create_comment_reaction'
import { createFileTool, createFileV2Tool } from '@/tools/github/create_file'
import { createGistTool, createGistV2Tool } from '@/tools/github/create_gist'
import { createIssueTool, createIssueV2Tool } from '@/tools/github/create_issue'
import {
createIssueReactionTool,
createIssueReactionV2Tool,
} from '@/tools/github/create_issue_reaction'
import { createMilestoneTool, createMilestoneV2Tool } from '@/tools/github/create_milestone'
import { createPRTool, createPRV2Tool } from '@/tools/github/create_pr'
import { createPRReviewTool, createPRReviewV2Tool } from '@/tools/github/create_pr_review'
import { createProjectTool, createProjectV2Tool } from '@/tools/github/create_project'
import { createReleaseTool, createReleaseV2Tool } from '@/tools/github/create_release'
import { deleteBranchTool, deleteBranchV2Tool } from '@/tools/github/delete_branch'
import { deleteCommentTool, deleteCommentV2Tool } from '@/tools/github/delete_comment'
import {
deleteCommentReactionTool,
deleteCommentReactionV2Tool,
} from '@/tools/github/delete_comment_reaction'
import { deleteFileTool, deleteFileV2Tool } from '@/tools/github/delete_file'
import { deleteGistTool, deleteGistV2Tool } from '@/tools/github/delete_gist'
import {
deleteIssueReactionTool,
deleteIssueReactionV2Tool,
} from '@/tools/github/delete_issue_reaction'
import { deleteMilestoneTool, deleteMilestoneV2Tool } from '@/tools/github/delete_milestone'
import { deleteProjectTool, deleteProjectV2Tool } from '@/tools/github/delete_project'
import { deleteReleaseTool, deleteReleaseV2Tool } from '@/tools/github/delete_release'
import { forkGistTool, forkGistV2Tool } from '@/tools/github/fork_gist'
import { forkRepoTool, forkRepoV2Tool } from '@/tools/github/fork_repo'
import { getBranchTool, getBranchV2Tool } from '@/tools/github/get_branch'
import {
getBranchProtectionTool,
getBranchProtectionV2Tool,
} from '@/tools/github/get_branch_protection'
import { getCommitTool, getCommitV2Tool } from '@/tools/github/get_commit'
import { getFileContentTool, getFileContentV2Tool } from '@/tools/github/get_file_content'
import { getGistTool, getGistV2Tool } from '@/tools/github/get_gist'
import { getIssueTool, getIssueV2Tool } from '@/tools/github/get_issue'
import { getLatestReleaseTool, getLatestReleaseV2Tool } from '@/tools/github/get_latest_release'
import { getMilestoneTool, getMilestoneV2Tool } from '@/tools/github/get_milestone'
import { getPRFilesTool, getPRFilesV2Tool } from '@/tools/github/get_pr_files'
import { getProjectTool, getProjectV2Tool } from '@/tools/github/get_project'
import { getReadmeTool, getReadmeV2Tool } from '@/tools/github/get_readme'
import { getReleaseTool, getReleaseV2Tool } from '@/tools/github/get_release'
import { getTreeTool, getTreeV2Tool } from '@/tools/github/get_tree'
import { getWorkflowTool, getWorkflowV2Tool } from '@/tools/github/get_workflow'
import { getWorkflowRunTool, getWorkflowRunV2Tool } from '@/tools/github/get_workflow_run'
import { issueCommentTool, issueCommentV2Tool } from '@/tools/github/issue_comment'
import { latestCommitTool, latestCommitV2Tool } from '@/tools/github/latest_commit'
import { listBranchesTool, listBranchesV2Tool } from '@/tools/github/list_branches'
import { listCommitsTool, listCommitsV2Tool } from '@/tools/github/list_commits'
import { listForksTool, listForksV2Tool } from '@/tools/github/list_forks'
import { listGistsTool, listGistsV2Tool } from '@/tools/github/list_gists'
import { listIssueCommentsTool, listIssueCommentsV2Tool } from '@/tools/github/list_issue_comments'
import { listIssuesTool, listIssuesV2Tool } from '@/tools/github/list_issues'
import { listMilestonesTool, listMilestonesV2Tool } from '@/tools/github/list_milestones'
import { listPRCommentsTool, listPRCommentsV2Tool } from '@/tools/github/list_pr_comments'
import { listProjectsTool, listProjectsV2Tool } from '@/tools/github/list_projects'
import { listPRsTool, listPRsV2Tool } from '@/tools/github/list_prs'
import { listReleasesTool, listReleasesV2Tool } from '@/tools/github/list_releases'
import { listStargazersTool, listStargazersV2Tool } from '@/tools/github/list_stargazers'
import { listTagsTool, listTagsV2Tool } from '@/tools/github/list_tags'
import { listWorkflowRunsTool, listWorkflowRunsV2Tool } from '@/tools/github/list_workflow_runs'
import { listWorkflowsTool, listWorkflowsV2Tool } from '@/tools/github/list_workflows'
import { mergePRTool, mergePRV2Tool } from '@/tools/github/merge_pr'
import { prTool, prV2Tool } from '@/tools/github/pr'
import { removeLabelTool, removeLabelV2Tool } from '@/tools/github/remove_label'
import { repoInfoTool, repoInfoV2Tool } from '@/tools/github/repo_info'
import { requestReviewersTool, requestReviewersV2Tool } from '@/tools/github/request_reviewers'
import { rerunWorkflowTool, rerunWorkflowV2Tool } from '@/tools/github/rerun_workflow'
import { searchCodeTool, searchCodeV2Tool } from '@/tools/github/search_code'
import { searchCommitsTool, searchCommitsV2Tool } from '@/tools/github/search_commits'
import { searchIssuesTool, searchIssuesV2Tool } from '@/tools/github/search_issues'
import { searchReposTool, searchReposV2Tool } from '@/tools/github/search_repos'
import { searchUsersTool, searchUsersV2Tool } from '@/tools/github/search_users'
import { starGistTool, starGistV2Tool } from '@/tools/github/star_gist'
import { starRepoTool, starRepoV2Tool } from '@/tools/github/star_repo'
import { triggerWorkflowTool, triggerWorkflowV2Tool } from '@/tools/github/trigger_workflow'
import { unstarGistTool, unstarGistV2Tool } from '@/tools/github/unstar_gist'
import { unstarRepoTool, unstarRepoV2Tool } from '@/tools/github/unstar_repo'
import {
updateBranchProtectionTool,
updateBranchProtectionV2Tool,
} from '@/tools/github/update_branch_protection'
import { updateCommentTool, updateCommentV2Tool } from '@/tools/github/update_comment'
import { updateFileTool, updateFileV2Tool } from '@/tools/github/update_file'
import { updateGistTool, updateGistV2Tool } from '@/tools/github/update_gist'
import { updateIssueTool, updateIssueV2Tool } from '@/tools/github/update_issue'
import { updateMilestoneTool, updateMilestoneV2Tool } from '@/tools/github/update_milestone'
import { updatePRTool, updatePRV2Tool } from '@/tools/github/update_pr'
import { updateProjectTool, updateProjectV2Tool } from '@/tools/github/update_project'
import { updateReleaseTool, updateReleaseV2Tool } from '@/tools/github/update_release'
// Existing exports
export const githubAddAssigneesTool = addAssigneesTool
export const githubAddAssigneesV2Tool = addAssigneesV2Tool
export const githubAddLabelsTool = addLabelsTool
export const githubAddLabelsV2Tool = addLabelsV2Tool
export const githubCancelWorkflowRunTool = cancelWorkflowRunTool
export const githubCancelWorkflowRunV2Tool = cancelWorkflowRunV2Tool
export const githubCloseIssueTool = closeIssueTool
export const githubCloseIssueV2Tool = closeIssueV2Tool
export const githubClosePRTool = closePRTool
export const githubClosePRV2Tool = closePRV2Tool
export const githubCommentTool = commentTool
export const githubCommentV2Tool = commentV2Tool
export const githubCreateBranchTool = createBranchTool
export const githubCreateBranchV2Tool = createBranchV2Tool
export const githubCreateFileTool = createFileTool
export const githubCreateFileV2Tool = createFileV2Tool
export const githubCreateIssueTool = createIssueTool
export const githubCreateIssueV2Tool = createIssueV2Tool
export const githubCreatePRTool = createPRTool
export const githubCreatePRV2Tool = createPRV2Tool
export const githubCreateProjectTool = createProjectTool
export const githubCreateProjectV2Tool = createProjectV2Tool
export const githubCreateReleaseTool = createReleaseTool
export const githubCreateReleaseV2Tool = createReleaseV2Tool
export const githubDeleteBranchTool = deleteBranchTool
export const githubDeleteBranchV2Tool = deleteBranchV2Tool
export const githubDeleteCommentTool = deleteCommentTool
export const githubDeleteCommentV2Tool = deleteCommentV2Tool
export const githubDeleteFileTool = deleteFileTool
export const githubDeleteFileV2Tool = deleteFileV2Tool
export const githubDeleteProjectTool = deleteProjectTool
export const githubDeleteProjectV2Tool = deleteProjectV2Tool
export const githubDeleteReleaseTool = deleteReleaseTool
export const githubDeleteReleaseV2Tool = deleteReleaseV2Tool
export const githubGetBranchTool = getBranchTool
export const githubGetBranchV2Tool = getBranchV2Tool
export const githubGetBranchProtectionTool = getBranchProtectionTool
export const githubGetBranchProtectionV2Tool = getBranchProtectionV2Tool
export const githubGetFileContentTool = getFileContentTool
export const githubGetFileContentV2Tool = getFileContentV2Tool
export const githubGetIssueTool = getIssueTool
export const githubGetIssueV2Tool = getIssueV2Tool
export const githubGetPRFilesTool = getPRFilesTool
export const githubGetPRFilesV2Tool = getPRFilesV2Tool
export const githubGetProjectTool = getProjectTool
export const githubGetProjectV2Tool = getProjectV2Tool
export const githubGetReleaseTool = getReleaseTool
export const githubGetReleaseV2Tool = getReleaseV2Tool
export const githubGetTreeTool = getTreeTool
export const githubGetTreeV2Tool = getTreeV2Tool
export const githubGetWorkflowTool = getWorkflowTool
export const githubGetWorkflowV2Tool = getWorkflowV2Tool
export const githubGetWorkflowRunTool = getWorkflowRunTool
export const githubGetWorkflowRunV2Tool = getWorkflowRunV2Tool
export const githubIssueCommentTool = issueCommentTool
export const githubIssueCommentV2Tool = issueCommentV2Tool
export const githubLatestCommitTool = latestCommitTool
export const githubLatestCommitV2Tool = latestCommitV2Tool
export const githubListBranchesTool = listBranchesTool
export const githubListBranchesV2Tool = listBranchesV2Tool
export const githubListIssueCommentsTool = listIssueCommentsTool
export const githubListIssueCommentsV2Tool = listIssueCommentsV2Tool
export const githubListIssuesTool = listIssuesTool
export const githubListIssuesV2Tool = listIssuesV2Tool
export const githubListPRCommentsTool = listPRCommentsTool
export const githubListPRCommentsV2Tool = listPRCommentsV2Tool
export const githubListPRsTool = listPRsTool
export const githubListPRsV2Tool = listPRsV2Tool
export const githubListProjectsTool = listProjectsTool
export const githubListProjectsV2Tool = listProjectsV2Tool
export const githubListReleasesTool = listReleasesTool
export const githubListReleasesV2Tool = listReleasesV2Tool
export const githubListWorkflowRunsTool = listWorkflowRunsTool
export const githubListWorkflowRunsV2Tool = listWorkflowRunsV2Tool
export const githubListWorkflowsTool = listWorkflowsTool
export const githubListWorkflowsV2Tool = listWorkflowsV2Tool
export const githubMergePRTool = mergePRTool
export const githubMergePRV2Tool = mergePRV2Tool
export const githubPrTool = prTool
export const githubPrV2Tool = prV2Tool
export const githubRemoveLabelTool = removeLabelTool
export const githubRemoveLabelV2Tool = removeLabelV2Tool
export const githubRepoInfoTool = repoInfoTool
export const githubRepoInfoV2Tool = repoInfoV2Tool
export const githubRequestReviewersTool = requestReviewersTool
export const githubRequestReviewersV2Tool = requestReviewersV2Tool
export const githubRerunWorkflowTool = rerunWorkflowTool
export const githubRerunWorkflowV2Tool = rerunWorkflowV2Tool
export const githubTriggerWorkflowTool = triggerWorkflowTool
export const githubTriggerWorkflowV2Tool = triggerWorkflowV2Tool
export const githubUpdateBranchProtectionTool = updateBranchProtectionTool
export const githubUpdateBranchProtectionV2Tool = updateBranchProtectionV2Tool
export const githubUpdateCommentTool = updateCommentTool
export const githubUpdateCommentV2Tool = updateCommentV2Tool
export const githubUpdateFileTool = updateFileTool
export const githubUpdateFileV2Tool = updateFileV2Tool
export const githubUpdateIssueTool = updateIssueTool
export const githubUpdateIssueV2Tool = updateIssueV2Tool
export const githubUpdatePRTool = updatePRTool
export const githubUpdatePRV2Tool = updatePRV2Tool
export const githubUpdateProjectTool = updateProjectTool
export const githubUpdateProjectV2Tool = updateProjectV2Tool
export const githubUpdateReleaseTool = updateReleaseTool
export const githubUpdateReleaseV2Tool = updateReleaseV2Tool
// New exports - Search tools
export const githubSearchCodeTool = searchCodeTool
export const githubSearchCodeV2Tool = searchCodeV2Tool
export const githubSearchCommitsTool = searchCommitsTool
export const githubSearchCommitsV2Tool = searchCommitsV2Tool
export const githubSearchIssuesTool = searchIssuesTool
export const githubSearchIssuesV2Tool = searchIssuesV2Tool
export const githubSearchReposTool = searchReposTool
export const githubSearchReposV2Tool = searchReposV2Tool
export const githubSearchUsersTool = searchUsersTool
export const githubSearchUsersV2Tool = searchUsersV2Tool
// New exports - Commit tools
export const githubListCommitsTool = listCommitsTool
export const githubListCommitsV2Tool = listCommitsV2Tool
export const githubGetCommitTool = getCommitTool
export const githubGetCommitV2Tool = getCommitV2Tool
export const githubCompareCommitsTool = compareCommitsTool
export const githubCompareCommitsV2Tool = compareCommitsV2Tool
// New exports - Gist tools
export const githubCreateGistTool = createGistTool
export const githubCreateGistV2Tool = createGistV2Tool
export const githubGetGistTool = getGistTool
export const githubGetGistV2Tool = getGistV2Tool
export const githubListGistsTool = listGistsTool
export const githubListGistsV2Tool = listGistsV2Tool
export const githubUpdateGistTool = updateGistTool
export const githubUpdateGistV2Tool = updateGistV2Tool
export const githubDeleteGistTool = deleteGistTool
export const githubDeleteGistV2Tool = deleteGistV2Tool
export const githubForkGistTool = forkGistTool
export const githubForkGistV2Tool = forkGistV2Tool
export const githubStarGistTool = starGistTool
export const githubStarGistV2Tool = starGistV2Tool
export const githubUnstarGistTool = unstarGistTool
export const githubUnstarGistV2Tool = unstarGistV2Tool
// New exports - Fork tools
export const githubForkRepoTool = forkRepoTool
export const githubForkRepoV2Tool = forkRepoV2Tool
export const githubListForksTool = listForksTool
export const githubListForksV2Tool = listForksV2Tool
// New exports - Milestone tools
export const githubCreateMilestoneTool = createMilestoneTool
export const githubCreateMilestoneV2Tool = createMilestoneV2Tool
export const githubGetMilestoneTool = getMilestoneTool
export const githubGetMilestoneV2Tool = getMilestoneV2Tool
export const githubListMilestonesTool = listMilestonesTool
export const githubListMilestonesV2Tool = listMilestonesV2Tool
export const githubUpdateMilestoneTool = updateMilestoneTool
export const githubUpdateMilestoneV2Tool = updateMilestoneV2Tool
export const githubDeleteMilestoneTool = deleteMilestoneTool
export const githubDeleteMilestoneV2Tool = deleteMilestoneV2Tool
// New exports - Reaction tools
export const githubCreateIssueReactionTool = createIssueReactionTool
export const githubCreateIssueReactionV2Tool = createIssueReactionV2Tool
export const githubDeleteIssueReactionTool = deleteIssueReactionTool
export const githubDeleteIssueReactionV2Tool = deleteIssueReactionV2Tool
export const githubCreateCommentReactionTool = createCommentReactionTool
export const githubCreateCommentReactionV2Tool = createCommentReactionV2Tool
export const githubDeleteCommentReactionTool = deleteCommentReactionTool
export const githubDeleteCommentReactionV2Tool = deleteCommentReactionV2Tool
// New exports - Star tools
export const githubStarRepoTool = starRepoTool
export const githubStarRepoV2Tool = starRepoV2Tool
export const githubUnstarRepoTool = unstarRepoTool
export const githubUnstarRepoV2Tool = unstarRepoV2Tool
export const githubCheckStarTool = checkStarTool
export const githubCheckStarV2Tool = checkStarV2Tool
export const githubListStargazersTool = listStargazersTool
export const githubListStargazersV2Tool = listStargazersV2Tool
// New exports - Repository content tools
export const githubGetReadmeTool = getReadmeTool
export const githubGetReadmeV2Tool = getReadmeV2Tool
export const githubListTagsTool = listTagsTool
export const githubListTagsV2Tool = listTagsV2Tool
// New exports - Release tools
export const githubGetLatestReleaseTool = getLatestReleaseTool
export const githubGetLatestReleaseV2Tool = getLatestReleaseV2Tool
// New exports - Pull request review tools
export const githubCreatePRReviewTool = createPRReviewTool
export const githubCreatePRReviewV2Tool = createPRReviewV2Tool
+133
View File
@@ -0,0 +1,133 @@
import { truncate } from '@sim/utils/string'
import type { CreateIssueCommentParams, IssueCommentResponse } from '@/tools/github/types'
import { COMMENT_OUTPUT_PROPERTIES, USER_OUTPUT } 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()}: "${truncate(data.body, 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' },
},
},
},
},
},
}
export const issueCommentV2Tool: ToolConfig = {
id: 'github_issue_comment_v2',
name: issueCommentTool.name,
description: issueCommentTool.description,
version: '2.0.0',
params: issueCommentTool.params,
request: issueCommentTool.request,
oauth: issueCommentTool.oauth,
transformResponse: async (response: Response) => {
const comment = await response.json()
return {
success: true,
output: {
id: comment.id,
body: comment.body,
html_url: comment.html_url,
user: comment.user,
created_at: comment.created_at,
updated_at: comment.updated_at,
},
}
},
outputs: {
...COMMENT_OUTPUT_PROPERTIES,
user: USER_OUTPUT,
},
}
+101
View File
@@ -0,0 +1,101 @@
import {
COMMIT_DATA_OUTPUT,
type LatestCommitParams,
type LatestCommitResponse,
USER_FULL_OUTPUT,
} from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const latestCommitTool: ToolConfig<LatestCommitParams, LatestCommitResponse> = {
id: 'github_latest_commit',
name: 'GitHub Latest Commit',
description: 'Retrieve the latest commit from a GitHub repository',
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: false,
visibility: 'user-or-llm',
description: "Branch name (defaults to the repository's default branch)",
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: '/api/tools/github/latest-commit',
method: 'POST',
headers: () => ({
'Content-Type': 'application/json',
}),
body: (params) => ({
owner: params.owner,
repo: params.repo,
branch: params.branch,
apiKey: params.apiKey,
}),
},
outputs: {
content: { type: 'string', description: 'Human-readable commit summary' },
metadata: {
type: 'object',
description: 'Commit metadata',
},
},
}
export const latestCommitV2Tool: ToolConfig = {
id: 'github_latest_commit_v2',
name: latestCommitTool.name,
description: latestCommitTool.description,
version: '2.0.0',
params: latestCommitTool.params,
request: latestCommitTool.request,
oauth: latestCommitTool.oauth,
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || 'Failed to fetch latest commit')
}
const metadata = data.output?.metadata ?? {}
return {
success: true,
output: {
sha: metadata.sha ?? '',
html_url: metadata.html_url ?? '',
commit: {
message: metadata.commit_message ?? '',
author: metadata.author ?? null,
committer: metadata.committer ?? null,
},
author: metadata.author ?? null,
committer: metadata.committer ?? null,
},
}
},
outputs: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'GitHub web URL' },
commit: COMMIT_DATA_OUTPUT,
author: USER_FULL_OUTPUT,
committer: USER_FULL_OUTPUT,
},
}
+170
View File
@@ -0,0 +1,170 @@
import type { BranchListResponse, ListBranchesParams } from '@/tools/github/types'
import { BRANCH_OUTPUT_PROPERTIES } 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', Number(params.per_page).toString())
}
if (params.page) {
queryParams.append('page', Number(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' },
},
},
},
}
export const listBranchesV2Tool: ToolConfig<ListBranchesParams, any> = {
id: 'github_list_branches_v2',
name: listBranchesTool.name,
description: listBranchesTool.description,
version: '2.0.0',
params: listBranchesTool.params,
request: listBranchesTool.request,
transformResponse: async (response: Response) => {
const branches = await response.json()
return {
success: true,
output: {
items: branches ?? [],
count: branches?.length ?? 0,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of branch objects',
items: {
type: 'object',
properties: BRANCH_OUTPUT_PROPERTIES,
},
},
count: { type: 'number', description: 'Number of branches returned' },
},
}
+255
View File
@@ -0,0 +1,255 @@
import {
COMMIT_DATA_OUTPUT,
COMMIT_PARENT_OUTPUT_PROPERTIES,
COMMIT_SUMMARY_OUTPUT_PROPERTIES,
USER_FULL_OUTPUT,
} from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface ListCommitsParams {
owner: string
repo: string
sha?: string
path?: string
author?: string
committer?: string
since?: string
until?: string
per_page?: number
page?: number
apiKey: string
}
interface ListCommitsResponse {
success: boolean
output: {
content: string
metadata: {
commits: Array<{
sha: string
html_url: string
message: string
author: { name: string; email: string; date: string; login?: string }
committer: { name: string; email: string; date: string; login?: string }
}>
count: number
}
}
}
export const listCommitsTool: ToolConfig<ListCommitsParams, ListCommitsResponse> = {
id: 'github_list_commits',
name: 'GitHub List Commits',
description:
'List commits in a repository with optional filtering by SHA, path, author, committer, or date range',
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',
},
sha: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'SHA or branch to start listing commits from',
},
path: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Only commits containing this file path',
},
author: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'GitHub login or email address to filter by author',
},
committer: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'GitHub login or email address to filter by committer',
},
since: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Only commits after this date (ISO 8601 format)',
},
until: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Only commits before this date (ISO 8601 format)',
},
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}/commits`)
if (params.sha) url.searchParams.append('sha', params.sha)
if (params.path) url.searchParams.append('path', params.path)
if (params.author) url.searchParams.append('author', params.author)
if (params.committer) url.searchParams.append('committer', params.committer)
if (params.since) url.searchParams.append('since', params.since)
if (params.until) url.searchParams.append('until', params.until)
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
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 data = await response.json()
const commits = data.map((item: any) => ({
sha: item.sha,
html_url: item.html_url,
message: item.commit.message,
author: {
name: item.commit.author.name,
email: item.commit.author.email,
date: item.commit.author.date,
login: item.author?.login,
},
committer: {
name: item.commit.committer.name,
email: item.commit.committer.email,
date: item.commit.committer.date,
login: item.committer?.login,
},
}))
const content = `Found ${commits.length} commit(s):
${commits
.map(
(c: any) =>
`${c.sha.substring(0, 7)} - ${c.message.split('\n')[0]}
Author: ${c.author.login ?? c.author.name} (${c.author.date})
${c.html_url}`
)
.join('\n\n')}`
return {
success: true,
output: {
content,
metadata: {
commits,
count: commits.length,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable commit list' },
metadata: {
type: 'object',
description: 'Commits metadata',
properties: {
commits: {
type: 'array',
description: 'Array of commits',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'GitHub web URL' },
message: { type: 'string', description: 'Commit message' },
author: { type: 'object', description: 'Author info' },
committer: { type: 'object', description: 'Committer info' },
},
},
},
count: { type: 'number', description: 'Number of commits returned' },
},
},
},
}
export const listCommitsV2Tool: ToolConfig<ListCommitsParams, any> = {
id: 'github_list_commits_v2',
name: listCommitsTool.name,
description: listCommitsTool.description,
version: '2.0.0',
params: listCommitsTool.params,
request: listCommitsTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
items: data.map((item: any) => ({
...item,
author: item.author ?? null,
committer: item.committer ?? null,
})),
count: data.length,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of commit objects from GitHub API',
items: {
type: 'object',
properties: {
...COMMIT_SUMMARY_OUTPUT_PROPERTIES,
commit: COMMIT_DATA_OUTPUT,
author: USER_FULL_OUTPUT,
committer: USER_FULL_OUTPUT,
parents: {
type: 'array',
description: 'Parent commits',
items: {
type: 'object',
properties: COMMIT_PARENT_OUTPUT_PROPERTIES,
},
},
},
},
},
count: { type: 'number', description: 'Number of commits returned' },
},
}
+202
View File
@@ -0,0 +1,202 @@
import { REPO_FULL_OUTPUT_PROPERTIES, USER_FULL_OUTPUT_PROPERTIES } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface ListForksParams {
owner: string
repo: string
sort?: 'newest' | 'oldest' | 'stargazers' | 'watchers'
per_page?: number
page?: number
apiKey: string
}
interface ListForksResponse {
success: boolean
output: {
content: string
metadata: {
forks: Array<{
id: number
full_name: string
html_url: string
owner: { login: string }
stargazers_count: number
forks_count: number
created_at: string
updated_at: string
default_branch: string
}>
count: number
}
}
}
export const listForksTool: ToolConfig<ListForksParams, ListForksResponse> = {
id: 'github_list_forks',
name: 'GitHub List Forks',
description: 'List forks of a 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',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort by: newest, oldest, stargazers, watchers (default: newest)',
default: 'newest',
},
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}/forks`)
if (params.sort) url.searchParams.append('sort', params.sort)
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
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 data = await response.json()
const forks = data.map((f: any) => ({
id: f.id,
full_name: f.full_name,
html_url: f.html_url,
owner: { login: f.owner?.login ?? 'unknown' },
stargazers_count: f.stargazers_count,
forks_count: f.forks_count,
created_at: f.created_at,
updated_at: f.updated_at,
default_branch: f.default_branch,
}))
const content = `Found ${forks.length} fork(s):
${forks
.map(
(f: any) =>
`${f.full_name}${f.stargazers_count}
Owner: ${f.owner.login}
${f.html_url}`
)
.join('\n\n')}`
return {
success: true,
output: {
content,
metadata: {
forks,
count: forks.length,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable fork list' },
metadata: {
type: 'object',
description: 'Forks metadata',
properties: {
forks: {
type: 'array',
description: 'Array of forks',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Repository ID' },
full_name: { type: 'string', description: 'Full name' },
html_url: { type: 'string', description: 'Web URL' },
owner: { type: 'object', description: 'Owner info' },
stargazers_count: { type: 'number', description: 'Star count' },
forks_count: { type: 'number', description: 'Fork count' },
created_at: { type: 'string', description: 'Creation date' },
updated_at: { type: 'string', description: 'Update date' },
default_branch: { type: 'string', description: 'Default branch' },
},
},
},
count: { type: 'number', description: 'Number of forks returned' },
},
},
},
}
export const listForksV2Tool: ToolConfig<ListForksParams, any> = {
id: 'github_list_forks_v2',
name: listForksTool.name,
description: listForksTool.description,
version: '2.0.0',
params: listForksTool.params,
request: listForksTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
items: data,
count: data.length,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of fork repository objects from GitHub API',
items: {
type: 'object',
properties: {
...REPO_FULL_OUTPUT_PROPERTIES,
owner: {
type: 'object',
description: 'Fork owner',
properties: USER_FULL_OUTPUT_PROPERTIES,
},
},
},
},
count: { type: 'number', description: 'Number of forks returned' },
},
}
+199
View File
@@ -0,0 +1,199 @@
import { GIST_FILES_OUTPUT, GIST_OUTPUT_PROPERTIES, GIST_OWNER_OUTPUT } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface ListGistsParams {
username?: string
since?: string
per_page?: number
page?: number
apiKey: string
}
interface ListGistsResponse {
success: boolean
output: {
content: string
metadata: {
gists: Array<{
id: string
html_url: string
description: string | null
public: boolean
created_at: string
updated_at: string
files: string[]
owner: { login: string }
comments: number
}>
count: number
}
}
}
export const listGistsTool: ToolConfig<ListGistsParams, ListGistsResponse> = {
id: 'github_list_gists',
name: 'GitHub List Gists',
description: 'List gists for a user or the authenticated user',
version: '1.0.0',
params: {
username: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: "GitHub username (omit for authenticated user's gists)",
},
since: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Only gists updated after this time (ISO 8601)',
},
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 baseUrl = params.username
? `https://api.github.com/users/${params.username}/gists`
: 'https://api.github.com/gists'
const url = new URL(baseUrl)
if (params.since) url.searchParams.append('since', params.since)
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
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 data = await response.json()
const gists = data.map((g: any) => ({
id: g.id,
html_url: g.html_url,
description: g.description ?? null,
public: g.public,
created_at: g.created_at,
updated_at: g.updated_at,
files: Object.keys(g.files ?? {}),
owner: { login: g.owner?.login ?? 'unknown' },
comments: g.comments ?? 0,
}))
const content = `Found ${gists.length} gist(s):
${gists
.map(
(g: any) =>
`${g.id} - ${g.description ?? 'No description'} (${g.public ? 'public' : 'secret'})
Files: ${g.files.join(', ')}
${g.html_url}`
)
.join('\n\n')}`
return {
success: true,
output: {
content,
metadata: {
gists,
count: gists.length,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable gist list' },
metadata: {
type: 'object',
description: 'Gists metadata',
properties: {
gists: {
type: 'array',
description: 'Array of gists',
items: {
type: 'object',
properties: {
id: { type: 'string', description: 'Gist ID' },
html_url: { type: 'string', description: 'Web URL' },
description: { type: 'string', description: 'Description', optional: true },
public: { type: 'boolean', description: 'Is public' },
created_at: { type: 'string', description: 'Creation date' },
updated_at: { type: 'string', description: 'Update date' },
files: { type: 'array', description: 'File names' },
owner: { type: 'object', description: 'Owner info' },
comments: { type: 'number', description: 'Comment count' },
},
},
},
count: { type: 'number', description: 'Number of gists returned' },
},
},
},
}
export const listGistsV2Tool: ToolConfig<ListGistsParams, any> = {
id: 'github_list_gists_v2',
name: listGistsTool.name,
description: listGistsTool.description,
version: '2.0.0',
params: listGistsTool.params,
request: listGistsTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
items: data.map((g: any) => ({
...g,
description: g.description ?? null,
files: g.files ?? {},
comments: g.comments ?? 0,
})),
count: data.length,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of gist objects from GitHub API',
items: {
type: 'object',
properties: {
...GIST_OUTPUT_PROPERTIES,
files: GIST_FILES_OUTPUT,
owner: GIST_OWNER_OUTPUT,
},
},
},
count: { type: 'number', description: 'Number of gists returned' },
},
}
@@ -0,0 +1,177 @@
import { truncate } from '@sim/utils/string'
import type { CommentsListResponse, ListIssueCommentsParams } from '@/tools/github/types'
import { COMMENT_OUTPUT_PROPERTIES, USER_OUTPUT } 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', Number(params.per_page).toString())
if (params.page) queryParams.append('page', Number(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()}): "${truncate(c.body, 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' },
},
},
},
}
export const listIssueCommentsV2Tool: ToolConfig<ListIssueCommentsParams, any> = {
id: 'github_list_issue_comments_v2',
name: listIssueCommentsTool.name,
description: listIssueCommentsTool.description,
version: '2.0.0',
params: listIssueCommentsTool.params,
request: listIssueCommentsTool.request,
transformResponse: async (response: Response) => {
const comments = await response.json()
return {
success: true,
output: {
items: comments ?? [],
count: comments?.length ?? 0,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of comment objects',
items: {
type: 'object',
properties: {
...COMMENT_OUTPUT_PROPERTIES,
user: USER_OUTPUT,
},
},
},
count: { type: 'number', description: 'Number of comments returned' },
},
}
+229
View File
@@ -0,0 +1,229 @@
import type { IssuesListResponse, ListIssuesParams } from '@/tools/github/types'
import {
ISSUE_OUTPUT_PROPERTIES,
LABEL_OUTPUT,
MILESTONE_OUTPUT,
USER_OUTPUT,
} 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', Number(params.per_page).toString())
if (params.page) url.searchParams.append('page', Number(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' },
},
},
},
}
export const listIssuesV2Tool: ToolConfig<ListIssuesParams, any> = {
id: 'github_list_issues_v2',
name: listIssuesTool.name,
description: listIssuesTool.description,
version: '2.0.0',
params: listIssuesTool.params,
request: listIssuesTool.request,
transformResponse: async (response: Response) => {
const issues = await response.json()
const items = issues.map((issue: any) => ({
...issue,
body: issue.body ?? null,
milestone: issue.milestone ?? null,
closed_at: issue.closed_at ?? null,
labels: issue.labels ?? [],
assignees: issue.assignees ?? [],
}))
return {
success: true,
output: {
items,
count: issues.length,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of issue objects from GitHub API',
items: {
type: 'object',
properties: {
...ISSUE_OUTPUT_PROPERTIES,
user: USER_OUTPUT,
labels: {
type: 'array',
description: 'Array of label objects',
items: LABEL_OUTPUT,
},
assignees: {
type: 'array',
description: 'Array of assignee objects',
items: USER_OUTPUT,
},
milestone: { ...MILESTONE_OUTPUT, optional: true },
},
},
},
count: { type: 'number', description: 'Number of issues returned' },
},
}
+223
View File
@@ -0,0 +1,223 @@
import { MILESTONE_CREATOR_OUTPUT, MILESTONE_V2_OUTPUT_PROPERTIES } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface ListMilestonesParams {
owner: string
repo: string
state?: 'open' | 'closed' | 'all'
sort?: 'due_on' | 'completeness'
direction?: 'asc' | 'desc'
per_page?: number
page?: number
apiKey: string
}
interface ListMilestonesResponse {
success: boolean
output: {
content: string
metadata: {
milestones: Array<{
number: number
title: string
description: string | null
state: string
html_url: string
due_on: string | null
open_issues: number
closed_issues: number
}>
count: number
}
}
}
export const listMilestonesTool: ToolConfig<ListMilestonesParams, ListMilestonesResponse> = {
id: 'github_list_milestones',
name: 'GitHub List Milestones',
description: 'List milestones in a 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, all (default: open)',
default: 'open',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort by: due_on or completeness (default: due_on)',
default: 'due_on',
},
direction: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort direction: asc or desc (default: asc)',
default: 'asc',
},
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}/milestones`)
if (params.state) url.searchParams.append('state', params.state)
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', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
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 data = await response.json()
const milestones = data.map((m: any) => {
const total = m.open_issues + m.closed_issues
const progress = total > 0 ? Math.round((m.closed_issues / total) * 100) : 0
return {
number: m.number,
title: m.title,
description: m.description ?? null,
state: m.state,
html_url: m.html_url,
due_on: m.due_on ?? null,
open_issues: m.open_issues,
closed_issues: m.closed_issues,
progress,
}
})
const content = `Found ${milestones.length} milestone(s):
${milestones
.map(
(m: any) =>
`#${m.number}: ${m.title} (${m.state})
Progress: ${m.progress}% (${m.closed_issues}/${m.open_issues + m.closed_issues} issues)
Due: ${m.due_on ?? 'No due date'}
${m.html_url}`
)
.join('\n\n')}`
return {
success: true,
output: {
content,
metadata: {
milestones,
count: milestones.length,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable milestone list' },
metadata: {
type: 'object',
description: 'Milestones metadata',
properties: {
milestones: {
type: 'array',
description: 'Array of milestones',
items: {
type: 'object',
properties: {
number: { type: 'number', description: 'Milestone number' },
title: { type: 'string', description: 'Title' },
description: { type: 'string', description: 'Description', optional: true },
state: { type: 'string', description: 'State' },
html_url: { type: 'string', description: 'Web URL' },
due_on: { type: 'string', description: 'Due date', optional: true },
open_issues: { type: 'number', description: 'Open issues' },
closed_issues: { type: 'number', description: 'Closed issues' },
},
},
},
count: { type: 'number', description: 'Number of milestones returned' },
},
},
},
}
export const listMilestonesV2Tool: ToolConfig<ListMilestonesParams, any> = {
id: 'github_list_milestones_v2',
name: listMilestonesTool.name,
description: listMilestonesTool.description,
version: '2.0.0',
params: listMilestonesTool.params,
request: listMilestonesTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
items: data.map((m: any) => ({
...m,
description: m.description ?? null,
due_on: m.due_on ?? null,
})),
count: data.length,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of milestone objects from GitHub API',
items: {
type: 'object',
properties: {
...MILESTONE_V2_OUTPUT_PROPERTIES,
creator: MILESTONE_CREATOR_OUTPUT,
},
},
},
count: { type: 'number', description: 'Number of milestones returned' },
},
}
+193
View File
@@ -0,0 +1,193 @@
import { truncate } from '@sim/utils/string'
import type { CommentsListResponse, ListPRCommentsParams } from '@/tools/github/types'
import { PR_COMMENT_OUTPUT_PROPERTIES, USER_OUTPUT } 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', Number(params.per_page).toString())
if (params.page) queryParams.append('page', Number(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()}): "${truncate(c.body, 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' },
},
},
},
}
export const listPRCommentsV2Tool: ToolConfig<ListPRCommentsParams, any> = {
id: 'github_list_pr_comments_v2',
name: listPRCommentsTool.name,
description: listPRCommentsTool.description,
version: '2.0.0',
params: listPRCommentsTool.params,
request: listPRCommentsTool.request,
transformResponse: async (response: Response) => {
const comments = await response.json()
return {
success: true,
output: {
items: comments ?? [],
count: comments?.length ?? 0,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of review comment objects',
items: {
type: 'object',
properties: {
...PR_COMMENT_OUTPUT_PROPERTIES,
user: USER_OUTPUT,
},
},
},
count: { type: 'number', description: 'Number of comments returned' },
},
}
+233
View File
@@ -0,0 +1,233 @@
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' },
},
},
},
}
export const listProjectsV2Tool: ToolConfig<ListProjectsParams, any> = {
id: 'github_list_projects_v2',
name: listProjectsTool.name,
description: listProjectsTool.description,
version: '2.0.0',
params: listProjectsTool.params,
request: listProjectsTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
output: { items: [], totalCount: 0 },
error: data.errors[0].message,
}
}
const ownerData = data.data?.organization || data.data?.user
const projectsData = ownerData?.projectsV2
if (!projectsData) {
return {
success: false,
output: { items: [], totalCount: 0 },
error: 'No projects data found',
}
}
return {
success: true,
output: {
items: 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 ?? null,
})),
totalCount: projectsData.totalCount,
},
}
},
outputs: {
items: {
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: 'Short description', optional: true },
},
},
},
totalCount: { type: 'number', description: 'Total number of projects' },
},
}
+191
View File
@@ -0,0 +1,191 @@
import type { ListPRsParams, PRListResponse } from '@/tools/github/types'
import { BRANCH_REF_OUTPUT, PR_SUMMARY_OUTPUT_PROPERTIES, USER_OUTPUT } 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', Number(params.per_page).toString())
if (params.page) url.searchParams.append('page', Number(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' },
},
},
},
}
export const listPRsV2Tool: ToolConfig<ListPRsParams, any> = {
id: 'github_list_prs_v2',
name: listPRsTool.name,
description: listPRsTool.description,
version: '2.0.0',
params: listPRsTool.params,
request: listPRsTool.request,
transformResponse: async (response: Response) => {
const prs = await response.json()
return {
success: true,
output: {
items: prs ?? [],
count: prs?.length ?? 0,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of pull request objects',
items: {
type: 'object',
properties: {
...PR_SUMMARY_OUTPUT_PROPERTIES,
user: USER_OUTPUT,
head: BRANCH_REF_OUTPUT,
base: BRANCH_REF_OUTPUT,
},
},
},
count: { type: 'number', description: 'Number of PRs returned' },
},
}
+192
View File
@@ -0,0 +1,192 @@
import type { ListReleasesParams, ListReleasesResponse } from '@/tools/github/types'
import {
RELEASE_ASSET_OUTPUT_PROPERTIES,
RELEASE_OUTPUT_PROPERTIES,
USER_OUTPUT,
} 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', Number(params.per_page).toString())
}
if (params.page) {
url.searchParams.append('page', Number(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' },
},
},
},
},
},
},
}
export const listReleasesV2Tool: ToolConfig<ListReleasesParams, any> = {
id: 'github_list_releases_v2',
name: listReleasesTool.name,
description: listReleasesTool.description,
version: '2.0.0',
params: listReleasesTool.params,
request: listReleasesTool.request,
transformResponse: async (response: Response) => {
const releases = await response.json()
return {
success: true,
output: {
items: releases.map((release: any) => ({
...release,
body: release.body ?? null,
published_at: release.published_at ?? null,
})),
count: releases.length,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of release objects',
items: {
type: 'object',
properties: {
...RELEASE_OUTPUT_PROPERTIES,
author: USER_OUTPUT,
assets: {
type: 'array',
description: 'Release assets',
items: {
type: 'object',
properties: {
...RELEASE_ASSET_OUTPUT_PROPERTIES,
uploader: USER_OUTPUT,
},
},
},
},
},
},
count: { type: 'number', description: 'Number of releases returned' },
},
}
+175
View File
@@ -0,0 +1,175 @@
import { USER_FULL_OUTPUT_PROPERTIES } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface ListStargazersParams {
owner: string
repo: string
per_page?: number
page?: number
apiKey: string
}
interface ListStargazersResponse {
success: boolean
output: {
content: string
metadata: {
stargazers: Array<{
login: string
id: number
avatar_url: string
html_url: string
type: string
}>
count: number
}
}
}
export const listStargazersTool: ToolConfig<ListStargazersParams, ListStargazersResponse> = {
id: 'github_list_stargazers',
name: 'GitHub List Stargazers',
description: 'List users who have starred a 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',
},
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}/stargazers`)
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
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 data = await response.json()
const stargazers = data.map((u: any) => ({
login: u.login,
id: u.id,
avatar_url: u.avatar_url,
html_url: u.html_url,
type: u.type,
}))
const content = `Found ${stargazers.length} stargazer(s):
${stargazers.map((u: any) => `@${u.login} (${u.type}) - ${u.html_url}`).join('\n')}`
return {
success: true,
output: {
content,
metadata: {
stargazers,
count: stargazers.length,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable stargazer list' },
metadata: {
type: 'object',
description: 'Stargazers metadata',
properties: {
stargazers: {
type: 'array',
description: 'Array of stargazers',
items: {
type: 'object',
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
avatar_url: { type: 'string', description: 'Avatar URL' },
html_url: { type: 'string', description: 'Profile URL' },
type: { type: 'string', description: 'User or Organization' },
},
},
},
count: { type: 'number', description: 'Number of stargazers returned' },
},
},
},
}
export const listStargazersV2Tool: ToolConfig<ListStargazersParams, any> = {
id: 'github_list_stargazers_v2',
name: listStargazersTool.name,
description: listStargazersTool.description,
version: '2.0.0',
params: listStargazersTool.params,
request: listStargazersTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
items: data,
count: data.length,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of user objects from GitHub API',
items: {
type: 'object',
properties: {
...USER_FULL_OUTPUT_PROPERTIES,
gravatar_id: { type: 'string', description: 'Gravatar ID' },
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 API URL' },
repos_url: { type: 'string', description: 'Repos API URL' },
},
},
},
count: { type: 'number', description: 'Number of stargazers returned' },
},
}
+181
View File
@@ -0,0 +1,181 @@
import type { ListTagsParams, TagsListResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const listTagsTool: ToolConfig<ListTagsParams, TagsListResponse> = {
id: 'github_list_tags',
name: 'GitHub List Tags',
description:
'List tags for a GitHub repository. Returns tag names with their commit SHA 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}/tags`)
if (params.per_page) {
url.searchParams.append('per_page', Number(params.per_page).toString())
}
if (params.page) {
url.searchParams.append('page', Number(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) => {
if (!response.ok) {
const error = await response.json().catch(() => ({}))
return {
success: false,
error: error.message || `Failed to list tags (HTTP ${response.status})`,
output: { content: '', metadata: { total_count: 0, tags: [] } },
}
}
const tags = await response.json()
const tagsList = tags
.map(
(tag: any, index: number) => `${index + 1}. ${tag.name} (${tag.commit?.sha ?? 'unknown'})`
)
.join('\n')
const content = `Total tags: ${tags.length}
${tagsList}`
return {
success: true,
output: {
content,
metadata: {
total_count: tags.length,
tags: tags.map((tag: any) => ({
name: tag.name,
commit_sha: tag.commit?.sha ?? '',
zipball_url: tag.zipball_url,
tarball_url: tag.tarball_url,
})),
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable list of tags' },
metadata: {
type: 'object',
description: 'Tags metadata',
properties: {
total_count: { type: 'number', description: 'Total number of tags returned' },
tags: {
type: 'array',
description: 'Array of tag objects',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Tag name' },
commit_sha: { type: 'string', description: 'Commit SHA the tag points to' },
zipball_url: { type: 'string', description: 'Zipball download URL' },
tarball_url: { type: 'string', description: 'Tarball download URL' },
},
},
},
},
},
},
}
export const listTagsV2Tool: ToolConfig<ListTagsParams, any> = {
id: 'github_list_tags_v2',
name: listTagsTool.name,
description: listTagsTool.description,
version: '2.0.0',
params: listTagsTool.params,
request: listTagsTool.request,
transformResponse: async (response: Response) => {
if (!response.ok) {
const error = await response.json().catch(() => ({}))
return {
success: false,
error: error.message || `Failed to list tags (HTTP ${response.status})`,
output: { items: [], count: 0 },
}
}
const tags = await response.json()
return {
success: true,
output: {
items: tags,
count: tags.length,
},
}
},
outputs: {
items: {
type: 'array',
description: 'Array of tag objects',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'Tag name' },
zipball_url: { type: 'string', description: 'Zipball download URL' },
tarball_url: { type: 'string', description: 'Tarball download URL' },
node_id: { type: 'string', description: 'Node ID' },
commit: {
type: 'object',
description: 'Commit the tag points to',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
url: { type: 'string', description: 'Commit API URL' },
},
},
},
},
},
count: { type: 'number', description: 'Number of tags returned' },
},
}
+247
View File
@@ -0,0 +1,247 @@
import type { ListWorkflowRunsParams, ListWorkflowRunsResponse } from '@/tools/github/types'
import {
HEAD_COMMIT_OUTPUT,
PR_REFERENCE_OUTPUT,
REFERENCED_WORKFLOW_OUTPUT,
USER_OUTPUT,
WORKFLOW_RUN_OUTPUT_PROPERTIES,
} 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', Number(params.per_page).toString())
}
if (params.page) {
url.searchParams.append('page', Number(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' },
},
},
},
},
},
},
}
export const listWorkflowRunsV2Tool: ToolConfig<ListWorkflowRunsParams, any> = {
id: 'github_list_workflow_runs_v2',
name: listWorkflowRunsTool.name,
description: listWorkflowRunsTool.description,
version: '2.0.0',
params: listWorkflowRunsTool.params,
request: listWorkflowRunsTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
total_count: data.total_count,
items: (data.workflow_runs ?? []).map((run: Record<string, unknown>) => ({
...run,
conclusion: run.conclusion ?? null,
triggering_actor: run.triggering_actor,
pull_requests: run.pull_requests ?? [],
referenced_workflows: run.referenced_workflows ?? [],
head_commit: run.head_commit,
run_started_at: run.run_started_at,
})),
},
}
},
outputs: {
total_count: { type: 'number', description: 'Total number of workflow runs' },
items: {
type: 'array',
description: 'Array of workflow run objects',
items: {
type: 'object',
properties: {
...WORKFLOW_RUN_OUTPUT_PROPERTIES,
triggering_actor: USER_OUTPUT,
pull_requests: {
type: 'array',
description: 'Associated pull requests',
items: PR_REFERENCE_OUTPUT,
},
referenced_workflows: {
type: 'array',
description: 'Referenced workflows',
items: REFERENCED_WORKFLOW_OUTPUT,
},
head_commit: HEAD_COMMIT_OUTPUT,
},
},
},
},
}
+171
View File
@@ -0,0 +1,171 @@
import type { ListWorkflowsParams, ListWorkflowsResponse } from '@/tools/github/types'
import { WORKFLOW_OUTPUT_PROPERTIES } 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', Number(params.per_page).toString())
}
if (params.page) {
url.searchParams.append('page', Number(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' },
},
},
},
},
},
},
}
export const listWorkflowsV2Tool: ToolConfig<ListWorkflowsParams, any> = {
id: 'github_list_workflows_v2',
name: listWorkflowsTool.name,
description: listWorkflowsTool.description,
version: '2.0.0',
params: listWorkflowsTool.params,
request: listWorkflowsTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
total_count: data.total_count,
items: (data.workflows ?? []).map((workflow: Record<string, unknown>) => ({
...workflow,
deleted_at: workflow.deleted_at ?? null,
})),
},
}
},
outputs: {
total_count: { type: 'number', description: 'Total number of workflows' },
items: {
type: 'array',
description: 'Array of workflow objects',
items: {
type: 'object',
properties: WORKFLOW_OUTPUT_PROPERTIES,
},
},
},
}
+172
View File
@@ -0,0 +1,172 @@
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 || response.status === 409) {
const error = await response.json()
const message =
error.message ||
(response.status === 409
? 'Head branch was modified; review and try the merge again'
: 'Pull request is not mergeable')
return {
success: false,
error: message,
output: {
content: '',
metadata: {
sha: '',
merged: false,
message,
},
},
}
}
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' },
},
},
},
}
export const mergePRV2Tool: ToolConfig<MergePRParams, any> = {
id: 'github_merge_pr_v2',
name: mergePRTool.name,
description: mergePRTool.description,
version: '2.0.0',
params: mergePRTool.params,
request: mergePRTool.request,
transformResponse: async (response: Response) => {
if (response.status === 405 || response.status === 409) {
const error = await response.json()
const message =
error.message ||
(response.status === 409
? 'Head branch was modified; review and try the merge again'
: 'Pull request is not mergeable')
return {
success: false,
error: message,
output: {
sha: null,
merged: false,
message,
},
}
}
const result = await response.json()
return {
success: true,
output: {
sha: result.sha,
merged: result.merged,
message: result.message,
},
}
},
outputs: {
sha: { type: 'string', description: 'Merge commit SHA', optional: true },
merged: { type: 'boolean', description: 'Whether merge was successful' },
message: { type: 'string', description: 'Response message' },
},
}
+276
View File
@@ -0,0 +1,276 @@
import type { PROperationParams, PullRequestResponse } from '@/tools/github/types'
import { BRANCH_REF_OUTPUT, PR_FILE_OUTPUT_PROPERTIES, USER_OUTPUT } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const prTool: ToolConfig<PROperationParams, PullRequestResponse> = {
id: 'github_pr',
name: 'GitHub PR Reader',
description: 'Fetch PR details including diff and files changed',
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: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
}),
},
transformResponse: async (response, params) => {
const pr = await response.json()
const filesResponse = await fetch(
`https://api.github.com/repos/${pr.base.repo.owner.login}/${pr.base.repo.name}/pulls/${pr.number}/files`,
{
headers: {
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params?.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
},
}
)
if (!filesResponse.ok) {
const error = await filesResponse.json().catch(() => ({}))
return {
success: false,
error: error.message || `Failed to fetch PR files (HTTP ${filesResponse.status})`,
output: {
content: '',
metadata: {
number: pr.number,
title: pr.title,
state: pr.state,
html_url: pr.html_url,
diff_url: pr.diff_url,
created_at: pr.created_at,
updated_at: pr.updated_at,
files: [],
},
},
}
}
const filesJson = await filesResponse.json()
const files = Array.isArray(filesJson) ? filesJson : []
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}
URL: ${pr.html_url}`
return {
success: true,
output: {
content,
metadata: {
number: pr.number,
title: pr.title,
state: pr.state,
html_url: pr.html_url,
diff_url: pr.diff_url,
created_at: pr.created_at,
updated_at: pr.updated_at,
files: files.map((file: any) => ({
filename: file.filename,
additions: file.additions,
deletions: file.deletions,
changes: file.changes,
patch: file.patch,
blob_url: file.blob_url,
raw_url: file.raw_url,
status: file.status,
})),
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable PR summary' },
metadata: {
type: 'object',
description: 'Detailed PR metadata including file changes',
properties: {
number: { type: 'number', description: 'Pull request number' },
title: { type: 'string', description: 'PR title' },
state: { type: 'string', description: 'PR state (open/closed/merged)' },
html_url: { type: 'string', description: 'GitHub web URL' },
diff_url: { type: 'string', description: 'Raw diff URL' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
files: {
type: 'array',
description: 'Files changed in the PR',
items: {
type: 'object',
properties: {
filename: { type: 'string', description: 'File path' },
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' },
status: { type: 'string', description: 'Change type (added/modified/deleted)' },
},
},
},
},
},
},
}
export const prV2Tool: ToolConfig<PROperationParams, any> = {
id: 'github_pr_v2',
name: prTool.name,
description: prTool.description,
version: '2.0.0',
params: prTool.params,
request: prTool.request,
transformResponse: async (response: Response, params) => {
const pr = await response.json()
const filesResponse = await fetch(
`https://api.github.com/repos/${pr.base.repo.owner.login}/${pr.base.repo.name}/pulls/${pr.number}/files`,
{
headers: {
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${params?.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
},
}
)
if (!filesResponse.ok) {
const error = await filesResponse.json().catch(() => ({}))
return {
success: false,
error: error.message || `Failed to fetch PR files (HTTP ${filesResponse.status})`,
output: {
id: pr.id,
number: pr.number,
title: pr.title,
state: pr.state,
html_url: pr.html_url,
diff_url: pr.diff_url,
body: pr.body ?? null,
user: pr.user,
head: pr.head,
base: pr.base,
merged: pr.merged,
mergeable: pr.mergeable ?? null,
merged_by: pr.merged_by ?? null,
comments: pr.comments,
review_comments: pr.review_comments,
commits: pr.commits,
additions: pr.additions,
deletions: pr.deletions,
changed_files: pr.changed_files,
created_at: pr.created_at,
updated_at: pr.updated_at,
closed_at: pr.closed_at ?? null,
merged_at: pr.merged_at ?? null,
files: [],
},
}
}
const filesJson = await filesResponse.json()
const files = Array.isArray(filesJson) ? filesJson : []
return {
success: true,
output: {
id: pr.id,
number: pr.number,
title: pr.title,
state: pr.state,
html_url: pr.html_url,
diff_url: pr.diff_url,
body: pr.body ?? null,
user: pr.user,
head: pr.head,
base: pr.base,
merged: pr.merged,
mergeable: pr.mergeable ?? null,
merged_by: pr.merged_by ?? null,
comments: pr.comments,
review_comments: pr.review_comments,
commits: pr.commits,
additions: pr.additions,
deletions: pr.deletions,
changed_files: pr.changed_files,
created_at: pr.created_at,
updated_at: pr.updated_at,
closed_at: pr.closed_at ?? null,
merged_at: pr.merged_at ?? null,
files: files ?? [],
},
}
},
outputs: {
id: { type: 'number', description: 'Pull request ID' },
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' },
diff_url: { type: 'string', description: 'Raw diff URL' },
body: { type: 'string', description: 'PR description' },
user: USER_OUTPUT,
head: BRANCH_REF_OUTPUT,
base: BRANCH_REF_OUTPUT,
merged: { type: 'boolean', description: 'Whether PR is merged' },
mergeable: { type: 'boolean', description: 'Whether PR is mergeable' },
merged_by: USER_OUTPUT,
comments: { type: 'number', description: 'Number of comments' },
review_comments: { type: 'number', description: 'Number of review comments' },
commits: { type: 'number', description: 'Number of commits' },
additions: { type: 'number', description: 'Lines added' },
deletions: { type: 'number', description: 'Lines deleted' },
changed_files: { type: 'number', description: 'Number of changed files' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
closed_at: { type: 'string', description: 'Close timestamp' },
merged_at: { type: 'string', description: 'Merge timestamp' },
files: {
type: 'array',
description: 'Array of changed file objects',
items: {
type: 'object',
properties: PR_FILE_OUTPUT_PROPERTIES,
},
},
},
}
+144
View File
@@ -0,0 +1,144 @@
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' },
},
},
},
}
export const removeLabelV2Tool: ToolConfig = {
id: 'github_remove_label_v2',
name: removeLabelTool.name,
description: removeLabelTool.description,
version: '2.0.0',
params: removeLabelTool.params,
request: removeLabelTool.request,
oauth: removeLabelTool.oauth,
transformResponse: async (response: Response) => {
if (response.status === 200) {
const labels = await response.json()
return {
success: true,
output: {
items: labels.map((label: any) => ({
...label,
description: label.description ?? null,
})),
count: labels.length,
},
}
}
return { success: true, output: { items: [], count: 0 } }
},
outputs: {
items: {
type: 'array',
description: 'Remaining labels on the issue',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Label ID' },
name: { type: 'string', description: 'Label name' },
color: { type: 'string', description: 'Label color' },
description: { type: 'string', description: 'Label description', optional: true },
},
},
},
count: { type: 'number', description: 'Number of remaining labels' },
},
}
+163
View File
@@ -0,0 +1,163 @@
import type { BaseGitHubParams, RepoInfoResponse } from '@/tools/github/types'
import {
LICENSE_OUTPUT,
LICENSE_OUTPUT_PROPERTIES,
USER_FULL_OUTPUT,
USER_FULL_OUTPUT_PROPERTIES,
} from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const repoInfoTool: ToolConfig<BaseGitHubParams, RepoInfoResponse> = {
id: 'github_repo_info',
name: 'GitHub Repository Info',
description:
'Retrieve comprehensive GitHub repository metadata including stars, forks, issues, and primary language. Supports both public and private repositories with optional authentication.',
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',
},
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}`,
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 = `Repository: ${data.name}
Description: ${data.description || 'No description'}
Language: ${data.language || 'Not specified'}
Stars: ${data.stargazers_count}
Forks: ${data.forks_count}
Open Issues: ${data.open_issues_count}
URL: ${data.html_url}`
return {
success: true,
output: {
content,
metadata: {
name: data.name,
description: data.description || '',
stars: data.stargazers_count,
forks: data.forks_count,
openIssues: data.open_issues_count,
language: data.language || 'Not specified',
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable repository summary' },
metadata: {
type: 'object',
description: 'Repository metadata',
properties: {
name: { type: 'string', description: 'Repository name' },
description: { type: 'string', description: 'Repository description' },
stars: { type: 'number', description: 'Number of stars' },
forks: { type: 'number', description: 'Number of forks' },
openIssues: { type: 'number', description: 'Number of open issues' },
language: { type: 'string', description: 'Primary programming language' },
},
},
},
}
export const repoInfoV2Tool: ToolConfig<BaseGitHubParams, any> = {
id: 'github_repo_info_v2',
name: repoInfoTool.name,
description: repoInfoTool.description,
version: '2.0.0',
params: repoInfoTool.params,
request: repoInfoTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
id: data.id,
name: data.name,
full_name: data.full_name,
description: data.description ?? null,
html_url: data.html_url,
homepage: data.homepage ?? null,
language: data.language ?? null,
default_branch: data.default_branch,
visibility: data.visibility,
private: data.private,
fork: data.fork,
archived: data.archived,
disabled: data.disabled,
stargazers_count: data.stargazers_count,
watchers_count: data.watchers_count,
forks_count: data.forks_count,
open_issues_count: data.open_issues_count,
topics: data.topics ?? [],
created_at: data.created_at,
updated_at: data.updated_at,
pushed_at: data.pushed_at,
owner: data.owner,
license: data.license ?? null,
},
}
},
outputs: {
id: { type: 'number', description: 'Repository ID' },
name: { type: 'string', description: 'Repository name' },
full_name: { type: 'string', description: 'Full repository name (owner/repo)' },
description: { type: 'string', description: 'Repository description', optional: true },
html_url: { type: 'string', description: 'GitHub web URL' },
homepage: { type: 'string', description: 'Homepage URL', optional: true },
language: { type: 'string', description: 'Primary programming language', optional: true },
default_branch: { type: 'string', description: 'Default branch name' },
visibility: { type: 'string', description: 'Repository visibility (public/private)' },
private: { type: 'boolean', description: 'Whether the repository is private' },
fork: { type: 'boolean', description: 'Whether this is a fork' },
archived: { type: 'boolean', description: 'Whether the repository is archived' },
disabled: { type: 'boolean', description: 'Whether the repository is disabled' },
stargazers_count: { type: 'number', description: 'Number of stars' },
watchers_count: { type: 'number', description: 'Number of watchers' },
forks_count: { type: 'number', description: 'Number of forks' },
open_issues_count: { type: 'number', description: 'Number of open issues' },
topics: { type: 'array', description: 'Repository topics' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
pushed_at: { type: 'string', description: 'Last push timestamp' },
owner: {
...USER_FULL_OUTPUT,
properties: USER_FULL_OUTPUT_PROPERTIES,
},
license: {
...LICENSE_OUTPUT,
properties: LICENSE_OUTPUT_PROPERTIES,
},
},
}
+183
View File
@@ -0,0 +1,183 @@
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: false,
visibility: 'user-or-llm',
description:
'Comma-separated list of user logins to request reviews from (at least one of reviewers or team_reviewers is required)',
},
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 body: Record<string, any> = {}
if (params.reviewers) {
const reviewersArray = params.reviewers
.split(',')
.map((r) => r.trim())
.filter((r) => r)
if (reviewersArray.length > 0) {
body.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' },
},
},
},
},
},
},
}
export const requestReviewersV2Tool: ToolConfig = {
id: 'github_request_reviewers_v2',
name: requestReviewersTool.name,
description: requestReviewersTool.description,
version: '2.0.0',
params: requestReviewersTool.params,
request: requestReviewersTool.request,
oauth: requestReviewersTool.oauth,
transformResponse: async (response: Response) => {
const pr = await response.json()
return {
success: true,
output: {
id: pr.id,
number: pr.number,
title: pr.title,
html_url: pr.html_url,
requested_reviewers: pr.requested_reviewers ?? [],
requested_teams: pr.requested_teams ?? [],
},
}
},
outputs: {
id: { type: 'number', description: 'PR ID' },
number: { type: 'number', description: 'PR number' },
title: { type: 'string', description: 'PR title' },
html_url: { type: 'string', description: 'GitHub web URL' },
requested_reviewers: { type: 'array', description: 'Array of requested reviewer objects' },
requested_teams: { type: 'array', description: 'Array of requested team objects' },
},
}
+125
View File
@@ -0,0 +1,125 @@
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)' },
},
},
},
}
export const rerunWorkflowV2Tool: ToolConfig = {
id: 'github_rerun_workflow_v2',
name: rerunWorkflowTool.name,
description: rerunWorkflowTool.description,
version: '2.0.0',
params: rerunWorkflowTool.params,
request: rerunWorkflowTool.request,
oauth: rerunWorkflowTool.oauth,
transformResponse: async (response: Response, params) => {
return {
success: true,
output: {
rerun_requested: response.status === 201,
run_id: params?.run_id ?? null,
},
}
},
outputs: {
rerun_requested: { type: 'boolean', description: 'Whether rerun was requested' },
run_id: { type: 'number', description: 'Workflow run ID', optional: true },
},
}
+270
View File
@@ -0,0 +1,270 @@
import type { ToolConfig } from '@/tools/types'
interface SearchCodeParams {
q: string
sort?: 'indexed'
order?: 'asc' | 'desc'
per_page?: number
page?: number
apiKey: string
}
interface SearchCodeResponse {
success: boolean
output: {
content: string
metadata: {
total_count: number
incomplete_results: boolean
items: Array<{
name: string
path: string
sha: string
html_url: string
repository: {
full_name: string
html_url: string
}
}>
}
}
}
export const searchCodeTool: ToolConfig<SearchCodeParams, SearchCodeResponse> = {
id: 'github_search_code',
name: 'GitHub Search Code',
description:
'Search for code across GitHub repositories. Use qualifiers like repo:owner/name, language:js, path:src, extension:py',
version: '1.0.0',
params: {
q: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Search query with optional qualifiers (repo:, language:, path:, extension:, user:, org:)',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort by indexed date (default: best match)',
},
order: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order: asc or 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/search/code')
url.searchParams.append('q', params.q)
if (params.sort) url.searchParams.append('sort', params.sort)
if (params.order) url.searchParams.append('order', params.order)
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
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 data = await response.json()
const items = data.items.map((item: any) => ({
name: item.name,
path: item.path,
sha: item.sha,
html_url: item.html_url,
repository: {
full_name: item.repository.full_name,
html_url: item.repository.html_url,
},
}))
const content = `Found ${data.total_count} code result(s)${data.incomplete_results ? ' (incomplete)' : ''}:
${items
.map(
(item: any) =>
`- ${item.repository.full_name}/${item.path}
${item.html_url}`
)
.join('\n')}`
return {
success: true,
output: {
content,
metadata: {
total_count: data.total_count,
incomplete_results: data.incomplete_results,
items,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable search results' },
metadata: {
type: 'object',
description: 'Search results metadata',
properties: {
total_count: { type: 'number', description: 'Total matching results' },
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
items: {
type: 'array',
description: 'Array of code matches',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'File name' },
path: { type: 'string', description: 'File path' },
sha: { type: 'string', description: 'Blob SHA' },
html_url: { type: 'string', description: 'GitHub web URL' },
repository: {
type: 'object',
description: 'Repository info',
properties: {
full_name: { type: 'string', description: 'Repository full name' },
html_url: { type: 'string', description: 'Repository URL' },
},
},
},
},
},
},
},
},
}
export const searchCodeV2Tool: ToolConfig<SearchCodeParams, any> = {
id: 'github_search_code_v2',
name: searchCodeTool.name,
description: searchCodeTool.description,
version: '2.0.0',
params: searchCodeTool.params,
request: searchCodeTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
total_count: data.total_count,
incomplete_results: data.incomplete_results,
items: data.items.map((item: any) => ({
...item,
text_matches: item.text_matches ?? [],
})),
},
}
},
outputs: {
total_count: { type: 'number', description: 'Total matching results' },
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
items: {
type: 'array',
description: 'Array of code matches from GitHub API',
items: {
type: 'object',
properties: {
name: { type: 'string', description: 'File name' },
path: { type: 'string', description: 'File path' },
sha: { type: 'string', description: 'Blob SHA' },
url: { type: 'string', description: 'API URL' },
git_url: { type: 'string', description: 'Git blob URL' },
html_url: { type: 'string', description: 'GitHub web URL' },
score: { type: 'number', description: 'Search relevance score' },
repository: {
type: 'object',
description: 'Repository containing the code',
properties: {
id: { type: 'number', description: 'Repository ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
name: { type: 'string', description: 'Repository name' },
full_name: { type: 'string', description: 'Full name (owner/repo)' },
private: { type: 'boolean', description: 'Whether repository is private' },
html_url: { type: 'string', description: 'GitHub web URL' },
description: {
type: 'string',
description: 'Repository description',
optional: true,
},
fork: { type: 'boolean', description: 'Whether this is a fork' },
url: { type: 'string', description: 'API URL' },
owner: {
type: 'object',
description: 'Repository owner',
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
},
text_matches: {
type: 'array',
description: 'Text matches showing context',
items: {
type: 'object',
properties: {
object_url: { type: 'string', description: 'Object URL' },
object_type: { type: 'string', description: 'Object type', optional: true },
property: { type: 'string', description: 'Property matched' },
fragment: { type: 'string', description: 'Text fragment with match' },
matches: {
type: 'array',
description: 'Match indices',
items: {
type: 'object',
properties: {
text: { type: 'string', description: 'Matched text' },
indices: { type: 'array', description: 'Start and end indices' },
},
},
},
},
},
},
},
},
},
},
}
+332
View File
@@ -0,0 +1,332 @@
import type { ToolConfig } from '@/tools/types'
interface SearchCommitsParams {
q: string
sort?: 'author-date' | 'committer-date'
order?: 'asc' | 'desc'
per_page?: number
page?: number
apiKey: string
}
interface SearchCommitsResponse {
success: boolean
output: {
content: string
metadata: {
total_count: number
incomplete_results: boolean
items: Array<{
sha: string
html_url: string
commit: {
message: string
author: { name: string; email: string; date: string }
committer: { name: string; email: string; date: string }
}
author: { login: string } | null
committer: { login: string } | null
repository: { full_name: string; html_url: string }
}>
}
}
}
export const searchCommitsTool: ToolConfig<SearchCommitsParams, SearchCommitsResponse> = {
id: 'github_search_commits',
name: 'GitHub Search Commits',
description:
'Search for commits across GitHub. Use qualifiers like repo:owner/name, author:user, committer:user, author-date:>2023-01-01',
version: '1.0.0',
params: {
q: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Search query with optional qualifiers (repo:, author:, committer:, author-date:, committer-date:, merge:true/false)',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort by: author-date or committer-date (default: best match)',
},
order: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order: asc or 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/search/commits')
url.searchParams.append('q', params.q)
if (params.sort) url.searchParams.append('sort', params.sort)
if (params.order) url.searchParams.append('order', params.order)
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
return url.toString()
},
method: 'GET',
headers: (params) => ({
Accept: 'application/vnd.github.cloak-preview+json',
Authorization: `Bearer ${params.apiKey}`,
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response) => {
const data = await response.json()
const items = data.items.map((item: any) => ({
sha: item.sha,
html_url: item.html_url,
commit: {
message: item.commit.message,
author: item.commit.author,
committer: item.commit.committer,
},
author: item.author ? { login: item.author.login } : null,
committer: item.committer ? { login: item.committer.login } : null,
repository: {
full_name: item.repository.full_name,
html_url: item.repository.html_url,
},
}))
const content = `Found ${data.total_count} commit(s)${data.incomplete_results ? ' (incomplete)' : ''}:
${items
.map(
(item: any) =>
`${item.sha.substring(0, 7)} - ${item.commit.message.split('\n')[0]}
Repository: ${item.repository.full_name}
Author: ${item.author?.login ?? item.commit.author.name} (${item.commit.author.date})
${item.html_url}`
)
.join('\n\n')}`
return {
success: true,
output: {
content,
metadata: {
total_count: data.total_count,
incomplete_results: data.incomplete_results,
items,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable search results' },
metadata: {
type: 'object',
description: 'Search results metadata',
properties: {
total_count: { type: 'number', description: 'Total matching results' },
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
items: {
type: 'array',
description: 'Array of commits',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
html_url: { type: 'string', description: 'GitHub web URL' },
commit: {
type: 'object',
description: 'Commit details',
properties: {
message: { type: 'string', description: 'Commit message' },
author: { type: 'object', description: 'Author info' },
committer: { type: 'object', description: 'Committer info' },
},
},
author: { type: 'object', description: 'GitHub user (author)', optional: true },
committer: { type: 'object', description: 'GitHub user (committer)', optional: true },
repository: { type: 'object', description: 'Repository info' },
},
},
},
},
},
},
}
export const searchCommitsV2Tool: ToolConfig<SearchCommitsParams, any> = {
id: 'github_search_commits_v2',
name: searchCommitsTool.name,
description: searchCommitsTool.description,
version: '2.0.0',
params: searchCommitsTool.params,
request: searchCommitsTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
total_count: data.total_count,
incomplete_results: data.incomplete_results,
items: data.items.map((item: any) => ({
...item,
author: item.author ?? null,
committer: item.committer ?? null,
})),
},
}
},
outputs: {
total_count: { type: 'number', description: 'Total matching results' },
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
items: {
type: 'array',
description: 'Array of commit objects from GitHub API',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Commit SHA' },
node_id: { type: 'string', description: 'GraphQL node ID' },
html_url: { type: 'string', description: 'Web URL' },
url: { type: 'string', description: 'API URL' },
comments_url: { type: 'string', description: 'Comments API URL' },
score: { type: 'number', description: 'Search relevance score' },
commit: {
type: 'object',
description: 'Core commit data',
properties: {
url: { type: 'string', description: 'Commit API URL' },
message: { type: 'string', description: 'Commit message' },
comment_count: { type: 'number', description: 'Number of comments' },
author: {
type: 'object',
description: 'Git author',
properties: {
name: { type: 'string', description: 'Author name' },
email: { type: 'string', description: 'Author email' },
date: { type: 'string', description: 'Author date (ISO 8601)' },
},
},
committer: {
type: 'object',
description: 'Git committer',
properties: {
name: { type: 'string', description: 'Committer name' },
email: { type: 'string', description: 'Committer email' },
date: { type: 'string', description: 'Commit date (ISO 8601)' },
},
},
tree: {
type: 'object',
description: 'Tree object',
properties: {
sha: { type: 'string', description: 'Tree SHA' },
url: { type: 'string', description: 'Tree API URL' },
},
},
},
},
author: {
type: 'object',
description: 'GitHub user (author)',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
committer: {
type: 'object',
description: 'GitHub user (committer)',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
repository: {
type: 'object',
description: 'Repository containing the commit',
properties: {
id: { type: 'number', description: 'Repository ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
name: { type: 'string', description: 'Repository name' },
full_name: { type: 'string', description: 'Full name (owner/repo)' },
private: { type: 'boolean', description: 'Whether repository is private' },
html_url: { type: 'string', description: 'GitHub web URL' },
description: {
type: 'string',
description: 'Repository description',
optional: true,
},
owner: {
type: 'object',
description: 'Repository owner',
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
},
parents: {
type: 'array',
description: 'Parent commits',
items: {
type: 'object',
properties: {
sha: { type: 'string', description: 'Parent SHA' },
url: { type: 'string', description: 'Parent API URL' },
html_url: { type: 'string', description: 'Parent web URL' },
},
},
},
},
},
},
},
}
+333
View File
@@ -0,0 +1,333 @@
import type { ToolConfig } from '@/tools/types'
interface SearchIssuesParams {
q: string
sort?:
| 'comments'
| 'reactions'
| 'reactions-+1'
| 'reactions--1'
| 'reactions-smile'
| 'reactions-thinking_face'
| 'reactions-heart'
| 'reactions-tada'
| 'interactions'
| 'created'
| 'updated'
order?: 'asc' | 'desc'
per_page?: number
page?: number
apiKey: string
}
interface SearchIssuesResponse {
success: boolean
output: {
content: string
metadata: {
total_count: number
incomplete_results: boolean
items: Array<{
number: number
title: string
state: string
html_url: string
user: { login: string }
labels: string[]
created_at: string
updated_at: string
comments: number
is_pull_request: boolean
repository_url: string
}>
}
}
}
export const searchIssuesTool: ToolConfig<SearchIssuesParams, SearchIssuesResponse> = {
id: 'github_search_issues',
name: 'GitHub Search Issues',
description:
'Search for issues and pull requests across GitHub. Use qualifiers like repo:owner/name, is:issue, is:pr, state:open, label:bug, author:user',
version: '1.0.0',
params: {
q: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Search query with optional qualifiers (repo:, is:issue, is:pr, state:, label:, author:, assignee:)',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Sort by: comments, reactions, created, updated, interactions (default: best match)',
},
order: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order: asc or 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/search/issues')
url.searchParams.append('q', params.q)
if (params.sort) url.searchParams.append('sort', params.sort)
if (params.order) url.searchParams.append('order', params.order)
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
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 data = await response.json()
const items = data.items.map((item: any) => ({
number: item.number,
title: item.title,
state: item.state,
html_url: item.html_url,
user: { login: item.user?.login ?? 'unknown' },
labels: item.labels?.map((l: any) => l.name) ?? [],
created_at: item.created_at,
updated_at: item.updated_at,
comments: item.comments ?? 0,
is_pull_request: !!item.pull_request,
repository_url: item.repository_url,
}))
const content = `Found ${data.total_count} result(s)${data.incomplete_results ? ' (incomplete)' : ''}:
${items
.map(
(item: any) =>
`#${item.number}: "${item.title}" (${item.state}) [${item.is_pull_request ? 'PR' : 'Issue'}]
${item.html_url}
Labels: ${item.labels.length > 0 ? item.labels.join(', ') : 'none'} | Comments: ${item.comments}`
)
.join('\n\n')}`
return {
success: true,
output: {
content,
metadata: {
total_count: data.total_count,
incomplete_results: data.incomplete_results,
items,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable search results' },
metadata: {
type: 'object',
description: 'Search results metadata',
properties: {
total_count: { type: 'number', description: 'Total matching results' },
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
items: {
type: 'array',
description: 'Array of issues/PRs',
items: {
type: 'object',
properties: {
number: { type: 'number', description: 'Issue/PR number' },
title: { type: 'string', description: 'Title' },
state: { type: 'string', description: 'State (open/closed)' },
html_url: { type: 'string', description: 'GitHub web URL' },
user: { type: 'object', description: 'Author info' },
labels: { type: 'array', description: 'Label names' },
created_at: { type: 'string', description: 'Creation date' },
updated_at: { type: 'string', description: 'Last update date' },
comments: { type: 'number', description: 'Comment count' },
is_pull_request: { type: 'boolean', description: 'Whether this is a PR' },
repository_url: { type: 'string', description: 'Repository API URL' },
},
},
},
},
},
},
}
export const searchIssuesV2Tool: ToolConfig<SearchIssuesParams, any> = {
id: 'github_search_issues_v2',
name: searchIssuesTool.name,
description: searchIssuesTool.description,
version: '2.0.0',
params: searchIssuesTool.params,
request: searchIssuesTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
total_count: data.total_count,
incomplete_results: data.incomplete_results,
items: data.items.map((item: any) => ({
...item,
body: item.body ?? null,
closed_at: item.closed_at ?? null,
milestone: item.milestone ?? null,
labels: item.labels ?? [],
assignees: item.assignees ?? [],
})),
},
}
},
outputs: {
total_count: { type: 'number', description: 'Total matching results' },
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
items: {
type: 'array',
description: 'Array of issue/PR objects from GitHub API',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Issue ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
number: { type: 'number', description: 'Issue number' },
title: { type: 'string', description: 'Title' },
state: { type: 'string', description: 'State (open or closed)' },
locked: { type: 'boolean', description: 'Whether issue is locked' },
html_url: { type: 'string', description: 'Web URL' },
url: { type: 'string', description: 'API URL' },
repository_url: { type: 'string', description: 'Repository API URL' },
comments_url: { type: 'string', description: 'Comments API URL' },
body: { type: 'string', description: 'Body text', optional: true },
comments: { type: 'number', description: 'Number of comments' },
score: { type: 'number', description: 'Search relevance score' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
closed_at: { type: 'string', description: 'Close timestamp', optional: true },
user: {
type: 'object',
description: 'Issue author',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
labels: {
type: 'array',
description: 'Issue labels',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Label ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
url: { type: 'string', description: 'API URL' },
name: { type: 'string', description: 'Label name' },
description: { type: 'string', description: 'Label description', optional: true },
color: { type: 'string', description: 'Hex color code' },
default: { type: 'boolean', description: 'Whether this is a default label' },
},
},
},
assignee: {
type: 'object',
description: 'Primary assignee',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
assignees: {
type: 'array',
description: 'All assignees',
items: {
type: 'object',
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
milestone: {
type: 'object',
description: 'Associated milestone',
optional: true,
properties: {
id: { type: 'number', description: 'Milestone ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
number: { type: 'number', description: 'Milestone number' },
title: { type: 'string', description: 'Milestone title' },
description: { type: 'string', description: 'Milestone description', optional: true },
state: { type: 'string', description: 'State (open or closed)' },
html_url: { type: 'string', description: 'Web URL' },
due_on: { type: 'string', description: 'Due date', optional: true },
},
},
pull_request: {
type: 'object',
description: 'Pull request details (if this is a PR)',
optional: true,
properties: {
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Web URL' },
diff_url: { type: 'string', description: 'Diff URL' },
patch_url: { type: 'string', description: 'Patch URL' },
},
},
},
},
},
},
}
+235
View File
@@ -0,0 +1,235 @@
import {
LICENSE_OUTPUT_PROPERTIES,
REPO_FULL_OUTPUT_PROPERTIES,
USER_FULL_OUTPUT_PROPERTIES,
} from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
interface SearchReposParams {
q: string
sort?: 'stars' | 'forks' | 'help-wanted-issues' | 'updated'
order?: 'asc' | 'desc'
per_page?: number
page?: number
apiKey: string
}
interface SearchReposResponse {
success: boolean
output: {
content: string
metadata: {
total_count: number
incomplete_results: boolean
items: Array<{
id: number
full_name: string
description: string | null
html_url: string
stargazers_count: number
forks_count: number
language: string | null
topics: string[]
created_at: string
updated_at: string
owner: { login: string }
}>
}
}
}
export const searchReposTool: ToolConfig<SearchReposParams, SearchReposResponse> = {
id: 'github_search_repos',
name: 'GitHub Search Repositories',
description:
'Search for repositories across GitHub. Use qualifiers like language:python, stars:>1000, topic:react, user:owner, org:name',
version: '1.0.0',
params: {
q: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Search query with optional qualifiers (language:, stars:, forks:, topic:, user:, org:, in:name,description,readme)',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort by: stars, forks, help-wanted-issues, updated (default: best match)',
},
order: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order: asc or 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/search/repositories')
url.searchParams.append('q', params.q)
if (params.sort) url.searchParams.append('sort', params.sort)
if (params.order) url.searchParams.append('order', params.order)
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
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 data = await response.json()
const items = data.items.map((item: any) => ({
id: item.id,
full_name: item.full_name,
description: item.description ?? null,
html_url: item.html_url,
stargazers_count: item.stargazers_count,
forks_count: item.forks_count,
language: item.language ?? null,
topics: item.topics ?? [],
created_at: item.created_at,
updated_at: item.updated_at,
owner: { login: item.owner?.login ?? 'unknown' },
}))
const content = `Found ${data.total_count} repository(s)${data.incomplete_results ? ' (incomplete)' : ''}:
${items
.map(
(item: any) =>
`${item.full_name}${item.stargazers_count} | 🍴 ${item.forks_count}
${item.description ?? 'No description'}
${item.html_url}
Language: ${item.language ?? 'N/A'} | Topics: ${item.topics.length > 0 ? item.topics.join(', ') : 'none'}`
)
.join('\n\n')}`
return {
success: true,
output: {
content,
metadata: {
total_count: data.total_count,
incomplete_results: data.incomplete_results,
items,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable search results' },
metadata: {
type: 'object',
description: 'Search results metadata',
properties: {
total_count: { type: 'number', description: 'Total matching results' },
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
items: {
type: 'array',
description: 'Array of repositories',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'Repository ID' },
full_name: { type: 'string', description: 'Full name (owner/repo)' },
description: { type: 'string', description: 'Description', optional: true },
html_url: { type: 'string', description: 'GitHub web URL' },
stargazers_count: { type: 'number', description: 'Star count' },
forks_count: { type: 'number', description: 'Fork count' },
language: { type: 'string', description: 'Primary language', optional: true },
topics: { type: 'array', description: 'Repository topics' },
created_at: { type: 'string', description: 'Creation date' },
updated_at: { type: 'string', description: 'Last update date' },
owner: { type: 'object', description: 'Owner info' },
},
},
},
},
},
},
}
export const searchReposV2Tool: ToolConfig<SearchReposParams, any> = {
id: 'github_search_repos_v2',
name: searchReposTool.name,
description: searchReposTool.description,
version: '2.0.0',
params: searchReposTool.params,
request: searchReposTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
total_count: data.total_count,
incomplete_results: data.incomplete_results,
items: data.items.map((item: any) => ({
...item,
description: item.description ?? null,
language: item.language ?? null,
topics: item.topics ?? [],
license: item.license ?? null,
})),
},
}
},
outputs: {
total_count: { type: 'number', description: 'Total matching results' },
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
items: {
type: 'array',
description: 'Array of repository objects from GitHub API',
items: {
type: 'object',
properties: {
...REPO_FULL_OUTPUT_PROPERTIES,
score: { type: 'number', description: 'Search relevance score' },
topics: { type: 'array', description: 'Repository topics' },
license: {
type: 'object',
description: 'License information',
optional: true,
properties: LICENSE_OUTPUT_PROPERTIES,
},
owner: {
type: 'object',
description: 'Repository owner',
properties: USER_FULL_OUTPUT_PROPERTIES,
},
},
},
},
},
}
+203
View File
@@ -0,0 +1,203 @@
import type { ToolConfig } from '@/tools/types'
interface SearchUsersParams {
q: string
sort?: 'followers' | 'repositories' | 'joined'
order?: 'asc' | 'desc'
per_page?: number
page?: number
apiKey: string
}
interface SearchUsersResponse {
success: boolean
output: {
content: string
metadata: {
total_count: number
incomplete_results: boolean
items: Array<{
id: number
login: string
html_url: string
avatar_url: string
type: string
score: number
}>
}
}
}
export const searchUsersTool: ToolConfig<SearchUsersParams, SearchUsersResponse> = {
id: 'github_search_users',
name: 'GitHub Search Users',
description:
'Search for users and organizations on GitHub. Use qualifiers like type:user, type:org, followers:>1000, repos:>10, location:city',
version: '1.0.0',
params: {
q: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Search query with optional qualifiers (type:user/org, followers:, repos:, location:, language:, created:)',
},
sort: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort by: followers, repositories, joined (default: best match)',
},
order: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order: asc or 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/search/users')
url.searchParams.append('q', params.q)
if (params.sort) url.searchParams.append('sort', params.sort)
if (params.order) url.searchParams.append('order', params.order)
if (params.per_page) url.searchParams.append('per_page', String(params.per_page))
if (params.page) url.searchParams.append('page', String(params.page))
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 data = await response.json()
const items = data.items.map((item: any) => ({
id: item.id,
login: item.login,
html_url: item.html_url,
avatar_url: item.avatar_url,
type: item.type,
score: item.score,
}))
const content = `Found ${data.total_count} user(s)/organization(s)${data.incomplete_results ? ' (incomplete)' : ''}:
${items.map((item: any) => `@${item.login} (${item.type}) - ${item.html_url}`).join('\n')}`
return {
success: true,
output: {
content,
metadata: {
total_count: data.total_count,
incomplete_results: data.incomplete_results,
items,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable search results' },
metadata: {
type: 'object',
description: 'Search results metadata',
properties: {
total_count: { type: 'number', description: 'Total matching results' },
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
items: {
type: 'array',
description: 'Array of users/orgs',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'User ID' },
login: { type: 'string', description: 'Username' },
html_url: { type: 'string', description: 'Profile URL' },
avatar_url: { type: 'string', description: 'Avatar URL' },
type: { type: 'string', description: 'User or Organization' },
score: { type: 'number', description: 'Search relevance score' },
},
},
},
},
},
},
}
export const searchUsersV2Tool: ToolConfig<SearchUsersParams, any> = {
id: 'github_search_users_v2',
name: searchUsersTool.name,
description: searchUsersTool.description,
version: '2.0.0',
params: searchUsersTool.params,
request: searchUsersTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
total_count: data.total_count,
incomplete_results: data.incomplete_results,
items: data.items,
},
}
},
outputs: {
total_count: { type: 'number', description: 'Total matching results' },
incomplete_results: { type: 'boolean', description: 'Whether results are incomplete' },
items: {
type: 'array',
description: 'Array of user objects from GitHub API',
items: {
type: 'object',
properties: {
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
login: { type: 'string', description: 'Username' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
gravatar_id: { type: 'string', description: 'Gravatar ID' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page 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 API URL' },
repos_url: { type: 'string', description: 'Repos API URL' },
organizations_url: { type: 'string', description: 'Organizations API URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
score: { type: 'number', description: 'Search relevance score' },
},
},
},
},
}
+104
View File
@@ -0,0 +1,104 @@
import type { ToolConfig } from '@/tools/types'
interface StarGistParams {
gist_id: string
apiKey: string
}
interface StarGistResponse {
success: boolean
output: {
content: string
metadata: {
starred: boolean
gist_id: string
}
}
}
export const starGistTool: ToolConfig<StarGistParams, StarGistResponse> = {
id: 'github_star_gist',
name: 'GitHub Star Gist',
description: 'Star a gist',
version: '1.0.0',
params: {
gist_id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The gist ID to star',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/gists/${params.gist_id?.trim()}/star`,
method: 'PUT',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'Content-Length': '0',
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response, params) => {
const starred = response.status === 204
return {
success: starred,
output: {
content: starred
? `Successfully starred gist ${params?.gist_id}`
: `Failed to star gist ${params?.gist_id}`,
metadata: {
starred,
gist_id: params?.gist_id ?? '',
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Star operation metadata',
properties: {
starred: { type: 'boolean', description: 'Whether starring succeeded' },
gist_id: { type: 'string', description: 'The gist ID' },
},
},
},
}
export const starGistV2Tool: ToolConfig<StarGistParams, any> = {
id: 'github_star_gist_v2',
name: starGistTool.name,
description: starGistTool.description,
version: '2.0.0',
params: starGistTool.params,
request: starGistTool.request,
transformResponse: async (response: Response, params) => {
const starred = response.status === 204
return {
success: starred,
output: {
starred,
gist_id: params?.gist_id ?? '',
},
}
},
outputs: {
starred: { type: 'boolean', description: 'Whether starring succeeded' },
gist_id: { type: 'string', description: 'The gist ID' },
},
}
+116
View File
@@ -0,0 +1,116 @@
import type { ToolConfig } from '@/tools/types'
interface StarRepoParams {
owner: string
repo: string
apiKey: string
}
interface StarRepoResponse {
success: boolean
output: {
content: string
metadata: {
starred: boolean
owner: string
repo: string
}
}
}
export const starRepoTool: ToolConfig<StarRepoParams, StarRepoResponse> = {
id: 'github_star_repo',
name: 'GitHub Star Repository',
description: 'Star a 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',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/user/starred/${params.owner}/${params.repo}`,
method: 'PUT',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'Content-Length': '0',
'X-GitHub-Api-Version': '2022-11-28',
}),
},
transformResponse: async (response, params) => {
const starred = response.status === 204
return {
success: starred,
output: {
content: starred
? `Successfully starred ${params?.owner}/${params?.repo}`
: `Failed to star ${params?.owner}/${params?.repo}`,
metadata: {
starred,
owner: params?.owner ?? '',
repo: params?.repo ?? '',
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Star operation metadata',
properties: {
starred: { type: 'boolean', description: 'Whether starring succeeded' },
owner: { type: 'string', description: 'Repository owner' },
repo: { type: 'string', description: 'Repository name' },
},
},
},
}
export const starRepoV2Tool: ToolConfig<StarRepoParams, any> = {
id: 'github_star_repo_v2',
name: starRepoTool.name,
description: starRepoTool.description,
version: '2.0.0',
params: starRepoTool.params,
request: starRepoTool.request,
transformResponse: async (response: Response, params) => {
const starred = response.status === 204
return {
success: starred,
output: {
starred,
owner: params?.owner ?? '',
repo: params?.repo ?? '',
},
}
},
outputs: {
starred: { type: 'boolean', description: 'Whether starring succeeded' },
owner: { type: 'string', description: 'Repository owner' },
repo: { type: 'string', description: 'Repository name' },
},
}
+110
View File
@@ -0,0 +1,110 @@
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)',
},
},
}
export const triggerWorkflowV2Tool: ToolConfig = {
id: 'github_trigger_workflow_v2',
name: triggerWorkflowTool.name,
description: triggerWorkflowTool.description,
version: '2.0.0',
params: triggerWorkflowTool.params,
request: triggerWorkflowTool.request,
oauth: triggerWorkflowTool.oauth,
transformResponse: async (response: Response, params) => {
return {
success: true,
output: {
triggered: response.status === 204,
workflow_id: params?.workflow_id ?? null,
ref: params?.ref ?? null,
},
}
},
outputs: {
triggered: { type: 'boolean', description: 'Whether workflow was triggered' },
workflow_id: { type: 'string', description: 'Workflow ID or filename', optional: true },
ref: { type: 'string', description: 'Git reference used', optional: true },
},
}
File diff suppressed because it is too large Load Diff
+103
View File
@@ -0,0 +1,103 @@
import type { ToolConfig } from '@/tools/types'
interface UnstarGistParams {
gist_id: string
apiKey: string
}
interface UnstarGistResponse {
success: boolean
output: {
content: string
metadata: {
unstarred: boolean
gist_id: string
}
}
}
export const unstarGistTool: ToolConfig<UnstarGistParams, UnstarGistResponse> = {
id: 'github_unstar_gist',
name: 'GitHub Unstar Gist',
description: 'Unstar a gist',
version: '1.0.0',
params: {
gist_id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The gist ID to unstar',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/gists/${params.gist_id?.trim()}/star`,
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) => {
const unstarred = response.status === 204
return {
success: unstarred,
output: {
content: unstarred
? `Successfully unstarred gist ${params?.gist_id}`
: `Failed to unstar gist ${params?.gist_id}`,
metadata: {
unstarred,
gist_id: params?.gist_id ?? '',
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Unstar operation metadata',
properties: {
unstarred: { type: 'boolean', description: 'Whether unstarring succeeded' },
gist_id: { type: 'string', description: 'The gist ID' },
},
},
},
}
export const unstarGistV2Tool: ToolConfig<UnstarGistParams, any> = {
id: 'github_unstar_gist_v2',
name: unstarGistTool.name,
description: unstarGistTool.description,
version: '2.0.0',
params: unstarGistTool.params,
request: unstarGistTool.request,
transformResponse: async (response: Response, params) => {
const unstarred = response.status === 204
return {
success: unstarred,
output: {
unstarred,
gist_id: params?.gist_id ?? '',
},
}
},
outputs: {
unstarred: { type: 'boolean', description: 'Whether unstarring succeeded' },
gist_id: { type: 'string', description: 'The gist ID' },
},
}
+115
View File
@@ -0,0 +1,115 @@
import type { ToolConfig } from '@/tools/types'
interface UnstarRepoParams {
owner: string
repo: string
apiKey: string
}
interface UnstarRepoResponse {
success: boolean
output: {
content: string
metadata: {
unstarred: boolean
owner: string
repo: string
}
}
}
export const unstarRepoTool: ToolConfig<UnstarRepoParams, UnstarRepoResponse> = {
id: 'github_unstar_repo',
name: 'GitHub Unstar Repository',
description: 'Remove star from a 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',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/user/starred/${params.owner}/${params.repo}`,
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) => {
const unstarred = response.status === 204
return {
success: unstarred,
output: {
content: unstarred
? `Successfully unstarred ${params?.owner}/${params?.repo}`
: `Failed to unstar ${params?.owner}/${params?.repo}`,
metadata: {
unstarred,
owner: params?.owner ?? '',
repo: params?.repo ?? '',
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Unstar operation metadata',
properties: {
unstarred: { type: 'boolean', description: 'Whether unstarring succeeded' },
owner: { type: 'string', description: 'Repository owner' },
repo: { type: 'string', description: 'Repository name' },
},
},
},
}
export const unstarRepoV2Tool: ToolConfig<UnstarRepoParams, any> = {
id: 'github_unstar_repo_v2',
name: unstarRepoTool.name,
description: unstarRepoTool.description,
version: '2.0.0',
params: unstarRepoTool.params,
request: unstarRepoTool.request,
transformResponse: async (response: Response, params) => {
const unstarred = response.status === 204
return {
success: unstarred,
output: {
unstarred,
owner: params?.owner ?? '',
repo: params?.repo ?? '',
},
}
},
outputs: {
unstarred: { type: 'boolean', description: 'Whether unstarring succeeded' },
owner: { type: 'string', description: 'Repository owner' },
repo: { type: 'string', description: 'Repository name' },
},
}
@@ -0,0 +1,249 @@
import type { BranchProtectionResponse, UpdateBranchProtectionParams } from '@/tools/github/types'
import { BRANCH_PROTECTION_OUTPUT_PROPERTIES } 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' },
},
},
},
},
},
},
}
export const updateBranchProtectionV2Tool: ToolConfig = {
id: 'github_update_branch_protection_v2',
name: updateBranchProtectionTool.name,
description: updateBranchProtectionTool.description,
version: '2.0.0',
params: updateBranchProtectionTool.params,
request: updateBranchProtectionTool.request,
oauth: updateBranchProtectionTool.oauth,
transformResponse: async (response: Response) => {
const protection = await response.json()
return {
success: true,
output: {
url: protection.url,
required_status_checks: protection.required_status_checks ?? null,
enforce_admins: protection.enforce_admins,
required_pull_request_reviews: protection.required_pull_request_reviews ?? null,
restrictions: protection.restrictions ?? null,
required_linear_history: protection.required_linear_history ?? null,
allow_force_pushes: protection.allow_force_pushes ?? null,
allow_deletions: protection.allow_deletions ?? null,
},
}
},
outputs: BRANCH_PROTECTION_OUTPUT_PROPERTIES,
}
+133
View File
@@ -0,0 +1,133 @@
import { truncate } from '@sim/utils/string'
import type { IssueCommentResponse, UpdateCommentParams } from '@/tools/github/types'
import { COMMENT_OUTPUT_PROPERTIES, USER_OUTPUT } 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: "${truncate(data.body, 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' },
},
},
},
},
},
}
export const updateCommentV2Tool: ToolConfig = {
id: 'github_update_comment_v2',
name: updateCommentTool.name,
description: updateCommentTool.description,
version: '2.0.0',
params: updateCommentTool.params,
request: updateCommentTool.request,
oauth: updateCommentTool.oauth,
transformResponse: async (response: Response) => {
const comment = await response.json()
return {
success: true,
output: {
id: comment.id,
body: comment.body,
html_url: comment.html_url,
user: comment.user,
created_at: comment.created_at,
updated_at: comment.updated_at,
},
}
},
outputs: {
...COMMENT_OUTPUT_PROPERTIES,
user: USER_OUTPUT,
},
}
+203
View File
@@ -0,0 +1,203 @@
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' },
},
},
},
},
},
}
export const updateFileV2Tool: ToolConfig = {
id: 'github_update_file_v2',
name: updateFileTool.name,
description: updateFileTool.description,
version: '2.0.0',
params: updateFileTool.params,
request: updateFileTool.request,
oauth: updateFileTool.oauth,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
content: data.content,
commit: data.commit,
},
}
},
outputs: {
content: { type: 'json', description: 'Updated file content info' },
commit: { type: 'json', description: 'Commit information' },
},
}
+193
View File
@@ -0,0 +1,193 @@
import type { ToolConfig } from '@/tools/types'
interface UpdateGistParams {
gist_id: string
description?: string
files?: string
apiKey: string
}
interface UpdateGistResponse {
success: boolean
output: {
content: string
metadata: {
id: string
html_url: string
description: string | null
public: boolean
updated_at: string
files: Record<
string,
{ filename: string; type: string; language: string | null; size: number }
>
}
}
}
export const updateGistTool: ToolConfig<UpdateGistParams, UpdateGistResponse> = {
id: 'github_update_gist',
name: 'GitHub Update Gist',
description:
'Update a gist description or files. To delete a file, set its value to null in files object',
version: '1.0.0',
params: {
gist_id: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The gist ID to update',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New description for the gist',
},
files: {
type: 'json',
required: false,
visibility: 'user-or-llm',
description:
'JSON object with filenames as keys. Set to null to delete, or provide content to update/add',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) => `https://api.github.com/gists/${params.gist_id?.trim()}`,
method: 'PATCH',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const body: Record<string, any> = {}
if (params.description !== undefined) body.description = params.description
if (params.files) {
body.files = typeof params.files === 'string' ? JSON.parse(params.files) : params.files
}
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
const files: Record<
string,
{ filename: string; type: string; language: string | null; size: number }
> = {}
for (const [key, value] of Object.entries(data.files ?? {})) {
const file = value as any
files[key] = {
filename: file.filename,
type: file.type,
language: file.language ?? null,
size: file.size,
}
}
const content = `Updated gist: ${data.html_url}
Description: ${data.description ?? 'No description'}
Files: ${Object.keys(files).join(', ')}`
return {
success: true,
output: {
content,
metadata: {
id: data.id,
html_url: data.html_url,
description: data.description ?? null,
public: data.public,
updated_at: data.updated_at,
files,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Updated gist metadata',
properties: {
id: { type: 'string', description: 'Gist ID' },
html_url: { type: 'string', description: 'Web URL' },
description: { type: 'string', description: 'Description', optional: true },
public: { type: 'boolean', description: 'Is public' },
updated_at: { type: 'string', description: 'Update date' },
files: { type: 'object', description: 'Current files' },
},
},
},
}
export const updateGistV2Tool: ToolConfig<UpdateGistParams, any> = {
id: 'github_update_gist_v2',
name: updateGistTool.name,
description: updateGistTool.description,
version: '2.0.0',
params: updateGistTool.params,
request: updateGistTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
...data,
description: data.description ?? null,
files: data.files ?? {},
},
}
},
outputs: {
id: { type: 'string', description: 'Gist ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Web URL' },
forks_url: { type: 'string', description: 'Forks API URL' },
commits_url: { type: 'string', description: 'Commits API URL' },
git_pull_url: { type: 'string', description: 'Git pull URL' },
git_push_url: { type: 'string', description: 'Git push URL' },
description: { type: 'string', description: 'Gist description', optional: true },
public: { type: 'boolean', description: 'Whether gist is public' },
truncated: { type: 'boolean', description: 'Whether files are truncated' },
comments: { type: 'number', description: 'Number of comments' },
comments_url: { type: 'string', description: 'Comments API URL' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
files: {
type: 'object',
description:
'Files in the gist (object with filenames as keys, each containing filename, type, language, raw_url, size, truncated, content)',
},
owner: {
type: 'object',
description: 'Gist owner',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
}
+191
View File
@@ -0,0 +1,191 @@
import type { IssueResponse, UpdateIssueParams } from '@/tools/github/types'
import {
ISSUE_OUTPUT_PROPERTIES,
LABEL_OUTPUT,
MILESTONE_OUTPUT,
USER_OUTPUT,
} 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' },
},
},
},
}
export const updateIssueV2Tool: ToolConfig = {
id: 'github_update_issue_v2',
name: updateIssueTool.name,
description: updateIssueTool.description,
version: '2.0.0',
params: updateIssueTool.params,
request: updateIssueTool.request,
oauth: updateIssueTool.oauth,
transformResponse: async (response: Response) => {
const issue = await response.json()
return {
success: true,
output: {
id: issue.id,
number: issue.number,
title: issue.title,
state: issue.state,
html_url: issue.html_url,
body: issue.body ?? null,
user: issue.user,
labels: issue.labels ?? [],
assignees: issue.assignees ?? [],
milestone: issue.milestone ?? null,
created_at: issue.created_at,
updated_at: issue.updated_at,
closed_at: issue.closed_at ?? null,
},
}
},
outputs: {
...ISSUE_OUTPUT_PROPERTIES,
user: USER_OUTPUT,
labels: {
type: 'array',
description: 'Array of label objects',
items: LABEL_OUTPUT,
},
assignees: {
type: 'array',
description: 'Array of assignee objects',
items: USER_OUTPUT,
},
milestone: { ...MILESTONE_OUTPUT, optional: true },
},
}
+208
View File
@@ -0,0 +1,208 @@
import type { ToolConfig } from '@/tools/types'
interface UpdateMilestoneParams {
owner: string
repo: string
milestone_number: number
title?: string
state?: 'open' | 'closed'
description?: string
due_on?: string
apiKey: string
}
interface UpdateMilestoneResponse {
success: boolean
output: {
content: string
metadata: {
number: number
title: string
description: string | null
state: string
html_url: string
due_on: string | null
open_issues: number
closed_issues: number
updated_at: string
}
}
}
export const updateMilestoneTool: ToolConfig<UpdateMilestoneParams, UpdateMilestoneResponse> = {
id: 'github_update_milestone',
name: 'GitHub Update Milestone',
description: 'Update a milestone in a 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',
},
milestone_number: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description: 'Milestone number to update',
},
title: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New milestone title',
},
state: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New state: open or closed',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New description',
},
due_on: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'New due date (ISO 8601 format)',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'GitHub API token',
},
},
request: {
url: (params) =>
`https://api.github.com/repos/${params.owner}/${params.repo}/milestones/${params.milestone_number}`,
method: 'PATCH',
headers: (params) => ({
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${params.apiKey}`,
'Content-Type': 'application/json',
'X-GitHub-Api-Version': '2022-11-28',
}),
body: (params) => {
const body: Record<string, any> = {}
if (params.title !== undefined) body.title = params.title
if (params.state !== undefined) body.state = params.state
if (params.description !== undefined) body.description = params.description
if (params.due_on !== undefined) body.due_on = params.due_on
return body
},
},
transformResponse: async (response) => {
const data = await response.json()
const content = `Updated milestone: ${data.title} (#${data.number})
State: ${data.state}
Due: ${data.due_on ?? 'No due date'}
${data.html_url}`
return {
success: true,
output: {
content,
metadata: {
number: data.number,
title: data.title,
description: data.description ?? null,
state: data.state,
html_url: data.html_url,
due_on: data.due_on ?? null,
open_issues: data.open_issues,
closed_issues: data.closed_issues,
updated_at: data.updated_at,
},
},
}
},
outputs: {
content: { type: 'string', description: 'Human-readable result' },
metadata: {
type: 'object',
description: 'Updated milestone metadata',
properties: {
number: { type: 'number', description: 'Milestone number' },
title: { type: 'string', description: 'Title' },
description: { type: 'string', description: 'Description', optional: true },
state: { type: 'string', description: 'State' },
html_url: { type: 'string', description: 'Web URL' },
due_on: { type: 'string', description: 'Due date', optional: true },
open_issues: { type: 'number', description: 'Open issues' },
closed_issues: { type: 'number', description: 'Closed issues' },
updated_at: { type: 'string', description: 'Update date' },
},
},
},
}
export const updateMilestoneV2Tool: ToolConfig<UpdateMilestoneParams, any> = {
id: 'github_update_milestone_v2',
name: updateMilestoneTool.name,
description: updateMilestoneTool.description,
version: '2.0.0',
params: updateMilestoneTool.params,
request: updateMilestoneTool.request,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
...data,
description: data.description ?? null,
due_on: data.due_on ?? null,
},
}
},
outputs: {
id: { type: 'number', description: 'Milestone ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
number: { type: 'number', description: 'Milestone number' },
title: { type: 'string', description: 'Milestone title' },
description: { type: 'string', description: 'Milestone description', optional: true },
state: { type: 'string', description: 'State (open or closed)' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'GitHub web URL' },
labels_url: { type: 'string', description: 'Labels API URL' },
due_on: { type: 'string', description: 'Due date (ISO 8601)', optional: true },
open_issues: { type: 'number', description: 'Number of open issues' },
closed_issues: { type: 'number', description: 'Number of closed issues' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
closed_at: { type: 'string', description: 'Close timestamp', optional: true },
creator: {
type: 'object',
description: 'Milestone creator',
optional: true,
properties: {
login: { type: 'string', description: 'Username' },
id: { type: 'number', description: 'User ID' },
node_id: { type: 'string', description: 'GraphQL node ID' },
avatar_url: { type: 'string', description: 'Avatar image URL' },
url: { type: 'string', description: 'API URL' },
html_url: { type: 'string', description: 'Profile page URL' },
type: { type: 'string', description: 'User or Organization' },
site_admin: { type: 'boolean', description: 'GitHub staff indicator' },
},
},
},
}
+167
View File
@@ -0,0 +1,167 @@
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' },
},
},
},
}
export const updatePRV2Tool: ToolConfig = {
id: 'github_update_pr_v2',
name: updatePRTool.name,
description: updatePRTool.description,
version: '2.0.0',
params: updatePRTool.params,
request: updatePRTool.request,
oauth: updatePRTool.oauth,
transformResponse: async (response: Response) => {
const pr = await response.json()
return {
success: true,
output: {
id: pr.id,
number: pr.number,
title: pr.title,
state: pr.state,
html_url: pr.html_url,
body: pr.body ?? null,
user: pr.user,
head: pr.head,
base: pr.base,
draft: pr.draft,
merged: pr.merged,
created_at: pr.created_at,
updated_at: pr.updated_at,
},
}
},
outputs: {
id: { type: 'number', description: 'PR ID' },
number: { type: 'number', description: 'PR number' },
title: { type: 'string', description: 'PR title' },
state: { type: 'string', description: 'PR state' },
html_url: { type: 'string', description: 'GitHub web URL' },
body: { type: 'string', description: 'PR description', optional: true },
user: { type: 'json', description: 'User who created the PR' },
head: { type: 'json', description: 'Head branch info' },
base: { type: 'json', description: 'Base branch info' },
draft: { type: 'boolean', description: 'Whether PR is a draft' },
merged: { type: 'boolean', description: 'Whether PR is merged' },
created_at: { type: 'string', description: 'Creation timestamp' },
updated_at: { type: 'string', description: 'Last update timestamp' },
},
}
+234
View File
@@ -0,0 +1,234 @@
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,
},
},
},
},
}
export const updateProjectV2Tool: ToolConfig = {
id: 'github_update_project_v2',
name: updateProjectTool.name,
description: updateProjectTool.description,
version: '2.0.0',
params: updateProjectTool.params,
request: updateProjectTool.request,
oauth: updateProjectTool.oauth,
transformResponse: async (response: Response) => {
const data = await response.json()
if (data.errors) {
return {
success: false,
output: { id: '', title: '', number: 0, url: '' },
error: data.errors[0].message,
}
}
const project = data.data?.updateProjectV2?.projectV2 || {}
return {
success: true,
output: {
id: project.id,
title: project.title,
number: project.number,
url: project.url,
closed: project.closed,
public: project.public,
shortDescription: project.shortDescription ?? null,
},
}
},
outputs: {
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: 'Short description', optional: true },
},
}
+213
View File
@@ -0,0 +1,213 @@
import type { ReleaseResponse, UpdateReleaseParams } from '@/tools/github/types'
import {
RELEASE_ASSET_OUTPUT_PROPERTIES,
RELEASE_OUTPUT_PROPERTIES,
USER_OUTPUT,
} 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' },
},
},
},
}
export const updateReleaseV2Tool: ToolConfig = {
id: 'github_update_release_v2',
name: updateReleaseTool.name,
description: updateReleaseTool.description,
version: '2.0.0',
params: updateReleaseTool.params,
request: updateReleaseTool.request,
oauth: updateReleaseTool.oauth,
transformResponse: async (response: Response) => {
const data = await response.json()
return {
success: true,
output: {
id: data.id,
tag_name: data.tag_name,
name: data.name,
body: data.body ?? null,
html_url: data.html_url,
tarball_url: data.tarball_url,
zipball_url: data.zipball_url,
draft: data.draft,
prerelease: data.prerelease,
author: data.author,
assets: data.assets,
target_commitish: data.target_commitish,
created_at: data.created_at,
published_at: data.published_at ?? null,
},
}
},
outputs: {
...RELEASE_OUTPUT_PROPERTIES,
author: USER_OUTPUT,
assets: {
type: 'array',
description: 'Release assets',
items: {
type: 'object',
properties: {
...RELEASE_ASSET_OUTPUT_PROPERTIES,
uploader: USER_OUTPUT,
},
},
},
},
}