diff --git a/apps/docs/content/docs/en/tools/github.mdx b/apps/docs/content/docs/en/tools/github.mdx index f63b381472..c54bf29f95 100644 --- a/apps/docs/content/docs/en/tools/github.mdx +++ b/apps/docs/content/docs/en/tools/github.mdx @@ -127,6 +127,1055 @@ Retrieve the latest commit from a GitHub repository | `content` | string | Human-readable commit summary | | `metadata` | object | Commit metadata | +### `github_issue_comment` + +Create a comment on a GitHub issue + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `issue_number` | number | Yes | Issue number | +| `body` | string | Yes | Comment content | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable comment confirmation | +| `metadata` | object | Comment metadata | + +### `github_list_issue_comments` + +List all comments on a GitHub issue + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `issue_number` | number | Yes | Issue number | +| `since` | string | No | Only show comments updated after this ISO 8601 timestamp | +| `per_page` | number | No | Number of results per page \(max 100\) | +| `page` | number | No | Page number | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable comments summary | +| `metadata` | object | Comments list metadata | + +### `github_update_comment` + +Update an existing comment on a GitHub issue or pull request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `comment_id` | number | Yes | Comment ID | +| `body` | string | Yes | Updated comment content | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable update confirmation | +| `metadata` | object | Updated comment metadata | + +### `github_delete_comment` + +Delete a comment on a GitHub issue or pull request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `comment_id` | number | Yes | Comment ID | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable deletion confirmation | +| `metadata` | object | Deletion result metadata | + +### `github_list_pr_comments` + +List all review comments on a GitHub pull request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `pullNumber` | number | Yes | Pull request number | +| `sort` | string | No | Sort by created or updated | +| `direction` | string | No | Sort direction \(asc or desc\) | +| `since` | string | No | Only show comments updated after this ISO 8601 timestamp | +| `per_page` | number | No | Number of results per page \(max 100\) | +| `page` | number | No | Page number | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable review comments summary | +| `metadata` | object | Review comments list metadata | + +### `github_create_pr` + +Create a new pull request in a GitHub repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `title` | string | Yes | Pull request title | +| `head` | string | Yes | The name of the branch where your changes are implemented | +| `base` | string | Yes | The name of the branch you want the changes pulled into | +| `body` | string | No | Pull request description \(Markdown\) | +| `draft` | boolean | No | Create as draft pull request | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable PR creation confirmation | +| `metadata` | object | Pull request metadata | + +### `github_update_pr` + +Update an existing pull request in a GitHub repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `pullNumber` | number | Yes | Pull request number | +| `title` | string | No | New pull request title | +| `body` | string | No | New pull request description \(Markdown\) | +| `state` | string | No | New state \(open or closed\) | +| `base` | string | No | New base branch name | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable PR update confirmation | +| `metadata` | object | Updated pull request metadata | + +### `github_merge_pr` + +Merge a pull request in a GitHub repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `pullNumber` | number | Yes | Pull request number | +| `commit_title` | string | No | Title for the merge commit | +| `commit_message` | string | No | Extra detail to append to merge commit message | +| `merge_method` | string | No | Merge method: merge, squash, or rebase | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable merge confirmation | +| `metadata` | object | Merge result metadata | + +### `github_list_prs` + +List pull requests in a GitHub repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `state` | string | No | Filter by state: open, closed, or all | +| `head` | string | No | Filter by head user or branch name \(format: user:ref-name or organization:ref-name\) | +| `base` | string | No | Filter by base branch name | +| `sort` | string | No | Sort by: created, updated, popularity, or long-running | +| `direction` | string | No | Sort direction: asc or desc | +| `per_page` | number | No | Results per page \(max 100\) | +| `page` | number | No | Page number | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable list of pull requests | +| `metadata` | object | Pull requests list metadata | + +### `github_get_pr_files` + +Get the list of files changed in a pull request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `pullNumber` | number | Yes | Pull request number | +| `per_page` | number | No | Results per page \(max 100\) | +| `page` | number | No | Page number | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable list of files changed in PR | +| `metadata` | object | PR files metadata | + +### `github_close_pr` + +Close a pull request in a GitHub repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `pullNumber` | number | Yes | Pull request number | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable PR close confirmation | +| `metadata` | object | Closed pull request metadata | + +### `github_request_reviewers` + +Request reviewers for a pull request + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `pullNumber` | number | Yes | Pull request number | +| `reviewers` | string | Yes | Comma-separated list of user logins to request reviews from | +| `team_reviewers` | string | No | Comma-separated list of team slugs to request reviews from | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable reviewer request confirmation | +| `metadata` | object | Requested reviewers metadata | + +### `github_get_file_content` + +Get the content of a file from a GitHub repository. Supports files up to 1MB. Content is returned decoded and human-readable. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `path` | string | Yes | Path to the file in the repository \(e.g., "src/index.ts"\) | +| `ref` | string | No | Branch name, tag, or commit SHA \(defaults to repository default branch\) | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable file information with content preview | +| `metadata` | object | File metadata including name, path, SHA, size, and URLs | + +### `github_create_file` + +Create a new file in a GitHub repository. The file content will be automatically Base64 encoded. Supports files up to 1MB. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `path` | string | Yes | Path where the file will be created \(e.g., "src/newfile.ts"\) | +| `message` | string | Yes | Commit message for this file creation | +| `content` | string | Yes | File content \(plain text, will be Base64 encoded automatically\) | +| `branch` | string | No | Branch to create the file in \(defaults to repository default branch\) | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable file creation confirmation | +| `metadata` | object | File and commit metadata | + +### `github_update_file` + +Update an existing file in a GitHub repository. Requires the file SHA. Content will be automatically Base64 encoded. Supports files up to 1MB. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `path` | string | Yes | Path to the file to update \(e.g., "src/index.ts"\) | +| `message` | string | Yes | Commit message for this file update | +| `content` | string | Yes | New file content \(plain text, will be Base64 encoded automatically\) | +| `sha` | string | Yes | The blob SHA of the file being replaced \(get from github_get_file_content\) | +| `branch` | string | No | Branch to update the file in \(defaults to repository default branch\) | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable file update confirmation | +| `metadata` | object | Updated file and commit metadata | + +### `github_delete_file` + +Delete a file from a GitHub repository. Requires the file SHA. This operation cannot be undone through the API. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `path` | string | Yes | Path to the file to delete \(e.g., "src/oldfile.ts"\) | +| `message` | string | Yes | Commit message for this file deletion | +| `sha` | string | Yes | The blob SHA of the file being deleted \(get from github_get_file_content\) | +| `branch` | string | No | Branch to delete the file from \(defaults to repository default branch\) | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable file deletion confirmation | +| `metadata` | object | Deletion confirmation and commit metadata | + +### `github_get_tree` + +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. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `path` | string | No | Directory path \(e.g., "src/components"\). Leave empty for root directory. | +| `ref` | string | No | Branch name, tag, or commit SHA \(defaults to repository default branch\) | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable directory tree listing | +| `metadata` | object | Directory contents metadata | + +### `github_list_branches` + +List all branches in a GitHub repository. Optionally filter by protected status and control pagination. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `protected` | boolean | No | Filter branches by protection status | +| `per_page` | number | No | Number of results per page \(max 100, default 30\) | +| `page` | number | No | Page number for pagination \(default 1\) | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable list of branches | +| `metadata` | object | Branch list metadata | + +### `github_get_branch` + +Get detailed information about a specific branch in a GitHub repository, including commit details and protection status. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `branch` | string | Yes | Branch name | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable branch details | +| `metadata` | object | Branch metadata | + +### `github_create_branch` + +Create a new branch in a GitHub repository by creating a git reference pointing to a specific commit SHA. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `branch` | string | Yes | Name of the branch to create | +| `sha` | string | Yes | Commit SHA to point the branch to | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable branch creation confirmation | +| `metadata` | object | Git reference metadata | + +### `github_delete_branch` + +Delete a branch from a GitHub repository by removing its git reference. Protected branches cannot be deleted. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `branch` | string | Yes | Name of the branch to delete | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable deletion confirmation | +| `metadata` | object | Deletion metadata | + +### `github_get_branch_protection` + +Get the branch protection rules for a specific branch, including status checks, review requirements, and restrictions. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `branch` | string | Yes | Branch name | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable branch protection summary | +| `metadata` | object | Branch protection configuration | + +### `github_update_branch_protection` + +Update branch protection rules for a specific branch, including status checks, review requirements, admin enforcement, and push restrictions. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `branch` | string | Yes | Branch name | +| `required_status_checks` | object | Yes | Required status check configuration \(null to disable\). Object with strict \(boolean\) and contexts \(string array\) | +| `enforce_admins` | boolean | Yes | Whether to enforce restrictions for administrators | +| `required_pull_request_reviews` | object | Yes | PR review requirements \(null to disable\). Object with optional required_approving_review_count, dismiss_stale_reviews, require_code_owner_reviews | +| `restrictions` | object | Yes | Push restrictions \(null to disable\). Object with users \(string array\) and teams \(string array\) | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable branch protection update summary | +| `metadata` | object | Updated branch protection configuration | + +### `github_create_issue` + +Create a new issue in a GitHub repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `title` | string | Yes | Issue title | +| `body` | string | No | Issue description/body | +| `assignees` | string | No | Comma-separated list of usernames to assign to this issue | +| `labels` | string | No | Comma-separated list of label names to add to this issue | +| `milestone` | number | No | Milestone number to associate with this issue | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable issue creation confirmation | +| `metadata` | object | Issue metadata | + +### `github_update_issue` + +Update an existing issue in a GitHub repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `issue_number` | number | Yes | Issue number | +| `title` | string | No | New issue title | +| `body` | string | No | New issue description/body | +| `state` | string | No | Issue state \(open or closed\) | +| `labels` | array | No | Array of label names \(replaces all existing labels\) | +| `assignees` | array | No | Array of usernames \(replaces all existing assignees\) | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable issue update confirmation | +| `metadata` | object | Updated issue metadata | + +### `github_list_issues` + +List issues in a GitHub repository. Note: This includes pull requests as PRs are considered issues in GitHub + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `state` | string | No | Filter by state: open, closed, or all \(default: open\) | +| `assignee` | string | No | Filter by assignee username | +| `creator` | string | No | Filter by creator username | +| `labels` | string | No | Comma-separated list of label names to filter by | +| `sort` | string | No | Sort by: created, updated, or comments \(default: created\) | +| `direction` | string | No | Sort direction: asc or desc \(default: desc\) | +| `per_page` | number | No | Results per page \(max 100, default: 30\) | +| `page` | number | No | Page number \(default: 1\) | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable list of issues | +| `metadata` | object | Issues list metadata | + +### `github_get_issue` + +Get detailed information about a specific issue in a GitHub repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `issue_number` | number | Yes | Issue number | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable issue details | +| `metadata` | object | Detailed issue metadata | + +### `github_close_issue` + +Close an issue in a GitHub repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `issue_number` | number | Yes | Issue number | +| `state_reason` | string | No | Reason for closing: completed or not_planned | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable issue close confirmation | +| `metadata` | object | Closed issue metadata | + +### `github_add_labels` + +Add labels to an issue in a GitHub repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `issue_number` | number | Yes | Issue number | +| `labels` | string | Yes | Comma-separated list of label names to add to the issue | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable labels confirmation | +| `metadata` | object | Labels metadata | + +### `github_remove_label` + +Remove a label from an issue in a GitHub repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `issue_number` | number | Yes | Issue number | +| `name` | string | Yes | Label name to remove | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable label removal confirmation | +| `metadata` | object | Remaining labels metadata | + +### `github_add_assignees` + +Add assignees to an issue in a GitHub repository + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner | +| `repo` | string | Yes | Repository name | +| `issue_number` | number | Yes | Issue number | +| `assignees` | string | Yes | Comma-separated list of usernames to assign to the issue | +| `apiKey` | string | Yes | GitHub API token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable assignees confirmation | +| `metadata` | object | Updated issue metadata with assignees | + +### `github_create_release` + +Create a new release for a GitHub repository. Specify tag name, target commit, title, description, and whether it should be a draft or prerelease. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `tag_name` | string | Yes | The name of the tag for this release | +| `target_commitish` | string | No | 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` | string | No | The name of the release | +| `body` | string | No | Text describing the contents of the release \(markdown supported\) | +| `draft` | boolean | No | true to create a draft \(unpublished\) release, false to create a published one | +| `prerelease` | boolean | No | true to identify the release as a prerelease, false to identify as a full release | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable release creation summary | +| `metadata` | object | Release metadata including download URLs | + +### `github_update_release` + +Update an existing GitHub release. Modify tag name, target commit, title, description, draft status, or prerelease status. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `release_id` | number | Yes | The unique identifier of the release | +| `tag_name` | string | No | The name of the tag | +| `target_commitish` | string | No | Specifies the commitish value for where the tag is created from | +| `name` | string | No | The name of the release | +| `body` | string | No | Text describing the contents of the release \(markdown supported\) | +| `draft` | boolean | No | true to set as draft, false to publish | +| `prerelease` | boolean | No | true to identify as a prerelease, false for a full release | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable release update summary | +| `metadata` | object | Updated release metadata including download URLs | + +### `github_list_releases` + +List all releases for a GitHub repository. Returns release information including tags, names, and download URLs. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `per_page` | number | No | Number of results per page \(max 100\) | +| `page` | number | No | Page number of the results to fetch | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable list of releases with summary | +| `metadata` | object | Releases metadata | + +### `github_get_release` + +Get detailed information about a specific GitHub release by ID. Returns release metadata including assets and download URLs. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `release_id` | number | Yes | The unique identifier of the release | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable release details | +| `metadata` | object | Release metadata including download URLs | + +### `github_delete_release` + +Delete a GitHub release by ID. This permanently removes the release but does not delete the associated Git tag. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `release_id` | number | Yes | The unique identifier of the release to delete | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable deletion confirmation | +| `metadata` | object | Deletion result metadata | + +### `github_list_workflows` + +List all workflows in a GitHub repository. Returns workflow details including ID, name, path, state, and badge URL. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `per_page` | number | No | Number of results per page \(default: 30, max: 100\) | +| `page` | number | No | Page number of results to fetch \(default: 1\) | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable workflows summary | +| `metadata` | object | Workflows metadata | + +### `github_get_workflow` + +Get details of a specific GitHub Actions workflow by ID or filename. Returns workflow information including name, path, state, and badge URL. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `workflow_id` | string | Yes | Workflow ID \(number\) or workflow filename \(e.g., "main.yaml"\) | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable workflow details | +| `metadata` | object | Workflow metadata | + +### `github_trigger_workflow` + +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. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `workflow_id` | string | Yes | Workflow ID \(number\) or workflow filename \(e.g., "main.yaml"\) | +| `ref` | string | Yes | Git reference \(branch or tag name\) to run the workflow on | +| `inputs` | object | No | Input keys and values configured in the workflow file | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Confirmation message | +| `metadata` | object | Empty metadata object \(204 No Content response\) | + +### `github_list_workflow_runs` + +List workflow runs for a repository. Supports filtering by actor, branch, event, and status. Returns run details including status, conclusion, and links. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `actor` | string | No | Filter by user who triggered the workflow | +| `branch` | string | No | Filter by branch name | +| `event` | string | No | Filter by event type \(e.g., push, pull_request, workflow_dispatch\) | +| `status` | string | No | Filter by status \(queued, in_progress, completed, waiting, requested, pending\) | +| `per_page` | number | No | Number of results per page \(default: 30, max: 100\) | +| `page` | number | No | Page number of results to fetch \(default: 1\) | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable workflow runs summary | +| `metadata` | object | Workflow runs metadata | + +### `github_get_workflow_run` + +Get detailed information about a specific workflow run by ID. Returns status, conclusion, timing, and links to the run. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `run_id` | number | Yes | Workflow run ID | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable workflow run details | +| `metadata` | object | Workflow run metadata | + +### `github_cancel_workflow_run` + +Cancel a workflow run. Returns 202 Accepted if cancellation is initiated, or 409 Conflict if the run cannot be cancelled (already completed). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `run_id` | number | Yes | Workflow run ID to cancel | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Cancellation status message | +| `metadata` | object | Cancellation metadata | + +### `github_rerun_workflow` + +Rerun a workflow run. Optionally enable debug logging for the rerun. Returns 201 Created on success. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner` | string | Yes | Repository owner \(user or organization\) | +| `repo` | string | Yes | Repository name | +| `run_id` | number | Yes | Workflow run ID to rerun | +| `enable_debug_logging` | boolean | No | Enable debug logging for the rerun \(default: false\) | +| `apiKey` | string | Yes | GitHub Personal Access Token | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Rerun confirmation message | +| `metadata` | object | Rerun metadata | + +### `github_list_projects` + +List GitHub Projects V2 for an organization or user. Returns up to 20 projects with their details including ID, title, number, URL, and status. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner_type` | string | Yes | Owner type: "org" for organization or "user" for user | +| `owner_login` | string | Yes | Organization or user login name | +| `apiKey` | string | Yes | GitHub Personal Access Token with project read permissions | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable list of projects | +| `metadata` | object | Projects metadata | + +### `github_get_project` + +Get detailed information about a specific GitHub Project V2 by its number. Returns project details including ID, title, description, URL, and status. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner_type` | string | Yes | Owner type: "org" for organization or "user" for user | +| `owner_login` | string | Yes | Organization or user login name | +| `project_number` | number | Yes | Project number | +| `apiKey` | string | Yes | GitHub Personal Access Token with project read permissions | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable project details | +| `metadata` | object | Project metadata | + +### `github_create_project` + +Create a new GitHub Project V2. Requires the owner Node ID (not login name). Returns the created project with ID, title, and URL. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `owner_id` | string | Yes | Owner Node ID \(format: PVT_... or MDQ6...\). Use GitHub GraphQL API to get this ID from organization or user login. | +| `title` | string | Yes | Project title | +| `apiKey` | string | Yes | GitHub Personal Access Token with project write permissions | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable confirmation message | +| `metadata` | object | Created project metadata | + +### `github_update_project` + +Update an existing GitHub Project V2. Can update title, description, visibility (public), or status (closed). Requires the project Node ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `project_id` | string | Yes | Project Node ID \(format: PVT_...\) | +| `title` | string | No | New project title | +| `shortDescription` | string | No | New project short description | +| `project_public` | boolean | No | Set project visibility \(true = public, false = private\) | +| `closed` | boolean | No | Set project status \(true = closed, false = open\) | +| `apiKey` | string | Yes | GitHub Personal Access Token with project write permissions | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable confirmation message | +| `metadata` | object | Updated project metadata | + +### `github_delete_project` + +Delete a GitHub Project V2. This action is permanent and cannot be undone. Requires the project Node ID. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `project_id` | string | Yes | Project Node ID \(format: PVT_...\) | +| `apiKey` | string | Yes | GitHub Personal Access Token with project admin permissions | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `content` | string | Human-readable confirmation message | +| `metadata` | object | Deleted project metadata | + ## Notes diff --git a/apps/sim/background/webhook-execution.ts b/apps/sim/background/webhook-execution.ts index 5c2080f4f0..39cdde6cae 100644 --- a/apps/sim/background/webhook-execution.ts +++ b/apps/sim/background/webhook-execution.ts @@ -133,16 +133,6 @@ async function executeWebhookJobInternal( const loggingSession = new LoggingSession(payload.workflowId, executionId, 'webhook', requestId) try { - await loggingSession.safeStart({ - userId: payload.userId, - workspaceId: '', // Will be resolved below - variables: {}, - triggerData: { - isTest: payload.testMode === true, - executionTarget: payload.executionTarget || 'deployed', - }, - }) - const workflowData = payload.executionTarget === 'live' ? await loadWorkflowFromNormalizedTables(payload.workflowId) @@ -478,6 +468,17 @@ async function executeWebhookJobInternal( }) try { + // Ensure logging session is started (safe to call multiple times) + await loggingSession.safeStart({ + userId: payload.userId, + workspaceId: '', // May not be available for early errors + variables: {}, + triggerData: { + isTest: payload.testMode === true, + executionTarget: payload.executionTarget || 'deployed', + }, + }) + const executionResult = (error?.executionResult as ExecutionResult | undefined) || { success: false, output: {}, diff --git a/apps/sim/blocks/blocks/github.ts b/apps/sim/blocks/blocks/github.ts index 80f41f2e33..c0c44b4f38 100644 --- a/apps/sim/blocks/blocks/github.ts +++ b/apps/sim/blocks/blocks/github.ts @@ -21,12 +21,67 @@ export const GitHubBlock: BlockConfig = { id: 'operation', title: 'Operation', type: 'dropdown', - layout: 'full', options: [ { label: 'Get PR details', id: 'github_pr' }, { label: 'Create PR comment', id: 'github_comment' }, { label: 'Get repository info', id: 'github_repo_info' }, { label: 'Get latest commit', id: 'github_latest_commit' }, + // Comment Operations + { label: 'Create issue comment', id: 'github_issue_comment' }, + { label: 'List issue comments', id: 'github_list_issue_comments' }, + { label: 'Update comment', id: 'github_update_comment' }, + { label: 'Delete comment', id: 'github_delete_comment' }, + { label: 'List PR comments', id: 'github_list_pr_comments' }, + // Pull Request Operations + { label: 'Create pull request', id: 'github_create_pr' }, + { label: 'Update pull request', id: 'github_update_pr' }, + { label: 'Merge pull request', id: 'github_merge_pr' }, + { label: 'List pull requests', id: 'github_list_prs' }, + { label: 'Get PR files', id: 'github_get_pr_files' }, + { label: 'Close pull request', id: 'github_close_pr' }, + { label: 'Request PR reviewers', id: 'github_request_reviewers' }, + // File Operations + { label: 'Get file content', id: 'github_get_file_content' }, + { label: 'Create file', id: 'github_create_file' }, + { label: 'Update file', id: 'github_update_file' }, + { label: 'Delete file', id: 'github_delete_file' }, + { label: 'Get directory tree', id: 'github_get_tree' }, + // Branch Operations + { label: 'List branches', id: 'github_list_branches' }, + { label: 'Get branch', id: 'github_get_branch' }, + { label: 'Create branch', id: 'github_create_branch' }, + { label: 'Delete branch', id: 'github_delete_branch' }, + { label: 'Get branch protection', id: 'github_get_branch_protection' }, + { label: 'Update branch protection', id: 'github_update_branch_protection' }, + // Issue Operations + { label: 'Create issue', id: 'github_create_issue' }, + { label: 'Update issue', id: 'github_update_issue' }, + { label: 'List issues', id: 'github_list_issues' }, + { label: 'Get issue', id: 'github_get_issue' }, + { label: 'Close issue', id: 'github_close_issue' }, + { label: 'Add issue labels', id: 'github_add_labels' }, + { label: 'Remove issue label', id: 'github_remove_label' }, + { label: 'Add issue assignees', id: 'github_add_assignees' }, + // Release Operations + { label: 'Create release', id: 'github_create_release' }, + { label: 'Update release', id: 'github_update_release' }, + { label: 'List releases', id: 'github_list_releases' }, + { label: 'Get release', id: 'github_get_release' }, + { label: 'Delete release', id: 'github_delete_release' }, + // Workflow Operations + { label: 'List workflows', id: 'github_list_workflows' }, + { label: 'Get workflow', id: 'github_get_workflow' }, + { label: 'Trigger workflow', id: 'github_trigger_workflow' }, + { label: 'List workflow runs', id: 'github_list_workflow_runs' }, + { label: 'Get workflow run', id: 'github_get_workflow_run' }, + { label: 'Cancel workflow run', id: 'github_cancel_workflow_run' }, + { label: 'Rerun workflow', id: 'github_rerun_workflow' }, + // Project Operations + { label: 'List projects', id: 'github_list_projects' }, + { label: 'Get project', id: 'github_get_project' }, + { label: 'Create project', id: 'github_create_project' }, + { label: 'Update project', id: 'github_update_project' }, + { label: 'Delete project', id: 'github_delete_project' }, ], value: () => 'github_pr', }, @@ -34,7 +89,6 @@ export const GitHubBlock: BlockConfig = { id: 'owner', title: 'Repository Owner', type: 'short-input', - layout: 'half', placeholder: 'e.g., microsoft', required: true, }, @@ -42,7 +96,6 @@ export const GitHubBlock: BlockConfig = { id: 'repo', title: 'Repository Name', type: 'short-input', - layout: 'half', placeholder: 'e.g., vscode', required: true, }, @@ -50,7 +103,6 @@ export const GitHubBlock: BlockConfig = { id: 'pullNumber', title: 'Pull Request Number', type: 'short-input', - layout: 'half', placeholder: 'e.g., 123', condition: { field: 'operation', value: 'github_pr' }, required: true, @@ -59,7 +111,6 @@ export const GitHubBlock: BlockConfig = { id: 'body', title: 'Comment', type: 'long-input', - layout: 'full', placeholder: 'Enter comment text', condition: { field: 'operation', value: 'github_comment' }, required: true, @@ -68,7 +119,6 @@ export const GitHubBlock: BlockConfig = { id: 'pullNumber', title: 'Pull Request Number', type: 'short-input', - layout: 'half', placeholder: 'e.g., 123', condition: { field: 'operation', value: 'github_comment' }, required: true, @@ -77,25 +127,898 @@ export const GitHubBlock: BlockConfig = { id: 'branch', title: 'Branch Name', type: 'short-input', - layout: 'half', placeholder: 'e.g., main (leave empty for default)', condition: { field: 'operation', value: 'github_latest_commit' }, }, + // Comment operations parameters + { + id: 'issue_number', + title: 'Issue Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_issue_comment' }, + }, + { + id: 'body', + title: 'Comment Text', + type: 'long-input', + placeholder: 'Enter comment text', + required: true, + condition: { field: 'operation', value: 'github_issue_comment' }, + }, + { + id: 'issue_number', + title: 'Issue Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_list_issue_comments' }, + }, + { + id: 'per_page', + title: 'Results Per Page', + type: 'short-input', + placeholder: 'e.g., 30 (default: 30, max: 100)', + condition: { field: 'operation', value: 'github_list_issue_comments' }, + }, + { + id: 'comment_id', + title: 'Comment ID', + type: 'short-input', + placeholder: 'e.g., 987654321', + required: true, + condition: { field: 'operation', value: 'github_update_comment' }, + }, + { + id: 'body', + title: 'Updated Comment Text', + type: 'long-input', + placeholder: 'Enter updated comment text', + required: true, + condition: { field: 'operation', value: 'github_update_comment' }, + }, + { + id: 'comment_id', + title: 'Comment ID', + type: 'short-input', + placeholder: 'e.g., 987654321', + required: true, + condition: { field: 'operation', value: 'github_delete_comment' }, + }, + { + id: 'pullNumber', + title: 'Pull Request Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_list_pr_comments' }, + }, + { + id: 'per_page', + title: 'Results Per Page', + type: 'short-input', + placeholder: 'e.g., 30 (default: 30, max: 100)', + condition: { field: 'operation', value: 'github_list_pr_comments' }, + }, + // Pull request operations parameters + { + id: 'title', + title: 'PR Title', + type: 'short-input', + placeholder: 'Enter pull request title', + required: true, + condition: { field: 'operation', value: 'github_create_pr' }, + }, + { + id: 'head', + title: 'Head Branch', + type: 'short-input', + placeholder: 'e.g., feature-branch', + required: true, + condition: { field: 'operation', value: 'github_create_pr' }, + }, + { + id: 'base', + title: 'Base Branch', + type: 'short-input', + placeholder: 'e.g., main', + required: true, + condition: { field: 'operation', value: 'github_create_pr' }, + }, + { + id: 'body', + title: 'PR Description', + type: 'long-input', + placeholder: 'Enter pull request description (optional)', + condition: { field: 'operation', value: 'github_create_pr' }, + }, + { + id: 'draft', + title: 'Create as Draft', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + condition: { field: 'operation', value: 'github_create_pr' }, + }, + { + id: 'pullNumber', + title: 'Pull Request Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_update_pr' }, + }, + { + id: 'title', + title: 'New Title', + type: 'short-input', + placeholder: 'Enter new title (optional)', + condition: { field: 'operation', value: 'github_update_pr' }, + }, + { + id: 'body', + title: 'New Description', + type: 'long-input', + placeholder: 'Enter new description (optional)', + condition: { field: 'operation', value: 'github_update_pr' }, + }, + { + id: 'state', + title: 'State', + type: 'dropdown', + options: [ + { label: 'Open', id: 'open' }, + { label: 'Closed', id: 'closed' }, + ], + condition: { field: 'operation', value: 'github_update_pr' }, + }, + { + id: 'pullNumber', + title: 'Pull Request Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_merge_pr' }, + }, + { + id: 'merge_method', + title: 'Merge Method', + type: 'dropdown', + options: [ + { label: 'Merge', id: 'merge' }, + { label: 'Squash', id: 'squash' }, + { label: 'Rebase', id: 'rebase' }, + ], + condition: { field: 'operation', value: 'github_merge_pr' }, + }, + { + id: 'commit_title', + title: 'Commit Title', + type: 'short-input', + placeholder: 'Enter commit title (optional)', + condition: { field: 'operation', value: 'github_merge_pr' }, + }, + { + id: 'state', + title: 'State Filter', + type: 'dropdown', + options: [ + { label: 'Open', id: 'open' }, + { label: 'Closed', id: 'closed' }, + { label: 'All', id: 'all' }, + ], + condition: { field: 'operation', value: 'github_list_prs' }, + }, + { + id: 'per_page', + title: 'Results Per Page', + type: 'short-input', + placeholder: 'e.g., 30 (default: 30, max: 100)', + condition: { field: 'operation', value: 'github_list_prs' }, + }, + { + id: 'pullNumber', + title: 'Pull Request Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_get_pr_files' }, + }, + { + id: 'pullNumber', + title: 'Pull Request Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_close_pr' }, + }, + { + id: 'pullNumber', + title: 'Pull Request Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_request_reviewers' }, + }, + { + id: 'reviewers', + title: 'Reviewer Usernames', + type: 'short-input', + placeholder: 'Comma-separated: user1,user2', + condition: { field: 'operation', value: 'github_request_reviewers' }, + }, + { + id: 'team_reviewers', + title: 'Team Slugs', + type: 'short-input', + placeholder: 'Comma-separated: team1,team2', + condition: { field: 'operation', value: 'github_request_reviewers' }, + }, + // File operations parameters + { + id: 'path', + title: 'File Path', + type: 'short-input', + placeholder: 'e.g., src/main.ts', + required: true, + condition: { field: 'operation', value: 'github_get_file_content' }, + }, + { + id: 'ref', + title: 'Branch/Tag/Commit', + type: 'short-input', + placeholder: 'e.g., main (optional)', + condition: { field: 'operation', value: 'github_get_file_content' }, + }, + { + id: 'path', + title: 'File Path', + type: 'short-input', + placeholder: 'e.g., src/main.ts', + required: true, + condition: { field: 'operation', value: 'github_create_file' }, + }, + { + id: 'content', + title: 'File Content', + type: 'long-input', + placeholder: 'Enter file content', + required: true, + condition: { field: 'operation', value: 'github_create_file' }, + }, + { + id: 'message', + title: 'Commit Message', + type: 'short-input', + placeholder: 'Enter commit message', + required: true, + condition: { field: 'operation', value: 'github_create_file' }, + }, + { + id: 'branch', + title: 'Branch Name', + type: 'short-input', + placeholder: 'e.g., main (optional)', + condition: { field: 'operation', value: 'github_create_file' }, + }, + { + id: 'path', + title: 'File Path', + type: 'short-input', + placeholder: 'e.g., src/main.ts', + required: true, + condition: { field: 'operation', value: 'github_update_file' }, + }, + { + id: 'content', + title: 'New File Content', + type: 'long-input', + placeholder: 'Enter updated file content', + required: true, + condition: { field: 'operation', value: 'github_update_file' }, + }, + { + id: 'message', + title: 'Commit Message', + type: 'short-input', + placeholder: 'Enter commit message', + required: true, + condition: { field: 'operation', value: 'github_update_file' }, + }, + { + id: 'sha', + title: 'File SHA', + type: 'short-input', + placeholder: 'File SHA from get operation', + required: true, + condition: { field: 'operation', value: 'github_update_file' }, + }, + { + id: 'branch', + title: 'Branch Name', + type: 'short-input', + placeholder: 'e.g., main (optional)', + condition: { field: 'operation', value: 'github_update_file' }, + }, + { + id: 'path', + title: 'File Path', + type: 'short-input', + placeholder: 'e.g., src/main.ts', + required: true, + condition: { field: 'operation', value: 'github_delete_file' }, + }, + { + id: 'message', + title: 'Commit Message', + type: 'short-input', + placeholder: 'Enter commit message', + required: true, + condition: { field: 'operation', value: 'github_delete_file' }, + }, + { + id: 'sha', + title: 'File SHA', + type: 'short-input', + placeholder: 'File SHA from get operation', + required: true, + condition: { field: 'operation', value: 'github_delete_file' }, + }, + { + id: 'branch', + title: 'Branch Name', + type: 'short-input', + placeholder: 'e.g., main (optional)', + condition: { field: 'operation', value: 'github_delete_file' }, + }, + { + id: 'path', + title: 'Directory Path', + type: 'short-input', + placeholder: 'e.g., src (leave empty for root)', + condition: { field: 'operation', value: 'github_get_tree' }, + }, + { + id: 'ref', + title: 'Branch/Tag/Commit', + type: 'short-input', + placeholder: 'e.g., main (optional)', + condition: { field: 'operation', value: 'github_get_tree' }, + }, + // Branch operations parameters + { + id: 'protected', + title: 'Filter by Protection', + type: 'dropdown', + options: [ + { label: 'All', id: 'all' }, + { label: 'Protected', id: 'true' }, + { label: 'Unprotected', id: 'false' }, + ], + condition: { field: 'operation', value: 'github_list_branches' }, + }, + { + id: 'branch', + title: 'Branch Name', + type: 'short-input', + placeholder: 'e.g., main', + required: true, + condition: { field: 'operation', value: 'github_get_branch' }, + }, + { + id: 'branch', + title: 'New Branch Name', + type: 'short-input', + placeholder: 'e.g., feature-branch', + required: true, + condition: { field: 'operation', value: 'github_create_branch' }, + }, + { + id: 'sha', + title: 'Source Commit SHA', + type: 'short-input', + placeholder: 'SHA to create branch from', + required: true, + condition: { field: 'operation', value: 'github_create_branch' }, + }, + { + id: 'branch', + title: 'Branch Name', + type: 'short-input', + placeholder: 'e.g., feature-branch', + required: true, + condition: { field: 'operation', value: 'github_delete_branch' }, + }, + { + id: 'branch', + title: 'Branch Name', + type: 'short-input', + placeholder: 'e.g., main', + required: true, + condition: { field: 'operation', value: 'github_get_branch_protection' }, + }, + { + id: 'branch', + title: 'Branch Name', + type: 'short-input', + placeholder: 'e.g., main', + required: true, + condition: { field: 'operation', value: 'github_update_branch_protection' }, + }, + { + id: 'required_status_checks', + title: 'Required Status Checks', + type: 'short-input', + placeholder: 'JSON: {"strict":true,"contexts":["ci/test"]}', + condition: { field: 'operation', value: 'github_update_branch_protection' }, + }, + { + id: 'enforce_admins', + title: 'Enforce for Admins', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + condition: { field: 'operation', value: 'github_update_branch_protection' }, + }, + { + id: 'required_pull_request_reviews', + title: 'Required PR Reviews', + type: 'short-input', + placeholder: 'JSON: {"required_approving_review_count":1}', + condition: { field: 'operation', value: 'github_update_branch_protection' }, + }, + // Issue operations parameters + { + id: 'title', + title: 'Issue Title', + type: 'short-input', + placeholder: 'Enter issue title', + required: true, + condition: { field: 'operation', value: 'github_create_issue' }, + }, + { + id: 'body', + title: 'Issue Description', + type: 'long-input', + placeholder: 'Enter issue description (optional)', + condition: { field: 'operation', value: 'github_create_issue' }, + }, + { + id: 'labels', + title: 'Labels', + type: 'short-input', + placeholder: 'Comma-separated: bug,enhancement', + condition: { field: 'operation', value: 'github_create_issue' }, + }, + { + id: 'assignees', + title: 'Assignees', + type: 'short-input', + placeholder: 'Comma-separated: user1,user2', + condition: { field: 'operation', value: 'github_create_issue' }, + }, + { + id: 'issue_number', + title: 'Issue Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_update_issue' }, + }, + { + id: 'title', + title: 'New Title', + type: 'short-input', + placeholder: 'Enter new title (optional)', + condition: { field: 'operation', value: 'github_update_issue' }, + }, + { + id: 'body', + title: 'New Description', + type: 'long-input', + placeholder: 'Enter new description (optional)', + condition: { field: 'operation', value: 'github_update_issue' }, + }, + { + id: 'state', + title: 'State', + type: 'dropdown', + options: [ + { label: 'Open', id: 'open' }, + { label: 'Closed', id: 'closed' }, + ], + condition: { field: 'operation', value: 'github_update_issue' }, + }, + { + id: 'state', + title: 'State Filter', + type: 'dropdown', + options: [ + { label: 'Open', id: 'open' }, + { label: 'Closed', id: 'closed' }, + { label: 'All', id: 'all' }, + ], + condition: { field: 'operation', value: 'github_list_issues' }, + }, + { + id: 'per_page', + title: 'Results Per Page', + type: 'short-input', + placeholder: 'e.g., 30 (default: 30, max: 100)', + condition: { field: 'operation', value: 'github_list_issues' }, + }, + { + id: 'issue_number', + title: 'Issue Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_get_issue' }, + }, + { + id: 'issue_number', + title: 'Issue Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_close_issue' }, + }, + { + id: 'issue_number', + title: 'Issue Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_add_labels' }, + }, + { + id: 'labels', + title: 'Labels', + type: 'short-input', + placeholder: 'Comma-separated: bug,enhancement', + required: true, + condition: { field: 'operation', value: 'github_add_labels' }, + }, + { + id: 'issue_number', + title: 'Issue Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_remove_label' }, + }, + { + id: 'name', + title: 'Label Name', + type: 'short-input', + placeholder: 'e.g., bug', + required: true, + condition: { field: 'operation', value: 'github_remove_label' }, + }, + { + id: 'issue_number', + title: 'Issue Number', + type: 'short-input', + placeholder: 'e.g., 123', + required: true, + condition: { field: 'operation', value: 'github_add_assignees' }, + }, + { + id: 'assignees', + title: 'Assignees', + type: 'short-input', + placeholder: 'Comma-separated: user1,user2', + required: true, + condition: { field: 'operation', value: 'github_add_assignees' }, + }, + // Release operations parameters + { + id: 'tag_name', + title: 'Tag Name', + type: 'short-input', + placeholder: 'e.g., v1.0.0', + required: true, + condition: { field: 'operation', value: 'github_create_release' }, + }, + { + id: 'name', + title: 'Release Name', + type: 'short-input', + placeholder: 'e.g., Version 1.0.0', + condition: { field: 'operation', value: 'github_create_release' }, + }, + { + id: 'body', + title: 'Release Notes', + type: 'long-input', + placeholder: 'Enter release notes (optional)', + condition: { field: 'operation', value: 'github_create_release' }, + }, + { + id: 'draft', + title: 'Draft', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + condition: { field: 'operation', value: 'github_create_release' }, + }, + { + id: 'prerelease', + title: 'Prerelease', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + condition: { field: 'operation', value: 'github_create_release' }, + }, + { + id: 'release_id', + title: 'Release ID', + type: 'short-input', + placeholder: 'e.g., 123456', + required: true, + condition: { field: 'operation', value: 'github_update_release' }, + }, + { + id: 'tag_name', + title: 'New Tag Name', + type: 'short-input', + placeholder: 'e.g., v1.0.1 (optional)', + condition: { field: 'operation', value: 'github_update_release' }, + }, + { + id: 'name', + title: 'New Release Name', + type: 'short-input', + placeholder: 'Enter new name (optional)', + condition: { field: 'operation', value: 'github_update_release' }, + }, + { + id: 'body', + title: 'New Release Notes', + type: 'long-input', + placeholder: 'Enter updated notes (optional)', + condition: { field: 'operation', value: 'github_update_release' }, + }, + { + id: 'per_page', + title: 'Results Per Page', + type: 'short-input', + placeholder: 'e.g., 30 (default: 30, max: 100)', + condition: { field: 'operation', value: 'github_list_releases' }, + }, + { + id: 'release_id', + title: 'Release ID', + type: 'short-input', + placeholder: 'e.g., 123456', + required: true, + condition: { field: 'operation', value: 'github_get_release' }, + }, + { + id: 'release_id', + title: 'Release ID', + type: 'short-input', + placeholder: 'e.g., 123456', + required: true, + condition: { field: 'operation', value: 'github_delete_release' }, + }, + // Workflow operations parameters + { + id: 'per_page', + title: 'Results Per Page', + type: 'short-input', + placeholder: 'e.g., 30 (default: 30, max: 100)', + condition: { field: 'operation', value: 'github_list_workflows' }, + }, + { + id: 'workflow_id', + title: 'Workflow ID or Filename', + type: 'short-input', + placeholder: 'e.g., 123456 or ci.yml', + required: true, + condition: { field: 'operation', value: 'github_get_workflow' }, + }, + { + id: 'workflow_id', + title: 'Workflow ID or Filename', + type: 'short-input', + placeholder: 'e.g., 123456 or ci.yml', + required: true, + condition: { field: 'operation', value: 'github_trigger_workflow' }, + }, + { + id: 'ref', + title: 'Branch/Tag to Run On', + type: 'short-input', + placeholder: 'e.g., main', + required: true, + condition: { field: 'operation', value: 'github_trigger_workflow' }, + }, + { + id: 'inputs', + title: 'Workflow Inputs', + type: 'long-input', + placeholder: 'JSON: {"key":"value"}', + condition: { field: 'operation', value: 'github_trigger_workflow' }, + }, + { + id: 'workflow_id', + title: 'Workflow ID or Filename', + type: 'short-input', + placeholder: 'e.g., 123456 or ci.yml (optional)', + condition: { field: 'operation', value: 'github_list_workflow_runs' }, + }, + { + id: 'status', + title: 'Status Filter', + type: 'dropdown', + options: [ + { label: 'All', id: 'all' }, + { label: 'Queued', id: 'queued' }, + { label: 'In Progress', id: 'in_progress' }, + { label: 'Completed', id: 'completed' }, + ], + condition: { field: 'operation', value: 'github_list_workflow_runs' }, + }, + { + id: 'per_page', + title: 'Results Per Page', + type: 'short-input', + placeholder: 'e.g., 30 (default: 30, max: 100)', + condition: { field: 'operation', value: 'github_list_workflow_runs' }, + }, + { + id: 'run_id', + title: 'Workflow Run ID', + type: 'short-input', + placeholder: 'e.g., 123456789', + required: true, + condition: { field: 'operation', value: 'github_get_workflow_run' }, + }, + { + id: 'run_id', + title: 'Workflow Run ID', + type: 'short-input', + placeholder: 'e.g., 123456789', + required: true, + condition: { field: 'operation', value: 'github_cancel_workflow_run' }, + }, + { + id: 'run_id', + title: 'Workflow Run ID', + type: 'short-input', + placeholder: 'e.g., 123456789', + required: true, + condition: { field: 'operation', value: 'github_rerun_workflow' }, + }, + // Project operations parameters + { + id: 'owner_login', + title: 'Owner Login', + type: 'short-input', + placeholder: 'e.g., octocat or org-name', + required: true, + condition: { field: 'operation', value: 'github_list_projects' }, + }, + { + id: 'owner_type', + title: 'Owner Type', + type: 'dropdown', + options: [ + { label: 'User', id: 'user' }, + { label: 'Organization', id: 'org' }, + ], + required: true, + condition: { field: 'operation', value: 'github_list_projects' }, + }, + { + id: 'project_number', + title: 'Project Number', + type: 'short-input', + placeholder: 'e.g., 1', + required: true, + condition: { field: 'operation', value: 'github_get_project' }, + }, + { + id: 'owner_login', + title: 'Owner Login', + type: 'short-input', + placeholder: 'e.g., octocat or org-name', + required: true, + condition: { field: 'operation', value: 'github_get_project' }, + }, + { + id: 'owner_type', + title: 'Owner Type', + type: 'dropdown', + options: [ + { label: 'User', id: 'user' }, + { label: 'Organization', id: 'org' }, + ], + required: true, + condition: { field: 'operation', value: 'github_get_project' }, + }, + { + id: 'owner_id', + title: 'Owner ID', + type: 'short-input', + placeholder: 'User or org node ID', + required: true, + condition: { field: 'operation', value: 'github_create_project' }, + }, + { + id: 'title', + title: 'Project Title', + type: 'short-input', + placeholder: 'Enter project title', + required: true, + condition: { field: 'operation', value: 'github_create_project' }, + }, + { + id: 'project_id', + title: 'Project ID', + type: 'short-input', + placeholder: 'Project node ID', + required: true, + condition: { field: 'operation', value: 'github_update_project' }, + }, + { + id: 'title', + title: 'New Title', + type: 'short-input', + placeholder: 'Enter new title (optional)', + condition: { field: 'operation', value: 'github_update_project' }, + }, + { + id: 'project_public', + title: 'Public', + type: 'dropdown', + options: [ + { label: 'Private', id: 'false' }, + { label: 'Public', id: 'true' }, + ], + condition: { field: 'operation', value: 'github_update_project' }, + }, + { + id: 'project_id', + title: 'Project ID', + type: 'short-input', + placeholder: 'Project node ID', + required: true, + condition: { field: 'operation', value: 'github_delete_project' }, + }, { id: 'apiKey', title: 'GitHub Token', type: 'short-input', - layout: 'full', placeholder: 'Enter GitHub Token', password: true, required: true, }, - ...getTrigger('github_webhook').subBlocks, + ...getTrigger('github_issue_opened').subBlocks, + ...getTrigger('github_issue_closed').subBlocks, + ...getTrigger('github_issue_comment').subBlocks, + ...getTrigger('github_pr_opened').subBlocks, + ...getTrigger('github_pr_closed').subBlocks, + ...getTrigger('github_pr_merged').subBlocks, + ...getTrigger('github_pr_comment').subBlocks, + ...getTrigger('github_pr_reviewed').subBlocks, + ...getTrigger('github_push').subBlocks, + ...getTrigger('github_release_published').subBlocks, + ...getTrigger('github_workflow_run').subBlocks, { id: 'commentType', title: 'Comment Type', type: 'dropdown', - layout: 'half', options: [ { label: 'General PR Comment', id: 'pr_comment' }, { label: 'File-specific Comment', id: 'file_comment' }, @@ -106,7 +1029,6 @@ export const GitHubBlock: BlockConfig = { id: 'path', title: 'File Path', type: 'short-input', - layout: 'half', placeholder: 'e.g., src/main.ts', condition: { field: 'operation', @@ -121,7 +1043,6 @@ export const GitHubBlock: BlockConfig = { id: 'line', title: 'Line Number', type: 'short-input', - layout: 'half', placeholder: 'e.g., 42', condition: { field: 'operation', @@ -134,7 +1055,68 @@ export const GitHubBlock: BlockConfig = { }, ], tools: { - access: ['github_pr', 'github_comment', 'github_repo_info', 'github_latest_commit'], + access: [ + 'github_pr', + 'github_comment', + 'github_repo_info', + 'github_latest_commit', + // Comment tools + 'github_issue_comment', + 'github_list_issue_comments', + 'github_update_comment', + 'github_delete_comment', + 'github_list_pr_comments', + // Pull request tools + 'github_create_pr', + 'github_update_pr', + 'github_merge_pr', + 'github_list_prs', + 'github_get_pr_files', + 'github_close_pr', + 'github_request_reviewers', + // File tools + 'github_get_file_content', + 'github_create_file', + 'github_update_file', + 'github_delete_file', + 'github_get_tree', + // Branch tools + 'github_list_branches', + 'github_get_branch', + 'github_create_branch', + 'github_delete_branch', + 'github_get_branch_protection', + 'github_update_branch_protection', + // Issue tools + 'github_create_issue', + 'github_update_issue', + 'github_list_issues', + 'github_get_issue', + 'github_close_issue', + 'github_add_labels', + 'github_remove_label', + 'github_add_assignees', + // Release tools + 'github_create_release', + 'github_update_release', + 'github_list_releases', + 'github_get_release', + 'github_delete_release', + // Workflow tools + 'github_list_workflows', + 'github_get_workflow', + 'github_trigger_workflow', + 'github_list_workflow_runs', + 'github_get_workflow_run', + 'github_cancel_workflow_run', + 'github_rerun_workflow', + // Project tools + 'github_list_projects', + 'github_get_project', + 'github_create_project', + 'github_update_project', + 'github_delete_project', + ], config: { tool: (params) => { switch (params.operation) { @@ -146,6 +1128,110 @@ export const GitHubBlock: BlockConfig = { return 'github_repo_info' case 'github_latest_commit': return 'github_latest_commit' + // Comment operations + case 'github_issue_comment': + return 'github_issue_comment' + case 'github_list_issue_comments': + return 'github_list_issue_comments' + case 'github_update_comment': + return 'github_update_comment' + case 'github_delete_comment': + return 'github_delete_comment' + case 'github_list_pr_comments': + return 'github_list_pr_comments' + // Pull request operations + case 'github_create_pr': + return 'github_create_pr' + case 'github_update_pr': + return 'github_update_pr' + case 'github_merge_pr': + return 'github_merge_pr' + case 'github_list_prs': + return 'github_list_prs' + case 'github_get_pr_files': + return 'github_get_pr_files' + case 'github_close_pr': + return 'github_close_pr' + case 'github_request_reviewers': + return 'github_request_reviewers' + // File operations + case 'github_get_file_content': + return 'github_get_file_content' + case 'github_create_file': + return 'github_create_file' + case 'github_update_file': + return 'github_update_file' + case 'github_delete_file': + return 'github_delete_file' + case 'github_get_tree': + return 'github_get_tree' + // Branch operations + case 'github_list_branches': + return 'github_list_branches' + case 'github_get_branch': + return 'github_get_branch' + case 'github_create_branch': + return 'github_create_branch' + case 'github_delete_branch': + return 'github_delete_branch' + case 'github_get_branch_protection': + return 'github_get_branch_protection' + case 'github_update_branch_protection': + return 'github_update_branch_protection' + // Issue operations + case 'github_create_issue': + return 'github_create_issue' + case 'github_update_issue': + return 'github_update_issue' + case 'github_list_issues': + return 'github_list_issues' + case 'github_get_issue': + return 'github_get_issue' + case 'github_close_issue': + return 'github_close_issue' + case 'github_add_labels': + return 'github_add_labels' + case 'github_remove_label': + return 'github_remove_label' + case 'github_add_assignees': + return 'github_add_assignees' + // Release operations + case 'github_create_release': + return 'github_create_release' + case 'github_update_release': + return 'github_update_release' + case 'github_list_releases': + return 'github_list_releases' + case 'github_get_release': + return 'github_get_release' + case 'github_delete_release': + return 'github_delete_release' + // Workflow operations + case 'github_list_workflows': + return 'github_list_workflows' + case 'github_get_workflow': + return 'github_get_workflow' + case 'github_trigger_workflow': + return 'github_trigger_workflow' + case 'github_list_workflow_runs': + return 'github_list_workflow_runs' + case 'github_get_workflow_run': + return 'github_get_workflow_run' + case 'github_cancel_workflow_run': + return 'github_cancel_workflow_run' + case 'github_rerun_workflow': + return 'github_rerun_workflow' + // Project operations + case 'github_list_projects': + return 'github_list_projects' + case 'github_get_project': + return 'github_get_project' + case 'github_create_project': + return 'github_create_project' + case 'github_update_project': + return 'github_update_project' + case 'github_delete_project': + return 'github_delete_project' default: return 'github_repo_info' } @@ -157,7 +1243,7 @@ export const GitHubBlock: BlockConfig = { owner: { type: 'string', description: 'Repository owner' }, repo: { type: 'string', description: 'Repository name' }, pullNumber: { type: 'number', description: 'Pull request number' }, - body: { type: 'string', description: 'Comment text' }, + body: { type: 'string', description: 'Comment text or description' }, apiKey: { type: 'string', description: 'GitHub access token' }, commentType: { type: 'string', description: 'Comment type' }, path: { type: 'string', description: 'File path' }, @@ -165,6 +1251,50 @@ export const GitHubBlock: BlockConfig = { side: { type: 'string', description: 'Comment side' }, commitId: { type: 'string', description: 'Commit identifier' }, branch: { type: 'string', description: 'Branch name' }, + // Comment parameters + issue_number: { type: 'number', description: 'Issue number' }, + comment_id: { type: 'number', description: 'Comment ID' }, + per_page: { type: 'number', description: 'Results per page' }, + // Pull request parameters + title: { type: 'string', description: 'Title' }, + head: { type: 'string', description: 'Head branch' }, + base: { type: 'string', description: 'Base branch' }, + draft: { type: 'boolean', description: 'Draft status' }, + state: { type: 'string', description: 'State filter or value' }, + merge_method: { type: 'string', description: 'Merge method' }, + commit_title: { type: 'string', description: 'Commit title' }, + reviewers: { type: 'string', description: 'Reviewer usernames' }, + team_reviewers: { type: 'string', description: 'Team reviewer slugs' }, + // File parameters + content: { type: 'string', description: 'File content' }, + message: { type: 'string', description: 'Commit message' }, + sha: { type: 'string', description: 'File or commit SHA' }, + ref: { type: 'string', description: 'Branch, tag, or commit reference' }, + // Branch parameters + protected: { type: 'string', description: 'Protection status filter' }, + required_status_checks: { type: 'string', description: 'Required status checks JSON' }, + enforce_admins: { type: 'boolean', description: 'Enforce for admins' }, + required_pull_request_reviews: { type: 'string', description: 'Required PR reviews JSON' }, + // Issue parameters + labels: { type: 'string', description: 'Comma-separated labels' }, + assignees: { type: 'string', description: 'Comma-separated assignees' }, + name: { type: 'string', description: 'Label or release name' }, + // Release parameters + tag_name: { type: 'string', description: 'Release tag name' }, + release_id: { type: 'number', description: 'Release ID' }, + prerelease: { type: 'boolean', description: 'Prerelease status' }, + // Workflow parameters + workflow_id: { type: 'string', description: 'Workflow ID or filename' }, + run_id: { type: 'number', description: 'Workflow run ID' }, + status: { type: 'string', description: 'Status filter' }, + inputs: { type: 'string', description: 'Workflow inputs JSON' }, + // Project parameters + owner_login: { type: 'string', description: 'Owner login' }, + owner_type: { type: 'string', description: 'Owner type (user or org)' }, + owner_id: { type: 'string', description: 'Owner node ID' }, + project_number: { type: 'number', description: 'Project number' }, + project_id: { type: 'string', description: 'Project node ID' }, + project_public: { type: 'boolean', description: 'Project public status' }, }, outputs: { content: { type: 'string', description: 'Response content' }, @@ -190,6 +1320,18 @@ export const GitHubBlock: BlockConfig = { }, triggers: { enabled: true, - available: ['github_webhook'], + available: [ + 'github_issue_opened', + 'github_issue_closed', + 'github_issue_comment', + 'github_pr_opened', + 'github_pr_closed', + 'github_pr_merged', + 'github_pr_comment', + 'github_pr_reviewed', + 'github_push', + 'github_release_published', + 'github_workflow_run', + ], }, } diff --git a/apps/sim/lib/logs/execution/logger.ts b/apps/sim/lib/logs/execution/logger.ts index eeb9044806..4c33469408 100644 --- a/apps/sim/lib/logs/execution/logger.ts +++ b/apps/sim/lib/logs/execution/logger.ts @@ -56,6 +56,39 @@ export class ExecutionLogger implements IExecutionLoggerService { logger.debug(`Starting workflow execution ${executionId} for workflow ${workflowId}`) + // Check if execution log already exists (idempotency check) + const existingLog = await db + .select() + .from(workflowExecutionLogs) + .where(eq(workflowExecutionLogs.executionId, executionId)) + .limit(1) + + if (existingLog.length > 0) { + logger.debug( + `Execution log already exists for ${executionId}, skipping duplicate INSERT (idempotent)` + ) + const snapshot = await snapshotService.getSnapshot(existingLog[0].stateSnapshotId) + if (!snapshot) { + throw new Error(`Snapshot ${existingLog[0].stateSnapshotId} not found for existing log`) + } + return { + workflowLog: { + id: existingLog[0].id, + workflowId: existingLog[0].workflowId, + executionId: existingLog[0].executionId, + stateSnapshotId: existingLog[0].stateSnapshotId, + level: existingLog[0].level as 'info' | 'error', + trigger: existingLog[0].trigger as ExecutionTrigger['type'], + startedAt: existingLog[0].startedAt.toISOString(), + endedAt: existingLog[0].endedAt?.toISOString() || existingLog[0].startedAt.toISOString(), + totalDurationMs: existingLog[0].totalDurationMs || 0, + executionData: existingLog[0].executionData as WorkflowExecutionLog['executionData'], + createdAt: existingLog[0].createdAt.toISOString(), + }, + snapshot, + } + } + const snapshotResult = await snapshotService.createSnapshotWithDeduplication( workflowId, workflowState diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index 88a7d863f9..b9eae32537 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -590,6 +590,37 @@ export async function queueWebhookExecution( return NextResponse.json({ error: 'Workspace billing account required' }, { status: 402 }) } + // GitHub event filtering for event-specific triggers + if (foundWebhook.provider === 'github') { + const providerConfig = (foundWebhook.providerConfig as Record) || {} + const triggerId = providerConfig.triggerId as string | undefined + + if (triggerId && triggerId !== 'github_webhook') { + const eventType = request.headers.get('x-github-event') + const action = body.action + + const { isGitHubEventMatch } = await import('@/triggers/github/utils') + + if (!isGitHubEventMatch(triggerId, eventType || '', action, body)) { + logger.debug( + `[${options.requestId}] GitHub event mismatch for trigger ${triggerId}. Event: ${eventType}, Action: ${action}. Skipping execution.`, + { + webhookId: foundWebhook.id, + workflowId: foundWorkflow.id, + triggerId, + receivedEvent: eventType, + receivedAction: action, + } + ) + + // Return 200 OK to prevent GitHub from retrying + return NextResponse.json({ + message: 'Event type does not match trigger configuration. Ignoring.', + }) + } + } + } + const headers = Object.fromEntries(request.headers.entries()) // For Microsoft Teams Graph notifications, extract unique identifiers for idempotency diff --git a/apps/sim/tools/github/add_assignees.ts b/apps/sim/tools/github/add_assignees.ts new file mode 100644 index 0000000000..21c031bde9 --- /dev/null +++ b/apps/sim/tools/github/add_assignees.ts @@ -0,0 +1,108 @@ +import type { AddAssigneesParams, IssueResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const addAssigneesTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/add_labels.ts b/apps/sim/tools/github/add_labels.ts new file mode 100644 index 0000000000..9feee76c0e --- /dev/null +++ b/apps/sim/tools/github/add_labels.ts @@ -0,0 +1,96 @@ +import type { AddLabelsParams, LabelsResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const addLabelsTool: ToolConfig = { + id: 'github_add_labels', + name: 'GitHub Add Labels', + description: 'Add labels to an issue in a GitHub repository', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + issue_number: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Issue number', + }, + labels: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Comma-separated list of label names to add to the issue', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub API token', + }, + }, + + request: { + url: (params) => + `https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/labels`, + method: 'POST', + headers: (params) => ({ + Accept: 'application/vnd.github.v3+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + body: (params) => { + const labelsArray = params.labels + .split(',') + .map((l) => l.trim()) + .filter((l) => l) + return { + labels: labelsArray, + } + }, + }, + + transformResponse: async (response) => { + const labelsData = await response.json() + + const labels = labelsData.map((label: any) => label.name) + + const content = `Labels added to issue successfully! +All labels on issue: ${labels.join(', ')}` + + return { + success: true, + output: { + content, + metadata: { + labels, + issue_number: 0, // Will be filled from params in actual implementation + html_url: '', // Will be constructed from params + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable labels confirmation' }, + metadata: { + type: 'object', + description: 'Labels metadata', + properties: { + labels: { type: 'array', description: 'All labels currently on the issue' }, + issue_number: { type: 'number', description: 'Issue number' }, + html_url: { type: 'string', description: 'GitHub issue URL' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/cancel_workflow_run.ts b/apps/sim/tools/github/cancel_workflow_run.ts new file mode 100644 index 0000000000..24f813c2e6 --- /dev/null +++ b/apps/sim/tools/github/cancel_workflow_run.ts @@ -0,0 +1,124 @@ +import type { CancelWorkflowRunParams, CancelWorkflowRunResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const cancelWorkflowRunTool: ToolConfig = + { + 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)', + }, + }, + }, + }, + } diff --git a/apps/sim/tools/github/close_issue.ts b/apps/sim/tools/github/close_issue.ts new file mode 100644 index 0000000000..2db0bb7090 --- /dev/null +++ b/apps/sim/tools/github/close_issue.ts @@ -0,0 +1,115 @@ +import type { CloseIssueParams, IssueResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const closeIssueTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/close_pr.ts b/apps/sim/tools/github/close_pr.ts new file mode 100644 index 0000000000..2cf3615772 --- /dev/null +++ b/apps/sim/tools/github/close_pr.ts @@ -0,0 +1,92 @@ +import type { ClosePRParams, PRResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const closePRTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/create_branch.ts b/apps/sim/tools/github/create_branch.ts new file mode 100644 index 0000000000..08e45cf8a6 --- /dev/null +++ b/apps/sim/tools/github/create_branch.ts @@ -0,0 +1,93 @@ +import type { CreateBranchParams, RefResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const createBranchTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/create_file.ts b/apps/sim/tools/github/create_file.ts new file mode 100644 index 0000000000..c2d66f9d2d --- /dev/null +++ b/apps/sim/tools/github/create_file.ts @@ -0,0 +1,172 @@ +import type { CreateFileParams, FileOperationResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const createFileTool: ToolConfig = { + 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 = { + 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' }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/create_issue.ts b/apps/sim/tools/github/create_issue.ts new file mode 100644 index 0000000000..041d6dac0d --- /dev/null +++ b/apps/sim/tools/github/create_issue.ts @@ -0,0 +1,143 @@ +import type { CreateIssueParams, IssueResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const createIssueTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/create_pr.ts b/apps/sim/tools/github/create_pr.ts new file mode 100644 index 0000000000..4c1a8b7c72 --- /dev/null +++ b/apps/sim/tools/github/create_pr.ts @@ -0,0 +1,120 @@ +import type { CreatePRParams, PRResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const createPRTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/create_project.ts b/apps/sim/tools/github/create_project.ts new file mode 100644 index 0000000000..f86fffab6f --- /dev/null +++ b/apps/sim/tools/github/create_project.ts @@ -0,0 +1,148 @@ +import type { CreateProjectParams, ProjectResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const createProjectTool: ToolConfig = { + 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, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/create_release.ts b/apps/sim/tools/github/create_release.ts new file mode 100644 index 0000000000..9f9396f8dd --- /dev/null +++ b/apps/sim/tools/github/create_release.ts @@ -0,0 +1,157 @@ +import type { CreateReleaseParams, ReleaseResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const createReleaseTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/delete_branch.ts b/apps/sim/tools/github/delete_branch.ts new file mode 100644 index 0000000000..d218dc59e8 --- /dev/null +++ b/apps/sim/tools/github/delete_branch.ts @@ -0,0 +1,89 @@ +import type { DeleteBranchParams, DeleteBranchResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const deleteBranchTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/delete_comment.ts b/apps/sim/tools/github/delete_comment.ts new file mode 100644 index 0000000000..05ae139bec --- /dev/null +++ b/apps/sim/tools/github/delete_comment.ts @@ -0,0 +1,74 @@ +import type { DeleteCommentParams, DeleteCommentResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const deleteCommentTool: ToolConfig = { + id: 'github_delete_comment', + name: 'GitHub Comment Deleter', + description: 'Delete a comment on a GitHub issue or pull request', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + comment_id: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Comment ID', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub API token', + }, + }, + + request: { + url: (params) => + `https://api.github.com/repos/${params.owner}/${params.repo}/issues/comments/${params.comment_id}`, + method: 'DELETE', + headers: (params) => ({ + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + }, + + transformResponse: async (response) => { + const content = `Comment #${response.url.split('/').pop()} successfully deleted` + + return { + success: true, + output: { + content, + metadata: { + deleted: true, + comment_id: Number(response.url.split('/').pop()), + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable deletion confirmation' }, + metadata: { + type: 'object', + description: 'Deletion result metadata', + properties: { + deleted: { type: 'boolean', description: 'Whether deletion was successful' }, + comment_id: { type: 'number', description: 'Deleted comment ID' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/delete_file.ts b/apps/sim/tools/github/delete_file.ts new file mode 100644 index 0000000000..c759510530 --- /dev/null +++ b/apps/sim/tools/github/delete_file.ts @@ -0,0 +1,149 @@ +import type { DeleteFileParams, DeleteFileResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const deleteFileTool: ToolConfig = { + 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 = { + message: params.message, + sha: params.sha, // Required for delete + } + + if (params.branch) { + body.branch = params.branch + } + + return body + }, + }, + + transformResponse: async (response) => { + const data = await response.json() + const content = `File deleted successfully! + +Path: ${data.commit.sha ? 'File removed from repository' : 'Unknown'} +Deleted: Yes + +Commit: +- SHA: ${data.commit.sha} +- Message: ${data.commit.message || 'N/A'} +- Author: ${data.commit.author?.name || 'N/A'} +- Date: ${data.commit.author?.date || 'N/A'} + +View commit: ${data.commit.html_url || 'N/A'}` + + return { + success: true, + output: { + content, + metadata: { + deleted: true, + path: data.commit.tree?.sha || 'N/A', + commit: { + sha: data.commit.sha, + message: data.commit.message || '', + author: { + name: data.commit.author?.name || '', + email: data.commit.author?.email || '', + date: data.commit.author?.date || '', + }, + committer: { + name: data.commit.committer?.name || '', + email: data.commit.committer?.email || '', + date: data.commit.committer?.date || '', + }, + html_url: data.commit.html_url || '', + }, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable file deletion confirmation' }, + metadata: { + type: 'object', + description: 'Deletion confirmation and commit metadata', + properties: { + deleted: { type: 'boolean', description: 'Whether the file was deleted' }, + path: { type: 'string', description: 'File path that was deleted' }, + commit: { + type: 'object', + description: 'Commit information', + properties: { + sha: { type: 'string', description: 'Commit SHA' }, + message: { type: 'string', description: 'Commit message' }, + author: { + type: 'object', + description: 'Author information', + }, + committer: { + type: 'object', + description: 'Committer information', + }, + html_url: { type: 'string', description: 'Commit URL' }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/delete_project.ts b/apps/sim/tools/github/delete_project.ts new file mode 100644 index 0000000000..c39e2806ff --- /dev/null +++ b/apps/sim/tools/github/delete_project.ts @@ -0,0 +1,128 @@ +import type { DeleteProjectParams, ProjectResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const deleteProjectTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/delete_release.ts b/apps/sim/tools/github/delete_release.ts new file mode 100644 index 0000000000..579c6a1e2e --- /dev/null +++ b/apps/sim/tools/github/delete_release.ts @@ -0,0 +1,98 @@ +import type { DeleteReleaseParams, DeleteReleaseResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const deleteReleaseTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/get_branch.ts b/apps/sim/tools/github/get_branch.ts new file mode 100644 index 0000000000..bbafee5cfd --- /dev/null +++ b/apps/sim/tools/github/get_branch.ts @@ -0,0 +1,92 @@ +import type { BranchResponse, GetBranchParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const getBranchTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/get_branch_protection.ts b/apps/sim/tools/github/get_branch_protection.ts new file mode 100644 index 0000000000..de583986ab --- /dev/null +++ b/apps/sim/tools/github/get_branch_protection.ts @@ -0,0 +1,183 @@ +import type { BranchProtectionResponse, GetBranchProtectionParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const getBranchProtectionTool: ToolConfig< + GetBranchProtectionParams, + BranchProtectionResponse +> = { + id: 'github_get_branch_protection', + name: 'GitHub Get Branch Protection', + description: + 'Get the branch protection rules for a specific branch, including status checks, review requirements, and restrictions.', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner (user or organization)', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + branch: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Branch name', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub Personal Access Token', + }, + }, + + request: { + url: (params) => + `https://api.github.com/repos/${params.owner}/${params.repo}/branches/${params.branch}/protection`, + method: 'GET', + headers: (params) => ({ + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + }, + + transformResponse: async (response) => { + const protection = await response.json() + + let content = `Branch Protection for "${protection.url.split('/branches/')[1].split('/protection')[0]}": + +Enforce Admins: ${protection.enforce_admins?.enabled ? 'Yes' : 'No'}` + + if (protection.required_status_checks) { + content += `\n\nRequired Status Checks: +- Strict: ${protection.required_status_checks.strict} +- Contexts: ${protection.required_status_checks.contexts.length > 0 ? protection.required_status_checks.contexts.join(', ') : 'None'}` + } else { + content += '\n\nRequired Status Checks: None' + } + + if (protection.required_pull_request_reviews) { + content += `\n\nRequired Pull Request Reviews: +- Required Approving Reviews: ${protection.required_pull_request_reviews.required_approving_review_count || 0} +- Dismiss Stale Reviews: ${protection.required_pull_request_reviews.dismiss_stale_reviews ? 'Yes' : 'No'} +- Require Code Owner Reviews: ${protection.required_pull_request_reviews.require_code_owner_reviews ? 'Yes' : 'No'}` + } else { + content += '\n\nRequired Pull Request Reviews: None' + } + + if (protection.restrictions) { + const users = protection.restrictions.users?.map((u: any) => u.login) || [] + const teams = protection.restrictions.teams?.map((t: any) => t.slug) || [] + content += `\n\nRestrictions: +- Users: ${users.length > 0 ? users.join(', ') : 'None'} +- Teams: ${teams.length > 0 ? teams.join(', ') : 'None'}` + } else { + content += '\n\nRestrictions: None' + } + + return { + success: true, + output: { + content, + metadata: { + required_status_checks: protection.required_status_checks + ? { + strict: protection.required_status_checks.strict, + contexts: protection.required_status_checks.contexts, + } + : null, + enforce_admins: { + enabled: protection.enforce_admins?.enabled || false, + }, + required_pull_request_reviews: protection.required_pull_request_reviews + ? { + required_approving_review_count: + protection.required_pull_request_reviews.required_approving_review_count || 0, + dismiss_stale_reviews: + protection.required_pull_request_reviews.dismiss_stale_reviews || false, + require_code_owner_reviews: + protection.required_pull_request_reviews.require_code_owner_reviews || false, + } + : null, + restrictions: protection.restrictions + ? { + users: protection.restrictions.users?.map((u: any) => u.login) || [], + teams: protection.restrictions.teams?.map((t: any) => t.slug) || [], + } + : null, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable branch protection summary' }, + metadata: { + type: 'object', + description: 'Branch protection configuration', + properties: { + required_status_checks: { + type: 'object', + description: 'Status check requirements (null if not configured)', + properties: { + strict: { type: 'boolean', description: 'Require branches to be up to date' }, + contexts: { + type: 'array', + description: 'Required status check contexts', + items: { type: 'string' }, + }, + }, + }, + enforce_admins: { + type: 'object', + description: 'Admin enforcement settings', + properties: { + enabled: { type: 'boolean', description: 'Enforce for administrators' }, + }, + }, + required_pull_request_reviews: { + type: 'object', + description: 'Pull request review requirements (null if not configured)', + properties: { + required_approving_review_count: { + type: 'number', + description: 'Number of approving reviews required', + }, + dismiss_stale_reviews: { + type: 'boolean', + description: 'Dismiss stale pull request approvals', + }, + require_code_owner_reviews: { + type: 'boolean', + description: 'Require review from code owners', + }, + }, + }, + restrictions: { + type: 'object', + description: 'Push restrictions (null if not configured)', + properties: { + users: { + type: 'array', + description: 'Users who can push', + items: { type: 'string' }, + }, + teams: { + type: 'array', + description: 'Teams who can push', + items: { type: 'string' }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/get_file_content.ts b/apps/sim/tools/github/get_file_content.ts new file mode 100644 index 0000000000..a5956d6655 --- /dev/null +++ b/apps/sim/tools/github/get_file_content.ts @@ -0,0 +1,138 @@ +import type { FileContentResponse, GetFileContentParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const getFileContentTool: ToolConfig = { + id: 'github_get_file_content', + name: 'GitHub Get File Content', + description: + 'Get the content of a file from a GitHub repository. Supports files up to 1MB. Content is returned decoded and human-readable.', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner (user or organization)', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + path: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Path to the file in the repository (e.g., "src/index.ts")', + }, + ref: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Branch name, tag, or commit SHA (defaults to repository default branch)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub Personal Access Token', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/contents/${params.path}` + return params.ref ? `${baseUrl}?ref=${params.ref}` : baseUrl + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + if (Array.isArray(data)) { + return { + success: false, + error: 'Path points to a directory. Use github_get_tree to list directory contents.', + output: { + content: '', + metadata: { + name: '', + path: '', + sha: '', + size: 0, + type: 'dir', + download_url: '', + html_url: '', + }, + }, + } + } + + let decodedContent = '' + if (data.content) { + try { + decodedContent = Buffer.from(data.content, 'base64').toString('utf-8') + } catch (error) { + decodedContent = '[Binary file - content cannot be displayed as text]' + } + } + + const contentPreview = + decodedContent.length > 500 + ? `${decodedContent.substring(0, 500)}...\n\n[Content truncated. Full content available in metadata]` + : decodedContent + + const content = `File: ${data.name} +Path: ${data.path} +Size: ${data.size} bytes +Type: ${data.type} +SHA: ${data.sha} + +Content Preview: +${contentPreview}` + + return { + success: true, + output: { + content, + metadata: { + name: data.name, + path: data.path, + sha: data.sha, + size: data.size, + type: data.type, + download_url: data.download_url, + html_url: data.html_url, + }, + }, + } + }, + + outputs: { + content: { + type: 'string', + description: 'Human-readable file information with content preview', + }, + metadata: { + type: 'object', + description: 'File metadata including name, path, SHA, size, and URLs', + properties: { + name: { type: 'string', description: 'File name' }, + path: { type: 'string', description: 'Full path in repository' }, + sha: { type: 'string', description: 'Git blob SHA' }, + size: { type: 'number', description: 'File size in bytes' }, + type: { type: 'string', description: 'Content type (file or dir)' }, + download_url: { type: 'string', description: 'Direct download URL', optional: true }, + html_url: { type: 'string', description: 'GitHub web UI URL', optional: true }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/get_issue.ts b/apps/sim/tools/github/get_issue.ts new file mode 100644 index 0000000000..e08fa82257 --- /dev/null +++ b/apps/sim/tools/github/get_issue.ts @@ -0,0 +1,106 @@ +import type { GetIssueParams, IssueResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const getIssueTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/get_pr_files.ts b/apps/sim/tools/github/get_pr_files.ts new file mode 100644 index 0000000000..e0fab19e24 --- /dev/null +++ b/apps/sim/tools/github/get_pr_files.ts @@ -0,0 +1,138 @@ +import type { GetPRFilesParams, PRFilesListResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const getPRFilesTool: ToolConfig = { + id: 'github_get_pr_files', + name: 'GitHub Get PR Files', + description: 'Get the list of files changed in a pull request', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + pullNumber: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Pull request number', + }, + per_page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Results per page (max 100)', + default: 30, + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number', + default: 1, + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub API token', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/files` + ) + if (params.per_page) url.searchParams.append('per_page', params.per_page.toString()) + if (params.page) url.searchParams.append('page', params.page.toString()) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/vnd.github.v3+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + }, + + transformResponse: async (response) => { + const files = await response.json() + + const totalAdditions = files.reduce((sum: number, file: any) => sum + file.additions, 0) + const totalDeletions = files.reduce((sum: number, file: any) => sum + file.deletions, 0) + const totalChanges = files.reduce((sum: number, file: any) => sum + file.changes, 0) + + const content = `Found ${files.length} file(s) changed in PR +Total additions: ${totalAdditions}, Total deletions: ${totalDeletions}, Total changes: ${totalChanges} + +Files: +${files + .map( + (file: any) => + `- ${file.filename} (${file.status}) + +${file.additions} -${file.deletions} (~${file.changes} changes)` + ) + .join('\n')}` + + return { + success: true, + output: { + content, + metadata: { + files: files.map((file: any) => ({ + filename: file.filename, + status: file.status, + additions: file.additions, + deletions: file.deletions, + changes: file.changes, + patch: file.patch, + blob_url: file.blob_url, + raw_url: file.raw_url, + })), + total_count: files.length, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable list of files changed in PR' }, + metadata: { + type: 'object', + description: 'PR files metadata', + properties: { + files: { + type: 'array', + description: 'Array of file changes', + items: { + type: 'object', + properties: { + filename: { type: 'string', description: 'File path' }, + status: { + type: 'string', + description: 'Change type (added/modified/deleted/renamed)', + }, + additions: { type: 'number', description: 'Lines added' }, + deletions: { type: 'number', description: 'Lines deleted' }, + changes: { type: 'number', description: 'Total changes' }, + patch: { type: 'string', description: 'File diff patch' }, + blob_url: { type: 'string', description: 'GitHub blob URL' }, + raw_url: { type: 'string', description: 'Raw file URL' }, + }, + }, + }, + total_count: { type: 'number', description: 'Total number of files changed' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/get_project.ts b/apps/sim/tools/github/get_project.ts new file mode 100644 index 0000000000..75f221ca4b --- /dev/null +++ b/apps/sim/tools/github/get_project.ts @@ -0,0 +1,163 @@ +import type { GetProjectParams, ProjectResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const getProjectTool: ToolConfig = { + 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, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/get_release.ts b/apps/sim/tools/github/get_release.ts new file mode 100644 index 0000000000..5371156b54 --- /dev/null +++ b/apps/sim/tools/github/get_release.ts @@ -0,0 +1,111 @@ +import type { GetReleaseParams, ReleaseResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const getReleaseTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/get_tree.ts b/apps/sim/tools/github/get_tree.ts new file mode 100644 index 0000000000..b1647fb526 --- /dev/null +++ b/apps/sim/tools/github/get_tree.ts @@ -0,0 +1,163 @@ +import type { GetTreeParams, TreeResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const getTreeTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/get_workflow.ts b/apps/sim/tools/github/get_workflow.ts new file mode 100644 index 0000000000..51d46e10bd --- /dev/null +++ b/apps/sim/tools/github/get_workflow.ts @@ -0,0 +1,89 @@ +import type { GetWorkflowParams, WorkflowResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const getWorkflowTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/get_workflow_run.ts b/apps/sim/tools/github/get_workflow_run.ts new file mode 100644 index 0000000000..ff297ad00d --- /dev/null +++ b/apps/sim/tools/github/get_workflow_run.ts @@ -0,0 +1,95 @@ +import type { GetWorkflowRunParams, WorkflowRunResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const getWorkflowRunTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/index.ts b/apps/sim/tools/github/index.ts index 703c8ec6d0..ba3a57da36 100644 --- a/apps/sim/tools/github/index.ts +++ b/apps/sim/tools/github/index.ts @@ -1,9 +1,105 @@ +import { addAssigneesTool } from '@/tools/github/add_assignees' +import { addLabelsTool } from '@/tools/github/add_labels' +import { cancelWorkflowRunTool } from '@/tools/github/cancel_workflow_run' +import { closeIssueTool } from '@/tools/github/close_issue' +import { closePRTool } from '@/tools/github/close_pr' import { commentTool } from '@/tools/github/comment' +import { createBranchTool } from '@/tools/github/create_branch' +import { createFileTool } from '@/tools/github/create_file' +import { createIssueTool } from '@/tools/github/create_issue' +import { createPRTool } from '@/tools/github/create_pr' +import { createProjectTool } from '@/tools/github/create_project' +import { createReleaseTool } from '@/tools/github/create_release' +import { deleteBranchTool } from '@/tools/github/delete_branch' +import { deleteCommentTool } from '@/tools/github/delete_comment' +import { deleteFileTool } from '@/tools/github/delete_file' +import { deleteProjectTool } from '@/tools/github/delete_project' +import { deleteReleaseTool } from '@/tools/github/delete_release' +import { getBranchTool } from '@/tools/github/get_branch' +import { getBranchProtectionTool } from '@/tools/github/get_branch_protection' +import { getFileContentTool } from '@/tools/github/get_file_content' +import { getIssueTool } from '@/tools/github/get_issue' +import { getPRFilesTool } from '@/tools/github/get_pr_files' +import { getProjectTool } from '@/tools/github/get_project' +import { getReleaseTool } from '@/tools/github/get_release' +import { getTreeTool } from '@/tools/github/get_tree' +import { getWorkflowTool } from '@/tools/github/get_workflow' +import { getWorkflowRunTool } from '@/tools/github/get_workflow_run' +import { issueCommentTool } from '@/tools/github/issue_comment' import { latestCommitTool } from '@/tools/github/latest_commit' +import { listBranchesTool } from '@/tools/github/list_branches' +import { listIssueCommentsTool } from '@/tools/github/list_issue_comments' +import { listIssuesTool } from '@/tools/github/list_issues' +import { listPRCommentsTool } from '@/tools/github/list_pr_comments' +import { listProjectsTool } from '@/tools/github/list_projects' +import { listPRsTool } from '@/tools/github/list_prs' +import { listReleasesTool } from '@/tools/github/list_releases' +import { listWorkflowRunsTool } from '@/tools/github/list_workflow_runs' +import { listWorkflowsTool } from '@/tools/github/list_workflows' +import { mergePRTool } from '@/tools/github/merge_pr' import { prTool } from '@/tools/github/pr' +import { removeLabelTool } from '@/tools/github/remove_label' import { repoInfoTool } from '@/tools/github/repo_info' +import { requestReviewersTool } from '@/tools/github/request_reviewers' +import { rerunWorkflowTool } from '@/tools/github/rerun_workflow' +import { triggerWorkflowTool } from '@/tools/github/trigger_workflow' +import { updateBranchProtectionTool } from '@/tools/github/update_branch_protection' +import { updateCommentTool } from '@/tools/github/update_comment' +import { updateFileTool } from '@/tools/github/update_file' +import { updateIssueTool } from '@/tools/github/update_issue' +import { updatePRTool } from '@/tools/github/update_pr' +import { updateProjectTool } from '@/tools/github/update_project' +import { updateReleaseTool } from '@/tools/github/update_release' +export const githubCancelWorkflowRunTool = cancelWorkflowRunTool +export const githubClosePRTool = closePRTool export const githubCommentTool = commentTool +export const githubCreateBranchTool = createBranchTool +export const githubCreateFileTool = createFileTool +export const githubCreatePRTool = createPRTool +export const githubCreateProjectTool = createProjectTool +export const githubCreateReleaseTool = createReleaseTool +export const githubDeleteBranchTool = deleteBranchTool +export const githubDeleteCommentTool = deleteCommentTool +export const githubDeleteFileTool = deleteFileTool +export const githubDeleteProjectTool = deleteProjectTool +export const githubDeleteReleaseTool = deleteReleaseTool +export const githubGetBranchTool = getBranchTool +export const githubGetBranchProtectionTool = getBranchProtectionTool +export const githubGetFileContentTool = getFileContentTool +export const githubGetPRFilesTool = getPRFilesTool +export const githubGetProjectTool = getProjectTool +export const githubGetReleaseTool = getReleaseTool +export const githubGetTreeTool = getTreeTool +export const githubGetWorkflowTool = getWorkflowTool +export const githubGetWorkflowRunTool = getWorkflowRunTool +export const githubIssueCommentTool = issueCommentTool export const githubLatestCommitTool = latestCommitTool +export const githubListBranchesTool = listBranchesTool +export const githubListIssueCommentsTool = listIssueCommentsTool +export const githubListPRCommentsTool = listPRCommentsTool +export const githubListPRsTool = listPRsTool +export const githubListProjectsTool = listProjectsTool +export const githubListReleasesTool = listReleasesTool +export const githubListWorkflowRunsTool = listWorkflowRunsTool +export const githubListWorkflowsTool = listWorkflowsTool +export const githubMergePRTool = mergePRTool export const githubPrTool = prTool export const githubRepoInfoTool = repoInfoTool +export const githubRequestReviewersTool = requestReviewersTool +export const githubRerunWorkflowTool = rerunWorkflowTool +export const githubTriggerWorkflowTool = triggerWorkflowTool +export const githubUpdateBranchProtectionTool = updateBranchProtectionTool +export const githubUpdateCommentTool = updateCommentTool +export const githubUpdateFileTool = updateFileTool +export const githubUpdatePRTool = updatePRTool +export const githubUpdateProjectTool = updateProjectTool +export const githubUpdateReleaseTool = updateReleaseTool +export const githubAddAssigneesTool = addAssigneesTool +export const githubAddLabelsTool = addLabelsTool +export const githubCloseIssueTool = closeIssueTool +export const githubCreateIssueTool = createIssueTool +export const githubGetIssueTool = getIssueTool +export const githubListIssuesTool = listIssuesTool +export const githubRemoveLabelTool = removeLabelTool +export const githubUpdateIssueTool = updateIssueTool diff --git a/apps/sim/tools/github/issue_comment.ts b/apps/sim/tools/github/issue_comment.ts new file mode 100644 index 0000000000..8228e359b3 --- /dev/null +++ b/apps/sim/tools/github/issue_comment.ts @@ -0,0 +1,103 @@ +import type { CreateIssueCommentParams, IssueCommentResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const issueCommentTool: ToolConfig = { + id: 'github_issue_comment', + name: 'GitHub Issue Comment Creator', + description: 'Create a comment on a GitHub issue', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + issue_number: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Issue number', + }, + body: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Comment content', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub API token', + }, + }, + + request: { + url: (params) => + `https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/comments`, + method: 'POST', + headers: (params) => ({ + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + body: (params) => ({ + body: params.body, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + const content = `Comment created on issue #${data.issue_url.split('/').pop()}: "${data.body.substring(0, 100)}${data.body.length > 100 ? '...' : ''}"` + + return { + success: true, + output: { + content, + metadata: { + id: data.id, + html_url: data.html_url, + body: data.body, + created_at: data.created_at, + updated_at: data.updated_at, + user: { + login: data.user.login, + id: data.user.id, + }, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable comment confirmation' }, + metadata: { + type: 'object', + description: 'Comment metadata', + properties: { + id: { type: 'number', description: 'Comment ID' }, + html_url: { type: 'string', description: 'GitHub web URL' }, + body: { type: 'string', description: 'Comment body' }, + created_at: { type: 'string', description: 'Creation timestamp' }, + updated_at: { type: 'string', description: 'Last update timestamp' }, + user: { + type: 'object', + description: 'User who created the comment', + properties: { + login: { type: 'string', description: 'User login' }, + id: { type: 'number', description: 'User ID' }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/list_branches.ts b/apps/sim/tools/github/list_branches.ts new file mode 100644 index 0000000000..a5ef3ef538 --- /dev/null +++ b/apps/sim/tools/github/list_branches.ts @@ -0,0 +1,137 @@ +import type { BranchListResponse, ListBranchesParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const listBranchesTool: ToolConfig = { + id: 'github_list_branches', + name: 'GitHub List Branches', + description: + 'List all branches in a GitHub repository. Optionally filter by protected status and control pagination.', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner (user or organization)', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + protected: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Filter branches by protection status', + }, + per_page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (max 100, default 30)', + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number for pagination (default 1)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub Personal Access Token', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/branches` + const queryParams = new URLSearchParams() + + if (params.protected !== undefined) { + queryParams.append('protected', params.protected.toString()) + } + if (params.per_page) { + queryParams.append('per_page', params.per_page.toString()) + } + if (params.page) { + queryParams.append('page', params.page.toString()) + } + + const query = queryParams.toString() + return query ? `${baseUrl}?${query}` : baseUrl + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + }, + + transformResponse: async (response) => { + const branches = await response.json() + + const branchList = branches + .map( + (branch: any) => + `- ${branch.name} (SHA: ${branch.commit.sha.substring(0, 7)}${branch.protected ? ', Protected' : ''})` + ) + .join('\n') + + const content = `Found ${branches.length} branch${branches.length !== 1 ? 'es' : ''}: +${branchList}` + + return { + success: true, + output: { + content, + metadata: { + branches: branches.map((branch: any) => ({ + name: branch.name, + commit: { + sha: branch.commit.sha, + url: branch.commit.url, + }, + protected: branch.protected, + })), + total_count: branches.length, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable list of branches' }, + metadata: { + type: 'object', + description: 'Branch list metadata', + properties: { + branches: { + type: 'array', + description: 'Array of branch objects', + items: { + type: 'object', + properties: { + name: { type: 'string', description: 'Branch name' }, + commit: { + type: 'object', + description: 'Commit information', + properties: { + sha: { type: 'string', description: 'Commit SHA' }, + url: { type: 'string', description: 'Commit API URL' }, + }, + }, + protected: { type: 'boolean', description: 'Whether branch is protected' }, + }, + }, + }, + total_count: { type: 'number', description: 'Total number of branches' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/list_issue_comments.ts b/apps/sim/tools/github/list_issue_comments.ts new file mode 100644 index 0000000000..4dc76f4720 --- /dev/null +++ b/apps/sim/tools/github/list_issue_comments.ts @@ -0,0 +1,140 @@ +import type { CommentsListResponse, ListIssueCommentsParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const listIssueCommentsTool: ToolConfig = { + id: 'github_list_issue_comments', + name: 'GitHub Issue Comments Lister', + description: 'List all comments on a GitHub issue', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + issue_number: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Issue number', + }, + since: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only show comments updated after this ISO 8601 timestamp', + }, + per_page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (max 100)', + default: 30, + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number', + default: 1, + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub API token', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/issues/${params.issue_number}/comments` + const queryParams = new URLSearchParams() + + if (params.since) queryParams.append('since', params.since) + if (params.per_page) queryParams.append('per_page', params.per_page.toString()) + if (params.page) queryParams.append('page', params.page.toString()) + + const query = queryParams.toString() + return query ? `${baseUrl}?${query}` : baseUrl + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + const content = `Found ${data.length} comment${data.length !== 1 ? 's' : ''} on issue #${response.url.split('/').slice(-2, -1)[0]}${ + data.length > 0 + ? `\n\nRecent comments:\n${data + .slice(0, 5) + .map( + (c: any) => + `- ${c.user.login} (${new Date(c.created_at).toLocaleDateString()}): "${c.body.substring(0, 80)}${c.body.length > 80 ? '...' : ''}"` + ) + .join('\n')}` + : '' + }` + + return { + success: true, + output: { + content, + metadata: { + comments: data.map((comment: any) => ({ + id: comment.id, + body: comment.body, + user: { login: comment.user.login }, + created_at: comment.created_at, + html_url: comment.html_url, + })), + total_count: data.length, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable comments summary' }, + metadata: { + type: 'object', + description: 'Comments list metadata', + properties: { + comments: { + type: 'array', + description: 'Array of comment objects', + items: { + type: 'object', + properties: { + id: { type: 'number', description: 'Comment ID' }, + body: { type: 'string', description: 'Comment body' }, + user: { + type: 'object', + description: 'User who created the comment', + properties: { + login: { type: 'string', description: 'User login' }, + }, + }, + created_at: { type: 'string', description: 'Creation timestamp' }, + html_url: { type: 'string', description: 'GitHub web URL' }, + }, + }, + }, + total_count: { type: 'number', description: 'Total number of comments' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/list_issues.ts b/apps/sim/tools/github/list_issues.ts new file mode 100644 index 0000000000..a6aaa3de19 --- /dev/null +++ b/apps/sim/tools/github/list_issues.ts @@ -0,0 +1,169 @@ +import type { IssuesListResponse, ListIssuesParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const listIssuesTool: ToolConfig = { + id: 'github_list_issues', + name: 'GitHub List Issues', + description: + 'List issues in a GitHub repository. Note: This includes pull requests as PRs are considered issues in GitHub', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + state: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by state: open, closed, or all (default: open)', + default: 'open', + }, + assignee: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by assignee username', + }, + creator: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by creator username', + }, + labels: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of label names to filter by', + }, + sort: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort by: created, updated, or comments (default: created)', + default: 'created', + }, + direction: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort direction: asc or desc (default: desc)', + default: 'desc', + }, + per_page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Results per page (max 100, default: 30)', + default: 30, + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number (default: 1)', + default: 1, + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub API token', + }, + }, + + request: { + url: (params) => { + const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/issues`) + if (params.state) url.searchParams.append('state', params.state) + if (params.assignee) url.searchParams.append('assignee', params.assignee) + if (params.creator) url.searchParams.append('creator', params.creator) + if (params.labels) url.searchParams.append('labels', params.labels) + if (params.sort) url.searchParams.append('sort', params.sort) + if (params.direction) url.searchParams.append('direction', params.direction) + if (params.per_page) url.searchParams.append('per_page', params.per_page.toString()) + if (params.page) url.searchParams.append('page', params.page.toString()) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/vnd.github.v3+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + }, + + transformResponse: async (response) => { + const issues = await response.json() + + const transformedIssues = issues.map((issue: any) => ({ + number: issue.number, + title: issue.title, + state: issue.state, + html_url: issue.html_url, + labels: issue.labels?.map((label: any) => label.name) || [], + assignees: issue.assignees?.map((assignee: any) => assignee.login) || [], + created_at: issue.created_at, + updated_at: issue.updated_at, + })) + + const content = `Found ${issues.length} issue(s): +${transformedIssues + .map( + (issue: any) => + `#${issue.number}: "${issue.title}" (${issue.state}) - ${issue.html_url} + ${issue.labels.length > 0 ? `Labels: ${issue.labels.join(', ')}` : 'No labels'} + ${issue.assignees.length > 0 ? `Assignees: ${issue.assignees.join(', ')}` : 'No assignees'}` + ) + .join('\n\n')}` + + return { + success: true, + output: { + content, + metadata: { + issues: transformedIssues, + total_count: issues.length, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable list of issues' }, + metadata: { + type: 'object', + description: 'Issues list metadata', + properties: { + issues: { + type: 'array', + description: 'Array of issues', + items: { + type: 'object', + properties: { + number: { type: 'number', description: 'Issue number' }, + title: { type: 'string', description: 'Issue title' }, + state: { type: 'string', description: 'Issue state' }, + html_url: { type: 'string', description: 'GitHub web URL' }, + labels: { type: 'array', description: 'Array of label names' }, + assignees: { type: 'array', description: 'Array of assignee usernames' }, + created_at: { type: 'string', description: 'Creation timestamp' }, + updated_at: { type: 'string', description: 'Last update timestamp' }, + }, + }, + }, + total_count: { type: 'number', description: 'Total number of issues returned' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/list_pr_comments.ts b/apps/sim/tools/github/list_pr_comments.ts new file mode 100644 index 0000000000..8767daae0f --- /dev/null +++ b/apps/sim/tools/github/list_pr_comments.ts @@ -0,0 +1,156 @@ +import type { CommentsListResponse, ListPRCommentsParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const listPRCommentsTool: ToolConfig = { + id: 'github_list_pr_comments', + name: 'GitHub PR Review Comments Lister', + description: 'List all review comments on a GitHub pull request', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + pullNumber: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Pull request number', + }, + sort: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort by created or updated', + default: 'created', + }, + direction: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort direction (asc or desc)', + default: 'desc', + }, + since: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Only show comments updated after this ISO 8601 timestamp', + }, + per_page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (max 100)', + default: 30, + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number', + default: 1, + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub API token', + }, + }, + + request: { + url: (params) => { + const baseUrl = `https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/comments` + const queryParams = new URLSearchParams() + + if (params.sort) queryParams.append('sort', params.sort) + if (params.direction) queryParams.append('direction', params.direction) + if (params.since) queryParams.append('since', params.since) + if (params.per_page) queryParams.append('per_page', params.per_page.toString()) + if (params.page) queryParams.append('page', params.page.toString()) + + const query = queryParams.toString() + return query ? `${baseUrl}?${query}` : baseUrl + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + const content = `Found ${data.length} review comment${data.length !== 1 ? 's' : ''} on PR #${response.url.split('/').slice(-2, -1)[0]}${ + data.length > 0 + ? `\n\nRecent review comments:\n${data + .slice(0, 5) + .map( + (c: any) => + `- ${c.user.login} on ${c.path}${c.line ? `:${c.line}` : ''} (${new Date(c.created_at).toLocaleDateString()}): "${c.body.substring(0, 80)}${c.body.length > 80 ? '...' : ''}"` + ) + .join('\n')}` + : '' + }` + + return { + success: true, + output: { + content, + metadata: { + comments: data.map((comment: any) => ({ + id: comment.id, + body: comment.body, + user: { login: comment.user.login }, + created_at: comment.created_at, + html_url: comment.html_url, + })), + total_count: data.length, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable review comments summary' }, + metadata: { + type: 'object', + description: 'Review comments list metadata', + properties: { + comments: { + type: 'array', + description: 'Array of review comment objects', + items: { + type: 'object', + properties: { + id: { type: 'number', description: 'Comment ID' }, + body: { type: 'string', description: 'Comment body' }, + user: { + type: 'object', + description: 'User who created the comment', + properties: { + login: { type: 'string', description: 'User login' }, + }, + }, + created_at: { type: 'string', description: 'Creation timestamp' }, + html_url: { type: 'string', description: 'GitHub web URL' }, + }, + }, + }, + total_count: { type: 'number', description: 'Total number of review comments' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/list_projects.ts b/apps/sim/tools/github/list_projects.ts new file mode 100644 index 0000000000..9f25bc362e --- /dev/null +++ b/apps/sim/tools/github/list_projects.ts @@ -0,0 +1,165 @@ +import type { ListProjectsParams, ListProjectsResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const listProjectsTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/list_prs.ts b/apps/sim/tools/github/list_prs.ts new file mode 100644 index 0000000000..8f4854b1ee --- /dev/null +++ b/apps/sim/tools/github/list_prs.ts @@ -0,0 +1,153 @@ +import type { ListPRsParams, PRListResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const listPRsTool: ToolConfig = { + id: 'github_list_prs', + name: 'GitHub List Pull Requests', + description: 'List pull requests in a GitHub repository', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + state: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by state: open, closed, or all', + default: 'open', + }, + head: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Filter by head user or branch name (format: user:ref-name or organization:ref-name)', + }, + base: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by base branch name', + }, + sort: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort by: created, updated, popularity, or long-running', + default: 'created', + }, + direction: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Sort direction: asc or desc', + default: 'desc', + }, + per_page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Results per page (max 100)', + default: 30, + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number', + default: 1, + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub API token', + }, + }, + + request: { + url: (params) => { + const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/pulls`) + if (params.state) url.searchParams.append('state', params.state) + if (params.head) url.searchParams.append('head', params.head) + if (params.base) url.searchParams.append('base', params.base) + if (params.sort) url.searchParams.append('sort', params.sort) + if (params.direction) url.searchParams.append('direction', params.direction) + if (params.per_page) url.searchParams.append('per_page', params.per_page.toString()) + if (params.page) url.searchParams.append('page', params.page.toString()) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/vnd.github.v3+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + }, + + transformResponse: async (response) => { + const prs = await response.json() + + const openCount = prs.filter((pr: any) => pr.state === 'open').length + const closedCount = prs.filter((pr: any) => pr.state === 'closed').length + + const content = `Found ${prs.length} pull request(s) +Open: ${openCount}, Closed: ${closedCount} + +${prs + .map( + (pr: any) => + `#${pr.number}: ${pr.title} (${pr.state}) + URL: ${pr.html_url}` + ) + .join('\n\n')}` + + return { + success: true, + output: { + content, + metadata: { + prs: prs.map((pr: any) => ({ + number: pr.number, + title: pr.title, + state: pr.state, + html_url: pr.html_url, + created_at: pr.created_at, + updated_at: pr.updated_at, + })), + total_count: prs.length, + open_count: openCount, + closed_count: closedCount, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable list of pull requests' }, + metadata: { + type: 'object', + description: 'Pull requests list metadata', + properties: { + prs: { + type: 'array', + description: 'Array of pull request summaries', + }, + total_count: { type: 'number', description: 'Total number of PRs returned' }, + open_count: { type: 'number', description: 'Number of open PRs' }, + closed_count: { type: 'number', description: 'Number of closed PRs' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/list_releases.ts b/apps/sim/tools/github/list_releases.ts new file mode 100644 index 0000000000..3e9b0889ae --- /dev/null +++ b/apps/sim/tools/github/list_releases.ts @@ -0,0 +1,137 @@ +import type { ListReleasesParams, ListReleasesResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const listReleasesTool: ToolConfig = { + id: 'github_list_releases', + name: 'GitHub List Releases', + description: + 'List all releases for a GitHub repository. Returns release information including tags, names, and download URLs.', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner (user or organization)', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + per_page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (max 100)', + default: 30, + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number of the results to fetch', + default: 1, + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub Personal Access Token', + }, + }, + + request: { + url: (params) => { + const url = new URL(`https://api.github.com/repos/${params.owner}/${params.repo}/releases`) + if (params.per_page) { + url.searchParams.append('per_page', params.per_page.toString()) + } + if (params.page) { + url.searchParams.append('page', params.page.toString()) + } + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + }, + + transformResponse: async (response) => { + const releases = await response.json() + + const totalReleases = releases.length + const releasesList = releases + .map( + (release: any, index: number) => + `${index + 1}. ${release.name || release.tag_name} (${release.tag_name}) + ${release.draft ? '[DRAFT] ' : ''}${release.prerelease ? '[PRERELEASE] ' : ''} + Published: ${release.published_at || 'Not published'} + URL: ${release.html_url}` + ) + .join('\n\n') + + const content = `Total releases: ${totalReleases} + +${releasesList} + +Summary of tags: ${releases.map((r: any) => r.tag_name).join(', ')}` + + return { + success: true, + output: { + content, + metadata: { + total_count: totalReleases, + releases: releases.map((release: any) => ({ + id: release.id, + tag_name: release.tag_name, + name: release.name || release.tag_name, + html_url: release.html_url, + tarball_url: release.tarball_url, + zipball_url: release.zipball_url, + draft: release.draft, + prerelease: release.prerelease, + created_at: release.created_at, + published_at: release.published_at, + })), + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable list of releases with summary' }, + metadata: { + type: 'object', + description: 'Releases metadata', + properties: { + total_count: { type: 'number', description: 'Total number of releases returned' }, + releases: { + type: 'array', + description: 'Array of release objects', + items: { + type: 'object', + properties: { + id: { type: 'number', description: 'Release ID' }, + tag_name: { type: 'string', description: 'Git tag name' }, + name: { type: 'string', description: 'Release name' }, + html_url: { type: 'string', description: 'GitHub web URL' }, + tarball_url: { type: 'string', description: 'Tarball download URL' }, + zipball_url: { type: 'string', description: 'Zipball download URL' }, + draft: { type: 'boolean', description: 'Is draft release' }, + prerelease: { type: 'boolean', description: 'Is prerelease' }, + created_at: { type: 'string', description: 'Creation timestamp' }, + published_at: { type: 'string', description: 'Publication timestamp' }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/list_workflow_runs.ts b/apps/sim/tools/github/list_workflow_runs.ts new file mode 100644 index 0000000000..28057bd7c0 --- /dev/null +++ b/apps/sim/tools/github/list_workflow_runs.ts @@ -0,0 +1,186 @@ +import type { ListWorkflowRunsParams, ListWorkflowRunsResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const listWorkflowRunsTool: ToolConfig = { + id: 'github_list_workflow_runs', + name: 'GitHub List Workflow Runs', + description: + 'List workflow runs for a repository. Supports filtering by actor, branch, event, and status. Returns run details including status, conclusion, and links.', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner (user or organization)', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + actor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by user who triggered the workflow', + }, + branch: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by branch name', + }, + event: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by event type (e.g., push, pull_request, workflow_dispatch)', + }, + status: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Filter by status (queued, in_progress, completed, waiting, requested, pending)', + }, + per_page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (default: 30, max: 100)', + default: 30, + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number of results to fetch (default: 1)', + default: 1, + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub Personal Access Token', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `https://api.github.com/repos/${params.owner}/${params.repo}/actions/runs` + ) + if (params.actor) { + url.searchParams.append('actor', params.actor) + } + if (params.branch) { + url.searchParams.append('branch', params.branch) + } + if (params.event) { + url.searchParams.append('event', params.event) + } + if (params.status) { + url.searchParams.append('status', params.status) + } + if (params.per_page) { + url.searchParams.append('per_page', params.per_page.toString()) + } + if (params.page) { + url.searchParams.append('page', params.page.toString()) + } + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + const statusCounts = data.workflow_runs.reduce((acc: Record, run: any) => { + acc[run.status] = (acc[run.status] || 0) + 1 + return acc + }, {}) + + const conclusionCounts = data.workflow_runs.reduce((acc: Record, 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' }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/list_workflows.ts b/apps/sim/tools/github/list_workflows.ts new file mode 100644 index 0000000000..db68d2df5d --- /dev/null +++ b/apps/sim/tools/github/list_workflows.ts @@ -0,0 +1,135 @@ +import type { ListWorkflowsParams, ListWorkflowsResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const listWorkflowsTool: ToolConfig = { + id: 'github_list_workflows', + name: 'GitHub List Workflows', + description: + 'List all workflows in a GitHub repository. Returns workflow details including ID, name, path, state, and badge URL.', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner (user or organization)', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + per_page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Number of results per page (default: 30, max: 100)', + default: 30, + }, + page: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Page number of results to fetch (default: 1)', + default: 1, + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub Personal Access Token', + }, + }, + + request: { + url: (params) => { + const url = new URL( + `https://api.github.com/repos/${params.owner}/${params.repo}/actions/workflows` + ) + if (params.per_page) { + url.searchParams.append('per_page', params.per_page.toString()) + } + if (params.page) { + url.searchParams.append('page', params.page.toString()) + } + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + const stateCounts = data.workflows.reduce((acc: Record, 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' }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/merge_pr.ts b/apps/sim/tools/github/merge_pr.ts new file mode 100644 index 0000000000..619c1169d0 --- /dev/null +++ b/apps/sim/tools/github/merge_pr.ts @@ -0,0 +1,122 @@ +import type { MergePRParams, MergeResultResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const mergePRTool: ToolConfig = { + 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 = {} + if (params.commit_title !== undefined) body.commit_title = params.commit_title + if (params.commit_message !== undefined) body.commit_message = params.commit_message + if (params.merge_method !== undefined) body.merge_method = params.merge_method + return body + }, + }, + + transformResponse: async (response) => { + if (response.status === 405) { + const error = await response.json() + return { + success: false, + error: error.message || 'Pull request is not mergeable', + output: { + content: '', + metadata: { + sha: '', + merged: false, + message: error.message || 'Pull request is not mergeable', + }, + }, + } + } + + const result = await response.json() + + const content = `PR merged successfully! +SHA: ${result.sha} +Message: ${result.message}` + + return { + success: true, + output: { + content, + metadata: { + sha: result.sha, + merged: result.merged, + message: result.message, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable merge confirmation' }, + metadata: { + type: 'object', + description: 'Merge result metadata', + properties: { + sha: { type: 'string', description: 'Merge commit SHA' }, + merged: { type: 'boolean', description: 'Whether merge was successful' }, + message: { type: 'string', description: 'Response message' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/pr.ts b/apps/sim/tools/github/pr.ts index 4b360060c6..c83f982384 100644 --- a/apps/sim/tools/github/pr.ts +++ b/apps/sim/tools/github/pr.ts @@ -47,17 +47,14 @@ export const prTool: ToolConfig = { transformResponse: async (response) => { const pr = await response.json() - // Fetch the PR diff const diffResponse = await fetch(pr.diff_url) const _diff = await diffResponse.text() - // Fetch files changed const filesResponse = await fetch( `https://api.github.com/repos/${pr.base.repo.owner.login}/${pr.base.repo.name}/pulls/${pr.number}/files` ) const files = await filesResponse.json() - // Create a human-readable content string const content = `PR #${pr.number}: "${pr.title}" (${pr.state}) - Created: ${pr.created_at}, Updated: ${pr.updated_at} Description: ${pr.body || 'No description'} Files changed: ${files.length} diff --git a/apps/sim/tools/github/remove_label.ts b/apps/sim/tools/github/remove_label.ts new file mode 100644 index 0000000000..e708060107 --- /dev/null +++ b/apps/sim/tools/github/remove_label.ts @@ -0,0 +1,102 @@ +import type { LabelsResponse, RemoveLabelParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const removeLabelTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/repo_info.ts b/apps/sim/tools/github/repo_info.ts index 23222ba4aa..78dc0dcbdd 100644 --- a/apps/sim/tools/github/repo_info.ts +++ b/apps/sim/tools/github/repo_info.ts @@ -42,7 +42,6 @@ export const repoInfoTool: ToolConfig = { transformResponse: async (response) => { const data = await response.json() - // Create a human-readable content string const content = `Repository: ${data.name} Description: ${data.description || 'No description'} Language: ${data.language || 'Not specified'} diff --git a/apps/sim/tools/github/request_reviewers.ts b/apps/sim/tools/github/request_reviewers.ts new file mode 100644 index 0000000000..71fcd5b9fb --- /dev/null +++ b/apps/sim/tools/github/request_reviewers.ts @@ -0,0 +1,147 @@ +import type { RequestReviewersParams, ReviewersResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const requestReviewersTool: ToolConfig = { + id: 'github_request_reviewers', + name: 'GitHub Request Reviewers', + description: 'Request reviewers for a pull request', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + pullNumber: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Pull request number', + }, + reviewers: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Comma-separated list of user logins to request reviews from', + }, + team_reviewers: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Comma-separated list of team slugs to request reviews from', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub API token', + }, + }, + + request: { + url: (params) => + `https://api.github.com/repos/${params.owner}/${params.repo}/pulls/${params.pullNumber}/requested_reviewers`, + method: 'POST', + headers: (params) => ({ + Accept: 'application/vnd.github.v3+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + body: (params) => { + const reviewersArray = params.reviewers + .split(',') + .map((r) => r.trim()) + .filter((r) => r) + const body: Record = { + 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' }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/rerun_workflow.ts b/apps/sim/tools/github/rerun_workflow.ts new file mode 100644 index 0000000000..42f6baad07 --- /dev/null +++ b/apps/sim/tools/github/rerun_workflow.ts @@ -0,0 +1,102 @@ +import type { RerunWorkflowParams, RerunWorkflowResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const rerunWorkflowTool: ToolConfig = { + 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)' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/trigger_workflow.ts b/apps/sim/tools/github/trigger_workflow.ts new file mode 100644 index 0000000000..c440c9ced5 --- /dev/null +++ b/apps/sim/tools/github/trigger_workflow.ts @@ -0,0 +1,85 @@ +import type { TriggerWorkflowParams, TriggerWorkflowResponse } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const triggerWorkflowTool: ToolConfig = { + 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)', + }, + }, +} diff --git a/apps/sim/tools/github/types.ts b/apps/sim/tools/github/types.ts index 7ed2bcb4b8..b4325b9f7f 100644 --- a/apps/sim/tools/github/types.ts +++ b/apps/sim/tools/github/types.ts @@ -28,6 +28,62 @@ export interface LatestCommitParams extends BaseGitHubParams { branch?: string } +// Create PR parameters +export interface CreatePRParams extends BaseGitHubParams { + title: string + head: string + base: string + body?: string + draft?: boolean +} + +// Update PR parameters +export interface UpdatePRParams extends BaseGitHubParams { + pullNumber: number + title?: string + body?: string + state?: 'open' | 'closed' + base?: string +} + +// Merge PR parameters +export interface MergePRParams extends BaseGitHubParams { + pullNumber: number + commit_title?: string + commit_message?: string + merge_method?: 'merge' | 'squash' | 'rebase' +} + +// List PRs parameters +export interface ListPRsParams extends BaseGitHubParams { + state?: 'open' | 'closed' | 'all' + head?: string + base?: string + sort?: 'created' | 'updated' | 'popularity' | 'long-running' + direction?: 'asc' | 'desc' + per_page?: number + page?: number +} + +// Get PR files parameters +export interface GetPRFilesParams extends BaseGitHubParams { + pullNumber: number + per_page?: number + page?: number +} + +// Close PR parameters +export interface ClosePRParams extends BaseGitHubParams { + pullNumber: number +} + +// Request reviewers parameters +export interface RequestReviewersParams extends BaseGitHubParams { + pullNumber: number + reviewers: string + team_reviewers?: string +} + // Response metadata interfaces interface BasePRMetadata { number: number @@ -119,6 +175,64 @@ interface RepoMetadata { language: string } +// PR operation response metadata +interface PRMetadata { + number: number + title: string + state: string + html_url: string + merged: boolean + draft: boolean + created_at?: string + updated_at?: string + merge_commit_sha?: string +} + +interface MergeResultMetadata { + sha: string + merged: boolean + message: string +} + +interface PRListMetadata { + prs: Array<{ + number: number + title: string + state: string + html_url: string + created_at: string + updated_at: string + }> + total_count: number + open_count?: number + closed_count?: number +} + +interface PRFilesListMetadata { + files: Array<{ + filename: string + status: string + additions: number + deletions: number + changes: number + patch?: string + blob_url: string + raw_url: string + }> + total_count: number +} + +interface ReviewersMetadata { + requested_reviewers: Array<{ + login: string + id: number + }> + requested_teams?: Array<{ + name: string + id: number + }> +} + // Response types export interface PullRequestResponse extends ToolResponse { output: { @@ -148,8 +262,771 @@ export interface RepoInfoResponse extends ToolResponse { } } +// Issue comment operation parameters +export interface CreateIssueCommentParams extends BaseGitHubParams { + issue_number: number + body: string +} + +export interface ListIssueCommentsParams extends BaseGitHubParams { + issue_number: number + since?: string + per_page?: number + page?: number +} + +export interface UpdateCommentParams extends BaseGitHubParams { + comment_id: number + body: string +} + +export interface DeleteCommentParams extends BaseGitHubParams { + comment_id: number +} + +export interface ListPRCommentsParams extends BaseGitHubParams { + pullNumber: number + sort?: 'created' | 'updated' + direction?: 'asc' | 'desc' + since?: string + per_page?: number + page?: number +} + +// Branch operation parameters +export interface ListBranchesParams extends BaseGitHubParams { + protected?: boolean + per_page?: number + page?: number +} + +export interface GetBranchParams extends BaseGitHubParams { + branch: string +} + +export interface CreateBranchParams extends BaseGitHubParams { + branch: string + sha: string // commit SHA to point to +} + +export interface DeleteBranchParams extends BaseGitHubParams { + branch: string +} + +export interface GetBranchProtectionParams extends BaseGitHubParams { + branch: string +} + +export interface UpdateBranchProtectionParams extends BaseGitHubParams { + branch: string + required_status_checks: { + strict: boolean + contexts: string[] + } | null + enforce_admins: boolean + required_pull_request_reviews: { + required_approving_review_count?: number + dismiss_stale_reviews?: boolean + require_code_owner_reviews?: boolean + } | null + restrictions: { + users: string[] + teams: string[] + } | null +} + +// Issue comment response metadata +interface IssueCommentMetadata { + id: number + html_url: string + body: string + created_at: string + updated_at: string + user: { + login: string + id: number + } +} + +interface CommentsListMetadata { + comments: Array<{ + id: number + body: string + user: { login: string } + created_at: string + html_url: string + }> + total_count: number +} + +// Response types for new tools +export interface IssueCommentResponse extends ToolResponse { + output: { + content: string + metadata: IssueCommentMetadata + } +} + +export interface CommentsListResponse extends ToolResponse { + output: { + content: string + metadata: CommentsListMetadata + } +} + +export interface DeleteCommentResponse extends ToolResponse { + output: { + content: string + metadata: { + deleted: boolean + comment_id: number + } + } +} + +// New PR operation response types +export interface PRResponse extends ToolResponse { + output: { + content: string + metadata: PRMetadata + } +} + +export interface MergeResultResponse extends ToolResponse { + output: { + content: string + metadata: MergeResultMetadata + } +} + +export interface PRListResponse extends ToolResponse { + output: { + content: string + metadata: PRListMetadata + } +} + +export interface PRFilesListResponse extends ToolResponse { + output: { + content: string + metadata: PRFilesListMetadata + } +} + +export interface ReviewersResponse extends ToolResponse { + output: { + content: string + metadata: ReviewersMetadata + } +} + +// Branch response metadata +interface BranchMetadata { + name: string + commit: { + sha: string + url: string + } + protected: boolean +} + +interface BranchListMetadata { + branches: Array<{ + name: string + commit: { + sha: string + url: string + } + protected: boolean + }> + total_count: number +} + +interface BranchProtectionMetadata { + required_status_checks: { + strict: boolean + contexts: string[] + } | null + enforce_admins: { + enabled: boolean + } + required_pull_request_reviews: { + required_approving_review_count: number + dismiss_stale_reviews: boolean + require_code_owner_reviews: boolean + } | null + restrictions: { + users: string[] + teams: string[] + } | null +} + +interface RefMetadata { + ref: string + url: string + sha: string +} + +interface DeleteBranchMetadata { + deleted: boolean + branch: string +} + +// Branch response types +export interface BranchResponse extends ToolResponse { + output: { + content: string + metadata: BranchMetadata + } +} + +export interface BranchListResponse extends ToolResponse { + output: { + content: string + metadata: BranchListMetadata + } +} + +export interface BranchProtectionResponse extends ToolResponse { + output: { + content: string + metadata: BranchProtectionMetadata + } +} + +export interface RefResponse extends ToolResponse { + output: { + content: string + metadata: RefMetadata + } +} + +export interface DeleteBranchResponse extends ToolResponse { + output: { + content: string + metadata: DeleteBranchMetadata + } +} + +// GitHub Projects V2 parameters +export interface ListProjectsParams { + owner_type: 'org' | 'user' + owner_login: string + apiKey: string +} + +export interface GetProjectParams { + owner_type: 'org' | 'user' + owner_login: string + project_number: number + apiKey: string +} + +export interface CreateProjectParams { + owner_id: string // Node ID + title: string + apiKey: string +} + +export interface UpdateProjectParams { + project_id: string // Node ID + title?: string + shortDescription?: string + project_public?: boolean + closed?: boolean + apiKey: string +} + +export interface DeleteProjectParams { + project_id: string + apiKey: string +} + +// GitHub Projects V2 response metadata +interface ProjectMetadata { + id: string + title: string + number?: number + url: string + closed?: boolean + public?: boolean + shortDescription?: string +} + +// GitHub Projects V2 response types +export interface ListProjectsResponse extends ToolResponse { + output: { + content: string + metadata: { + projects: ProjectMetadata[] + totalCount: number + } + } +} + +export interface ProjectResponse extends ToolResponse { + output: { + content: string + metadata: ProjectMetadata + } +} + +// Workflow operation parameters +export interface ListWorkflowsParams extends BaseGitHubParams { + per_page?: number + page?: number +} + +export interface GetWorkflowParams extends BaseGitHubParams { + workflow_id: number | string +} + +export interface TriggerWorkflowParams extends BaseGitHubParams { + workflow_id: number | string + ref: string // branch or tag name + inputs?: Record +} + +export interface ListWorkflowRunsParams extends BaseGitHubParams { + actor?: string + branch?: string + event?: string + status?: string + per_page?: number + page?: number +} + +export interface GetWorkflowRunParams extends BaseGitHubParams { + run_id: number +} + +export interface CancelWorkflowRunParams extends BaseGitHubParams { + run_id: number +} + +export interface RerunWorkflowParams extends BaseGitHubParams { + run_id: number + enable_debug_logging?: boolean +} + +// Workflow response metadata interfaces +interface WorkflowMetadata { + id: number + name: string + path: string + state: string + badge_url: string +} + +interface WorkflowRunMetadata { + id: number + name: string + status: string + conclusion: string + html_url: string + run_number: number +} + +interface ListWorkflowsMetadata { + total_count: number + workflows: Array<{ + id: number + name: string + path: string + state: string + badge_url: string + }> +} + +interface ListWorkflowRunsMetadata { + total_count: number + workflow_runs: Array<{ + id: number + name: string + status: string + conclusion: string + html_url: string + run_number: number + }> +} + +// Workflow response types +export interface WorkflowResponse extends ToolResponse { + output: { + content: string + metadata: WorkflowMetadata + } +} + +export interface WorkflowRunResponse extends ToolResponse { + output: { + content: string + metadata: WorkflowRunMetadata + } +} + +export interface ListWorkflowsResponse extends ToolResponse { + output: { + content: string + metadata: ListWorkflowsMetadata + } +} + +export interface ListWorkflowRunsResponse extends ToolResponse { + output: { + content: string + metadata: ListWorkflowRunsMetadata + } +} + +export interface TriggerWorkflowResponse extends ToolResponse { + output: { + content: string + metadata: Record + } +} + +export interface CancelWorkflowRunResponse extends ToolResponse { + output: { + content: string + metadata: { + run_id: number + status: string + } + } +} + +export interface RerunWorkflowResponse extends ToolResponse { + output: { + content: string + metadata: { + run_id: number + status: string + } + } +} + export type GitHubResponse = | PullRequestResponse | CreateCommentResponse | LatestCommitResponse | RepoInfoResponse + | IssueCommentResponse + | CommentsListResponse + | DeleteCommentResponse + | PRResponse + | MergeResultResponse + | PRListResponse + | PRFilesListResponse + | ReviewersResponse + | ListProjectsResponse + | ProjectResponse + | BranchResponse + | BranchListResponse + | BranchProtectionResponse + | RefResponse + | DeleteBranchResponse + | WorkflowResponse + | WorkflowRunResponse + | ListWorkflowsResponse + | ListWorkflowRunsResponse + | TriggerWorkflowResponse + | CancelWorkflowRunResponse + | RerunWorkflowResponse + | IssueResponse + | IssuesListResponse + | LabelsResponse + +// Release operation parameters +export interface CreateReleaseParams extends BaseGitHubParams { + tag_name: string + target_commitish?: string + name?: string + body?: string + draft?: boolean + prerelease?: boolean +} + +export interface UpdateReleaseParams extends BaseGitHubParams { + release_id: number + tag_name?: string + target_commitish?: string + name?: string + body?: string + draft?: boolean + prerelease?: boolean +} + +export interface ListReleasesParams extends BaseGitHubParams { + per_page?: number + page?: number +} + +export interface GetReleaseParams extends BaseGitHubParams { + release_id: number +} + +export interface DeleteReleaseParams extends BaseGitHubParams { + release_id: number +} + +// Release metadata interface +interface ReleaseMetadata { + id: number + tag_name: string + name: string + html_url: string + tarball_url: string + zipball_url: string + draft: boolean + prerelease: boolean + created_at: string + published_at: string +} + +// Response types for releases +export interface ReleaseResponse extends ToolResponse { + output: { + content: string + metadata: ReleaseMetadata + } +} + +export interface ListReleasesResponse extends ToolResponse { + output: { + content: string + metadata: { + total_count: number + releases: Array + } + } +} + +export interface DeleteReleaseResponse extends ToolResponse { + output: { + content: string + metadata: { + deleted: boolean + release_id: number + } + } +} + +// Issue operation parameters +export interface CreateIssueParams extends BaseGitHubParams { + title: string + body?: string + assignees?: string + labels?: string + milestone?: number +} + +export interface UpdateIssueParams extends BaseGitHubParams { + issue_number: number + title?: string + body?: string + state?: 'open' | 'closed' + labels?: string[] + assignees?: string[] +} + +export interface ListIssuesParams extends BaseGitHubParams { + state?: 'open' | 'closed' | 'all' + assignee?: string + creator?: string + labels?: string + sort?: 'created' | 'updated' | 'comments' + direction?: 'asc' | 'desc' + per_page?: number + page?: number +} + +export interface GetIssueParams extends BaseGitHubParams { + issue_number: number +} + +export interface CloseIssueParams extends BaseGitHubParams { + issue_number: number + state_reason?: 'completed' | 'not_planned' +} + +export interface AddLabelsParams extends BaseGitHubParams { + issue_number: number + labels: string +} + +export interface RemoveLabelParams extends BaseGitHubParams { + issue_number: number + name: string +} + +export interface AddAssigneesParams extends BaseGitHubParams { + issue_number: number + assignees: string +} + +// Issue response metadata +interface IssueMetadata { + number: number + title: string + state: string + html_url: string + labels: string[] + assignees: string[] + created_at?: string + updated_at?: string + closed_at?: string + body?: string +} + +interface IssuesListMetadata { + issues: Array<{ + number: number + title: string + state: string + html_url: string + labels: string[] + assignees: string[] + created_at: string + updated_at: string + }> + total_count: number + page?: number +} + +interface LabelsMetadata { + labels: string[] + issue_number: number + html_url: string +} + +// Issue response types +export interface IssueResponse extends ToolResponse { + output: { + content: string + metadata: IssueMetadata + } +} + +export interface IssuesListResponse extends ToolResponse { + output: { + content: string + metadata: IssuesListMetadata + } +} + +export interface LabelsResponse extends ToolResponse { + output: { + content: string + metadata: LabelsMetadata + } +} + +export interface GetFileContentParams extends BaseGitHubParams { + path: string + ref?: string // branch, tag, or commit SHA +} + +export interface CreateFileParams extends BaseGitHubParams { + path: string + message: string + content: string // Plain text (will be Base64 encoded internally) + branch?: string +} + +export interface UpdateFileParams extends BaseGitHubParams { + path: string + message: string + content: string // Plain text (will be Base64 encoded internally) + sha: string // Required for update + branch?: string +} + +export interface DeleteFileParams extends BaseGitHubParams { + path: string + message: string + sha: string // Required + branch?: string +} + +export interface GetTreeParams extends BaseGitHubParams { + path?: string + ref?: string +} + +// File/Content metadata interfaces +export interface FileContentMetadata { + name: string + path: string + sha: string + size: number + type: 'file' | 'dir' + download_url?: string + html_url?: string +} + +export interface FileCommitMetadata { + sha: string + message: string + author: { + name: string + email: string + date: string + } + committer: { + name: string + email: string + date: string + } + html_url: string +} + +export interface TreeItemMetadata { + name: string + path: string + sha: string + size: number + type: 'file' | 'dir' | 'symlink' | 'submodule' + download_url?: string + html_url?: string +} + +// Response types +export interface FileContentResponse extends ToolResponse { + output: { + content: string + metadata: FileContentMetadata + } +} + +export interface FileOperationResponse extends ToolResponse { + output: { + content: string + metadata: { + file: FileContentMetadata + commit: FileCommitMetadata + } + } +} + +export interface DeleteFileResponse extends ToolResponse { + output: { + content: string + metadata: { + deleted: boolean + path: string + commit: FileCommitMetadata + } + } +} + +export interface TreeResponse extends ToolResponse { + output: { + content: string + metadata: { + path: string + items: TreeItemMetadata[] + total_count: number + } + } +} diff --git a/apps/sim/tools/github/update_branch_protection.ts b/apps/sim/tools/github/update_branch_protection.ts new file mode 100644 index 0000000000..c49b277511 --- /dev/null +++ b/apps/sim/tools/github/update_branch_protection.ts @@ -0,0 +1,221 @@ +import type { BranchProtectionResponse, UpdateBranchProtectionParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const updateBranchProtectionTool: ToolConfig< + UpdateBranchProtectionParams, + BranchProtectionResponse +> = { + id: 'github_update_branch_protection', + name: 'GitHub Update Branch Protection', + description: + 'Update branch protection rules for a specific branch, including status checks, review requirements, admin enforcement, and push restrictions.', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner (user or organization)', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + branch: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Branch name', + }, + required_status_checks: { + type: 'object', + required: true, + visibility: 'user-or-llm', + description: + 'Required status check configuration (null to disable). Object with strict (boolean) and contexts (string array)', + }, + enforce_admins: { + type: 'boolean', + required: true, + visibility: 'user-or-llm', + description: 'Whether to enforce restrictions for administrators', + }, + required_pull_request_reviews: { + type: 'object', + required: true, + visibility: 'user-or-llm', + description: + 'PR review requirements (null to disable). Object with optional required_approving_review_count, dismiss_stale_reviews, require_code_owner_reviews', + }, + restrictions: { + type: 'object', + required: true, + visibility: 'user-or-llm', + description: + 'Push restrictions (null to disable). Object with users (string array) and teams (string array)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub Personal Access Token', + }, + }, + + request: { + url: (params) => + `https://api.github.com/repos/${params.owner}/${params.repo}/branches/${params.branch}/protection`, + method: 'PUT', + headers: (params) => ({ + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + 'Content-Type': 'application/json', + }), + body: (params) => { + const body: any = { + required_status_checks: params.required_status_checks, + enforce_admins: params.enforce_admins, + required_pull_request_reviews: params.required_pull_request_reviews, + restrictions: params.restrictions, + } + + return body + }, + }, + + transformResponse: async (response) => { + const protection = await response.json() + + let content = `Branch Protection updated successfully for "${protection.url.split('/branches/')[1].split('/protection')[0]}": + +Enforce Admins: ${protection.enforce_admins?.enabled ? 'Yes' : 'No'}` + + if (protection.required_status_checks) { + content += `\n\nRequired Status Checks: +- Strict: ${protection.required_status_checks.strict} +- Contexts: ${protection.required_status_checks.contexts.length > 0 ? protection.required_status_checks.contexts.join(', ') : 'None'}` + } else { + content += '\n\nRequired Status Checks: Disabled' + } + + if (protection.required_pull_request_reviews) { + content += `\n\nRequired Pull Request Reviews: +- Required Approving Reviews: ${protection.required_pull_request_reviews.required_approving_review_count || 0} +- Dismiss Stale Reviews: ${protection.required_pull_request_reviews.dismiss_stale_reviews ? 'Yes' : 'No'} +- Require Code Owner Reviews: ${protection.required_pull_request_reviews.require_code_owner_reviews ? 'Yes' : 'No'}` + } else { + content += '\n\nRequired Pull Request Reviews: Disabled' + } + + if (protection.restrictions) { + const users = protection.restrictions.users?.map((u: any) => u.login) || [] + const teams = protection.restrictions.teams?.map((t: any) => t.slug) || [] + content += `\n\nRestrictions: +- Users: ${users.length > 0 ? users.join(', ') : 'None'} +- Teams: ${teams.length > 0 ? teams.join(', ') : 'None'}` + } else { + content += '\n\nRestrictions: Disabled' + } + + return { + success: true, + output: { + content, + metadata: { + required_status_checks: protection.required_status_checks + ? { + strict: protection.required_status_checks.strict, + contexts: protection.required_status_checks.contexts, + } + : null, + enforce_admins: { + enabled: protection.enforce_admins?.enabled || false, + }, + required_pull_request_reviews: protection.required_pull_request_reviews + ? { + required_approving_review_count: + protection.required_pull_request_reviews.required_approving_review_count || 0, + dismiss_stale_reviews: + protection.required_pull_request_reviews.dismiss_stale_reviews || false, + require_code_owner_reviews: + protection.required_pull_request_reviews.require_code_owner_reviews || false, + } + : null, + restrictions: protection.restrictions + ? { + users: protection.restrictions.users?.map((u: any) => u.login) || [], + teams: protection.restrictions.teams?.map((t: any) => t.slug) || [], + } + : null, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable branch protection update summary' }, + metadata: { + type: 'object', + description: 'Updated branch protection configuration', + properties: { + required_status_checks: { + type: 'object', + description: 'Status check requirements (null if disabled)', + properties: { + strict: { type: 'boolean', description: 'Require branches to be up to date' }, + contexts: { + type: 'array', + description: 'Required status check contexts', + items: { type: 'string' }, + }, + }, + }, + enforce_admins: { + type: 'object', + description: 'Admin enforcement settings', + properties: { + enabled: { type: 'boolean', description: 'Enforce for administrators' }, + }, + }, + required_pull_request_reviews: { + type: 'object', + description: 'Pull request review requirements (null if disabled)', + properties: { + required_approving_review_count: { + type: 'number', + description: 'Number of approving reviews required', + }, + dismiss_stale_reviews: { + type: 'boolean', + description: 'Dismiss stale pull request approvals', + }, + require_code_owner_reviews: { + type: 'boolean', + description: 'Require review from code owners', + }, + }, + }, + restrictions: { + type: 'object', + description: 'Push restrictions (null if disabled)', + properties: { + users: { + type: 'array', + description: 'Users who can push', + items: { type: 'string' }, + }, + teams: { + type: 'array', + description: 'Teams who can push', + items: { type: 'string' }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/update_comment.ts b/apps/sim/tools/github/update_comment.ts new file mode 100644 index 0000000000..75116b8d68 --- /dev/null +++ b/apps/sim/tools/github/update_comment.ts @@ -0,0 +1,103 @@ +import type { IssueCommentResponse, UpdateCommentParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const updateCommentTool: ToolConfig = { + id: 'github_update_comment', + name: 'GitHub Comment Updater', + description: 'Update an existing comment on a GitHub issue or pull request', + version: '1.0.0', + + params: { + owner: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository owner', + }, + repo: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Repository name', + }, + comment_id: { + type: 'number', + required: true, + visibility: 'user-or-llm', + description: 'Comment ID', + }, + body: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'Updated comment content', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'GitHub API token', + }, + }, + + request: { + url: (params) => + `https://api.github.com/repos/${params.owner}/${params.repo}/issues/comments/${params.comment_id}`, + method: 'PATCH', + headers: (params) => ({ + Accept: 'application/vnd.github+json', + Authorization: `Bearer ${params.apiKey}`, + 'X-GitHub-Api-Version': '2022-11-28', + }), + body: (params) => ({ + body: params.body, + }), + }, + + transformResponse: async (response) => { + const data = await response.json() + + const content = `Comment #${data.id} updated: "${data.body.substring(0, 100)}${data.body.length > 100 ? '...' : ''}"` + + return { + success: true, + output: { + content, + metadata: { + id: data.id, + html_url: data.html_url, + body: data.body, + created_at: data.created_at, + updated_at: data.updated_at, + user: { + login: data.user.login, + id: data.user.id, + }, + }, + }, + } + }, + + outputs: { + content: { type: 'string', description: 'Human-readable update confirmation' }, + metadata: { + type: 'object', + description: 'Updated comment metadata', + properties: { + id: { type: 'number', description: 'Comment ID' }, + html_url: { type: 'string', description: 'GitHub web URL' }, + body: { type: 'string', description: 'Updated comment body' }, + created_at: { type: 'string', description: 'Creation timestamp' }, + updated_at: { type: 'string', description: 'Last update timestamp' }, + user: { + type: 'object', + description: 'User who created the comment', + properties: { + login: { type: 'string', description: 'User login' }, + id: { type: 'number', description: 'User ID' }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/update_file.ts b/apps/sim/tools/github/update_file.ts new file mode 100644 index 0000000000..4d4d6fde2f --- /dev/null +++ b/apps/sim/tools/github/update_file.ts @@ -0,0 +1,179 @@ +import type { FileOperationResponse, UpdateFileParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const updateFileTool: ToolConfig = { + 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 = { + 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' }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/update_issue.ts b/apps/sim/tools/github/update_issue.ts new file mode 100644 index 0000000000..0beb675051 --- /dev/null +++ b/apps/sim/tools/github/update_issue.ts @@ -0,0 +1,139 @@ +import type { IssueResponse, UpdateIssueParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const updateIssueTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/update_pr.ts b/apps/sim/tools/github/update_pr.ts new file mode 100644 index 0000000000..85e302bb19 --- /dev/null +++ b/apps/sim/tools/github/update_pr.ts @@ -0,0 +1,121 @@ +import type { PRResponse, UpdatePRParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const updatePRTool: ToolConfig = { + 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 = {} + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/update_project.ts b/apps/sim/tools/github/update_project.ts new file mode 100644 index 0000000000..28968a8ca5 --- /dev/null +++ b/apps/sim/tools/github/update_project.ts @@ -0,0 +1,192 @@ +import type { ProjectResponse, UpdateProjectParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const updateProjectTool: ToolConfig = { + 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 = { + 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, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/github/update_release.ts b/apps/sim/tools/github/update_release.ts new file mode 100644 index 0000000000..4e233cf5a2 --- /dev/null +++ b/apps/sim/tools/github/update_release.ts @@ -0,0 +1,161 @@ +import type { ReleaseResponse, UpdateReleaseParams } from '@/tools/github/types' +import type { ToolConfig } from '@/tools/types' + +export const updateReleaseTool: ToolConfig = { + 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' }, + }, + }, + }, +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index f16995b71b..274f21786d 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -27,10 +27,58 @@ import { fileParseTool } from '@/tools/file' import { crawlTool, scrapeTool, searchTool } from '@/tools/firecrawl' import { functionExecuteTool } from '@/tools/function' import { + githubAddAssigneesTool, + githubAddLabelsTool, + githubCancelWorkflowRunTool, + githubCloseIssueTool, + githubClosePRTool, githubCommentTool, + githubCreateBranchTool, + githubCreateFileTool, + githubCreateIssueTool, + githubCreatePRTool, + githubCreateProjectTool, + githubCreateReleaseTool, + githubDeleteBranchTool, + githubDeleteCommentTool, + githubDeleteFileTool, + githubDeleteProjectTool, + githubDeleteReleaseTool, + githubGetBranchProtectionTool, + githubGetBranchTool, + githubGetFileContentTool, + githubGetIssueTool, + githubGetPRFilesTool, + githubGetProjectTool, + githubGetReleaseTool, + githubGetTreeTool, + githubGetWorkflowRunTool, + githubGetWorkflowTool, + githubIssueCommentTool, githubLatestCommitTool, + githubListBranchesTool, + githubListIssueCommentsTool, + githubListIssuesTool, + githubListPRCommentsTool, + githubListPRsTool, + githubListProjectsTool, + githubListReleasesTool, + githubListWorkflowRunsTool, + githubListWorkflowsTool, + githubMergePRTool, githubPrTool, + githubRemoveLabelTool, githubRepoInfoTool, + githubRequestReviewersTool, + githubRerunWorkflowTool, + githubTriggerWorkflowTool, + githubUpdateBranchProtectionTool, + githubUpdateCommentTool, + githubUpdateFileTool, + githubUpdateIssueTool, + githubUpdatePRTool, + githubUpdateProjectTool, + githubUpdateReleaseTool, } from '@/tools/github' import { gmailAddLabelTool, @@ -391,6 +439,54 @@ export const tools: Record = { mysql_execute: mysqlExecuteTool, github_pr: githubPrTool, github_comment: githubCommentTool, + github_issue_comment: githubIssueCommentTool, + github_list_issue_comments: githubListIssueCommentsTool, + github_update_comment: githubUpdateCommentTool, + github_delete_comment: githubDeleteCommentTool, + github_list_pr_comments: githubListPRCommentsTool, + github_create_pr: githubCreatePRTool, + github_update_pr: githubUpdatePRTool, + github_merge_pr: githubMergePRTool, + github_list_prs: githubListPRsTool, + github_get_pr_files: githubGetPRFilesTool, + github_close_pr: githubClosePRTool, + github_request_reviewers: githubRequestReviewersTool, + github_get_file_content: githubGetFileContentTool, + github_create_file: githubCreateFileTool, + github_update_file: githubUpdateFileTool, + github_delete_file: githubDeleteFileTool, + github_get_tree: githubGetTreeTool, + github_list_branches: githubListBranchesTool, + github_get_branch: githubGetBranchTool, + github_create_branch: githubCreateBranchTool, + github_delete_branch: githubDeleteBranchTool, + github_get_branch_protection: githubGetBranchProtectionTool, + github_update_branch_protection: githubUpdateBranchProtectionTool, + github_create_issue: githubCreateIssueTool, + github_update_issue: githubUpdateIssueTool, + github_list_issues: githubListIssuesTool, + github_get_issue: githubGetIssueTool, + github_close_issue: githubCloseIssueTool, + github_add_labels: githubAddLabelsTool, + github_remove_label: githubRemoveLabelTool, + github_add_assignees: githubAddAssigneesTool, + github_create_release: githubCreateReleaseTool, + github_update_release: githubUpdateReleaseTool, + github_list_releases: githubListReleasesTool, + github_get_release: githubGetReleaseTool, + github_delete_release: githubDeleteReleaseTool, + github_list_workflows: githubListWorkflowsTool, + github_get_workflow: githubGetWorkflowTool, + github_trigger_workflow: githubTriggerWorkflowTool, + github_list_workflow_runs: githubListWorkflowRunsTool, + github_get_workflow_run: githubGetWorkflowRunTool, + github_cancel_workflow_run: githubCancelWorkflowRunTool, + github_rerun_workflow: githubRerunWorkflowTool, + github_list_projects: githubListProjectsTool, + github_get_project: githubGetProjectTool, + github_create_project: githubCreateProjectTool, + github_update_project: githubUpdateProjectTool, + github_delete_project: githubDeleteProjectTool, exa_search: exaSearchTool, exa_get_contents: exaGetContentsTool, exa_find_similar_links: exaFindSimilarLinksTool, diff --git a/apps/sim/triggers/github/index.ts b/apps/sim/triggers/github/index.ts index 3466962ad8..ead08a3495 100644 --- a/apps/sim/triggers/github/index.ts +++ b/apps/sim/triggers/github/index.ts @@ -1 +1,12 @@ +export { githubIssueClosedTrigger } from './issue_closed' +export { githubIssueCommentTrigger } from './issue_comment' +export { githubIssueOpenedTrigger } from './issue_opened' +export { githubPRClosedTrigger } from './pr_closed' +export { githubPRCommentTrigger } from './pr_comment' +export { githubPRMergedTrigger } from './pr_merged' +export { githubPROpenedTrigger } from './pr_opened' +export { githubPRReviewedTrigger } from './pr_reviewed' +export { githubPushTrigger } from './push' +export { githubReleasePublishedTrigger } from './release_published' export { githubWebhookTrigger } from './webhook' +export { githubWorkflowRunTrigger } from './workflow_run' diff --git a/apps/sim/triggers/github/issue_closed.ts b/apps/sim/triggers/github/issue_closed.ts new file mode 100644 index 0000000000..3267d60a69 --- /dev/null +++ b/apps/sim/triggers/github/issue_closed.ts @@ -0,0 +1,401 @@ +import { GithubIcon } from '@/components/icons' +import type { TriggerConfig } from '@/triggers/types' + +export const githubIssueClosedTrigger: TriggerConfig = { + id: 'github_issue_closed', + name: 'GitHub Issue Closed', + provider: 'github', + description: 'Trigger workflow when an issue is closed in a GitHub repository', + version: '1.0.0', + icon: GithubIcon, + + subBlocks: [ + { + id: 'webhookUrlDisplay', + title: 'Webhook URL', + type: 'short-input', + readOnly: true, + showCopyButton: true, + useWebhookUrl: true, + placeholder: 'Webhook URL will be generated', + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_closed', + }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { + label: 'application/x-www-form-urlencoded', + id: 'application/x-www-form-urlencoded', + }, + ], + defaultValue: 'application/json', + description: 'Format GitHub will use when sending the webhook payload.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_closed', + }, + }, + { + id: 'webhookSecret', + title: 'Webhook Secret', + type: 'short-input', + placeholder: 'Generate or enter a strong secret', + description: 'Validates that webhook deliveries originate from GitHub.', + password: true, + required: false, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_closed', + }, + }, + { + id: 'sslVerification', + title: 'SSL Verification', + type: 'dropdown', + options: [ + { label: 'Enabled', id: 'enabled' }, + { label: 'Disabled', id: 'disabled' }, + ], + defaultValue: 'enabled', + description: 'GitHub verifies SSL certificates when delivering webhooks.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_closed', + }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + type: 'text', + defaultValue: [ + 'Go to your GitHub Repository > Settings > Webhooks.', + 'Click "Add webhook".', + 'Paste the Webhook URL above into the "Payload URL" field.', + 'Select your chosen Content Type from the dropdown.', + 'Enter the Webhook Secret into the "Secret" field if you\'ve configured one.', + 'Set SSL verification according to your selection.', + 'Select "Let me select individual events" and check issues (closed action).', + 'Ensure "Active" is checked and click "Add webhook".', + ] + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join(''), + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_closed', + }, + }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'github_issue_closed', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_closed', + }, + }, + { + id: 'samplePayload', + title: 'Event Payload Example', + type: 'code', + language: 'json', + defaultValue: JSON.stringify( + { + action: 'closed', + issue: { + id: 1234567890, + number: 123, + title: 'Bug: Application crashes on startup', + body: 'When I try to start the application, it immediately crashes with error code 500.', + state: 'closed', + state_reason: 'completed', + html_url: 'https://github.com/owner/repo/issues/123', + user: { + login: 'octocat', + id: 1, + avatar_url: 'https://github.com/images/error/octocat_happy.gif', + html_url: 'https://github.com/octocat', + user_type: 'User', + }, + labels: [ + { + name: 'bug', + color: 'd73a4a', + }, + ], + assignees: [], + created_at: '2025-01-15T10:30:00Z', + updated_at: '2025-01-15T14:20:00Z', + closed_at: '2025-01-15T14:20:00Z', + }, + repository: { + id: 123456, + name: 'repo-name', + full_name: 'owner/repo-name', + html_url: 'https://github.com/owner/repo-name', + repo_description: 'A sample repository', + private: false, + owner: { + login: 'owner', + id: 7890, + avatar_url: 'https://github.com/images/error/owner.gif', + owner_type: 'User', + }, + }, + sender: { + login: 'maintainer', + id: 2, + avatar_url: 'https://github.com/images/error/maintainer.gif', + user_type: 'User', + }, + }, + null, + 2 + ), + readOnly: true, + collapsible: true, + defaultCollapsed: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_closed', + }, + }, + ], + + outputs: { + action: { + type: 'string', + description: 'Action performed (opened, closed, reopened, edited, etc.)', + }, + issue: { + id: { + type: 'number', + description: 'Issue ID', + }, + node_id: { + type: 'string', + description: 'Issue node ID', + }, + number: { + type: 'number', + description: 'Issue number', + }, + title: { + type: 'string', + description: 'Issue title', + }, + body: { + type: 'string', + description: 'Issue body/description', + }, + state: { + type: 'string', + description: 'Issue state (open, closed)', + }, + state_reason: { + type: 'string', + description: 'Reason for state (completed, not_planned, reopened)', + }, + html_url: { + type: 'string', + description: 'Issue HTML URL', + }, + user: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + labels: { + type: 'array', + description: 'Array of label objects', + }, + assignees: { + type: 'array', + description: 'Array of assigned users', + }, + milestone: { + type: 'object', + description: 'Milestone object if assigned', + }, + created_at: { + type: 'string', + description: 'Issue creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Issue last update timestamp', + }, + closed_at: { + type: 'string', + description: 'Issue closed timestamp', + }, + }, + repository: { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name (owner/repo)', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + html_url: { + type: 'string', + description: 'Repository HTML URL', + }, + repo_description: { + type: 'string', + description: 'Repository description', + }, + fork: { + type: 'boolean', + description: 'Whether the repository is a fork', + }, + url: { + type: 'string', + description: 'Repository API URL', + }, + homepage: { + type: 'string', + description: 'Repository homepage URL', + }, + size: { + type: 'number', + description: 'Repository size in KB', + }, + stargazers_count: { + type: 'number', + description: 'Number of stars', + }, + watchers_count: { + type: 'number', + description: 'Number of watchers', + }, + language: { + type: 'string', + description: 'Primary programming language', + }, + forks_count: { + type: 'number', + description: 'Number of forks', + }, + open_issues_count: { + type: 'number', + description: 'Number of open issues', + }, + default_branch: { + type: 'string', + description: 'Default branch name', + }, + owner: { + login: { + type: 'string', + description: 'Owner username', + }, + id: { + type: 'number', + description: 'Owner ID', + }, + avatar_url: { + type: 'string', + description: 'Owner avatar URL', + }, + html_url: { + type: 'string', + description: 'Owner profile URL', + }, + owner_type: { + type: 'string', + description: 'Owner type (User, Organization)', + }, + }, + }, + sender: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + }, + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GitHub-Event': 'issues', + 'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'X-Hub-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/github/issue_comment.ts b/apps/sim/triggers/github/issue_comment.ts new file mode 100644 index 0000000000..1e5e49f9be --- /dev/null +++ b/apps/sim/triggers/github/issue_comment.ts @@ -0,0 +1,409 @@ +import { GithubIcon } from '@/components/icons' +import type { TriggerConfig } from '@/triggers/types' + +export const githubIssueCommentTrigger: TriggerConfig = { + id: 'github_issue_comment', + name: 'GitHub Issue Comment', + provider: 'github', + description: 'Trigger workflow when a comment is added to an issue (not pull requests)', + version: '1.0.0', + icon: GithubIcon, + + subBlocks: [ + { + id: 'webhookUrlDisplay', + title: 'Webhook URL', + type: 'short-input', + readOnly: true, + showCopyButton: true, + useWebhookUrl: true, + placeholder: 'Webhook URL will be generated', + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_comment', + }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { + label: 'application/x-www-form-urlencoded', + id: 'application/x-www-form-urlencoded', + }, + ], + defaultValue: 'application/json', + description: 'Format GitHub will use when sending the webhook payload.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_comment', + }, + }, + { + id: 'webhookSecret', + title: 'Webhook Secret', + type: 'short-input', + placeholder: 'Generate or enter a strong secret', + description: 'Validates that webhook deliveries originate from GitHub.', + password: true, + required: false, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_comment', + }, + }, + { + id: 'sslVerification', + title: 'SSL Verification', + type: 'dropdown', + options: [ + { label: 'Enabled', id: 'enabled' }, + { label: 'Disabled', id: 'disabled' }, + ], + defaultValue: 'enabled', + description: 'GitHub verifies SSL certificates when delivering webhooks.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_comment', + }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + type: 'text', + defaultValue: [ + 'Go to your GitHub Repository > Settings > Webhooks.', + 'Click "Add webhook".', + 'Paste the Webhook URL above into the "Payload URL" field.', + 'Select your chosen Content Type from the dropdown.', + 'Enter the Webhook Secret into the "Secret" field if you\'ve configured one.', + 'Set SSL verification according to your selection.', + 'Select "Let me select individual events" and check issue_comment (created, edited, deleted actions).', + 'Ensure "Active" is checked and click "Add webhook".', + 'Note: This trigger filters for issue comments only. For PR comments, use the "GitHub PR Comment" trigger.', + ] + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join(''), + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_comment', + }, + }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'github_issue_comment', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_comment', + }, + }, + { + id: 'samplePayload', + title: 'Event Payload Example', + type: 'code', + language: 'json', + defaultValue: JSON.stringify( + { + action: 'created', + issue: { + number: 123, + title: 'Bug: Application crashes on startup', + state: 'open', + html_url: 'https://github.com/owner/repo/issues/123', + user: { + login: 'octocat', + id: 1, + avatar_url: 'https://github.com/images/error/octocat_happy.gif', + user_type: 'User', + }, + }, + comment: { + id: 987654321, + body: 'I can confirm this bug. It happens on my machine too.', + html_url: 'https://github.com/owner/repo/issues/123#issuecomment-987654321', + user: { + login: 'commenter', + id: 3, + avatar_url: 'https://github.com/images/error/commenter.gif', + user_type: 'User', + }, + created_at: '2025-01-15T11:00:00Z', + updated_at: '2025-01-15T11:00:00Z', + }, + repository: { + id: 123456, + name: 'repo-name', + full_name: 'owner/repo-name', + html_url: 'https://github.com/owner/repo-name', + owner: { + login: 'owner', + id: 7890, + owner_type: 'User', + }, + }, + sender: { + login: 'commenter', + id: 3, + user_type: 'User', + }, + }, + null, + 2 + ), + readOnly: true, + collapsible: true, + defaultCollapsed: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_comment', + }, + }, + ], + + outputs: { + action: { + type: 'string', + description: 'Action performed (created, edited, deleted)', + }, + issue: { + number: { + type: 'number', + description: 'Issue number', + }, + title: { + type: 'string', + description: 'Issue title', + }, + state: { + type: 'string', + description: 'Issue state (open, closed)', + }, + html_url: { + type: 'string', + description: 'Issue HTML URL', + }, + user: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + }, + comment: { + id: { + type: 'number', + description: 'Comment ID', + }, + node_id: { + type: 'string', + description: 'Comment node ID', + }, + body: { + type: 'string', + description: 'Comment text', + }, + html_url: { + type: 'string', + description: 'Comment HTML URL', + }, + user: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + created_at: { + type: 'string', + description: 'Comment creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Comment last update timestamp', + }, + }, + repository: { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name (owner/repo)', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + html_url: { + type: 'string', + description: 'Repository HTML URL', + }, + repo_description: { + type: 'string', + description: 'Repository description', + }, + fork: { + type: 'boolean', + description: 'Whether the repository is a fork', + }, + url: { + type: 'string', + description: 'Repository API URL', + }, + homepage: { + type: 'string', + description: 'Repository homepage URL', + }, + size: { + type: 'number', + description: 'Repository size in KB', + }, + stargazers_count: { + type: 'number', + description: 'Number of stars', + }, + watchers_count: { + type: 'number', + description: 'Number of watchers', + }, + language: { + type: 'string', + description: 'Primary programming language', + }, + forks_count: { + type: 'number', + description: 'Number of forks', + }, + open_issues_count: { + type: 'number', + description: 'Number of open issues', + }, + default_branch: { + type: 'string', + description: 'Default branch name', + }, + owner: { + login: { + type: 'string', + description: 'Owner username', + }, + id: { + type: 'number', + description: 'Owner ID', + }, + avatar_url: { + type: 'string', + description: 'Owner avatar URL', + }, + html_url: { + type: 'string', + description: 'Owner profile URL', + }, + owner_type: { + type: 'string', + description: 'Owner type (User, Organization)', + }, + }, + }, + sender: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + }, + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GitHub-Event': 'issue_comment', + 'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'X-Hub-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/github/issue_opened.ts b/apps/sim/triggers/github/issue_opened.ts new file mode 100644 index 0000000000..0d1508ed11 --- /dev/null +++ b/apps/sim/triggers/github/issue_opened.ts @@ -0,0 +1,420 @@ +import { GithubIcon } from '@/components/icons' +import type { TriggerConfig } from '@/triggers/types' + +export const githubIssueOpenedTrigger: TriggerConfig = { + id: 'github_issue_opened', + name: 'GitHub Issue Opened', + provider: 'github', + description: 'Trigger workflow when a new issue is opened in a GitHub repository', + version: '1.0.0', + icon: GithubIcon, + + subBlocks: [ + { + id: 'selectedTriggerId', + title: 'Trigger Type', + type: 'dropdown', + mode: 'trigger', + options: [ + { label: 'Issue Opened', id: 'github_issue_opened' }, + { label: 'Issue Closed', id: 'github_issue_closed' }, + { label: 'Issue Comment', id: 'github_issue_comment' }, + { label: 'PR Opened', id: 'github_pr_opened' }, + { label: 'PR Closed', id: 'github_pr_closed' }, + { label: 'PR Merged', id: 'github_pr_merged' }, + { label: 'PR Comment', id: 'github_pr_comment' }, + { label: 'PR Reviewed', id: 'github_pr_reviewed' }, + { label: 'Code Push', id: 'github_push' }, + { label: 'Release Published', id: 'github_release_published' }, + { label: 'Actions Workflow Run', id: 'github_workflow_run' }, + ], + value: () => 'github_issue_opened', + required: true, + }, + { + id: 'webhookUrlDisplay', + title: 'Webhook URL', + type: 'short-input', + readOnly: true, + showCopyButton: true, + useWebhookUrl: true, + placeholder: 'Webhook URL will be generated', + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_opened', + }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { + label: 'application/x-www-form-urlencoded', + id: 'application/x-www-form-urlencoded', + }, + ], + defaultValue: 'application/json', + description: 'Format GitHub will use when sending the webhook payload.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_opened', + }, + }, + { + id: 'webhookSecret', + title: 'Webhook Secret', + type: 'short-input', + placeholder: 'Generate or enter a strong secret', + description: 'Validates that webhook deliveries originate from GitHub.', + password: true, + required: false, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_opened', + }, + }, + { + id: 'sslVerification', + title: 'SSL Verification', + type: 'dropdown', + options: [ + { label: 'Enabled', id: 'enabled' }, + { label: 'Disabled', id: 'disabled' }, + ], + defaultValue: 'enabled', + description: 'GitHub verifies SSL certificates when delivering webhooks.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_opened', + }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + type: 'text', + defaultValue: [ + 'Go to your GitHub Repository > Settings > Webhooks.', + 'Click "Add webhook".', + 'Paste the Webhook URL above into the "Payload URL" field.', + 'Select your chosen Content Type from the dropdown.', + 'Enter the Webhook Secret into the "Secret" field if you\'ve configured one.', + 'Set SSL verification according to your selection.', + 'Select "Let me select individual events" and check issues (opened action).', + 'Ensure "Active" is checked and click "Add webhook".', + ] + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join(''), + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_opened', + }, + }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'github_issue_opened', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_opened', + }, + }, + { + id: 'samplePayload', + title: 'Event Payload Example', + type: 'code', + language: 'json', + defaultValue: JSON.stringify( + { + action: 'opened', + issue: { + id: 1234567890, + number: 123, + title: 'Bug: Application crashes on startup', + body: 'When I try to start the application, it immediately crashes with error code 500.', + state: 'open', + html_url: 'https://github.com/owner/repo/issues/123', + user: { + login: 'octocat', + id: 1, + avatar_url: 'https://github.com/images/error/octocat_happy.gif', + html_url: 'https://github.com/octocat', + user_type: 'User', + }, + labels: [ + { + name: 'bug', + color: 'd73a4a', + }, + ], + assignees: [], + created_at: '2025-01-15T10:30:00Z', + updated_at: '2025-01-15T10:30:00Z', + }, + repository: { + id: 123456, + name: 'repo-name', + full_name: 'owner/repo-name', + html_url: 'https://github.com/owner/repo-name', + repo_description: 'A sample repository', + private: false, + owner: { + login: 'owner', + id: 7890, + avatar_url: 'https://github.com/images/error/owner.gif', + owner_type: 'User', + }, + }, + sender: { + login: 'octocat', + id: 1, + avatar_url: 'https://github.com/images/error/octocat_happy.gif', + user_type: 'User', + }, + }, + null, + 2 + ), + readOnly: true, + collapsible: true, + defaultCollapsed: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_issue_opened', + }, + }, + ], + + outputs: { + action: { + type: 'string', + description: 'Action performed (opened, closed, reopened, edited, etc.)', + }, + issue: { + id: { + type: 'number', + description: 'Issue ID', + }, + node_id: { + type: 'string', + description: 'Issue node ID', + }, + number: { + type: 'number', + description: 'Issue number', + }, + title: { + type: 'string', + description: 'Issue title', + }, + body: { + type: 'string', + description: 'Issue body/description', + }, + state: { + type: 'string', + description: 'Issue state (open, closed)', + }, + state_reason: { + type: 'string', + description: 'Reason for state (completed, not_planned, reopened)', + }, + html_url: { + type: 'string', + description: 'Issue HTML URL', + }, + user: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + labels: { + type: 'array', + description: 'Array of label objects', + }, + assignees: { + type: 'array', + description: 'Array of assigned users', + }, + milestone: { + type: 'object', + description: 'Milestone object if assigned', + }, + created_at: { + type: 'string', + description: 'Issue creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Issue last update timestamp', + }, + closed_at: { + type: 'string', + description: 'Issue closed timestamp', + }, + }, + repository: { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name (owner/repo)', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + html_url: { + type: 'string', + description: 'Repository HTML URL', + }, + repo_description: { + type: 'string', + description: 'Repository description', + }, + fork: { + type: 'boolean', + description: 'Whether the repository is a fork', + }, + url: { + type: 'string', + description: 'Repository API URL', + }, + homepage: { + type: 'string', + description: 'Repository homepage URL', + }, + size: { + type: 'number', + description: 'Repository size in KB', + }, + stargazers_count: { + type: 'number', + description: 'Number of stars', + }, + watchers_count: { + type: 'number', + description: 'Number of watchers', + }, + language: { + type: 'string', + description: 'Primary programming language', + }, + forks_count: { + type: 'number', + description: 'Number of forks', + }, + open_issues_count: { + type: 'number', + description: 'Number of open issues', + }, + default_branch: { + type: 'string', + description: 'Default branch name', + }, + owner: { + login: { + type: 'string', + description: 'Owner username', + }, + id: { + type: 'number', + description: 'Owner ID', + }, + avatar_url: { + type: 'string', + description: 'Owner avatar URL', + }, + html_url: { + type: 'string', + description: 'Owner profile URL', + }, + owner_type: { + type: 'string', + description: 'Owner type (User, Organization)', + }, + }, + }, + sender: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + }, + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GitHub-Event': 'issues', + 'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'X-Hub-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/github/pr_closed.ts b/apps/sim/triggers/github/pr_closed.ts new file mode 100644 index 0000000000..dce0bf1721 --- /dev/null +++ b/apps/sim/triggers/github/pr_closed.ts @@ -0,0 +1,518 @@ +import { GithubIcon } from '@/components/icons' +import type { TriggerConfig } from '@/triggers/types' + +export const githubPRClosedTrigger: TriggerConfig = { + id: 'github_pr_closed', + name: 'GitHub PR Closed', + provider: 'github', + description: + 'Trigger workflow when a pull request is closed without being merged (e.g., abandoned) in a GitHub repository', + version: '1.0.0', + icon: GithubIcon, + + subBlocks: [ + { + id: 'webhookUrlDisplay', + title: 'Webhook URL', + type: 'short-input', + readOnly: true, + showCopyButton: true, + useWebhookUrl: true, + placeholder: 'Webhook URL will be generated', + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_closed', + }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { + label: 'application/x-www-form-urlencoded', + id: 'application/x-www-form-urlencoded', + }, + ], + defaultValue: 'application/json', + description: 'Format GitHub will use when sending the webhook payload.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_closed', + }, + }, + { + id: 'webhookSecret', + title: 'Webhook Secret', + type: 'short-input', + placeholder: 'Generate or enter a strong secret', + description: 'Validates that webhook deliveries originate from GitHub.', + password: true, + required: false, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_closed', + }, + }, + { + id: 'sslVerification', + title: 'SSL Verification', + type: 'dropdown', + options: [ + { label: 'Enabled', id: 'enabled' }, + { label: 'Disabled', id: 'disabled' }, + ], + defaultValue: 'enabled', + description: 'GitHub verifies SSL certificates when delivering webhooks.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_closed', + }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + type: 'text', + defaultValue: [ + 'Go to your GitHub Repository > Settings > Webhooks.', + 'Click "Add webhook".', + 'Paste the Webhook URL above into the "Payload URL" field.', + 'Select your chosen Content Type from the dropdown.', + 'Enter the Webhook Secret into the "Secret" field if you\'ve configured one.', + 'Set SSL verification according to your selection.', + 'Select "Let me select individual events" and check pull_request (closed action).', + 'Ensure "Active" is checked and click "Add webhook".', + ] + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join(''), + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_closed', + }, + }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'github_pr_closed', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_closed', + }, + }, + { + id: 'samplePayload', + title: 'Event Payload Example', + type: 'code', + language: 'json', + defaultValue: JSON.stringify( + { + action: 'closed', + number: 42, + pull_request: { + id: 1234567890, + number: 42, + title: 'Add new feature', + body: 'This PR adds a new feature that improves performance.', + state: 'closed', + merged: false, + draft: false, + html_url: 'https://github.com/owner/repo/pull/42', + diff_url: 'https://github.com/owner/repo/pull/42.diff', + patch_url: 'https://github.com/owner/repo/pull/42.patch', + user: { + login: 'developer', + id: 5, + avatar_url: 'https://github.com/images/error/developer.gif', + html_url: 'https://github.com/developer', + user_type: 'User', + }, + head: { + ref: 'feature-branch', + sha: 'abc123def456', + repo: { + name: 'repo-name', + full_name: 'owner/repo-name', + }, + }, + base: { + ref: 'main', + sha: '789ghi012jkl', + repo: { + name: 'repo-name', + full_name: 'owner/repo-name', + }, + }, + additions: 245, + deletions: 67, + changed_files: 12, + labels: [], + assignees: [], + requested_reviewers: [ + { + login: 'reviewer1', + id: 6, + }, + ], + created_at: '2025-01-15T12:00:00Z', + updated_at: '2025-01-15T14:30:00Z', + closed_at: '2025-01-15T14:30:00Z', + }, + repository: { + id: 123456, + name: 'repo-name', + full_name: 'owner/repo-name', + html_url: 'https://github.com/owner/repo-name', + repo_description: 'A sample repository', + private: false, + owner: { + login: 'owner', + id: 7890, + owner_type: 'User', + }, + }, + sender: { + login: 'developer', + id: 5, + user_type: 'User', + }, + }, + null, + 2 + ), + readOnly: true, + collapsible: true, + defaultCollapsed: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_closed', + }, + }, + ], + + outputs: { + action: { + type: 'string', + description: 'Action performed (opened, closed, synchronize, reopened, edited, etc.)', + }, + number: { + type: 'number', + description: 'Pull request number', + }, + pull_request: { + id: { + type: 'number', + description: 'Pull request ID', + }, + node_id: { + type: 'string', + description: 'Pull request node ID', + }, + number: { + type: 'number', + description: 'Pull request number', + }, + title: { + type: 'string', + description: 'Pull request title', + }, + body: { + type: 'string', + description: 'Pull request description', + }, + state: { + type: 'string', + description: 'Pull request state (open, closed)', + }, + merged: { + type: 'boolean', + description: 'Whether the PR was merged', + }, + merged_at: { + type: 'string', + description: 'Timestamp when PR was merged', + }, + merged_by: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + draft: { + type: 'boolean', + description: 'Whether the PR is a draft', + }, + html_url: { + type: 'string', + description: 'Pull request HTML URL', + }, + diff_url: { + type: 'string', + description: 'Pull request diff URL', + }, + patch_url: { + type: 'string', + description: 'Pull request patch URL', + }, + user: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + head: { + ref: { + type: 'string', + description: 'Source branch name', + }, + sha: { + type: 'string', + description: 'Source branch commit SHA', + }, + repo: { + name: { + type: 'string', + description: 'Source repository name', + }, + full_name: { + type: 'string', + description: 'Source repository full name', + }, + }, + }, + base: { + ref: { + type: 'string', + description: 'Target branch name', + }, + sha: { + type: 'string', + description: 'Target branch commit SHA', + }, + repo: { + name: { + type: 'string', + description: 'Target repository name', + }, + full_name: { + type: 'string', + description: 'Target repository full name', + }, + }, + }, + additions: { + type: 'number', + description: 'Number of lines added', + }, + deletions: { + type: 'number', + description: 'Number of lines deleted', + }, + changed_files: { + type: 'number', + description: 'Number of files changed', + }, + labels: { + type: 'array', + description: 'Array of label objects', + }, + assignees: { + type: 'array', + description: 'Array of assigned users', + }, + requested_reviewers: { + type: 'array', + description: 'Array of requested reviewers', + }, + created_at: { + type: 'string', + description: 'Pull request creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Pull request last update timestamp', + }, + closed_at: { + type: 'string', + description: 'Pull request closed timestamp', + }, + }, + repository: { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name (owner/repo)', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + html_url: { + type: 'string', + description: 'Repository HTML URL', + }, + repo_description: { + type: 'string', + description: 'Repository description', + }, + fork: { + type: 'boolean', + description: 'Whether the repository is a fork', + }, + url: { + type: 'string', + description: 'Repository API URL', + }, + homepage: { + type: 'string', + description: 'Repository homepage URL', + }, + size: { + type: 'number', + description: 'Repository size in KB', + }, + stargazers_count: { + type: 'number', + description: 'Number of stars', + }, + watchers_count: { + type: 'number', + description: 'Number of watchers', + }, + language: { + type: 'string', + description: 'Primary programming language', + }, + forks_count: { + type: 'number', + description: 'Number of forks', + }, + open_issues_count: { + type: 'number', + description: 'Number of open issues', + }, + default_branch: { + type: 'string', + description: 'Default branch name', + }, + owner: { + login: { + type: 'string', + description: 'Owner username', + }, + id: { + type: 'number', + description: 'Owner ID', + }, + avatar_url: { + type: 'string', + description: 'Owner avatar URL', + }, + html_url: { + type: 'string', + description: 'Owner profile URL', + }, + }, + }, + sender: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + }, + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GitHub-Event': 'pull_request', + 'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'X-Hub-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/github/pr_comment.ts b/apps/sim/triggers/github/pr_comment.ts new file mode 100644 index 0000000000..7e52b4f00e --- /dev/null +++ b/apps/sim/triggers/github/pr_comment.ts @@ -0,0 +1,450 @@ +import { GithubIcon } from '@/components/icons' +import type { TriggerConfig } from '@/triggers/types' + +export const githubPRCommentTrigger: TriggerConfig = { + id: 'github_pr_comment', + name: 'GitHub PR Comment', + provider: 'github', + description: 'Trigger workflow when a comment is added to a pull request in a GitHub repository', + version: '1.0.0', + icon: GithubIcon, + + subBlocks: [ + { + id: 'webhookUrlDisplay', + title: 'Webhook URL', + type: 'short-input', + readOnly: true, + showCopyButton: true, + useWebhookUrl: true, + placeholder: 'Webhook URL will be generated', + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_comment', + }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { + label: 'application/x-www-form-urlencoded', + id: 'application/x-www-form-urlencoded', + }, + ], + defaultValue: 'application/json', + description: 'Format GitHub will use when sending the webhook payload.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_comment', + }, + }, + { + id: 'webhookSecret', + title: 'Webhook Secret', + type: 'short-input', + placeholder: 'Generate or enter a strong secret', + description: 'Validates that webhook deliveries originate from GitHub.', + password: true, + required: false, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_comment', + }, + }, + { + id: 'sslVerification', + title: 'SSL Verification', + type: 'dropdown', + options: [ + { label: 'Enabled', id: 'enabled' }, + { label: 'Disabled', id: 'disabled' }, + ], + defaultValue: 'enabled', + description: 'GitHub verifies SSL certificates when delivering webhooks.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_comment', + }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + type: 'text', + defaultValue: [ + 'Go to your GitHub Repository > Settings > Webhooks.', + 'Click "Add webhook".', + 'Paste the Webhook URL above into the "Payload URL" field.', + 'Select your chosen Content Type from the dropdown.', + 'Enter the Webhook Secret into the "Secret" field if you\'ve configured one.', + 'Set SSL verification according to your selection.', + 'Select "Let me select individual events" and check Issue comments.', + 'Ensure "Active" is checked and click "Add webhook".', + 'Note: Pull request comments use the issue_comment event. You can filter for PR comments by checking if issue.pull_request exists.', + ] + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join(''), + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_comment', + }, + }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'github_pr_comment', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_comment', + }, + }, + { + id: 'samplePayload', + title: 'Event Payload Example', + type: 'code', + language: 'json', + defaultValue: JSON.stringify( + { + action: 'created', + issue: { + id: 1234567890, + number: 42, + title: 'Add new feature', + body: 'This PR adds a new feature that improves performance.', + state: 'open', + html_url: 'https://github.com/owner/repo/issues/42', + user: { + login: 'developer', + id: 5, + node_id: 'MDQ6VXNlcjU=', + avatar_url: 'https://github.com/images/error/developer.gif', + html_url: 'https://github.com/developer', + type: 'User', + }, + labels: [], + assignees: [], + pull_request: { + url: 'https://api.github.com/repos/owner/repo/pulls/42', + html_url: 'https://github.com/owner/repo/pull/42', + diff_url: 'https://github.com/owner/repo/pull/42.diff', + patch_url: 'https://github.com/owner/repo/pull/42.patch', + }, + created_at: '2025-01-15T12:00:00Z', + updated_at: '2025-01-15T12:15:00Z', + }, + comment: { + id: 987654321, + node_id: 'MDEyOklzc3VlQ29tbWVudDk4NzY1NDMyMQ==', + url: 'https://api.github.com/repos/owner/repo/issues/comments/987654321', + html_url: 'https://github.com/owner/repo/issues/42#issuecomment-987654321', + body: 'Great work! This looks good to me.', + user: { + login: 'reviewer', + id: 6, + node_id: 'MDQ6VXNlcjY=', + avatar_url: 'https://github.com/images/error/reviewer.gif', + html_url: 'https://github.com/reviewer', + type: 'User', + }, + created_at: '2025-01-15T12:15:00Z', + updated_at: '2025-01-15T12:15:00Z', + }, + repository: { + id: 123456, + node_id: 'MDEwOlJlcG9zaXRvcnkxMjM0NTY=', + name: 'repo-name', + full_name: 'owner/repo-name', + html_url: 'https://github.com/owner/repo-name', + description: 'A sample repository', + private: false, + owner: { + login: 'owner', + id: 7890, + node_id: 'MDQ6VXNlcjc4OTA=', + avatar_url: 'https://github.com/images/error/owner.gif', + html_url: 'https://github.com/owner', + type: 'User', + }, + }, + sender: { + login: 'reviewer', + id: 6, + node_id: 'MDQ6VXNlcjY=', + avatar_url: 'https://github.com/images/error/reviewer.gif', + html_url: 'https://github.com/reviewer', + type: 'User', + }, + }, + null, + 2 + ), + readOnly: true, + collapsible: true, + defaultCollapsed: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_comment', + }, + }, + ], + + outputs: { + action: { + type: 'string', + description: 'Action performed (created, edited, deleted)', + }, + issue: { + id: { + type: 'number', + description: 'Issue ID', + }, + node_id: { + type: 'string', + description: 'Issue node ID', + }, + number: { + type: 'number', + description: 'Issue/PR number', + }, + title: { + type: 'string', + description: 'Issue/PR title', + }, + body: { + type: 'string', + description: 'Issue/PR description', + }, + state: { + type: 'string', + description: 'Issue/PR state (open, closed)', + }, + html_url: { + type: 'string', + description: 'Issue/PR HTML URL', + }, + user: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + labels: { + type: 'array', + description: 'Array of label objects', + }, + assignees: { + type: 'array', + description: 'Array of assigned users', + }, + pull_request: { + url: { + type: 'string', + description: 'Pull request API URL (present only for PR comments)', + }, + html_url: { + type: 'string', + description: 'Pull request HTML URL', + }, + diff_url: { + type: 'string', + description: 'Pull request diff URL', + }, + patch_url: { + type: 'string', + description: 'Pull request patch URL', + }, + }, + created_at: { + type: 'string', + description: 'Issue/PR creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Issue/PR last update timestamp', + }, + }, + comment: { + id: { + type: 'number', + description: 'Comment ID', + }, + node_id: { + type: 'string', + description: 'Comment node ID', + }, + url: { + type: 'string', + description: 'Comment API URL', + }, + html_url: { + type: 'string', + description: 'Comment HTML URL', + }, + body: { + type: 'string', + description: 'Comment text', + }, + user: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + created_at: { + type: 'string', + description: 'Comment creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Comment last update timestamp', + }, + }, + repository: { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name (owner/repo)', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + html_url: { + type: 'string', + description: 'Repository HTML URL', + }, + repo_description: { + type: 'string', + description: 'Repository description', + }, + owner: { + login: { + type: 'string', + description: 'Owner username', + }, + id: { + type: 'number', + description: 'Owner ID', + }, + node_id: { + type: 'string', + description: 'Owner node ID', + }, + avatar_url: { + type: 'string', + description: 'Owner avatar URL', + }, + html_url: { + type: 'string', + description: 'Owner profile URL', + }, + owner_type: { + type: 'string', + description: 'Owner type (User, Organization)', + }, + }, + }, + sender: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + }, + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GitHub-Event': 'issue_comment', + 'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'X-Hub-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/github/pr_merged.ts b/apps/sim/triggers/github/pr_merged.ts new file mode 100644 index 0000000000..0c76f29290 --- /dev/null +++ b/apps/sim/triggers/github/pr_merged.ts @@ -0,0 +1,525 @@ +import { GithubIcon } from '@/components/icons' +import type { TriggerConfig } from '@/triggers/types' + +export const githubPRMergedTrigger: TriggerConfig = { + id: 'github_pr_merged', + name: 'GitHub PR Merged', + provider: 'github', + description: 'Trigger workflow when a pull request is successfully merged in a GitHub repository', + version: '1.0.0', + icon: GithubIcon, + + subBlocks: [ + { + id: 'webhookUrlDisplay', + title: 'Webhook URL', + type: 'short-input', + readOnly: true, + showCopyButton: true, + useWebhookUrl: true, + placeholder: 'Webhook URL will be generated', + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_merged', + }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { + label: 'application/x-www-form-urlencoded', + id: 'application/x-www-form-urlencoded', + }, + ], + defaultValue: 'application/json', + description: 'Format GitHub will use when sending the webhook payload.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_merged', + }, + }, + { + id: 'webhookSecret', + title: 'Webhook Secret', + type: 'short-input', + placeholder: 'Generate or enter a strong secret', + description: 'Validates that webhook deliveries originate from GitHub.', + password: true, + required: false, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_merged', + }, + }, + { + id: 'sslVerification', + title: 'SSL Verification', + type: 'dropdown', + options: [ + { label: 'Enabled', id: 'enabled' }, + { label: 'Disabled', id: 'disabled' }, + ], + defaultValue: 'enabled', + description: 'GitHub verifies SSL certificates when delivering webhooks.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_merged', + }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + type: 'text', + defaultValue: [ + 'Go to your GitHub Repository > Settings > Webhooks.', + 'Click "Add webhook".', + 'Paste the Webhook URL above into the "Payload URL" field.', + 'Select your chosen Content Type from the dropdown.', + 'Enter the Webhook Secret into the "Secret" field if you\'ve configured one.', + 'Set SSL verification according to your selection.', + 'Select "Let me select individual events" and check pull_request. Note: Merged PRs have action=\'closed\' AND merged=true.', + 'Ensure "Active" is checked and click "Add webhook".', + ] + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join(''), + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_merged', + }, + }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'github_pr_merged', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_merged', + }, + }, + { + id: 'samplePayload', + title: 'Event Payload Example', + type: 'code', + language: 'json', + defaultValue: JSON.stringify( + { + action: 'closed', + number: 42, + pull_request: { + id: 1234567890, + number: 42, + title: 'Add new feature', + body: 'This PR adds a new feature that improves performance.', + state: 'closed', + merged: true, + merge_commit_sha: 'mno345pqr678', + merged_at: '2025-01-15T13:30:00Z', + merged_by: { + login: 'maintainer', + id: 8, + avatar_url: 'https://github.com/images/error/maintainer.gif', + html_url: 'https://github.com/maintainer', + user_type: 'User', + }, + draft: false, + html_url: 'https://github.com/owner/repo/pull/42', + diff_url: 'https://github.com/owner/repo/pull/42.diff', + patch_url: 'https://github.com/owner/repo/pull/42.patch', + user: { + login: 'developer', + id: 5, + avatar_url: 'https://github.com/images/error/developer.gif', + html_url: 'https://github.com/developer', + user_type: 'User', + }, + head: { + ref: 'feature-branch', + sha: 'abc123def456', + repo: { + name: 'repo-name', + full_name: 'owner/repo-name', + }, + }, + base: { + ref: 'main', + sha: '789ghi012jkl', + repo: { + name: 'repo-name', + full_name: 'owner/repo-name', + }, + }, + additions: 245, + deletions: 67, + changed_files: 12, + labels: [], + assignees: [], + requested_reviewers: [ + { + login: 'reviewer1', + id: 6, + }, + ], + created_at: '2025-01-15T12:00:00Z', + updated_at: '2025-01-15T13:30:00Z', + }, + repository: { + id: 123456, + name: 'repo-name', + full_name: 'owner/repo-name', + html_url: 'https://github.com/owner/repo-name', + repo_description: 'A sample repository', + private: false, + owner: { + login: 'owner', + id: 7890, + owner_type: 'User', + }, + }, + sender: { + login: 'developer', + id: 5, + user_type: 'User', + }, + }, + null, + 2 + ), + readOnly: true, + collapsible: true, + defaultCollapsed: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_merged', + }, + }, + ], + + outputs: { + action: { + type: 'string', + description: 'Action performed (opened, closed, synchronize, reopened, edited, etc.)', + }, + number: { + type: 'number', + description: 'Pull request number', + }, + pull_request: { + id: { + type: 'number', + description: 'Pull request ID', + }, + node_id: { + type: 'string', + description: 'Pull request node ID', + }, + number: { + type: 'number', + description: 'Pull request number', + }, + title: { + type: 'string', + description: 'Pull request title', + }, + body: { + type: 'string', + description: 'Pull request description', + }, + state: { + type: 'string', + description: 'Pull request state (open, closed)', + }, + merged: { + type: 'boolean', + description: 'Whether the PR was merged', + }, + merged_at: { + type: 'string', + description: 'Timestamp when PR was merged', + }, + merged_by: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + draft: { + type: 'boolean', + description: 'Whether the PR is a draft', + }, + html_url: { + type: 'string', + description: 'Pull request HTML URL', + }, + diff_url: { + type: 'string', + description: 'Pull request diff URL', + }, + patch_url: { + type: 'string', + description: 'Pull request patch URL', + }, + user: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + head: { + ref: { + type: 'string', + description: 'Source branch name', + }, + sha: { + type: 'string', + description: 'Source branch commit SHA', + }, + repo: { + name: { + type: 'string', + description: 'Source repository name', + }, + full_name: { + type: 'string', + description: 'Source repository full name', + }, + }, + }, + base: { + ref: { + type: 'string', + description: 'Target branch name', + }, + sha: { + type: 'string', + description: 'Target branch commit SHA', + }, + repo: { + name: { + type: 'string', + description: 'Target repository name', + }, + full_name: { + type: 'string', + description: 'Target repository full name', + }, + }, + }, + additions: { + type: 'number', + description: 'Number of lines added', + }, + deletions: { + type: 'number', + description: 'Number of lines deleted', + }, + changed_files: { + type: 'number', + description: 'Number of files changed', + }, + labels: { + type: 'array', + description: 'Array of label objects', + }, + assignees: { + type: 'array', + description: 'Array of assigned users', + }, + requested_reviewers: { + type: 'array', + description: 'Array of requested reviewers', + }, + created_at: { + type: 'string', + description: 'Pull request creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Pull request last update timestamp', + }, + closed_at: { + type: 'string', + description: 'Pull request closed timestamp', + }, + }, + repository: { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name (owner/repo)', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + html_url: { + type: 'string', + description: 'Repository HTML URL', + }, + repo_description: { + type: 'string', + description: 'Repository description', + }, + fork: { + type: 'boolean', + description: 'Whether the repository is a fork', + }, + url: { + type: 'string', + description: 'Repository API URL', + }, + homepage: { + type: 'string', + description: 'Repository homepage URL', + }, + size: { + type: 'number', + description: 'Repository size in KB', + }, + stargazers_count: { + type: 'number', + description: 'Number of stars', + }, + watchers_count: { + type: 'number', + description: 'Number of watchers', + }, + language: { + type: 'string', + description: 'Primary programming language', + }, + forks_count: { + type: 'number', + description: 'Number of forks', + }, + open_issues_count: { + type: 'number', + description: 'Number of open issues', + }, + default_branch: { + type: 'string', + description: 'Default branch name', + }, + owner: { + login: { + type: 'string', + description: 'Owner username', + }, + id: { + type: 'number', + description: 'Owner ID', + }, + avatar_url: { + type: 'string', + description: 'Owner avatar URL', + }, + html_url: { + type: 'string', + description: 'Owner profile URL', + }, + }, + }, + sender: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + }, + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GitHub-Event': 'pull_request', + 'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'X-Hub-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/github/pr_opened.ts b/apps/sim/triggers/github/pr_opened.ts new file mode 100644 index 0000000000..2e0bf8b2d9 --- /dev/null +++ b/apps/sim/triggers/github/pr_opened.ts @@ -0,0 +1,520 @@ +import { GithubIcon } from '@/components/icons' +import type { TriggerConfig } from '@/triggers/types' + +export const githubPROpenedTrigger: TriggerConfig = { + id: 'github_pr_opened', + name: 'GitHub PR Opened', + provider: 'github', + description: 'Trigger workflow when a new pull request is opened in a GitHub repository', + version: '1.0.0', + icon: GithubIcon, + + subBlocks: [ + { + id: 'webhookUrlDisplay', + title: 'Webhook URL', + type: 'short-input', + readOnly: true, + showCopyButton: true, + useWebhookUrl: true, + placeholder: 'Webhook URL will be generated', + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_opened', + }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { + label: 'application/x-www-form-urlencoded', + id: 'application/x-www-form-urlencoded', + }, + ], + defaultValue: 'application/json', + description: 'Format GitHub will use when sending the webhook payload.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_opened', + }, + }, + { + id: 'webhookSecret', + title: 'Webhook Secret', + type: 'short-input', + placeholder: 'Generate or enter a strong secret', + description: 'Validates that webhook deliveries originate from GitHub.', + password: true, + required: false, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_opened', + }, + }, + { + id: 'sslVerification', + title: 'SSL Verification', + type: 'dropdown', + options: [ + { label: 'Enabled', id: 'enabled' }, + { label: 'Disabled', id: 'disabled' }, + ], + defaultValue: 'enabled', + description: 'GitHub verifies SSL certificates when delivering webhooks.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_opened', + }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + type: 'text', + defaultValue: [ + 'Go to your GitHub Repository > Settings > Webhooks.', + 'Click "Add webhook".', + 'Paste the Webhook URL above into the "Payload URL" field.', + 'Select your chosen Content Type from the dropdown.', + 'Enter the Webhook Secret into the "Secret" field if you\'ve configured one.', + 'Set SSL verification according to your selection.', + 'Select "Let me select individual events" and check pull_request (opened action).', + 'Ensure "Active" is checked and click "Add webhook".', + ] + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join(''), + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_opened', + }, + }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'github_pr_opened', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_opened', + }, + }, + { + id: 'samplePayload', + title: 'Event Payload Example', + type: 'code', + language: 'json', + defaultValue: JSON.stringify( + { + action: 'opened', + number: 42, + pull_request: { + id: 1234567890, + number: 42, + title: 'Add new feature', + body: 'This PR adds a new feature that improves performance.', + state: 'open', + merged: false, + draft: false, + html_url: 'https://github.com/owner/repo/pull/42', + diff_url: 'https://github.com/owner/repo/pull/42.diff', + patch_url: 'https://github.com/owner/repo/pull/42.patch', + user: { + login: 'developer', + id: 5, + avatar_url: 'https://github.com/images/error/developer.gif', + html_url: 'https://github.com/developer', + user_type: 'User', + }, + head: { + ref: 'feature-branch', + sha: 'abc123def456', + repo: { + name: 'repo-name', + full_name: 'owner/repo-name', + }, + }, + base: { + ref: 'main', + sha: '789ghi012jkl', + repo: { + name: 'repo-name', + full_name: 'owner/repo-name', + }, + }, + additions: 245, + deletions: 67, + changed_files: 12, + labels: [], + assignees: [], + requested_reviewers: [ + { + login: 'reviewer1', + id: 6, + }, + ], + created_at: '2025-01-15T12:00:00Z', + updated_at: '2025-01-15T12:00:00Z', + }, + repository: { + id: 123456, + name: 'repo-name', + full_name: 'owner/repo-name', + html_url: 'https://github.com/owner/repo-name', + repo_description: 'A sample repository', + private: false, + owner: { + login: 'owner', + id: 7890, + owner_type: 'User', + }, + }, + sender: { + login: 'developer', + id: 5, + user_type: 'User', + }, + }, + null, + 2 + ), + readOnly: true, + collapsible: true, + defaultCollapsed: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_opened', + }, + }, + ], + + outputs: { + action: { + type: 'string', + description: 'Action performed (opened, closed, synchronize, reopened, edited, etc.)', + }, + number: { + type: 'number', + description: 'Pull request number', + }, + pull_request: { + id: { + type: 'number', + description: 'Pull request ID', + }, + node_id: { + type: 'string', + description: 'Pull request node ID', + }, + number: { + type: 'number', + description: 'Pull request number', + }, + title: { + type: 'string', + description: 'Pull request title', + }, + body: { + type: 'string', + description: 'Pull request description', + }, + state: { + type: 'string', + description: 'Pull request state (open, closed)', + }, + merged: { + type: 'boolean', + description: 'Whether the PR was merged', + }, + merged_at: { + type: 'string', + description: 'Timestamp when PR was merged', + }, + merged_by: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + draft: { + type: 'boolean', + description: 'Whether the PR is a draft', + }, + html_url: { + type: 'string', + description: 'Pull request HTML URL', + }, + diff_url: { + type: 'string', + description: 'Pull request diff URL', + }, + patch_url: { + type: 'string', + description: 'Pull request patch URL', + }, + user: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + head: { + ref: { + type: 'string', + description: 'Source branch name', + }, + sha: { + type: 'string', + description: 'Source branch commit SHA', + }, + repo: { + name: { + type: 'string', + description: 'Source repository name', + }, + full_name: { + type: 'string', + description: 'Source repository full name', + }, + }, + }, + base: { + ref: { + type: 'string', + description: 'Target branch name', + }, + sha: { + type: 'string', + description: 'Target branch commit SHA', + }, + repo: { + name: { + type: 'string', + description: 'Target repository name', + }, + full_name: { + type: 'string', + description: 'Target repository full name', + }, + }, + }, + additions: { + type: 'number', + description: 'Number of lines added', + }, + deletions: { + type: 'number', + description: 'Number of lines deleted', + }, + changed_files: { + type: 'number', + description: 'Number of files changed', + }, + labels: { + type: 'array', + description: 'Array of label objects', + }, + assignees: { + type: 'array', + description: 'Array of assigned users', + }, + requested_reviewers: { + type: 'array', + description: 'Array of requested reviewers', + }, + created_at: { + type: 'string', + description: 'Pull request creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Pull request last update timestamp', + }, + closed_at: { + type: 'string', + description: 'Pull request closed timestamp', + }, + }, + repository: { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name (owner/repo)', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + html_url: { + type: 'string', + description: 'Repository HTML URL', + }, + repo_description: { + type: 'string', + description: 'Repository description', + }, + fork: { + type: 'boolean', + description: 'Whether the repository is a fork', + }, + url: { + type: 'string', + description: 'Repository API URL', + }, + homepage: { + type: 'string', + description: 'Repository homepage URL', + }, + size: { + type: 'number', + description: 'Repository size in KB', + }, + stargazers_count: { + type: 'number', + description: 'Number of stars', + }, + watchers_count: { + type: 'number', + description: 'Number of watchers', + }, + language: { + type: 'string', + description: 'Primary programming language', + }, + forks_count: { + type: 'number', + description: 'Number of forks', + }, + open_issues_count: { + type: 'number', + description: 'Number of open issues', + }, + default_branch: { + type: 'string', + description: 'Default branch name', + }, + owner: { + login: { + type: 'string', + description: 'Owner username', + }, + id: { + type: 'number', + description: 'Owner ID', + }, + avatar_url: { + type: 'string', + description: 'Owner avatar URL', + }, + html_url: { + type: 'string', + description: 'Owner profile URL', + }, + owner_type: { + type: 'string', + description: 'Owner type (User, Organization)', + }, + }, + }, + sender: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + }, + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GitHub-Event': 'pull_request', + 'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'X-Hub-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/github/pr_reviewed.ts b/apps/sim/triggers/github/pr_reviewed.ts new file mode 100644 index 0000000000..abfea8cabf --- /dev/null +++ b/apps/sim/triggers/github/pr_reviewed.ts @@ -0,0 +1,498 @@ +import { GithubIcon } from '@/components/icons' +import type { TriggerConfig } from '@/triggers/types' + +export const githubPRReviewedTrigger: TriggerConfig = { + id: 'github_pr_reviewed', + name: 'GitHub PR Reviewed', + provider: 'github', + description: + 'Trigger workflow when a pull request review is submitted, edited, or dismissed in a GitHub repository', + version: '1.0.0', + icon: GithubIcon, + + subBlocks: [ + { + id: 'webhookUrlDisplay', + title: 'Webhook URL', + type: 'short-input', + readOnly: true, + showCopyButton: true, + useWebhookUrl: true, + placeholder: 'Webhook URL will be generated', + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_reviewed', + }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { + label: 'application/x-www-form-urlencoded', + id: 'application/x-www-form-urlencoded', + }, + ], + defaultValue: 'application/json', + description: 'Format GitHub will use when sending the webhook payload.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_reviewed', + }, + }, + { + id: 'webhookSecret', + title: 'Webhook Secret', + type: 'short-input', + placeholder: 'Generate or enter a strong secret', + description: 'Validates that webhook deliveries originate from GitHub.', + password: true, + required: false, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_reviewed', + }, + }, + { + id: 'sslVerification', + title: 'SSL Verification', + type: 'dropdown', + options: [ + { label: 'Enabled', id: 'enabled' }, + { label: 'Disabled', id: 'disabled' }, + ], + defaultValue: 'enabled', + description: 'GitHub verifies SSL certificates when delivering webhooks.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_reviewed', + }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + type: 'text', + defaultValue: [ + 'Go to your GitHub Repository > Settings > Webhooks.', + 'Click "Add webhook".', + 'Paste the Webhook URL above into the "Payload URL" field.', + 'Select your chosen Content Type from the dropdown.', + 'Enter the Webhook Secret into the "Secret" field if you\'ve configured one.', + 'Set SSL verification according to your selection.', + 'Select "Let me select individual events" and check Pull request reviews.', + 'Ensure "Active" is checked and click "Add webhook".', + ] + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join(''), + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_reviewed', + }, + }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'github_pr_reviewed', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_reviewed', + }, + }, + { + id: 'samplePayload', + title: 'Event Payload Example', + type: 'code', + language: 'json', + defaultValue: JSON.stringify( + { + action: 'submitted', + review: { + id: 80, + node_id: 'MDE3OlB1bGxSZXF1ZXN0UmV2aWV3ODA=', + user: { + login: 'reviewer', + id: 6, + node_id: 'MDQ6VXNlcjY=', + avatar_url: 'https://github.com/images/error/reviewer.gif', + html_url: 'https://github.com/reviewer', + type: 'User', + }, + body: 'This looks great! Nice work.', + state: 'approved', + html_url: 'https://github.com/owner/repo/pull/42#pullrequestreview-80', + submitted_at: '2025-01-15T14:00:00Z', + commit_id: 'abc123def456', + author_association: 'COLLABORATOR', + }, + pull_request: { + id: 1234567890, + number: 42, + node_id: 'MDExOlB1bGxSZXF1ZXN0MQ==', + title: 'Add new feature', + body: 'This PR adds a new feature that improves performance.', + state: 'open', + merged: false, + draft: false, + html_url: 'https://github.com/owner/repo/pull/42', + diff_url: 'https://github.com/owner/repo/pull/42.diff', + patch_url: 'https://github.com/owner/repo/pull/42.patch', + user: { + login: 'developer', + id: 5, + node_id: 'MDQ6VXNlcjU=', + avatar_url: 'https://github.com/images/error/developer.gif', + html_url: 'https://github.com/developer', + type: 'User', + }, + head: { + ref: 'feature-branch', + sha: 'abc123def456', + repo: { + name: 'repo-name', + full_name: 'owner/repo-name', + }, + }, + base: { + ref: 'main', + sha: '789ghi012jkl', + repo: { + name: 'repo-name', + full_name: 'owner/repo-name', + }, + }, + created_at: '2025-01-15T12:00:00Z', + updated_at: '2025-01-15T14:00:00Z', + }, + repository: { + id: 123456, + node_id: 'MDEwOlJlcG9zaXRvcnkxMjM0NTY=', + name: 'repo-name', + full_name: 'owner/repo-name', + html_url: 'https://github.com/owner/repo-name', + description: 'A sample repository', + private: false, + owner: { + login: 'owner', + id: 7890, + node_id: 'MDQ6VXNlcjc4OTA=', + avatar_url: 'https://github.com/images/error/owner.gif', + html_url: 'https://github.com/owner', + type: 'User', + }, + }, + sender: { + login: 'reviewer', + id: 6, + node_id: 'MDQ6VXNlcjY=', + avatar_url: 'https://github.com/images/error/reviewer.gif', + html_url: 'https://github.com/reviewer', + type: 'User', + }, + }, + null, + 2 + ), + readOnly: true, + collapsible: true, + defaultCollapsed: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_pr_reviewed', + }, + }, + ], + + outputs: { + action: { + type: 'string', + description: 'Action performed (submitted, edited, dismissed)', + }, + review: { + id: { + type: 'number', + description: 'Review ID', + }, + node_id: { + type: 'string', + description: 'Review node ID', + }, + user: { + login: { + type: 'string', + description: 'Reviewer username', + }, + id: { + type: 'number', + description: 'Reviewer user ID', + }, + node_id: { + type: 'string', + description: 'Reviewer node ID', + }, + avatar_url: { + type: 'string', + description: 'Reviewer avatar URL', + }, + html_url: { + type: 'string', + description: 'Reviewer profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + body: { + type: 'string', + description: 'Review comment text', + }, + state: { + type: 'string', + description: 'Review state (approved, changes_requested, commented, dismissed)', + }, + html_url: { + type: 'string', + description: 'Review HTML URL', + }, + submitted_at: { + type: 'string', + description: 'Review submission timestamp', + }, + commit_id: { + type: 'string', + description: 'Commit SHA that was reviewed', + }, + author_association: { + type: 'string', + description: 'Author association (OWNER, MEMBER, COLLABORATOR, CONTRIBUTOR, etc.)', + }, + }, + pull_request: { + id: { + type: 'number', + description: 'Pull request ID', + }, + node_id: { + type: 'string', + description: 'Pull request node ID', + }, + number: { + type: 'number', + description: 'Pull request number', + }, + title: { + type: 'string', + description: 'Pull request title', + }, + body: { + type: 'string', + description: 'Pull request description', + }, + state: { + type: 'string', + description: 'Pull request state (open, closed)', + }, + merged: { + type: 'boolean', + description: 'Whether the PR was merged', + }, + draft: { + type: 'boolean', + description: 'Whether the PR is a draft', + }, + html_url: { + type: 'string', + description: 'Pull request HTML URL', + }, + diff_url: { + type: 'string', + description: 'Pull request diff URL', + }, + patch_url: { + type: 'string', + description: 'Pull request patch URL', + }, + user: { + login: { + type: 'string', + description: 'PR author username', + }, + id: { + type: 'number', + description: 'PR author user ID', + }, + node_id: { + type: 'string', + description: 'PR author node ID', + }, + avatar_url: { + type: 'string', + description: 'PR author avatar URL', + }, + html_url: { + type: 'string', + description: 'PR author profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + head: { + ref: { + type: 'string', + description: 'Source branch name', + }, + sha: { + type: 'string', + description: 'Source branch commit SHA', + }, + repo: { + name: { + type: 'string', + description: 'Source repository name', + }, + full_name: { + type: 'string', + description: 'Source repository full name', + }, + }, + }, + base: { + ref: { + type: 'string', + description: 'Target branch name', + }, + sha: { + type: 'string', + description: 'Target branch commit SHA', + }, + repo: { + name: { + type: 'string', + description: 'Target repository name', + }, + full_name: { + type: 'string', + description: 'Target repository full name', + }, + }, + }, + created_at: { + type: 'string', + description: 'Pull request creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Pull request last update timestamp', + }, + }, + repository: { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name (owner/repo)', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + html_url: { + type: 'string', + description: 'Repository HTML URL', + }, + repo_description: { + type: 'string', + description: 'Repository description', + }, + owner: { + login: { + type: 'string', + description: 'Owner username', + }, + id: { + type: 'number', + description: 'Owner ID', + }, + node_id: { + type: 'string', + description: 'Owner node ID', + }, + avatar_url: { + type: 'string', + description: 'Owner avatar URL', + }, + html_url: { + type: 'string', + description: 'Owner profile URL', + }, + owner_type: { + type: 'string', + description: 'Owner type (User, Organization)', + }, + }, + }, + sender: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + }, + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GitHub-Event': 'pull_request_review', + 'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'X-Hub-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/github/push.ts b/apps/sim/triggers/github/push.ts new file mode 100644 index 0000000000..5f304d7f29 --- /dev/null +++ b/apps/sim/triggers/github/push.ts @@ -0,0 +1,560 @@ +import { GithubIcon } from '@/components/icons' +import type { TriggerConfig } from '@/triggers/types' + +export const githubPushTrigger: TriggerConfig = { + id: 'github_push', + name: 'GitHub Push', + provider: 'github', + description: 'Trigger workflow when code is pushed to a repository', + version: '1.0.0', + icon: GithubIcon, + + subBlocks: [ + { + id: 'webhookUrlDisplay', + title: 'Webhook URL', + type: 'short-input', + readOnly: true, + showCopyButton: true, + useWebhookUrl: true, + placeholder: 'Webhook URL will be generated', + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_push', + }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { + label: 'application/x-www-form-urlencoded', + id: 'application/x-www-form-urlencoded', + }, + ], + defaultValue: 'application/json', + description: 'Format GitHub will use when sending the webhook payload.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_push', + }, + }, + { + id: 'webhookSecret', + title: 'Webhook Secret', + type: 'short-input', + placeholder: 'Generate or enter a strong secret', + description: 'Validates that webhook deliveries originate from GitHub.', + password: true, + required: false, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_push', + }, + }, + { + id: 'sslVerification', + title: 'SSL Verification', + type: 'dropdown', + options: [ + { label: 'Enabled', id: 'enabled' }, + { label: 'Disabled', id: 'disabled' }, + ], + defaultValue: 'enabled', + description: 'GitHub verifies SSL certificates when delivering webhooks.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_push', + }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + type: 'text', + defaultValue: [ + 'Go to your GitHub Repository > Settings > Webhooks.', + 'Click "Add webhook".', + 'Paste the Webhook URL above into the "Payload URL" field.', + 'Select your chosen Content Type from the dropdown.', + 'Enter the Webhook Secret into the "Secret" field if you\'ve configured one.', + 'Set SSL verification according to your selection.', + 'Select "Let me select individual events" and check Pushes.', + 'Ensure "Active" is checked and click "Add webhook".', + ] + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join(''), + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_push', + }, + }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'github_push', + condition: { + field: 'selectedTriggerId', + value: 'github_push', + }, + }, + { + id: 'samplePayload', + title: 'Event Payload Example', + type: 'code', + language: 'json', + defaultValue: JSON.stringify( + { + ref: 'refs/heads/main', + before: '0000000000000000000000000000000000000000', + after: 'abc123def456789ghi012jkl345mno678pqr901', + created: true, + deleted: false, + forced: false, + base_ref: null, + compare: 'https://github.com/owner/repo-name/compare/0000000000000000...abc123def456', + commits: [ + { + id: 'abc123def456789ghi012jkl345mno678pqr901', + tree_id: 'tree123abc456def789ghi012jkl345mno678', + distinct: true, + message: 'Add new feature to improve performance', + timestamp: '2025-01-15T12:00:00Z', + url: 'https://github.com/owner/repo-name/commit/abc123def456789ghi012jkl345mno678pqr901', + author: { + name: 'Developer Name', + email: 'developer@example.com', + username: 'developer', + }, + committer: { + name: 'Developer Name', + email: 'developer@example.com', + username: 'developer', + }, + added: ['src/features/new-feature.ts'], + removed: [], + modified: ['src/index.ts', 'README.md'], + }, + { + id: 'def456ghi789jkl012mno345pqr678stu901vwx', + tree_id: 'tree456def789ghi012jkl345mno678pqr901', + distinct: true, + message: 'Update documentation', + timestamp: '2025-01-15T12:15:00Z', + url: 'https://github.com/owner/repo-name/commit/def456ghi789jkl012mno345pqr678stu901vwx', + author: { + name: 'Developer Name', + email: 'developer@example.com', + username: 'developer', + }, + committer: { + name: 'Developer Name', + email: 'developer@example.com', + username: 'developer', + }, + added: [], + removed: [], + modified: ['docs/API.md'], + }, + ], + head_commit: { + id: 'def456ghi789jkl012mno345pqr678stu901vwx', + tree_id: 'tree456def789ghi012jkl345mno678pqr901', + distinct: true, + message: 'Update documentation', + timestamp: '2025-01-15T12:15:00Z', + url: 'https://github.com/owner/repo-name/commit/def456ghi789jkl012mno345pqr678stu901vwx', + author: { + name: 'Developer Name', + email: 'developer@example.com', + username: 'developer', + }, + committer: { + name: 'Developer Name', + email: 'developer@example.com', + username: 'developer', + }, + added: [], + removed: [], + modified: ['docs/API.md'], + }, + pusher: { + name: 'developer', + email: 'developer@example.com', + }, + repository: { + id: 123456, + node_id: 'MDEwOlJlcG9zaXRvcnkxMjM0NTY=', + name: 'repo-name', + full_name: 'owner/repo-name', + private: false, + html_url: 'https://github.com/owner/repo-name', + repo_description: 'A sample repository for demonstrating push events', + fork: false, + url: 'https://api.github.com/repos/owner/repo-name', + homepage: 'https://example.com', + size: 1024, + stargazers_count: 42, + watchers_count: 42, + language: 'TypeScript', + forks_count: 5, + open_issues_count: 3, + default_branch: 'main', + owner: { + login: 'owner', + id: 7890, + node_id: 'MDQ6VXNlcjc4OTA=', + avatar_url: 'https://github.com/images/error/owner.gif', + html_url: 'https://github.com/owner', + owner_type: 'User', + }, + }, + sender: { + login: 'developer', + id: 5, + node_id: 'MDQ6VXNlcjU=', + avatar_url: 'https://github.com/images/error/developer.gif', + html_url: 'https://github.com/developer', + user_type: 'User', + }, + }, + null, + 2 + ), + readOnly: true, + collapsible: true, + defaultCollapsed: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_push', + }, + }, + ], + + outputs: { + ref: { + type: 'string', + description: 'Git reference that was pushed (e.g., refs/heads/main)', + }, + before: { + type: 'string', + description: 'SHA of the commit before the push', + }, + after: { + type: 'string', + description: 'SHA of the commit after the push', + }, + created: { + type: 'boolean', + description: 'Whether this push created a new branch or tag', + }, + deleted: { + type: 'boolean', + description: 'Whether this push deleted a branch or tag', + }, + forced: { + type: 'boolean', + description: 'Whether this was a force push', + }, + base_ref: { + type: 'string', + description: 'Base reference for the push', + }, + compare: { + type: 'string', + description: 'URL to compare the changes', + }, + commits: { + type: 'array', + description: 'Array of commit objects included in this push', + items: { + id: { + type: 'string', + description: 'Commit SHA', + }, + tree_id: { + type: 'string', + description: 'Git tree SHA', + }, + distinct: { + type: 'boolean', + description: 'Whether this commit is distinct from others in the push', + }, + message: { + type: 'string', + description: 'Commit message', + }, + timestamp: { + type: 'string', + description: 'Commit timestamp', + }, + url: { + type: 'string', + description: 'Commit URL', + }, + author: { + name: { + type: 'string', + description: 'Author name', + }, + email: { + type: 'string', + description: 'Author email', + }, + username: { + type: 'string', + description: 'Author GitHub username', + }, + }, + committer: { + name: { + type: 'string', + description: 'Committer name', + }, + email: { + type: 'string', + description: 'Committer email', + }, + username: { + type: 'string', + description: 'Committer GitHub username', + }, + }, + added: { + type: 'array', + description: 'Array of file paths added in this commit', + }, + removed: { + type: 'array', + description: 'Array of file paths removed in this commit', + }, + modified: { + type: 'array', + description: 'Array of file paths modified in this commit', + }, + }, + }, + head_commit: { + id: { + type: 'string', + description: 'Commit SHA of the most recent commit', + }, + tree_id: { + type: 'string', + description: 'Git tree SHA', + }, + distinct: { + type: 'boolean', + description: 'Whether this commit is distinct', + }, + message: { + type: 'string', + description: 'Commit message', + }, + timestamp: { + type: 'string', + description: 'Commit timestamp', + }, + url: { + type: 'string', + description: 'Commit URL', + }, + author: { + name: { + type: 'string', + description: 'Author name', + }, + email: { + type: 'string', + description: 'Author email', + }, + username: { + type: 'string', + description: 'Author GitHub username', + }, + }, + committer: { + name: { + type: 'string', + description: 'Committer name', + }, + email: { + type: 'string', + description: 'Committer email', + }, + username: { + type: 'string', + description: 'Committer GitHub username', + }, + }, + added: { + type: 'array', + description: 'Array of file paths added in this commit', + }, + removed: { + type: 'array', + description: 'Array of file paths removed in this commit', + }, + modified: { + type: 'array', + description: 'Array of file paths modified in this commit', + }, + }, + pusher: { + name: { + type: 'string', + description: 'Pusher name', + }, + email: { + type: 'string', + description: 'Pusher email', + }, + }, + repository: { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name (owner/repo)', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + html_url: { + type: 'string', + description: 'Repository HTML URL', + }, + repo_description: { + type: 'string', + description: 'Repository description', + }, + fork: { + type: 'boolean', + description: 'Whether the repository is a fork', + }, + url: { + type: 'string', + description: 'Repository API URL', + }, + homepage: { + type: 'string', + description: 'Repository homepage URL', + }, + size: { + type: 'number', + description: 'Repository size in KB', + }, + stargazers_count: { + type: 'number', + description: 'Number of stars', + }, + watchers_count: { + type: 'number', + description: 'Number of watchers', + }, + language: { + type: 'string', + description: 'Primary programming language', + }, + forks_count: { + type: 'number', + description: 'Number of forks', + }, + open_issues_count: { + type: 'number', + description: 'Number of open issues', + }, + default_branch: { + type: 'string', + description: 'Default branch name', + }, + owner: { + login: { + type: 'string', + description: 'Owner username', + }, + id: { + type: 'number', + description: 'Owner ID', + }, + node_id: { + type: 'string', + description: 'Owner node ID', + }, + avatar_url: { + type: 'string', + description: 'Owner avatar URL', + }, + html_url: { + type: 'string', + description: 'Owner profile URL', + }, + owner_type: { + type: 'string', + description: 'Owner type (User, Organization)', + }, + }, + }, + sender: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + }, + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GitHub-Event': 'push', + 'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'X-Hub-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/github/release_published.ts b/apps/sim/triggers/github/release_published.ts new file mode 100644 index 0000000000..913062b25b --- /dev/null +++ b/apps/sim/triggers/github/release_published.ts @@ -0,0 +1,969 @@ +import { GithubIcon } from '@/components/icons' +import type { TriggerConfig } from '@/triggers/types' + +export const githubReleasePublishedTrigger: TriggerConfig = { + id: 'github_release_published', + name: 'GitHub Release Published', + provider: 'github', + description: 'Trigger workflow when a new release is published in a GitHub repository', + version: '1.0.0', + icon: GithubIcon, + + subBlocks: [ + { + id: 'webhookUrlDisplay', + title: 'Webhook URL', + type: 'short-input', + readOnly: true, + showCopyButton: true, + useWebhookUrl: true, + placeholder: 'Webhook URL will be generated', + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_release_published', + }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { + label: 'application/x-www-form-urlencoded', + id: 'application/x-www-form-urlencoded', + }, + ], + defaultValue: 'application/json', + description: 'Format GitHub will use when sending the webhook payload.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_release_published', + }, + }, + { + id: 'webhookSecret', + title: 'Webhook Secret', + type: 'short-input', + placeholder: 'Generate or enter a strong secret', + description: 'Validates that webhook deliveries originate from GitHub.', + password: true, + required: false, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_release_published', + }, + }, + { + id: 'sslVerification', + title: 'SSL Verification', + type: 'dropdown', + options: [ + { label: 'Enabled', id: 'enabled' }, + { label: 'Disabled', id: 'disabled' }, + ], + defaultValue: 'enabled', + description: 'GitHub verifies SSL certificates when delivering webhooks.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_release_published', + }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + type: 'text', + defaultValue: [ + 'Go to your GitHub Repository > Settings > Webhooks.', + 'Click "Add webhook".', + 'Paste the Webhook URL above into the "Payload URL" field.', + 'Select your chosen Content Type from the dropdown.', + 'Enter the Webhook Secret into the "Secret" field if you\'ve configured one.', + 'Set SSL verification according to your selection.', + 'Select "Let me select individual events" and check Releases event.', + 'Ensure "Active" is checked and click "Add webhook".', + ] + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join(''), + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_release_published', + }, + }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'github_release_published', + condition: { + field: 'selectedTriggerId', + value: 'github_release_published', + }, + }, + { + id: 'samplePayload', + title: 'Event Payload Example', + type: 'code', + language: 'json', + defaultValue: JSON.stringify( + { + action: 'published', + release: { + id: 123456789, + node_id: 'RE_kwDOABCDEF4HFGijkl', + tag_name: 'v1.0.0', + target_commitish: 'main', + name: 'v1.0.0 - Initial Release', + body: 'This is the first stable release of our project.\n\n## Features\n- Feature A\n- Feature B\n- Feature C\n\n## Bug Fixes\n- Fixed issue #123\n- Fixed issue #456', + draft: false, + prerelease: false, + created_at: '2025-01-15T10:00:00Z', + published_at: '2025-01-15T12:00:00Z', + url: 'https://api.github.com/repos/owner/repo-name/releases/123456789', + html_url: 'https://github.com/owner/repo-name/releases/tag/v1.0.0', + assets_url: 'https://api.github.com/repos/owner/repo-name/releases/123456789/assets', + upload_url: + 'https://uploads.github.com/repos/owner/repo-name/releases/123456789/assets{?name,label}', + tarball_url: 'https://api.github.com/repos/owner/repo-name/tarball/v1.0.0', + zipball_url: 'https://api.github.com/repos/owner/repo-name/zipball/v1.0.0', + discussion_url: 'https://github.com/owner/repo-name/discussions/100', + author: { + login: 'releasemanager', + id: 12345, + node_id: 'MDQ6VXNlcjEyMzQ1', + avatar_url: 'https://avatars.githubusercontent.com/u/12345?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/releasemanager', + html_url: 'https://github.com/releasemanager', + followers_url: 'https://api.github.com/users/releasemanager/followers', + following_url: 'https://api.github.com/users/releasemanager/following{/other_user}', + gists_url: 'https://api.github.com/users/releasemanager/gists{/gist_id}', + starred_url: 'https://api.github.com/users/releasemanager/starred{/owner}{/repo}', + subscriptions_url: 'https://api.github.com/users/releasemanager/subscriptions', + organizations_url: 'https://api.github.com/users/releasemanager/orgs', + repos_url: 'https://api.github.com/users/releasemanager/repos', + events_url: 'https://api.github.com/users/releasemanager/events{/privacy}', + received_events_url: 'https://api.github.com/users/releasemanager/received_events', + user_type: 'User', + site_admin: false, + }, + assets: [ + { + id: 987654321, + node_id: 'RA_kwDOABCDEF4DcXYZ', + name: 'release-v1.0.0-linux-amd64.tar.gz', + label: 'Linux AMD64 Binary', + content_type: 'application/gzip', + state: 'uploaded', + size: 15728640, + download_count: 42, + created_at: '2025-01-15T11:30:00Z', + updated_at: '2025-01-15T11:30:00Z', + browser_download_url: + 'https://github.com/owner/repo-name/releases/download/v1.0.0/release-v1.0.0-linux-amd64.tar.gz', + url: 'https://api.github.com/repos/owner/repo-name/releases/assets/987654321', + uploader: { + login: 'releasemanager', + id: 12345, + node_id: 'MDQ6VXNlcjEyMzQ1', + avatar_url: 'https://avatars.githubusercontent.com/u/12345?v=4', + html_url: 'https://github.com/releasemanager', + user_type: 'User', + }, + }, + { + id: 987654322, + node_id: 'RA_kwDOABCDEF4DcXYa', + name: 'release-v1.0.0-darwin-amd64.tar.gz', + label: 'macOS AMD64 Binary', + content_type: 'application/gzip', + state: 'uploaded', + size: 14680064, + download_count: 28, + created_at: '2025-01-15T11:30:00Z', + updated_at: '2025-01-15T11:30:00Z', + browser_download_url: + 'https://github.com/owner/repo-name/releases/download/v1.0.0/release-v1.0.0-darwin-amd64.tar.gz', + url: 'https://api.github.com/repos/owner/repo-name/releases/assets/987654322', + uploader: { + login: 'releasemanager', + id: 12345, + node_id: 'MDQ6VXNlcjEyMzQ1', + avatar_url: 'https://avatars.githubusercontent.com/u/12345?v=4', + html_url: 'https://github.com/releasemanager', + user_type: 'User', + }, + }, + ], + }, + repository: { + id: 123456, + node_id: 'R_kgDOABCDEF', + name: 'repo-name', + full_name: 'owner/repo-name', + private: false, + html_url: 'https://github.com/owner/repo-name', + repo_description: 'A sample repository for demonstrating GitHub release webhooks', + fork: false, + url: 'https://api.github.com/repos/owner/repo-name', + archive_url: 'https://api.github.com/repos/owner/repo-name/{archive_format}{/ref}', + assignees_url: 'https://api.github.com/repos/owner/repo-name/assignees{/user}', + blobs_url: 'https://api.github.com/repos/owner/repo-name/git/blobs{/sha}', + branches_url: 'https://api.github.com/repos/owner/repo-name/branches{/branch}', + collaborators_url: + 'https://api.github.com/repos/owner/repo-name/collaborators{/collaborator}', + comments_url: 'https://api.github.com/repos/owner/repo-name/comments{/number}', + commits_url: 'https://api.github.com/repos/owner/repo-name/commits{/sha}', + compare_url: 'https://api.github.com/repos/owner/repo-name/compare/{base}...{head}', + contents_url: 'https://api.github.com/repos/owner/repo-name/contents/{+path}', + contributors_url: 'https://api.github.com/repos/owner/repo-name/contributors', + deployments_url: 'https://api.github.com/repos/owner/repo-name/deployments', + downloads_url: 'https://api.github.com/repos/owner/repo-name/downloads', + events_url: 'https://api.github.com/repos/owner/repo-name/events', + forks_url: 'https://api.github.com/repos/owner/repo-name/forks', + git_commits_url: 'https://api.github.com/repos/owner/repo-name/git/commits{/sha}', + git_refs_url: 'https://api.github.com/repos/owner/repo-name/git/refs{/sha}', + git_tags_url: 'https://api.github.com/repos/owner/repo-name/git/tags{/sha}', + hooks_url: 'https://api.github.com/repos/owner/repo-name/hooks', + issue_comment_url: + 'https://api.github.com/repos/owner/repo-name/issues/comments{/number}', + issue_events_url: 'https://api.github.com/repos/owner/repo-name/issues/events{/number}', + issues_url: 'https://api.github.com/repos/owner/repo-name/issues{/number}', + keys_url: 'https://api.github.com/repos/owner/repo-name/keys{/key_id}', + labels_url: 'https://api.github.com/repos/owner/repo-name/labels{/name}', + languages_url: 'https://api.github.com/repos/owner/repo-name/languages', + merges_url: 'https://api.github.com/repos/owner/repo-name/merges', + milestones_url: 'https://api.github.com/repos/owner/repo-name/milestones{/number}', + notifications_url: + 'https://api.github.com/repos/owner/repo-name/notifications{?since,all,participating}', + pulls_url: 'https://api.github.com/repos/owner/repo-name/pulls{/number}', + releases_url: 'https://api.github.com/repos/owner/repo-name/releases{/id}', + stargazers_url: 'https://api.github.com/repos/owner/repo-name/stargazers', + statuses_url: 'https://api.github.com/repos/owner/repo-name/statuses/{sha}', + subscribers_url: 'https://api.github.com/repos/owner/repo-name/subscribers', + subscription_url: 'https://api.github.com/repos/owner/repo-name/subscription', + tags_url: 'https://api.github.com/repos/owner/repo-name/tags', + teams_url: 'https://api.github.com/repos/owner/repo-name/teams', + trees_url: 'https://api.github.com/repos/owner/repo-name/git/trees{/sha}', + homepage: 'https://example.com', + size: 1024, + stargazers_count: 350, + watchers_count: 350, + language: 'TypeScript', + has_issues: true, + has_projects: true, + has_downloads: true, + has_wiki: true, + has_pages: false, + forks_count: 42, + mirror_url: null, + archived: false, + disabled: false, + open_issues_count: 12, + license: { + key: 'mit', + name: 'MIT License', + spdx_id: 'MIT', + url: 'https://api.github.com/licenses/mit', + node_id: 'MDc6TGljZW5zZTEz', + }, + allow_forking: true, + is_template: false, + topics: ['javascript', 'typescript', 'nodejs'], + visibility: 'public', + forks: 42, + open_issues: 12, + watchers: 350, + default_branch: 'main', + created_at: '2020-01-01T00:00:00Z', + updated_at: '2025-01-15T12:00:00Z', + pushed_at: '2025-01-15T11:45:00Z', + owner: { + login: 'owner', + id: 7890, + node_id: 'MDQ6VXNlcjc4OTA=', + avatar_url: 'https://avatars.githubusercontent.com/u/7890?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/owner', + html_url: 'https://github.com/owner', + followers_url: 'https://api.github.com/users/owner/followers', + following_url: 'https://api.github.com/users/owner/following{/other_user}', + gists_url: 'https://api.github.com/users/owner/gists{/gist_id}', + starred_url: 'https://api.github.com/users/owner/starred{/owner}{/repo}', + subscriptions_url: 'https://api.github.com/users/owner/subscriptions', + organizations_url: 'https://api.github.com/users/owner/orgs', + repos_url: 'https://api.github.com/users/owner/repos', + events_url: 'https://api.github.com/users/owner/events{/privacy}', + received_events_url: 'https://api.github.com/users/owner/received_events', + owner_type: 'User', + site_admin: false, + }, + }, + sender: { + login: 'releasemanager', + id: 12345, + node_id: 'MDQ6VXNlcjEyMzQ1', + avatar_url: 'https://avatars.githubusercontent.com/u/12345?v=4', + gravatar_id: '', + url: 'https://api.github.com/users/releasemanager', + html_url: 'https://github.com/releasemanager', + followers_url: 'https://api.github.com/users/releasemanager/followers', + following_url: 'https://api.github.com/users/releasemanager/following{/other_user}', + gists_url: 'https://api.github.com/users/releasemanager/gists{/gist_id}', + starred_url: 'https://api.github.com/users/releasemanager/starred{/owner}{/repo}', + subscriptions_url: 'https://api.github.com/users/releasemanager/subscriptions', + organizations_url: 'https://api.github.com/users/releasemanager/orgs', + repos_url: 'https://api.github.com/users/releasemanager/repos', + events_url: 'https://api.github.com/users/releasemanager/events{/privacy}', + received_events_url: 'https://api.github.com/users/releasemanager/received_events', + user_type: 'User', + site_admin: false, + }, + }, + null, + 2 + ), + readOnly: true, + collapsible: true, + defaultCollapsed: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_release_published', + }, + }, + ], + + outputs: { + action: { + type: 'string', + description: + 'Action performed (published, unpublished, created, edited, deleted, prereleased, released)', + }, + release: { + id: { + type: 'number', + description: 'Release ID', + }, + node_id: { + type: 'string', + description: 'Release node ID', + }, + tag_name: { + type: 'string', + description: 'Git tag name for the release', + }, + target_commitish: { + type: 'string', + description: 'Target branch or commit SHA', + }, + name: { + type: 'string', + description: 'Release name/title', + }, + body: { + type: 'string', + description: 'Release description/notes in markdown format', + }, + draft: { + type: 'boolean', + description: 'Whether the release is a draft', + }, + prerelease: { + type: 'boolean', + description: 'Whether the release is a pre-release', + }, + created_at: { + type: 'string', + description: 'Release creation timestamp', + }, + published_at: { + type: 'string', + description: 'Release publication timestamp', + }, + url: { + type: 'string', + description: 'Release API URL', + }, + html_url: { + type: 'string', + description: 'Release HTML URL', + }, + assets_url: { + type: 'string', + description: 'Release assets API URL', + }, + upload_url: { + type: 'string', + description: 'URL for uploading release assets', + }, + tarball_url: { + type: 'string', + description: 'Source code tarball download URL', + }, + zipball_url: { + type: 'string', + description: 'Source code zipball download URL', + }, + discussion_url: { + type: 'string', + description: 'Discussion URL if available', + }, + author: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + gravatar_id: { + type: 'string', + description: 'Gravatar ID', + }, + url: { + type: 'string', + description: 'User API URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + followers_url: { + type: 'string', + description: 'Followers API URL', + }, + following_url: { + type: 'string', + description: 'Following API URL', + }, + gists_url: { + type: 'string', + description: 'Gists API URL', + }, + starred_url: { + type: 'string', + description: 'Starred repositories API URL', + }, + subscriptions_url: { + type: 'string', + description: 'Subscriptions API URL', + }, + organizations_url: { + type: 'string', + description: 'Organizations API URL', + }, + repos_url: { + type: 'string', + description: 'Repositories API URL', + }, + events_url: { + type: 'string', + description: 'Events API URL', + }, + received_events_url: { + type: 'string', + description: 'Received events API URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + site_admin: { + type: 'boolean', + description: 'Whether user is a site administrator', + }, + }, + assets: { + type: 'array', + description: 'Array of release asset objects with download URLs', + }, + }, + repository: { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name (owner/repo)', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + html_url: { + type: 'string', + description: 'Repository HTML URL', + }, + repo_description: { + type: 'string', + description: 'Repository description', + }, + fork: { + type: 'boolean', + description: 'Whether the repository is a fork', + }, + url: { + type: 'string', + description: 'Repository API URL', + }, + archive_url: { + type: 'string', + description: 'Archive API URL', + }, + assignees_url: { + type: 'string', + description: 'Assignees API URL', + }, + blobs_url: { + type: 'string', + description: 'Blobs API URL', + }, + branches_url: { + type: 'string', + description: 'Branches API URL', + }, + collaborators_url: { + type: 'string', + description: 'Collaborators API URL', + }, + comments_url: { + type: 'string', + description: 'Comments API URL', + }, + commits_url: { + type: 'string', + description: 'Commits API URL', + }, + compare_url: { + type: 'string', + description: 'Compare API URL', + }, + contents_url: { + type: 'string', + description: 'Contents API URL', + }, + contributors_url: { + type: 'string', + description: 'Contributors API URL', + }, + deployments_url: { + type: 'string', + description: 'Deployments API URL', + }, + downloads_url: { + type: 'string', + description: 'Downloads API URL', + }, + events_url: { + type: 'string', + description: 'Events API URL', + }, + forks_url: { + type: 'string', + description: 'Forks API URL', + }, + git_commits_url: { + type: 'string', + description: 'Git commits API URL', + }, + git_refs_url: { + type: 'string', + description: 'Git refs API URL', + }, + git_tags_url: { + type: 'string', + description: 'Git tags API URL', + }, + hooks_url: { + type: 'string', + description: 'Hooks API URL', + }, + issue_comment_url: { + type: 'string', + description: 'Issue comment API URL', + }, + issue_events_url: { + type: 'string', + description: 'Issue events API URL', + }, + issues_url: { + type: 'string', + description: 'Issues API URL', + }, + keys_url: { + type: 'string', + description: 'Keys API URL', + }, + labels_url: { + type: 'string', + description: 'Labels API URL', + }, + languages_url: { + type: 'string', + description: 'Languages API URL', + }, + merges_url: { + type: 'string', + description: 'Merges API URL', + }, + milestones_url: { + type: 'string', + description: 'Milestones API URL', + }, + notifications_url: { + type: 'string', + description: 'Notifications API URL', + }, + pulls_url: { + type: 'string', + description: 'Pull requests API URL', + }, + releases_url: { + type: 'string', + description: 'Releases API URL', + }, + stargazers_url: { + type: 'string', + description: 'Stargazers API URL', + }, + statuses_url: { + type: 'string', + description: 'Statuses API URL', + }, + subscribers_url: { + type: 'string', + description: 'Subscribers API URL', + }, + subscription_url: { + type: 'string', + description: 'Subscription API URL', + }, + tags_url: { + type: 'string', + description: 'Tags API URL', + }, + teams_url: { + type: 'string', + description: 'Teams API URL', + }, + trees_url: { + type: 'string', + description: 'Trees API URL', + }, + homepage: { + type: 'string', + description: 'Repository homepage URL', + }, + size: { + type: 'number', + description: 'Repository size in KB', + }, + stargazers_count: { + type: 'number', + description: 'Number of stars', + }, + watchers_count: { + type: 'number', + description: 'Number of watchers', + }, + language: { + type: 'string', + description: 'Primary programming language', + }, + has_issues: { + type: 'boolean', + description: 'Whether issues are enabled', + }, + has_projects: { + type: 'boolean', + description: 'Whether projects are enabled', + }, + has_downloads: { + type: 'boolean', + description: 'Whether downloads are enabled', + }, + has_wiki: { + type: 'boolean', + description: 'Whether wiki is enabled', + }, + has_pages: { + type: 'boolean', + description: 'Whether GitHub Pages is enabled', + }, + forks_count: { + type: 'number', + description: 'Number of forks', + }, + mirror_url: { + type: 'string', + description: 'Mirror URL if repository is a mirror', + }, + archived: { + type: 'boolean', + description: 'Whether the repository is archived', + }, + disabled: { + type: 'boolean', + description: 'Whether the repository is disabled', + }, + open_issues_count: { + type: 'number', + description: 'Number of open issues', + }, + license: { + key: { + type: 'string', + description: 'License key', + }, + name: { + type: 'string', + description: 'License name', + }, + spdx_id: { + type: 'string', + description: 'SPDX license identifier', + }, + url: { + type: 'string', + description: 'License API URL', + }, + node_id: { + type: 'string', + description: 'License node ID', + }, + }, + allow_forking: { + type: 'boolean', + description: 'Whether forking is allowed', + }, + is_template: { + type: 'boolean', + description: 'Whether repository is a template', + }, + topics: { + type: 'array', + description: 'Array of repository topics', + }, + visibility: { + type: 'string', + description: 'Repository visibility (public, private, internal)', + }, + forks: { + type: 'number', + description: 'Number of forks', + }, + open_issues: { + type: 'number', + description: 'Number of open issues', + }, + watchers: { + type: 'number', + description: 'Number of watchers', + }, + default_branch: { + type: 'string', + description: 'Default branch name', + }, + created_at: { + type: 'string', + description: 'Repository creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Repository last update timestamp', + }, + pushed_at: { + type: 'string', + description: 'Repository last push timestamp', + }, + owner: { + login: { + type: 'string', + description: 'Owner username', + }, + id: { + type: 'number', + description: 'Owner ID', + }, + node_id: { + type: 'string', + description: 'Owner node ID', + }, + avatar_url: { + type: 'string', + description: 'Owner avatar URL', + }, + gravatar_id: { + type: 'string', + description: 'Owner gravatar ID', + }, + url: { + type: 'string', + description: 'Owner API URL', + }, + html_url: { + type: 'string', + description: 'Owner profile URL', + }, + followers_url: { + type: 'string', + description: 'Followers API URL', + }, + following_url: { + type: 'string', + description: 'Following API URL', + }, + gists_url: { + type: 'string', + description: 'Gists API URL', + }, + starred_url: { + type: 'string', + description: 'Starred repositories API URL', + }, + subscriptions_url: { + type: 'string', + description: 'Subscriptions API URL', + }, + organizations_url: { + type: 'string', + description: 'Organizations API URL', + }, + repos_url: { + type: 'string', + description: 'Repositories API URL', + }, + events_url: { + type: 'string', + description: 'Events API URL', + }, + received_events_url: { + type: 'string', + description: 'Received events API URL', + }, + owner_type: { + type: 'string', + description: 'Owner type (User, Organization)', + }, + site_admin: { + type: 'boolean', + description: 'Whether owner is a site administrator', + }, + }, + }, + sender: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + gravatar_id: { + type: 'string', + description: 'Gravatar ID', + }, + url: { + type: 'string', + description: 'User API URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + followers_url: { + type: 'string', + description: 'Followers API URL', + }, + following_url: { + type: 'string', + description: 'Following API URL', + }, + gists_url: { + type: 'string', + description: 'Gists API URL', + }, + starred_url: { + type: 'string', + description: 'Starred repositories API URL', + }, + subscriptions_url: { + type: 'string', + description: 'Subscriptions API URL', + }, + organizations_url: { + type: 'string', + description: 'Organizations API URL', + }, + repos_url: { + type: 'string', + description: 'Repositories API URL', + }, + events_url: { + type: 'string', + description: 'Events API URL', + }, + received_events_url: { + type: 'string', + description: 'Received events API URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + site_admin: { + type: 'boolean', + description: 'Whether user is a site administrator', + }, + }, + }, + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GitHub-Event': 'release', + 'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'X-Hub-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/github/utils.ts b/apps/sim/triggers/github/utils.ts new file mode 100644 index 0000000000..281ab19347 --- /dev/null +++ b/apps/sim/triggers/github/utils.ts @@ -0,0 +1,893 @@ +import type { SubBlockConfig } from '@/blocks/types' +import type { TriggerOutput } from '@/triggers/types' + +/** + * Shared sub-blocks configuration for all GitHub webhook triggers + */ +export const githubWebhookSubBlocks: SubBlockConfig[] = [ + { + id: 'webhookUrlDisplay', + title: 'Webhook URL', + type: 'short-input', + readOnly: true, + showCopyButton: true, + useWebhookUrl: true, + placeholder: 'Webhook URL will be generated', + mode: 'trigger', + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { + label: 'application/x-www-form-urlencoded', + id: 'application/x-www-form-urlencoded', + }, + ], + defaultValue: 'application/json', + description: 'Format GitHub will use when sending the webhook payload.', + required: true, + mode: 'trigger', + }, + { + id: 'webhookSecret', + title: 'Webhook Secret', + type: 'short-input', + placeholder: 'Generate or enter a strong secret', + description: 'Validates that webhook deliveries originate from GitHub.', + password: true, + required: false, + mode: 'trigger', + }, + { + id: 'sslVerification', + title: 'SSL Verification', + type: 'dropdown', + options: [ + { label: 'Enabled', id: 'enabled' }, + { label: 'Disabled', id: 'disabled' }, + ], + defaultValue: 'enabled', + description: 'GitHub verifies SSL certificates when delivering webhooks.', + required: true, + mode: 'trigger', + }, +] + +/** + * Generate setup instructions for a specific GitHub event type + */ +export function githubSetupInstructions( + eventType: string, + actions?: string[], + additionalNotes?: string +): string { + const actionText = actions + ? ` (${actions.join(', ')} ${actions.length === 1 ? 'action' : 'actions'})` + : '' + + const instructions = [ + 'Go to your GitHub Repository > Settings > Webhooks.', + 'Click "Add webhook".', + 'Paste the Webhook URL above into the "Payload URL" field.', + 'Select your chosen Content Type from the dropdown.', + 'Enter the Webhook Secret into the "Secret" field if you\'ve configured one.', + 'Set SSL verification according to your selection.', + `Select "Let me select individual events" and check ${eventType}${actionText}.`, + 'Ensure "Active" is checked and click "Add webhook".', + ] + + if (additionalNotes) { + instructions.push(additionalNotes) + } + + return instructions + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join('') +} + +/** + * Shared repository output schema + */ +export const repositoryOutputs = { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name (owner/repo)', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + html_url: { + type: 'string', + description: 'Repository HTML URL', + }, + description: { + type: 'string', + description: 'Repository description', + }, + fork: { + type: 'boolean', + description: 'Whether the repository is a fork', + }, + url: { + type: 'string', + description: 'Repository API URL', + }, + homepage: { + type: 'string', + description: 'Repository homepage URL', + }, + size: { + type: 'number', + description: 'Repository size in KB', + }, + stargazers_count: { + type: 'number', + description: 'Number of stars', + }, + watchers_count: { + type: 'number', + description: 'Number of watchers', + }, + language: { + type: 'string', + description: 'Primary programming language', + }, + forks_count: { + type: 'number', + description: 'Number of forks', + }, + open_issues_count: { + type: 'number', + description: 'Number of open issues', + }, + default_branch: { + type: 'string', + description: 'Default branch name', + }, + owner: { + login: { + type: 'string', + description: 'Owner username', + }, + id: { + type: 'number', + description: 'Owner ID', + }, + avatar_url: { + type: 'string', + description: 'Owner avatar URL', + }, + html_url: { + type: 'string', + description: 'Owner profile URL', + }, + }, +} as const + +/** + * Shared sender/user output schema + */ +export const userOutputs = { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, +} as const + +/** + * Build output schema for issue events + */ +export function buildIssueOutputs(): Record { + return { + action: { + type: 'string', + description: 'Action performed (opened, closed, reopened, edited, etc.)', + }, + issue: { + id: { + type: 'number', + description: 'Issue ID', + }, + node_id: { + type: 'string', + description: 'Issue node ID', + }, + number: { + type: 'number', + description: 'Issue number', + }, + title: { + type: 'string', + description: 'Issue title', + }, + body: { + type: 'string', + description: 'Issue body/description', + }, + state: { + type: 'string', + description: 'Issue state (open, closed)', + }, + state_reason: { + type: 'string', + description: 'Reason for state (completed, not_planned, reopened)', + }, + html_url: { + type: 'string', + description: 'Issue HTML URL', + }, + user: userOutputs, + labels: { + type: 'array', + description: 'Array of label objects', + }, + assignees: { + type: 'array', + description: 'Array of assigned users', + }, + milestone: { + type: 'object', + description: 'Milestone object if assigned', + }, + created_at: { + type: 'string', + description: 'Issue creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Issue last update timestamp', + }, + closed_at: { + type: 'string', + description: 'Issue closed timestamp', + }, + }, + repository: repositoryOutputs, + sender: userOutputs, + } as any +} + +/** + * Build output schema for issue comment events + */ +export function buildIssueCommentOutputs(): Record { + return { + action: { + type: 'string', + description: 'Action performed (created, edited, deleted)', + }, + issue: { + number: { + type: 'number', + description: 'Issue number', + }, + title: { + type: 'string', + description: 'Issue title', + }, + state: { + type: 'string', + description: 'Issue state (open, closed)', + }, + html_url: { + type: 'string', + description: 'Issue HTML URL', + }, + user: userOutputs, + }, + comment: { + id: { + type: 'number', + description: 'Comment ID', + }, + node_id: { + type: 'string', + description: 'Comment node ID', + }, + body: { + type: 'string', + description: 'Comment text', + }, + html_url: { + type: 'string', + description: 'Comment HTML URL', + }, + user: userOutputs, + created_at: { + type: 'string', + description: 'Comment creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Comment last update timestamp', + }, + }, + repository: repositoryOutputs, + sender: userOutputs, + } as any +} + +/** + * Build output schema for pull request events + */ +export function buildPullRequestOutputs(): Record { + return { + action: { + type: 'string', + description: 'Action performed (opened, closed, synchronize, reopened, edited, etc.)', + }, + number: { + type: 'number', + description: 'Pull request number', + }, + pull_request: { + id: { + type: 'number', + description: 'Pull request ID', + }, + node_id: { + type: 'string', + description: 'Pull request node ID', + }, + number: { + type: 'number', + description: 'Pull request number', + }, + title: { + type: 'string', + description: 'Pull request title', + }, + body: { + type: 'string', + description: 'Pull request description', + }, + state: { + type: 'string', + description: 'Pull request state (open, closed)', + }, + merged: { + type: 'boolean', + description: 'Whether the PR was merged', + }, + merged_at: { + type: 'string', + description: 'Timestamp when PR was merged', + }, + merged_by: userOutputs, + draft: { + type: 'boolean', + description: 'Whether the PR is a draft', + }, + html_url: { + type: 'string', + description: 'Pull request HTML URL', + }, + diff_url: { + type: 'string', + description: 'Pull request diff URL', + }, + patch_url: { + type: 'string', + description: 'Pull request patch URL', + }, + user: userOutputs, + head: { + ref: { + type: 'string', + description: 'Source branch name', + }, + sha: { + type: 'string', + description: 'Source branch commit SHA', + }, + repo: { + name: { + type: 'string', + description: 'Source repository name', + }, + full_name: { + type: 'string', + description: 'Source repository full name', + }, + }, + }, + base: { + ref: { + type: 'string', + description: 'Target branch name', + }, + sha: { + type: 'string', + description: 'Target branch commit SHA', + }, + repo: { + name: { + type: 'string', + description: 'Target repository name', + }, + full_name: { + type: 'string', + description: 'Target repository full name', + }, + }, + }, + additions: { + type: 'number', + description: 'Number of lines added', + }, + deletions: { + type: 'number', + description: 'Number of lines deleted', + }, + changed_files: { + type: 'number', + description: 'Number of files changed', + }, + labels: { + type: 'array', + description: 'Array of label objects', + }, + assignees: { + type: 'array', + description: 'Array of assigned users', + }, + requested_reviewers: { + type: 'array', + description: 'Array of requested reviewers', + }, + created_at: { + type: 'string', + description: 'Pull request creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Pull request last update timestamp', + }, + closed_at: { + type: 'string', + description: 'Pull request closed timestamp', + }, + }, + repository: repositoryOutputs, + sender: userOutputs, + } as any +} + +/** + * Build output schema for PR comment events + */ +export function buildPRCommentOutputs(): Record { + return { + action: { + type: 'string', + description: 'Action performed (created, edited, deleted)', + }, + issue: { + number: { + type: 'number', + description: 'Pull request number', + }, + title: { + type: 'string', + description: 'Pull request title', + }, + state: { + type: 'string', + description: 'Pull request state (open, closed)', + }, + html_url: { + type: 'string', + description: 'Pull request HTML URL', + }, + user: userOutputs, + pull_request: { + url: { + type: 'string', + description: 'Pull request API URL', + }, + html_url: { + type: 'string', + description: 'Pull request HTML URL', + }, + diff_url: { + type: 'string', + description: 'Pull request diff URL', + }, + }, + }, + comment: { + id: { + type: 'number', + description: 'Comment ID', + }, + node_id: { + type: 'string', + description: 'Comment node ID', + }, + body: { + type: 'string', + description: 'Comment text', + }, + html_url: { + type: 'string', + description: 'Comment HTML URL', + }, + user: userOutputs, + created_at: { + type: 'string', + description: 'Comment creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Comment last update timestamp', + }, + }, + repository: repositoryOutputs, + sender: userOutputs, + } as any +} + +/** + * Build output schema for PR review events + */ +export function buildPRReviewOutputs(): Record { + return { + action: { + type: 'string', + description: 'Action performed (submitted, edited, dismissed)', + }, + review: { + id: { + type: 'number', + description: 'Review ID', + }, + node_id: { + type: 'string', + description: 'Review node ID', + }, + body: { + type: 'string', + description: 'Review comment body', + }, + state: { + type: 'string', + description: 'Review state (approved, changes_requested, commented, dismissed)', + }, + html_url: { + type: 'string', + description: 'Review HTML URL', + }, + user: userOutputs, + submitted_at: { + type: 'string', + description: 'Review submission timestamp', + }, + }, + pull_request: { + number: { + type: 'number', + description: 'Pull request number', + }, + title: { + type: 'string', + description: 'Pull request title', + }, + state: { + type: 'string', + description: 'Pull request state (open, closed)', + }, + html_url: { + type: 'string', + description: 'Pull request HTML URL', + }, + user: userOutputs, + head: { + ref: { + type: 'string', + description: 'Source branch name', + }, + }, + base: { + ref: { + type: 'string', + description: 'Target branch name', + }, + }, + }, + repository: repositoryOutputs, + sender: userOutputs, + } as any +} + +/** + * Build output schema for push events + */ +export function buildPushOutputs(): Record { + return { + ref: { + type: 'string', + description: 'Git reference (e.g., refs/heads/main)', + }, + before: { + type: 'string', + description: 'SHA of the commit before the push', + }, + after: { + type: 'string', + description: 'SHA of the commit after the push', + }, + created: { + type: 'boolean', + description: 'Whether the push created the reference', + }, + deleted: { + type: 'boolean', + description: 'Whether the push deleted the reference', + }, + forced: { + type: 'boolean', + description: 'Whether the push was forced', + }, + compare: { + type: 'string', + description: 'URL to compare the changes', + }, + commits: { + type: 'array', + description: 'Array of commit objects', + id: { + type: 'string', + description: 'Commit SHA', + }, + message: { + type: 'string', + description: 'Commit message', + }, + timestamp: { + type: 'string', + description: 'Commit timestamp', + }, + url: { + type: 'string', + description: 'Commit URL', + }, + author: { + name: { + type: 'string', + description: 'Author name', + }, + email: { + type: 'string', + description: 'Author email', + }, + }, + added: { + type: 'array', + description: 'Array of added files', + }, + removed: { + type: 'array', + description: 'Array of removed files', + }, + modified: { + type: 'array', + description: 'Array of modified files', + }, + }, + head_commit: { + id: { + type: 'string', + description: 'Commit SHA', + }, + message: { + type: 'string', + description: 'Commit message', + }, + timestamp: { + type: 'string', + description: 'Commit timestamp', + }, + author: { + name: { + type: 'string', + description: 'Author name', + }, + email: { + type: 'string', + description: 'Author email', + }, + }, + }, + pusher: { + name: { + type: 'string', + description: 'Pusher name', + }, + email: { + type: 'string', + description: 'Pusher email', + }, + }, + branch: { + type: 'string', + description: 'Branch name extracted from ref', + }, + repository: repositoryOutputs, + sender: userOutputs, + } as any +} + +/** + * Build output schema for release events + */ +export function buildReleaseOutputs(): Record { + return { + action: { + type: 'string', + description: 'Action performed (published, created, edited, deleted, prereleased, released)', + }, + release: { + id: { + type: 'number', + description: 'Release ID', + }, + node_id: { + type: 'string', + description: 'Release node ID', + }, + tag_name: { + type: 'string', + description: 'Git tag name', + }, + target_commitish: { + type: 'string', + description: 'Target branch or commit', + }, + name: { + type: 'string', + description: 'Release name/title', + }, + body: { + type: 'string', + description: 'Release notes/description', + }, + draft: { + type: 'boolean', + description: 'Whether the release is a draft', + }, + prerelease: { + type: 'boolean', + description: 'Whether the release is a pre-release', + }, + html_url: { + type: 'string', + description: 'Release HTML URL', + }, + tarball_url: { + type: 'string', + description: 'Tarball download URL', + }, + zipball_url: { + type: 'string', + description: 'Zipball download URL', + }, + author: userOutputs, + assets: { + type: 'array', + description: 'Array of release asset objects', + }, + created_at: { + type: 'string', + description: 'Release creation timestamp', + }, + published_at: { + type: 'string', + description: 'Release publication timestamp', + }, + }, + repository: repositoryOutputs, + sender: userOutputs, + } as any +} + +/** + * Check if a GitHub event matches the expected trigger configuration + * This is used for event filtering in the webhook processor + */ +export function isGitHubEventMatch( + triggerId: string, + eventType: string, + action?: string, + payload?: any +): boolean { + const eventMap: Record< + string, + { event: string; actions?: string[]; validator?: (payload: any) => boolean } + > = { + github_issue_opened: { event: 'issues', actions: ['opened'] }, + github_issue_closed: { event: 'issues', actions: ['closed'] }, + github_issue_comment: { + event: 'issue_comment', + validator: (p) => !p.issue?.pull_request, // Only issues, not PRs + }, + github_pr_opened: { event: 'pull_request', actions: ['opened'] }, + github_pr_closed: { + event: 'pull_request', + actions: ['closed'], + validator: (p) => p.pull_request?.merged === false, // Not merged + }, + github_pr_merged: { + event: 'pull_request', + actions: ['closed'], + validator: (p) => p.pull_request?.merged === true, // Merged + }, + github_pr_comment: { + event: 'issue_comment', + validator: (p) => !!p.issue?.pull_request, // Only PRs, not issues + }, + github_pr_reviewed: { event: 'pull_request_review', actions: ['submitted'] }, + github_push: { event: 'push' }, + github_release_published: { event: 'release', actions: ['published'] }, + } + + const config = eventMap[triggerId] + if (!config) { + return true // Unknown trigger, allow through + } + + // Check event type + if (config.event !== eventType) { + return false + } + + // Check action if specified + if (config.actions && action && !config.actions.includes(action)) { + return false + } + + // Run custom validator if provided + if (config.validator && payload) { + return config.validator(payload) + } + + return true +} diff --git a/apps/sim/triggers/github/webhook.ts b/apps/sim/triggers/github/webhook.ts index 12f62952d3..109f7e4e7d 100644 --- a/apps/sim/triggers/github/webhook.ts +++ b/apps/sim/triggers/github/webhook.ts @@ -19,6 +19,10 @@ export const githubWebhookTrigger: TriggerConfig = { useWebhookUrl: true, placeholder: 'Webhook URL will be generated', mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_webhook', + }, }, { id: 'contentType', @@ -32,16 +36,24 @@ export const githubWebhookTrigger: TriggerConfig = { description: 'Format GitHub will use when sending the webhook payload.', required: true, mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_webhook', + }, }, { id: 'webhookSecret', - title: 'Webhook Secret (Recommended)', + title: 'Webhook Secret', type: 'short-input', placeholder: 'Generate or enter a strong secret', description: 'Validates that webhook deliveries originate from GitHub.', password: true, required: false, mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_webhook', + }, }, { id: 'sslVerification', @@ -55,6 +67,10 @@ export const githubWebhookTrigger: TriggerConfig = { description: 'GitHub verifies SSL certificates when delivering webhooks.', required: true, mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_webhook', + }, }, { id: 'triggerInstructions', @@ -76,6 +92,10 @@ export const githubWebhookTrigger: TriggerConfig = { ) .join(''), mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_webhook', + }, }, { id: 'triggerSave', @@ -83,6 +103,10 @@ export const githubWebhookTrigger: TriggerConfig = { type: 'trigger-save', mode: 'trigger', triggerId: 'github_webhook', + condition: { + field: 'selectedTriggerId', + value: 'github_webhook', + }, }, { id: 'samplePayload', @@ -133,6 +157,10 @@ export const githubWebhookTrigger: TriggerConfig = { collapsible: true, defaultCollapsed: true, mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_webhook', + }, }, ], diff --git a/apps/sim/triggers/github/workflow_run.ts b/apps/sim/triggers/github/workflow_run.ts new file mode 100644 index 0000000000..47ea68e80a --- /dev/null +++ b/apps/sim/triggers/github/workflow_run.ts @@ -0,0 +1,609 @@ +import { GithubIcon } from '@/components/icons' +import type { TriggerConfig } from '@/triggers/types' + +export const githubWorkflowRunTrigger: TriggerConfig = { + id: 'github_workflow_run', + name: 'GitHub Actions Workflow Run', + provider: 'github', + description: + 'Trigger workflow when a GitHub Actions workflow run is requested, in progress, or completed', + version: '1.0.0', + icon: GithubIcon, + + subBlocks: [ + { + id: 'webhookUrlDisplay', + title: 'Webhook URL', + type: 'short-input', + readOnly: true, + showCopyButton: true, + useWebhookUrl: true, + placeholder: 'Webhook URL will be generated', + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_workflow_run', + }, + }, + { + id: 'contentType', + title: 'Content Type', + type: 'dropdown', + options: [ + { label: 'application/json', id: 'application/json' }, + { + label: 'application/x-www-form-urlencoded', + id: 'application/x-www-form-urlencoded', + }, + ], + defaultValue: 'application/json', + description: 'Format GitHub will use when sending the webhook payload.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_workflow_run', + }, + }, + { + id: 'webhookSecret', + title: 'Webhook Secret', + type: 'short-input', + placeholder: 'Generate or enter a strong secret', + description: 'Validates that webhook deliveries originate from GitHub.', + password: true, + required: false, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_workflow_run', + }, + }, + { + id: 'sslVerification', + title: 'SSL Verification', + type: 'dropdown', + options: [ + { label: 'Enabled', id: 'enabled' }, + { label: 'Disabled', id: 'disabled' }, + ], + defaultValue: 'enabled', + description: 'GitHub verifies SSL certificates when delivering webhooks.', + required: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_workflow_run', + }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + type: 'text', + defaultValue: [ + 'Go to your GitHub Repository > Settings > Webhooks.', + 'Click "Add webhook".', + 'Paste the Webhook URL above into the "Payload URL" field.', + 'Select your chosen Content Type from the dropdown.', + 'Enter the Webhook Secret into the "Secret" field if you\'ve configured one.', + 'Set SSL verification according to your selection.', + 'Select "Let me select individual events" and check Workflow runs.', + 'Ensure "Active" is checked and click "Add webhook".', + ] + .map( + (instruction, index) => + `
${index + 1}. ${instruction}
` + ) + .join(''), + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_workflow_run', + }, + }, + { + id: 'triggerSave', + title: '', + type: 'trigger-save', + mode: 'trigger', + triggerId: 'github_workflow_run', + condition: { + field: 'selectedTriggerId', + value: 'github_workflow_run', + }, + }, + { + id: 'samplePayload', + title: 'Event Payload Example', + type: 'code', + language: 'json', + defaultValue: JSON.stringify( + { + action: 'completed', + workflow_run: { + id: 30433642, + node_id: 'MDEyOldvcmtmbG93IFJ1bjI2OTI4OQ==', + name: 'Build', + workflow_id: 159038, + run_number: 562, + run_attempt: 1, + event: 'push', + status: 'completed', + conclusion: 'success', + head_branch: 'master', + head_sha: 'acb5820ced9479c074f688cc328bf03f341a511d', + path: '.github/workflows/build.yml', + display_title: 'Update README', + run_started_at: '2020-01-22T19:33:08Z', + created_at: '2020-01-22T19:33:08Z', + updated_at: '2020-01-22T19:33:08Z', + html_url: 'https://github.com/octo-org/octo-repo/actions/runs/30433642', + check_suite_id: 42, + check_suite_node_id: 'MDEwOkNoZWNrU3VpdGU0Mg==', + url: 'https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642', + actor: { + login: 'octocat', + id: 1, + node_id: 'MDQ6VXNlcjE=', + avatar_url: 'https://github.com/images/error/octocat_happy.gif', + html_url: 'https://github.com/octocat', + type: 'User', + }, + triggering_actor: { + login: 'octocat', + id: 1, + node_id: 'MDQ6VXNlcjE=', + avatar_url: 'https://github.com/images/error/octocat_happy.gif', + html_url: 'https://github.com/octocat', + type: 'User', + }, + repository: { + id: 1296269, + node_id: 'MDEwOlJlcG9zaXRvcnkxMjk2MjY5', + name: 'Hello-World', + full_name: 'octocat/Hello-World', + private: false, + }, + head_repository: { + id: 1296269, + node_id: 'MDEwOlJlcG9zaXRvcnkxMjk2MjY5', + name: 'Hello-World', + full_name: 'octocat/Hello-World', + private: false, + }, + head_commit: { + id: 'acb5820ced9479c074f688cc328bf03f341a511d', + tree_id: 'd23f6eedb1e1b34603681f77168dc1c4', + message: 'Update README.md', + timestamp: '2020-01-22T19:33:05Z', + author: { + name: 'Octo Cat', + email: 'octocat@github.com', + }, + committer: { + name: 'GitHub', + email: 'noreply@github.com', + }, + }, + pull_requests: [], + referenced_workflows: [], + }, + workflow: { + id: 159038, + node_id: 'MDg6V29ya2Zsb3cxNTkwMzg=', + name: 'Build', + path: '.github/workflows/build.yml', + state: 'active', + created_at: '2020-01-08T23:48:37.000-08:00', + updated_at: '2020-01-08T23:50:21.000-08:00', + url: 'https://api.github.com/repos/octo-org/octo-repo/actions/workflows/159038', + html_url: + 'https://github.com/octo-org/octo-repo/blob/master/.github/workflows/build.yml', + badge_url: 'https://github.com/octo-org/octo-repo/workflows/Build/badge.svg', + }, + repository: { + id: 1296269, + node_id: 'MDEwOlJlcG9zaXRvcnkxMjk2MjY5', + name: 'Hello-World', + full_name: 'octocat/Hello-World', + html_url: 'https://github.com/octocat/Hello-World', + description: 'This your first repo!', + private: false, + owner: { + login: 'octocat', + id: 1, + node_id: 'MDQ6VXNlcjE=', + avatar_url: 'https://github.com/images/error/octocat_happy.gif', + html_url: 'https://github.com/octocat', + type: 'User', + }, + }, + sender: { + login: 'octocat', + id: 1, + node_id: 'MDQ6VXNlcjE=', + avatar_url: 'https://github.com/images/error/octocat_happy.gif', + html_url: 'https://github.com/octocat', + type: 'User', + }, + }, + null, + 2 + ), + readOnly: true, + collapsible: true, + defaultCollapsed: true, + mode: 'trigger', + condition: { + field: 'selectedTriggerId', + value: 'github_workflow_run', + }, + }, + ], + + outputs: { + action: { + type: 'string', + description: 'Action performed (requested, in_progress, completed)', + }, + workflow_run: { + id: { + type: 'number', + description: 'Workflow run ID', + }, + node_id: { + type: 'string', + description: 'Workflow run node ID', + }, + name: { + type: 'string', + description: 'Workflow name', + }, + workflow_id: { + type: 'number', + description: 'Workflow ID', + }, + run_number: { + type: 'number', + description: 'Run number for this workflow', + }, + run_attempt: { + type: 'number', + description: 'Attempt number for this run', + }, + event: { + type: 'string', + description: 'Event that triggered the workflow (push, pull_request, etc.)', + }, + status: { + type: 'string', + description: 'Current status (queued, in_progress, completed)', + }, + conclusion: { + type: 'string', + description: + 'Conclusion (success, failure, cancelled, skipped, timed_out, action_required)', + }, + head_branch: { + type: 'string', + description: 'Branch name', + }, + head_sha: { + type: 'string', + description: 'Commit SHA that triggered the workflow', + }, + path: { + type: 'string', + description: 'Path to the workflow file', + }, + display_title: { + type: 'string', + description: 'Display title for the run', + }, + run_started_at: { + type: 'string', + description: 'Timestamp when the run started', + }, + created_at: { + type: 'string', + description: 'Workflow run creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Workflow run last update timestamp', + }, + html_url: { + type: 'string', + description: 'Workflow run HTML URL', + }, + check_suite_id: { + type: 'number', + description: 'Associated check suite ID', + }, + check_suite_node_id: { + type: 'string', + description: 'Associated check suite node ID', + }, + url: { + type: 'string', + description: 'Workflow run API URL', + }, + actor: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + triggering_actor: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + repository: { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name', + }, + private: { + type: 'boolean', + description: 'Whether repository is private', + }, + }, + head_repository: { + id: { + type: 'number', + description: 'Head repository ID', + }, + node_id: { + type: 'string', + description: 'Head repository node ID', + }, + name: { + type: 'string', + description: 'Head repository name', + }, + full_name: { + type: 'string', + description: 'Head repository full name', + }, + private: { + type: 'boolean', + description: 'Whether repository is private', + }, + }, + head_commit: { + id: { + type: 'string', + description: 'Commit SHA', + }, + tree_id: { + type: 'string', + description: 'Tree ID', + }, + message: { + type: 'string', + description: 'Commit message', + }, + timestamp: { + type: 'string', + description: 'Commit timestamp', + }, + author: { + name: { + type: 'string', + description: 'Author name', + }, + email: { + type: 'string', + description: 'Author email', + }, + }, + committer: { + name: { + type: 'string', + description: 'Committer name', + }, + email: { + type: 'string', + description: 'Committer email', + }, + }, + }, + pull_requests: { + type: 'array', + description: 'Array of associated pull requests', + }, + referenced_workflows: { + type: 'array', + description: 'Array of referenced workflow runs', + }, + }, + workflow: { + id: { + type: 'number', + description: 'Workflow ID', + }, + node_id: { + type: 'string', + description: 'Workflow node ID', + }, + name: { + type: 'string', + description: 'Workflow name', + }, + path: { + type: 'string', + description: 'Path to workflow file', + }, + state: { + type: 'string', + description: 'Workflow state (active, deleted, disabled_fork, etc.)', + }, + created_at: { + type: 'string', + description: 'Workflow creation timestamp', + }, + updated_at: { + type: 'string', + description: 'Workflow last update timestamp', + }, + url: { + type: 'string', + description: 'Workflow API URL', + }, + html_url: { + type: 'string', + description: 'Workflow HTML URL', + }, + badge_url: { + type: 'string', + description: 'Workflow badge URL', + }, + }, + repository: { + id: { + type: 'number', + description: 'Repository ID', + }, + node_id: { + type: 'string', + description: 'Repository node ID', + }, + name: { + type: 'string', + description: 'Repository name', + }, + full_name: { + type: 'string', + description: 'Repository full name (owner/repo)', + }, + private: { + type: 'boolean', + description: 'Whether the repository is private', + }, + html_url: { + type: 'string', + description: 'Repository HTML URL', + }, + repo_description: { + type: 'string', + description: 'Repository description', + }, + owner: { + login: { + type: 'string', + description: 'Owner username', + }, + id: { + type: 'number', + description: 'Owner ID', + }, + node_id: { + type: 'string', + description: 'Owner node ID', + }, + avatar_url: { + type: 'string', + description: 'Owner avatar URL', + }, + html_url: { + type: 'string', + description: 'Owner profile URL', + }, + owner_type: { + type: 'string', + description: 'Owner type (User, Organization)', + }, + }, + }, + sender: { + login: { + type: 'string', + description: 'Username', + }, + id: { + type: 'number', + description: 'User ID', + }, + node_id: { + type: 'string', + description: 'User node ID', + }, + avatar_url: { + type: 'string', + description: 'Avatar URL', + }, + html_url: { + type: 'string', + description: 'Profile URL', + }, + user_type: { + type: 'string', + description: 'User type (User, Bot, Organization)', + }, + }, + }, + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-GitHub-Event': 'workflow_run', + 'X-GitHub-Delivery': 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + 'X-Hub-Signature-256': 'sha256=...', + }, + }, +} diff --git a/apps/sim/triggers/registry.ts b/apps/sim/triggers/registry.ts index 56fd7c9ee0..8fe05ce105 100644 --- a/apps/sim/triggers/registry.ts +++ b/apps/sim/triggers/registry.ts @@ -1,6 +1,19 @@ import { airtableWebhookTrigger } from '@/triggers/airtable' import { genericWebhookTrigger } from '@/triggers/generic' -import { githubWebhookTrigger } from '@/triggers/github' +import { + githubIssueClosedTrigger, + githubIssueCommentTrigger, + githubIssueOpenedTrigger, + githubPRClosedTrigger, + githubPRCommentTrigger, + githubPRMergedTrigger, + githubPROpenedTrigger, + githubPRReviewedTrigger, + githubPushTrigger, + githubReleasePublishedTrigger, + githubWebhookTrigger, + githubWorkflowRunTrigger, +} from '@/triggers/github' import { gmailPollingTrigger } from '@/triggers/gmail' import { googleFormsWebhookTrigger } from '@/triggers/googleforms' import { @@ -27,6 +40,17 @@ export const TRIGGER_REGISTRY: TriggerRegistry = { airtable_webhook: airtableWebhookTrigger, generic_webhook: genericWebhookTrigger, github_webhook: githubWebhookTrigger, + github_issue_opened: githubIssueOpenedTrigger, + github_issue_closed: githubIssueClosedTrigger, + github_issue_comment: githubIssueCommentTrigger, + github_pr_opened: githubPROpenedTrigger, + github_pr_closed: githubPRClosedTrigger, + github_pr_merged: githubPRMergedTrigger, + github_pr_comment: githubPRCommentTrigger, + github_pr_reviewed: githubPRReviewedTrigger, + github_push: githubPushTrigger, + github_release_published: githubReleasePublishedTrigger, + github_workflow_run: githubWorkflowRunTrigger, gmail_poller: gmailPollingTrigger, microsoftteams_webhook: microsoftTeamsWebhookTrigger, microsoftteams_chat_subscription: microsoftTeamsChatSubscriptionTrigger,