Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brave-houses-dress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@github-tools/sdk": minor
---

feat: add repository management tools
3 changes: 3 additions & 0 deletions apps/chat/shared/utils/tools/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export const GITHUB_TOOL_META: Record<GithubToolName, GithubToolMeta> = {
getRepository: { title: 'Get Repository', label: 'Repository fetched', labelActive: 'Fetching repository', icon: 'i-simple-icons-github' },
listBranches: { title: 'List Branches', label: 'Branches listed', labelActive: 'Listing branches', icon: 'i-lucide-git-branch' },
getFileContent: { title: 'Get File Content', label: 'File read', labelActive: 'Reading file', icon: 'i-lucide-file-code' },
createBranch: { title: 'Create Branch', label: 'Branch created', labelActive: 'Creating branch', icon: 'i-lucide-git-branch-plus' },
forkRepository: { title: 'Fork Repository', label: 'Repository forked', labelActive: 'Forking repository', icon: 'i-lucide-git-fork' },
createRepository: { title: 'Create Repository', label: 'Repository created', labelActive: 'Creating repository', icon: 'i-lucide-plus' },
createOrUpdateFile: { title: 'Create / Update File', label: 'File updated', labelActive: 'Updating file', icon: 'i-lucide-file-pen' },
listPullRequests: { title: 'List Pull Requests', label: 'Pull requests listed', labelActive: 'Listing pull requests', icon: 'i-lucide-git-pull-request' },
getPullRequest: { title: 'Get Pull Request', label: 'Pull request fetched', labelActive: 'Fetching pull request', icon: 'i-lucide-git-pull-request' },
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/content/docs/1.getting-started/1.introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Pass them to `generateText`, `streamText`, or any agent loop — the model decid

## Explore the tools

The SDK ships **18 tools** covering repositories, pull requests, issues, commits, and code search. Each tool maps to a single GitHub REST endpoint and is fully typed with [Zod](https://zod.dev) schemas.
The SDK ships **21 tools** covering repositories, branches, pull requests, issues, commits, and code search. Each tool maps to a single GitHub REST endpoint and is fully typed with [Zod](https://zod.dev) schemas.

Browse the full list in the [Tools Catalog](/api/tools-catalog).

Expand Down Expand Up @@ -63,7 +63,7 @@ See [Examples](/guide/examples) for more agent patterns.
title: Tools
to: /api/tools-catalog
---
18 individual GitHub operations you wire into any AI SDK call.
21 individual GitHub operations you wire into any AI SDK call.
:::

:::card
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/content/docs/2.guide/2.presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const tools = createGithubTools({
| `repo-explorer` | repository metadata, branches, file content, code search | knowledge retrieval, repo Q&A |
| `code-review` | pull requests, commits, file diffs, review comments | PR copilots, change summaries |
| `issue-triage` | issues, labels, comments, close/create | support triage, backlog bots |
| `maintainer` | all tool families | operator workflows with strict approvals |
| `maintainer` | all tool families including branch creation, forking, and repo creation | operator workflows with strict approvals |

## Pair presets with token scopes

Expand Down
6 changes: 6 additions & 0 deletions apps/docs/content/docs/2.guide/3.approval-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ import { createGithubTools } from '@github-tools/sdk'

const tools = createGithubTools({
requireApproval: {
createBranch: false,
forkRepository: true,
createRepository: true,
mergePullRequest: true,
createOrUpdateFile: true,
closeIssue: true,
Expand All @@ -65,10 +68,13 @@ const tools = createGithubTools({

| Operation | Risk | Suggested policy |
|---|---|---|
| `createRepository` | High | Always require approval |
| `forkRepository` | High | Always require approval |
| `createOrUpdateFile` | High | Always require approval |
| `mergePullRequest` | High | Always require approval |
| `closeIssue` | Medium | Require in production repos |
| `createPullRequest` | Medium | Optional in trusted CI |
| `createBranch` | Low | Usually skip |
| `addPullRequestComment` | Low | Usually skip |
| `addIssueComment` | Low | Usually skip |

Expand Down
12 changes: 6 additions & 6 deletions apps/docs/content/docs/2.guide/5.token-and-permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ Fine-grained personal access tokens let you restrict access per repository and p

## Map permissions to presets

| Preset | Repository access | Contents | Pull requests | Issues |
|---|---|---|---|---|
| `repo-explorer` | selected repos | `read` | — | — |
| `code-review` | selected repos | `read` | `read` (or `write` for comments) | — |
| `issue-triage` | selected repos | `read` | — | `write` |
| `maintainer` | selected repos | `write` | `write` | `write` |
| Preset | Repository access | Contents | Pull requests | Issues | Administration |
|---|---|---|---|---|---|
| `repo-explorer` | selected repos | `read` | — | — | — |
| `code-review` | selected repos | `read` | `read` (or `write` for comments) | — | — |
| `issue-triage` | selected repos | `read` | — | `write` | — |
| `maintainer` | selected repos | `write` | `write` | `write` | `write` (for repo creation and forking) |

## Apply least-privilege step by step

Expand Down
5 changes: 4 additions & 1 deletion apps/docs/content/docs/3.api/1.tools-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ links:

## Repository tools

Available in all presets. These tools read repository metadata and file content:
Available in all presets. These tools manage repositories, branches, and file content:

| Tool | Capability | Write |
|---|---|---|
| `getRepository` | read repository metadata (name, description, stars, language) | — |
| `listBranches` | list branches and their HEAD commits | — |
| `getFileContent` | read a file at a specific path and ref | — |
| `createBranch` | create a new branch from an existing branch or commit SHA | Yes |
| `forkRepository` | fork a repository to your account or an organization | Yes |
| `createRepository` | create a new repository for the authenticated user or an organization | Yes |
| `createOrUpdateFile` | create or update a file in the repository | Yes |

## Pull request tools
Expand Down
12 changes: 9 additions & 3 deletions packages/github-tools/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { createOctokit } from './client'
import { getRepository, listBranches, getFileContent, createOrUpdateFile } from './tools/repository'
import { getRepository, listBranches, getFileContent, createBranch, forkRepository, createRepository, createOrUpdateFile } from './tools/repository'
import { listPullRequests, getPullRequest, createPullRequest, mergePullRequest, addPullRequestComment } from './tools/pull-requests'
import { listIssues, getIssue, createIssue, addIssueComment, closeIssue } from './tools/issues'
import { searchCode, searchRepositories } from './tools/search'
import { listCommits, getCommit } from './tools/commits'

export type GithubWriteToolName =
| 'createBranch'
| 'forkRepository'
| 'createRepository'
| 'createOrUpdateFile'
| 'createPullRequest'
| 'mergePullRequest'
Expand Down Expand Up @@ -60,7 +63,7 @@ const PRESET_TOOLS: Record<GithubToolPreset, string[]> = {
'searchCode', 'searchRepositories'
],
'maintainer': [
'getRepository', 'listBranches', 'getFileContent', 'createOrUpdateFile',
'getRepository', 'listBranches', 'getFileContent', 'createBranch', 'forkRepository', 'createRepository', 'createOrUpdateFile',
'listPullRequests', 'getPullRequest', 'createPullRequest', 'mergePullRequest', 'addPullRequestComment',
'listIssues', 'getIssue', 'createIssue', 'addIssueComment', 'closeIssue',
'listCommits', 'getCommit',
Expand Down Expand Up @@ -157,6 +160,9 @@ export function createGithubTools({ token, requireApproval = true, preset }: Git
searchRepositories: searchRepositories(octokit),
listCommits: listCommits(octokit),
getCommit: getCommit(octokit),
createBranch: createBranch(octokit, approval('createBranch')),
forkRepository: forkRepository(octokit, approval('forkRepository')),
createRepository: createRepository(octokit, approval('createRepository')),
createOrUpdateFile: createOrUpdateFile(octokit, approval('createOrUpdateFile')),
createPullRequest: createPullRequest(octokit, approval('createPullRequest')),
mergePullRequest: mergePullRequest(octokit, approval('mergePullRequest')),
Expand All @@ -177,7 +183,7 @@ export type GithubTools = ReturnType<typeof createGithubTools>

// Re-export individual tool factories for cherry-picking
export { createOctokit } from './client'
export { getRepository, listBranches, getFileContent, createOrUpdateFile } from './tools/repository'
export { getRepository, listBranches, getFileContent, createBranch, forkRepository, createRepository, createOrUpdateFile } from './tools/repository'
export { listPullRequests, getPullRequest, createPullRequest, mergePullRequest, addPullRequestComment } from './tools/pull-requests'
export { listIssues, getIssue, createIssue, addIssueComment, closeIssue } from './tools/issues'
export { searchCode, searchRepositories } from './tools/search'
Expand Down
105 changes: 105 additions & 0 deletions packages/github-tools/src/tools/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,111 @@ export const getFileContent = (octokit: Octokit) =>
},
})

export const createBranch = (octokit: Octokit, { needsApproval = true }: ToolOptions = {}) =>
tool({
description: 'Create a new branch in a GitHub repository from an existing branch or commit SHA',
needsApproval,
inputSchema: z.object({
owner: z.string().describe('Repository owner'),
repo: z.string().describe('Repository name'),
branch: z.string().describe('Name for the new branch'),
from: z.string().optional().describe('Source branch name or commit SHA to branch from (defaults to the default branch)'),
}),
execute: async ({ owner, repo, branch, from }) => {
let sha = from
if (!sha || !sha.match(/^[0-9a-f]{40}$/i)) {
const { data: ref } = await octokit.rest.git.getRef({
owner,
repo,
ref: `heads/${from || (await octokit.rest.repos.get({ owner, repo })).data.default_branch}`,
})
sha = ref.object.sha
}
const { data } = await octokit.rest.git.createRef({
owner,
repo,
ref: `refs/heads/${branch}`,
sha,
})
return {
ref: data.ref,
sha: data.object.sha,
url: data.url,
}
},
})

export const forkRepository = (octokit: Octokit, { needsApproval = true }: ToolOptions = {}) =>
tool({
description: 'Fork a GitHub repository to the authenticated user account or a specified organization',
needsApproval,
inputSchema: z.object({
owner: z.string().describe('Repository owner to fork from'),
repo: z.string().describe('Repository name to fork'),
organization: z.string().optional().describe('Organization to fork into (omit to fork to your personal account)'),
name: z.string().optional().describe('Name for the forked repository (defaults to the original name)'),
}),
execute: async ({ owner, repo, organization, name }) => {
const { data } = await octokit.rest.repos.createFork({
owner,
repo,
organization,
name,
})
return {
name: data.name,
fullName: data.full_name,
url: data.html_url,
cloneUrl: data.clone_url,
sshUrl: data.ssh_url,
defaultBranch: data.default_branch,
private: data.private,
parent: data.parent ? { fullName: data.parent.full_name, url: data.parent.html_url } : null,
}
},
})

export const createRepository = (octokit: Octokit, { needsApproval = true }: ToolOptions = {}) =>
tool({
description: 'Create a new GitHub repository for the authenticated user or a specified organization',
needsApproval,
inputSchema: z.object({
name: z.string().describe('Repository name'),
description: z.string().optional().describe('A short description of the repository'),
isPrivate: z.boolean().optional().default(false).describe('Whether the repository is private'),
autoInit: z.boolean().optional().default(false).describe('Create an initial commit with a README'),
gitignoreTemplate: z.string().optional().describe('Gitignore template to use (e.g. "Node", "Python")'),
licenseTemplate: z.string().optional().describe('License keyword (e.g. "mit", "apache-2.0")'),
org: z.string().optional().describe('Organization to create the repository in (omit for personal repo)'),
}),
execute: async ({ name, description, isPrivate, autoInit, gitignoreTemplate, licenseTemplate, org }) => {
const params = {
name,
description,
private: isPrivate,
auto_init: autoInit,
gitignore_template: gitignoreTemplate,
license_template: licenseTemplate,
}

const { data } = org
? await octokit.rest.repos.createInOrg({ org, ...params })
: await octokit.rest.repos.createForAuthenticatedUser(params)

return {
name: data.name,
fullName: data.full_name,
description: data.description,
url: data.html_url,
cloneUrl: data.clone_url,
sshUrl: data.ssh_url,
defaultBranch: data.default_branch,
private: data.private,
createdAt: data.created_at,
}
},
})

export const createOrUpdateFile = (octokit: Octokit, { needsApproval = true }: ToolOptions = {}) =>
tool({
description: 'Create or update a file in a GitHub repository. Provide the SHA when updating an existing file.',
Expand Down
Loading