Skip to content
Draft
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/guided-github-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---

@vercel vercel Bot Aug 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The channel/github guided-setup feature ships as a patch (→ 0.29.4) but its registry gate requires >=0.31.0, so the released version blocks users from running its own setup.

Fix on Vercel

"eve": patch
---

Add guided GitHub channel setup through `eve add channel/github`. The flow provisions a Vercel Connect GitHub App, routes verified webhooks, scaffolds the channel, and explains how to install and use the app.
13 changes: 5 additions & 8 deletions apps/docs/lib/integrations/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,26 +262,23 @@ TWILIO_AUTH_TOKEN=... # required for inbound signature verification
logo: "github",
docsHref: "/docs/channels/github",
keywords: ["issues", "pull requests", "app", "webhook", "code"],
install: `Add this channel from eve's registry. This writes \`agent/channels/github.ts\`:
install: `Add this channel from eve's registry to create a Vercel Connect GitHub App, route verified webhooks, and write \`agent/channels/github.ts\`:

\`\`\`bash
eve add channel/github
\`\`\``,
quickStart: `Create \`agent/channels/github.ts\`:
quickStart: `The guided setup writes \`agent/channels/github.ts\`:

\`\`\`ts
// agent/channels/github.ts
import { connectGitHubCredentials } from "@vercel/connect/eve";
import { githubChannel } from "eve/channels/github";

export default githubChannel({
credentials: {
appId: () => process.env.GITHUB_APP_ID!,
privateKey: () => process.env.GITHUB_APP_PRIVATE_KEY!,
webhookSecret: () => process.env.GITHUB_WEBHOOK_SECRET!,
},
credentials: connectGitHubCredentials("github/my-agent"),
});
\`\`\``,
configure: `Create a GitHub App, subscribe to issue and pull-request events, and set the webhook URL to eve's route (\`/eve/v1/github\`). Provide the app ID, private key, and webhook secret through environment variables. See the [GitHub channel docs](/docs/channels/github) for required permissions.`,
configure: `Sign in to Vercel, then let the guided flow create or link a project, provision the GitHub App, and attach its verified webhook trigger to \`/eve/v1/github\`. Deploy, install the app from Vercel Connect, then mention it in an issue, pull request, or review comment. See the [GitHub channel docs](/docs/channels/github) for permissions and events.`,
},
"linear-agent": {
logo: "linear",
Expand Down
30 changes: 15 additions & 15 deletions apps/docs/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -1086,14 +1086,14 @@
"requires": ">=0.27.8"
}
},
"dependencies": ["@vercel/connect@0.4.2"]
"dependencies": ["@vercel/connect@>=0.4.2"]
},
{
"name": "channel/discord",
"type": "registry:item",
"title": "Discord",
"description": "Connect an eve agent to Discord with guided connector and slash-command setup.",
"dependencies": ["@vercel/connect@0.6.0"],
"dependencies": ["@vercel/connect@>=0.6.0"],
"meta": {
"eve": {
"setup": {
Expand Down Expand Up @@ -1160,19 +1160,19 @@
"name": "channel/github",
"type": "registry:item",
"title": "GitHub",
"description": "Drive your agent from issues, pull requests, and comments.",
"envVars": {
"GITHUB_APP_ID": "",
"GITHUB_APP_PRIVATE_KEY": "",
"GITHUB_WEBHOOK_SECRET": ""
},
"files": [
{
"path": "registry/channels/github.ts",
"type": "registry:file",
"target": "agent/channels/github.ts"
"description": "Drive your agent from issues, pull requests, and comments, with guided Connect setup.",
"meta": {
"eve": {
"setup": {
"command": "eve",
"package": "eve",
"bin": "eve",
"args": ["integration", "setup", "github"]
},
"requires": ">=0.31.0"
}
]
},
"dependencies": ["@vercel/connect@>=0.6.0"]
},
{
"name": "channel/linear-agent",
Expand Down Expand Up @@ -1631,7 +1631,7 @@
"type": "registry:item",
"title": "Photon iMessage",
"description": "Connect an eve agent to iMessage through Photon with guided project and phone setup.",
"dependencies": ["@vercel/connect@0.5.0"],
"dependencies": ["@vercel/connect@>=0.5.0"],
"meta": {
"eve": {
"setup": {
Expand Down
7 changes: 2 additions & 5 deletions apps/docs/registry/channels/github.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { connectGitHubCredentials } from "@vercel/connect/eve";
import { githubChannel } from "eve/channels/github";

export default githubChannel({
credentials: {
appId: () => process.env.GITHUB_APP_ID!,
privateKey: () => process.env.GITHUB_APP_PRIVATE_KEY!,
webhookSecret: () => process.env.GITHUB_WEBHOOK_SECRET!,
},
credentials: connectGitHubCredentials("github/my-agent"),
});
2 changes: 2 additions & 0 deletions apps/docs/scripts/validate-channel-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const registrySlugsByCatalogSlug: Readonly<Record<string, string>> = {

const setupKindsByCatalogSlug: Readonly<Record<string, string>> = {
discord: "discord",
github: "github",
eve: "web",
photon: "photon",
};
Expand Down Expand Up @@ -114,6 +115,7 @@ for (const [index, item] of items.entries()) {
if (
entry.slug === "slack" ||
entry.slug === "discord" ||
entry.slug === "github" ||
entry.slug === "eve" ||
entry.slug === "photon"
) {
Expand Down
19 changes: 7 additions & 12 deletions docs/channels/github.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,19 @@ type: integration

The GitHub channel lets the agent work directly on a repository. Someone `@mentions` it in an issue, PR, or review comment, and the agent answers right there in the thread, with the PR diff already in context and the repo checked out into the sandbox. It takes GitHub App webhooks at `/eve/v1/github`, checks the signature, derives auth from whoever triggered the event, and replies on the native surface. Credentials can run through [Vercel Connect](../guides/auth-and-route-protection), which manages the GitHub App, the installation token, and inbound webhook verification, so there's no app private key or webhook secret for you to hold. See [Channels](./overview) for the contract this builds on.

## Set up Connect
## Guided Connect setup

Create a GitHub Connect client and copy its UID (e.g. `github/my-agent`), then attach this project as the trigger destination at eve's GitHub route:
Run the registry setup from the agent directory:

```bash
npm install -g vercel@latest
vercel connect create github --triggers
vercel connect detach <uid> --yes
vercel connect attach <uid> --triggers --trigger-path /eve/v1/github --yes
eve add channel/github
```

The `create` step provisions the GitHub App and a trigger destination at the default Connect path. `detach` then `attach --trigger-path /eve/v1/github` re-points the trigger at the eve GitHub route, since eve does not serve the default Connect path. `--triggers` makes Connect receive the App's webhooks, verify them, and forward them to your deployment. During registration, subscribe to `issue_comment` and `pull_request_review_comment` for mention-driven turns — the managed App defaults to `pull_request` only — and add `issues`, `pull_request`, `check_suite`, `check_run`, or `workflow_run` if you wire up their opt-in hooks. You can also create the client from the [Connect dashboard](https://vercel.com/d?to=/%5Bteam%5D/~/connect&title=Go+to+Connect).
The flow checks that the Vercel CLI is authenticated, creates or links a Vercel project when needed, provisions an app-scoped GitHub Connect client, and replaces its default trigger with `/eve/v1/github`. It then installs `@vercel/connect` and writes `agent/channels/github.ts` with the connector UID.

## Add the channel
Vercel Connect creates the GitHub App, receives and verifies its webhooks, and forwards them to the deployed agent. After deploying, open the GitHub App in the Connect dashboard and install it in the organization or account where you want to use it. Mention the generated app handle (for example, `@my-agent`) in an issue, pull request, or review comment to start a conversation.

```bash
npm install @vercel/connect
```
The generated channel uses Connect-managed credentials:

```ts title="agent/channels/github.ts"
import { connectGitHubCredentials } from "@vercel/connect/eve";
Expand Down Expand Up @@ -65,7 +60,7 @@ GITHUB_APP_SLUG=... # supplies botName when it is not set in config

`appId`/`privateKey`/`webhookSecret` also take a lazy resolver function if you'd rather fetch them on demand.

Point the GitHub App webhook URL at `https://<deployment>/eve/v1/github`. For mention-driven turns, subscribe to `issue_comment` and `pull_request_review_comment`; add `issues`, `pull_request`, `check_suite`, `check_run`, or `workflow_run` if you wire up their opt-in hooks. A comment that `@mention`s `botName` starts a turn.
Point the GitHub App webhook URL at `https://<deployment>/eve/v1/github`. For mention-driven turns, subscribe to `issue_comment` and `pull_request_review_comment`; add `issues`, `pull_request`, `check_suite`, `check_run`, or `workflow_run` if you wire up their opt-in hooks. After installing the App for the repository, a new comment that includes `@botName` starts a turn. GitHub may display the App as `botName[bot]`, but write the mention without the `[bot]` suffix.

## How the channel handles messages

Expand Down
3 changes: 2 additions & 1 deletion packages/eve-catalog/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ export const INTEGRATIONS: readonly IntegrationEntry[] = [
slug: "github",
name: "GitHub",
kind: "channel",
tagline: "Drive your agent from issues, pull requests, and comments.",
tagline:
"Drive your agent from issues, pull requests, and comments, with guided Connect setup.",
surfaces: { scaffoldable: false, gallery: true },
},
{
Expand Down
18 changes: 18 additions & 0 deletions packages/eve/src/cli/dev/tui/terminal-renderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,24 @@ describe("TerminalRenderer (inline scrollback)", () => {
expect(screen.snapshot()).toContain("⎿ ✓ Registry items added: connection/linear.");
});

it("keeps a setup command result tight after its flow closes", async () => {
const { screen, input, renderer } = makeRenderer();

const prompt = renderer.readPrompt();
input.type("/add");
input.enter();
await prompt;
renderer.setupFlow.begin("/add");
renderer.setupFlow.end({ preserveDiagnostics: false });
renderer.renderCommandResult("Registry items added: channel/github.", "success");
renderer.shutdown();

const lines = screen.snapshot().split("\n");
const command = lines.findIndex((line) => line.includes("│ /add"));
const result = lines.findIndex((line) => line.includes("⎿ ✓ Registry items added"));
expect(result).toBe(command + 1);
});

it("marks a failed automatic command and keeps its multiline outcome in one result block", () => {
const { screen, renderer } = makeRenderer();
renderer.renderCommandInvocation("/vc:login", "failed");
Expand Down
6 changes: 6 additions & 0 deletions packages/eve/src/cli/dev/tui/terminal-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1639,6 +1639,8 @@ export class TerminalRenderer implements AgentTUIRenderer {
const content = stripTerminalControls(text);
if (content.trim().length === 0) return;
this.#start();
const latest = this.#blocks.at(-1);
if (latest?.kind === "command") latest.live = false;
this.#pushBlock(
tone === "success"
? { kind: "result", body: content, live: false, status: "done" }
Expand Down Expand Up @@ -1686,6 +1688,10 @@ export class TerminalRenderer implements AgentTUIRenderer {
if (flow === undefined) return;
this.#setupFlow = undefined;
this.#stopTicker();
// The result belongs to the slash command that opened this flow. Its
// elbow should stay adjacent even though closing the panel repaints first.
const latest = this.#blocks.at(-1);
if (latest?.kind === "command") latest.live = true;

if (preserveDiagnostics) {
let evidence: string[] = [];
Expand Down
114 changes: 114 additions & 0 deletions packages/eve/src/setup/integrations/github/connect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { describe, expect, it, vi } from "vitest";

import type { ChannelSetupLog } from "#setup/cli/index.js";
import { parseCreatedGitHubConnector, provisionGitHubConnector } from "./connect.js";

function log(): ChannelSetupLog {
return {
message: vi.fn(),
info: vi.fn(),
success: vi.fn(),
warning: vi.fn(),
error: vi.fn(),
commandOutput: vi.fn(),
};
}

describe("GitHub Connect provisioning", () => {
it("parses an app-scoped GitHub connector", () => {
expect(
parseCreatedGitHubConnector(
JSON.stringify({
id: "scl_github",
uid: "github/agent",
supportedSubjectTypes: ["app"],
}),
),
).toEqual({ id: "scl_github", uid: "github/agent" });
});

it("creates the connector and replaces its trigger", async () => {
const runVercelCaptureStdout = vi
.fn()
.mockResolvedValueOnce({
ok: true as const,
stdout: JSON.stringify({
id: "scl_github",
uid: "github/agent",
supportedSubjectTypes: ["app"],
}),
stderr: "",
})
.mockResolvedValueOnce({
ok: true as const,
stdout: JSON.stringify({
data: { appSlug: "agent" },
id: "scl_github",
type: "github",
uid: "github/agent",
}),
stderr: "",
});
const runVercel = vi.fn(async () => true);

await expect(
provisionGitHubConnector({
events: ["issue_comment", "pull_request_review_comment"],
log: log(),
project: { orgId: "team_123", projectId: "prj_123" },
projectRoot: "/project",
slug: "agent",
deps: { runVercel, runVercelCaptureStdout },
}),
).resolves.toEqual({ appSlug: "agent", id: "scl_github", uid: "github/agent" });

expect(runVercelCaptureStdout).toHaveBeenCalledWith(
[
"connect",
"create",
"github",
"--name",
"agent",
"--triggers",
"--trigger-event",
"issue_comment",
"--trigger-event",
"pull_request_review_comment",
"-F",
"json",
"--scope",
"team_123",
],
expect.objectContaining({ cwd: "/project", nonInteractive: true }),
);
expect(runVercelCaptureStdout).toHaveBeenNthCalledWith(
2,
["api", "/v1/connect/connectors/scl_github", "--scope", "team_123", "--raw"],
expect.objectContaining({ cwd: "/project", nonInteractive: true }),
);
expect(runVercel).toHaveBeenNthCalledWith(
1,
["connect", "detach", "github/agent", "--project", "prj_123", "--yes", "--scope", "team_123"],
expect.objectContaining({ cwd: "/project", nonInteractive: true }),
);
expect(runVercel).toHaveBeenNthCalledWith(
2,
[
"connect",
"attach",
"github/agent",
"--project",
"prj_123",
"--environment",
"production",
"--triggers",
"--trigger-path",
"/eve/v1/github",
"--yes",
"--scope",
"team_123",
],
expect.objectContaining({ cwd: "/project", nonInteractive: true }),
);
});
});
Loading