@github-tools/sdk wraps GitHub's REST API as 97 AI SDK-compatible tools for agents and generateText/streamText calls — with presets, approval control, and integrations for eve, TanStack AI, Vercel Workflow, and Chat SDK. Docs: github-tools.com.
pnpm build # Build all packages (turbo)
pnpm lint # Lint all packages
pnpm typecheck # Type-check all packages
pnpm test # SDK vitest + eve extension evals (examples/eve-smoke, tests/eve-act-as, tests/eve-auto) + the eve-bench gh classifier test
pnpm dev # Run the chat app in dev mode
pnpm docs:dev # Run the docs site in dev mode
# SDK-specific
pnpm --filter @github-tools/sdk build
pnpm --filter @github-tools/sdk lint
pnpm --filter @github-tools/sdk typecheck
pnpm --filter @github-tools/sdk test
# Release
pnpm changeset # Create a changeset for user-facing changes
pnpm version-packages # Apply changesets
pnpm release # Build SDK + publishVerify changes with pnpm build && pnpm lint && pnpm typecheck && pnpm test. The SDK has vitest (packages/github-tools/**/*.test.ts). The eve extension is covered by examples/eve-smoke: eve eval mounts the built extension with a mockModel, a fixed routing model, and an in-process api.github.com stub, and checks routing, registration, execution, and approval gating.
pnpm workspaces + Turborepo, with published packages, apps, and examples:
packages/github-tools— the SDK (@github-tools/sdk), published to npm. Built withtsdownto ESM (.mjs/.d.mts).packages/github-tools-eve-extension— the published eve extension (@github-tools/eve-extension).examples/*— consumer integrations used for development, smoke tests (examples/eve-smoke), and canary checks.examples/eve— the Slack and GitHub agent people clone or deploy on its own (Deploy button,giget), so it must work outside the monorepo: noworkspace:*, no root config. It depends on@github-tools/eve-extensionfrom npm aslatest, because Changesets rewrites a semver range to the unreleased version in the Version PR, which breakspnpm install --frozen-lockfile. Only use released options in it; to try unreleased extension code, useexamples/eve-smokeortests/*. After a release, move its lockfile entry to the new version so CI builds against it: set the dependency to that version, runpnpm install, set it back tolatestand runpnpm installagain.pnpm update --filteralso refreshes unrelated transitive dependencies.tests/*— eval-only agents that are not usable examples:tests/eve-act-ascovers the eve extension'sactAs,approversandgithubChannel'srespondTo, andtests/eve-autocoverspreset: 'auto'andrequireApproval: 'auto', both with stubbed Vercel Connect and GitHub.apps/chat— Nuxt 4 demo app with NuxtHub (SQLite + blob), GitHub OAuth, dual-mode agent (standardToolLoopAgentvs durableWorkflowAgent).apps/docs— Nuxt 4 docs site built on Docus. Also publishes a consumer-facing Agent Skill atapps/docs/skills/github-tools-agents/(served via/.well-known/skills/, seeapps/docs/content/docs/1.start/4.agent-skills.md).
Turbo task dependencies: lint, lint:fix, typecheck, and test all depend on ^build (upstream packages must build first).
Every tool splits into a core function and a tool factory. This split is critical to maintain when adding tools:
// src/core/{domain}.ts — pure logic, no "use step", no approval concerns
export const myToolInputSchema = z.object({ /* .describe() on every field */ })
export const myToolDescription = 'One sentence, present tense.'
export async function myToolCore({ token, ...args }: { token: string /* ... */ }) {
return withOctokit(token, async (octokit) => {
const { data } = await octokit.rest.someEndpoint(...)
return shapedResult // Always shape the return — never return raw API responses
})
}
// src/tools/{domain}.ts — the "use step" directive + ai SDK wrapper
async function myToolStep(args: Parameters<typeof myToolCore>[0]) {
"use step" // required for Vercel Workflow durable steps
return myToolCore(args)
}
// Read tools take (token). Write tools also take ToolOptions for needsApproval.
export const myTool = (token: GithubTokenInput, { needsApproval = true }: ToolOptions = {}): GithubTool =>
tool({
description: myToolDescription,
needsApproval, // write tools only
inputSchema: myToolInputSchema,
execute: async args => myToolStep({ token: await resolveGithubToken(token), ...args }),
})REST list tools return { items, hasMore, page, perPage, nextPage? } via pagedList() (src/core/pagination.ts). Object-shaped lists (listCheckRuns, listWorkflowRuns, …) add the same paging fields next to their existing keys. hasMore must live on the execute result — eve toModelOutput cannot see input. Restore page on every REST list schema.
Adding a new tool? Follow the checklist in .github/CONTRIBUTING.md — registration files, chat metadata, docs, changeset.
src/index.ts— public API:createGithubTools(),allToolscomposition, re-exportssrc/agents.ts—createGithubAgent()(ToolLoopAgent); shared preset prompts live insrc/core/instructions.tssrc/tanstack/index.tsandsrc/tanstack/— native TanStack tools, agent, evaluation, and approval middleware; browser-safe interrupt definition at@github-tools/sdk/tanstack/interruptssrc/core/evaluation-policy.ts— runtime-independent auto-approval and preset evaluation policysrc/workflow.ts—createDurableGithubAgent()(WorkflowAgentfrom@ai-sdk/workflow), exported from@github-tools/sdk/workflowsubpathsrc/core/evaluation.ts—requireApproval: 'auto'andpreset: 'auto'on AI SDKexperimental_decide(default modeltypesafe-ai/jev-1.14.0, pinned inDEFAULT_EVALUATION_MODEL).aiis a static namespace import andexperimental_decide(orexperimental_evaluatebefore ai 7.0.128) is feature-detected, so the root entry still loads onai6. Keep it static: a dynamicimport('ai')splits the chunk and breakseve buildof the extensionsrc/eve-runtime.ts— shared eve primitives for@github-tools/eve-extension(listEveToolDescriptors,executeGithubEveTool, approval helpers); public export@github-tools/sdk/eve-runtimesrc/eve.ts— deprecated consumercreateGithubTools/ per-tool factories foragent/tools/(@github-tools/sdk/eve)src/client.ts—createOctokit(token)andwithOctokit(token, fn), which core functions use to call the GitHub APIsrc/types.ts—ToolOptions,CommitToolOptions,ToolOverrides,GithubToolsrc/tools/— domain files (theaiSDK wrapper layer):repository.ts,pull-requests.ts,issues.ts,reactions.ts,discussions.ts,notifications.ts,commits.ts,gists.ts,workflows.ts,search.ts,checks.ts,releases.ts,security.ts,bundles.tssrc/core/catalog.ts— single source of truth for tools: oneGITHUB_TOOL_CATALOGentry per tool (description, schema,writeflag, Connect scopes).GITHUB_TOOL_NAMES,GITHUB_WRITE_TOOLS,TOOL_CONNECT_SCOPES, and the eve tool registry are derived from it. Registering a tool = catalog entry +GITHUB_TOOL_CORESentry (src/core/cores.ts) +"use step"factory +allToolsinindex.ts(the last two are compile-enforced viasatisfies). Keep*Corereferences out of the catalog:createDurableGithubAgentruns in the workflow flow bundle, and workflow 5 rejects a flow bundle that reaches Octokitsrc/core/— matching domain files (pure logic: schema, description,*Corefunction) plustool-names.ts/write-tools.ts(derived shims),presets.ts(PRESET_TOOLS),token.ts(resolveGithubToken),approval.ts(resolveAiSdkApproval)
- Standard:
createGithubAgent()→ToolLoopAgent— supportsrequireApprovalfor human-in-the-loop - Durable:
createDurableGithubAgent()→WorkflowAgent— crash-safe, retryable;requireApprovalpauses the workflow until the user responds
createGithubTools accepts an overrides option for per-tool customization (description, title, needsApproval, etc.) without changing the tool's implementation. execute, inputSchema, and outputSchema cannot be overridden. The ToolOverrides type is re-exported for consumers.
Ten presets (code-review, issue-triage, repo-explorer, ci-ops, security-audit, release-manager, discussion-moderator, notification-inbox, pr-author, maintainer) defined in src/core/presets.ts as tool name arrays, with matching system prompts in src/core/instructions.ts. Composable via arrays.
extension/tools/github.ts default-exports defineDynamic(async (ctx) => tools). eve runs it before every model call and rebuilds every callback and schema from what it returns, so ordinary closures and Zod schemas are fine; there are no durable descriptors to preserve. Re-read config through buildSessionOptions() or extension.config. Work that must stay fixed across model calls or processes (the preset: 'auto' routing) goes in ctx.memo(key, fn) with a JSON result. examples/eve-smoke runs the built extension under eve eval in CI.
channels/index.ts (@github-tools/eve-extension/channels, the githubChannel wrapper with respondTo) is not part of the mounted extension: tsc -p tsconfig.channels.json compiles it to channels/dist. Keep it out of extension/ and dist/. eve binds every module under dist/extension to a mount, so an agent with two mounts of the package can't import one from a channel file, and building an agent that uses the workspace extension rebuilds dist/ and deletes anything else in it. A scheduled canary (.github/workflows/eve-canary.yml) builds examples/eve and runs that smoke eval against a coherent eve@latest / ai@latest pair.
- Frontend: Vue 3 +
@nuxt/ui. Chat pages atapp/pages/chat/[id].vuewith tool invocation rendering and approval UI. - Backend: Nitro server routes under
server/api/. Standard chat atserver/api/chats/[id].post.ts, durable chat atserver/api/workflow/chats/[id].post.ts. - Database: Drizzle ORM with SQLite (
@libsql/client). Schema inserver/db/schema.ts—users,chats,messagestables. - Auth: GitHub OAuth via
nuxt-auth-utils, storesgithubTokenin secure session. - AI routing: Uses
@ai-sdk/gatewayfor unified model routing. Model selected per-session via cookie.
- TypeScript: Strict mode, ESNext target,
verbatimModuleSyntax: true - ESLint:
typescript-eslintflat config for SDK;@nuxt/eslintwith stylistic rules for apps (no trailing commas, 1tbs brace style) - Peer deps:
aiandzodare peer deps of the SDK;workflowand@ai-sdk/workfloware optional peer deps for the workflow subpath;@github-tools/eve-extensiondeclareseveas*(the consumer supplies the runtime copy; eve validates the built capability metadata) and pins an exactevedevDependency as its authoring/build version; the SDK's optionalevepeer range ends in|| >=1.0.0-0because npm does not match prereleases such as1.0.0-rc.1against a plain>=range
- No gratuitous defensive code. Don't add try/catch, null checks, or input validation the surrounding file doesn't have — especially on paths already validated upstream. Match the file's level of paranoia.
- No silent fallbacks. No empty
catch, no?? defaultthat masks a bug, noas anyto silence TypeScript. If something can fail, let it fail loudly or handle it explicitly. - Comments are rare and earn their place. Only for constraints the code can't express. Never paraphrase the code, never narrate a change.
- This extends to all prose: test names, error/log messages, changeset descriptions, PR bodies. Factual and plain — no emoji, no superlatives, no filler.
- Use the
technical-writerskill (.agents/skills/technical-writer/SKILL.md) whenever you write or edit prose: READMEs, changesets, commit messages, PR titles and bodies, and issues. - No speculative code. No unrequested options or parameters, no "just in case" branches, no keeping the old code path alongside the new one.
- Shape every API response. Never return a raw Octokit response from a tool's
*Corefunction — pick the fields the model actually needs.
Every user-facing change to @github-tools/sdk or @github-tools/eve-extension must include a changeset. Before opening a PR for features, bug fixes, or breaking changes, run pnpm changeset and commit the generated .changeset/*.md file alongside the code.
- When to add one: any change that affects the public API, adds a tool or preset, fixes a bug, or introduces a breaking change.
- When you can skip: changes confined to
apps/*orexamples/*(docs included), CI config, or internal refactors that don't touch the published packages. - Bump type:
patchfor fixes,minorfor features (new tools, new presets),majorfor breaking changes. - Description: write from the consumer's perspective — what changed and how to use it.
On merge to main, changesets/action opens or updates the "Version Packages" PR; merging that publishes to npm and creates one GitHub release per package (tag {name}@{version}). scripts/release-notes.mjs then rewrites each release body from the PRs that actually touched that package's directory, grouped by the categories in .github/release.yml — see the script's header comment for why.
PR titles and commits follow Conventional Commits. The CI source of truth is .github/workflows/semantic-pull-request.yml (lints PR titles via amannn/action-semantic-pull-request); .github/pull_request_template.md mirrors the same lists for contributors.
- Subject must not start with an uppercase letter.
feat: add stream server✓ —feat: Add stream server✗. - Current scopes:
chat,deps,docs,eve,sdk. Individual tools/presets don't get their own scope — they usesdk. - When you add a new scope, register it in both the workflow and the template, alphabetically sorted. The check reads the scope list from the PR's base branch (
pull_request_target), so a brand-new scope can't validate the PR that introduces it — register it in a preceding PR, or omit the scope on the introducing PR.
apps/docs/skills/github-tools-agents/SKILL.md is published to consumers via /.well-known/skills/. When a change affects what it documents — tools, presets, approval control, agent setup — update it in the same PR. A skill describing stale behavior is worse than no skill.
Any install or CLI command in apps/docs/content/**/*.md that a reader would run in their own project must show all four package managers, wrapped in a :::code-group (see apps/docs/content/docs/1.start/2.installation.md for the canonical pattern):
:::code-group
```bash [pnpm]
pnpm add @github-tools/sdk
```
```bash [npm]
npm install @github-tools/sdk
```
```bash [yarn]
yarn add @github-tools/sdk
```
```bash [bun]
bun add @github-tools/sdk
```
:::This applies to every pnpm add/pnpm install in the docs site, including peer dependencies and framework-specific installs (eve extension, Vercel Connect, Workflow, Chat SDK). It does not apply to single, package-manager-agnostic commands (npx eve dev, npx skills add ...) or to packages/github-tools/README.md and the agent skill under apps/docs/skills/, which stay pnpm-only for brevity (npm's README renderer and Agent Skills consumed by an LLM don't support tabs).
A task is complete when all of the following pass:
pnpm build,pnpm lint,pnpm typecheck, andpnpm testexit 0- New tools follow the full checklist in
.github/CONTRIBUTING.md - A changeset is included for any user-facing change (
pnpm changeset) - New public APIs have JSDoc
- Docs are updated when behavior, tools, or paths change (
apps/docs/content/,packages/github-tools/README.md)
Always do:
- Run
pnpm build,pnpm lint,pnpm typecheck, andpnpm testbefore reporting done - Follow existing code patterns — read neighboring files (and the matching
core//tools/pair) before writing new ones - Add a changeset (
pnpm changeset) for every user-facing change
Ask first:
- Adding new dependencies —
pnpm-workspace.yamlsetsminimumReleaseAge: 2880: a package published less than 48h ago fails to install unless added tominimumReleaseAgeExclude - Changing package exports, the tool factory pattern, or approval defaults for an existing write tool
- Architectural decisions that affect multiple packages or presets
Never:
- Commit secrets,
.envfiles,GITHUB_TOKEN, or API keys - Skip lint, typecheck, or test to "fix later"
- Widen a type (
as any) or drop a.describe()to silence an error — fix the underlying issue - Return a raw, unshaped API response from a tool
- Modify
node_modules/or generated files (dist/,.nuxt/,.output/) - Open a PR for a user-facing change without a changeset
Default: anything that stays on the local clone is fine, anything that touches the remote or GitHub requires an explicit instruction in the task at hand. Never act on assumption — if the maintainer didn't ask for a push or a PR, prepare the branch locally and stop there.
OK (local-only, no ask needed):
git branch,git checkout,git switch,git checkout -b— create and move between branches freelygit add,git commit— staging and local commits are finegit status,git diff,git log,git show,git stash,git restore,git reset(local only)gh pr view,gh pr list,gh pr diff,gh issue view,gh run view— read-only GitHub queries
OK when the maintainer explicitly asks (in the current task):
git push -u origin <feature-branch>— push a feature branch you just preparedgh pr create --base main --head <feature-branch>— open a PR- Write a PR title (Conventional Commits, see above) and a PR body — keep the body factual, mirror the changeset, reference the issue (
Closes #X)
Never (no exceptions, even when asked):
- Push directly to
main— protected, always goes through a PR git push --forcewithout--with-lease,git push --tagsgh pr merge,gh pr close,gh pr review,gh issue create,gh issue edit,gh release create- Add a
Co-authored-by,Signed-off-by, "Generated with…", "🤖", or any signature/attribution that names an agent, model, or tool
- Unsure about the tool pattern or a touchpoint → read
.github/CONTRIBUTING.mdor ask - Unclear requirements → ask a clarifying question before making large speculative changes
This file is living documentation — keep it true. If you catch it contradicting the repo (a command that doesn't exist, a path that moved, a described pattern that isn't real), flag it immediately and propose the fix, even if it's unrelated to your task. Update it when you encounter a recurring mistake, explicit guidance from the maintainer, or a new convention that should be applied consistently. A correction is a few lines, not a rewrite — keep this file lean.