browser-testing-with-s…
Use when testing web applications with visual verification - automates Chrome browser interactions, element selection, and…
Use when implementing GitHub OAuth + GitHub App authentication with Nango - provides two-connection pattern for user login and repo access with webhook handling
Claude Code
$ npx -y skills add AgentWorkforce/relay --skill github-oauth-nango-integration --agent claude-code
Codex
$ npx skills add AgentWorkforce/relay --skill github-oauth-nango-integration -a codex -y
Cursor
$ npx skills add AgentWorkforce/relay --skill github-oauth-nango-integration -a cursor -y
Gemini CLI
$ npx skills add AgentWorkforce/relay --skill github-oauth-nango-integration -a gemini-cli -y
GitHub Copilot
$ npx skills add AgentWorkforce/relay --skill github-oauth-nango-integration -a github-copilot -y
OpenCode
$ npx skills add AgentWorkforce/relay --skill github-oauth-nango-integration -a opencode -y
Windsurf
$ npx skills add AgentWorkforce/relay --skill github-oauth-nango-integration -a windsurf -y
name: github-oauth-nango-integration description: Use when implementing GitHub OAuth + GitHub App authentication with Nango - provides two-connection pattern for user login and repo access with webhook handling
Implements dual-connection OAuth pattern: one for user identity (`github` integration), another for repository access (`github-app-oauth` integration). This separation enables secure login while maintaining granular repo permissions through GitHub App installations.
GitHub has **two different authentication mechanisms** that serve different purposes:
OAuth App alone: "User john@example.com logged in" → but which repos can they access? GitHub App alone: "Installation #12345 has access to repo X" → but who is the user?
**Solution**: Two separate OAuth flows linked by user ID:
1. **Login flow** → User authenticates → Store user identity + `nangoConnectionId` 2. **Repo flow** → Same user authorizes app → Store repos + link via `ownerId`
This lets you answer: "User john@example.com can access repos X, Y, Z"
| Connection Type | Nango Integration | Purpose | Stored In | | --------------- | ------------------ | -------------------------- | ------------------------- | | User Login | `github` | Authentication, identity | `users.nangoConnectionId` | | Repo Access | `github-app-oauth` | PR operations, file access | `repos.nangoConnectionId` |
| Flow | Endpoint | Webhook Type | | ------------ | ------------------------------ | --------------------------- | | Login | `GET /auth/nango-session` | `auth` + `github` | | Repo Connect | `GET /auth/github-app-session` | `auth` + `github-app-oauth` | | Data Sync | N/A (scheduled) | `sync` |
// users table - stores login connection
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
githubId: text('github_id').unique().notNull(),
githubUsername: text('github_username').notNull(),
email: text('email'),
avatarUrl: text('avatar_url'),
nangoConnectionId: text('nango_connection_id'), // Permanent login connection
incomingConnectionId: text('incoming_connection_id'), // Temp polling connection
pendingInstallationRequest: timestamp('pending_installation_request'), // Org approval wait
});
// repos table - stores per-repo app connection
export const repos = pgTable('repos', {
id: uuid('id').primaryKey().defaultRandom(),
githubRepoId: text('github_repo_id').unique().notNull(),
fullName: text('full_name').notNull(),
installationId: uuid('installation_id').references(() => githubInstallations.id),
ownerId: uuid('owner_id').references(() => users.id),
nangoConnectionId: text('nango_connection_id'), // App connection for this repo
});
// github_installations - tracks app installations
export const githubInstallations = pgTable('github_installations', {
id: uuid('id').primaryKey().defaultRandom(),
installationId: text('installation_id').unique().notNull(),
accountType: text('account_type'), // 'user' | 'organization'
accountLogin: text('account_login'),
installedById: uuid('installed_by_id').references(() => users.id),
});// constants.ts
export const NANGO_INTEGRATION = {
GITHUB_USER: 'github', // Login only
GITHUB_APP_OAUTH: 'github-app-oauth', // Repo access
} as const;// GET /auth/nango-session - Create login OAuth session
app.get('/auth/nango-session', async (c) => {
const tempUserId = randomUUID();
const { sessionToken } = await nangoClient.createConnectSession({
end_user: { id: tempUserId },
allowed_integrations: [NANGO_INTEGRATION.GITHUB_USER],
});
return c.json({ sessionToken, tempUserId });
});
// GET /auth/nango/status/:connectionId - Poll login completion
app.get('/auth/nango/status/:connectionId', async (c) => {
const { connectionId } = c.req.param();
// Check if user exists with this incoming connection
const user = await userRepo.findByIncomingConnectionId(connectionId);
if (!user) {
return c.json({ ready: false });
}
// Issue JWT and return
const token = authService.issueToken(user);
await userRepo.clearIncomingConnectionId(user.id);
return c.json({ ready: true, token, user });
});// GET /auth/github-app-session - Create app OAuth session (authenticated)
app.get('/auth/github-app-session', authMiddleware, async (c) => {
const user = c.get('user');
const { sessionToken } = await nangoClient.createConnectSession({
end_user: { id: user.id, email: user.email },
allowed_integrations: [NANGO_INTEGRATION.GITHUB_APP_OAUTH],
});
return c.json({ sessionToken });
});
// GET /auth/github-app/status/:connectionId - Poll repo sync
app.get('/auth/github-app/sTired of copy/pasting from Claude Code into Slack so your coworker can paste it into their agent? Ever put an important rule in a skill or AGENTS.md, only for the agent to completely ignore it? You and your teammates agents ever build the same thing?
Use when testing web applications with visual verification - automates Chrome browser interactions, element selection, and…
Use when coordinating multiple AI agents with Agent Relay's workflow engine and need to pick the right orchestration pattern -…
Use when creating or improving Claude Code agents. Expert guidance on agent file structure, frontmatter, persona definition, tool…
Use when creating or publishing Claude Code hooks - covers executable format, event types, JSON I/O, exit codes, security…
Use when creating or fixing .claude/rules/ files - provides correct paths frontmatter (not globs), glob patterns, and avoids…
Use when creating new Claude Code skills or improving existing ones - ensures skills are discoverable, scannable, and effective…
Direct browser control via CDP for web interaction: automation, scraping, testing, screenshots, and site/app work. Also covers…
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling…