Skip to main content

connector-generation

Guide for creating new external service connectors in DEVS. Use this when asked to add a new OAuth app connector, API connector, or integration with external services like Slack, Trello, Linear, Asana, Dropbox, GitHub, etc.

Quellinformationen

Repository
codename-co/devs
Letzte Quellaktivität
16. Januar 2026 um 23:22
Erkannte Sprache von SKILL.md
Englisch
Sterne
53
Forks
2

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
4 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
connector-generation
description
Guide for creating new external service connectors in DEVS. Use this when asked to add a new OAuth app connector, API connector, or integration with external services like Slack, Trello, Linear, Asana, Dropbox, GitHub, etc.
# Connector Generation for DEVS When creating new connectors for external services in DEVS, follow this comprehensive guide covering OAuth flow, provider implementation, registration, bridge proxy setup, and UI integration. ## Overview DEVS connectors enable synchronizing content from external services into the Knowledge Base. The architecture consists of: 1. **Provider Implementation** - OAuth + API integration in `src/features/connectors/providers/apps/` 2. **Provider Registry** - Lazy loading registration in `provider-registry.ts` 3. **OAuth Gateway** - OAuth config for popup-based auth flow 4. **Bridge Server** - Server-side proxy for OAuth secrets and CORS (in `utils/devs-bridge/`) 5. **UI Configuration** - Icons and display metadata ## Step 1: Define Provider Type Add the new provider to the type union in `src/features/connectors/types.ts`: ```typescript // In types.ts export type AppConnectorProvider = | 'google-drive' | 'gmail' | 'google-calendar' | 'google-meet' | 'google-tasks' | 'notion' | 'dropbox' | 'github' | 'qonto' | 'slack' // <-- Add new provider ``` Also add the config entry in `APP_CONNECTOR_CONFIGS`: ```typescript // In types.ts - APP_CONNECTOR_CONFIGS slack: { id: 'slack', category: 'app', name: 'Slack', icon: 'slack', color: '#4A154B', capabilities: ['read', 'search'], supportedTypes: ['message', 'channel', 'file'], maxFileSize: 10 * 1024 * 1024, rateLimit: { requests: 50, windowSeconds: 60 }, }, ``` ## Step 2: Create Provider Implementation Create a new file `src/features/connectors/providers/apps/{provider}.ts`: ```typescript /** * {Provider} Connector Provider * * Implements OAuth 2.0 authentication and API integration for {Provider}. * Supports listing, reading, searching content, and delta sync. */ import { BRIDGE_URL } from '@/config/bridge' import { BaseAppConnectorProvider } from '../../connector-provider' import type { Connector, ConnectorProviderConfig, OAuthResult, TokenRefreshResult, AccountInfo, ListOptions, ListResult, ContentResult, SearchResult, ChangesResult, ConnectorItem, } from '../../types' // ============================================================================= // Constants // ============================================================================= /** API base URL - use gateway proxy to avoid CORS and keep secrets safe */ const API_BASE = `${BRIDGE_URL}/api/{provider}` /** OAuth endpoints */ const AUTH_URL = 'https://{provider}.com/oauth/authorize' const TOKEN_URL = 'https://{provider}.com/oauth/token' const REVOKE_URL = 'https://{provider}.com/oauth/revoke' const USERINFO_URL = 'https://{provider}.com/api/users/me' // ============================================================================= // Types // ============================================================================= // Define types for API responses (raw JSON from the provider's API) interface RawItem { id: string name: string // ... provider-specific fields } interface ListResponse { items: RawItem[] next_cursor?: string } interface TokenResponse { access_token: string refresh_token?: string expires_in?: number scope: string token_type: string } interface UserInfoResponse { id: string email?: string name?: string avatar?: string } // ============================================================================= // Provider Implementation // ============================================================================= export class {Provider}Provider extends BaseAppConnectorProvider { readonly id = '{provider-id}' as const readonly config: ConnectorProviderConfig = { id: '{provider-id}', category: 'app', name: '{Provider Name}', icon: '{provider-icon}', color: '#HEXCOLOR', capabilities: ['read', 'search'], supportedTypes: ['*'], maxFileSize: 10 * 1024 * 1024, rateLimit: { requests: 100, windowSeconds: 60 }, } /** Get the OAuth client ID from environment */ private get clientId(): string { return import.meta.env.VITE_{PROVIDER}_CLIENT_ID || '' } /** Get the OAuth redirect URI */ private get redirectUri(): string { return `${window.location.origin}/oauth/callback` } // =========================================================================== // OAuth Methods // =========================================================================== /** * Generate the OAuth authorization URL. * * @param state - State parameter for CSRF protection * @param codeChallenge - PKCE code challenge (S256) * @returns The full authorization URL */ getAuthUrl(state: string, codeChallenge: string): string { const params = new URLSearchParams({ client_id: this.clientId, redirect_uri: this.redirectUri, response_type: 'code', scope: 'read:user read:content', // Provider-specific scopes state, // Include PKCE if provider supports it code_challenge: codeChallenge, code_challenge_method: 'S256', }) return `${AUTH_URL}?${params.toString()}` } /** * Exchange an authorization code for access and refresh tokens. * * NOTE: Token exchange should go through BRIDGE_URL to inject client_secret */ async exchangeCode(code: string, codeVerifier: string): Promise<OAuthResult> { const response = await fetch(`${BRIDGE_URL}/api/{provider}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ code, code_verifier: codeVerifier, grant_type: 'authorization_code', redirect_uri: this.redirectUri, // client_id and client_secret injected by bridge server }), }) if (!response.ok) { const errorText = await response.text() throw new Error(`Token exchange failed: ${response.status} ${errorText}`) } const data: TokenResponse = await response.json() return { accessToken: data.access_token, refreshToken: data.refresh_token, expiresIn: data.expires_in, scope: data.scope, tokenType: data.token_type, } } /** * Refresh an expired access token using the refresh token. */ async refreshToken(connector: Connector): Promise<TokenRefreshResult> { const refreshToken = await this.getDecryptedRefreshToken(connector) if (!refreshToken) { throw new Error('No refresh token available') } const response = await fetch(`${BRIDGE_URL}/api/{provider}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ refresh_token: refreshToken, grant_type: 'refresh_token', // client_id and client_secret injected by bridge server }), }) if (!response.ok) { const errorText = await response.text() throw new Error(`Token refresh failed: ${response.status} ${errorText}`) } const data: TokenResponse = await response.json() return { accessToken: data.access_token, expiresIn: data.expires_in, } } /** * Validate that a token is still valid. */ async validateToken(token: string): Promise<boolean> { try { const response = await fetch(USERINFO_URL, { headers: { Authorization: `Bearer ${token}` }, }) return response.ok } catch { return false } } /** * Revoke all access for a connector. */ async revokeAccess(connector: Connector): Promise<void> { const token = await this.getDecryptedToken(connector) const response = await fetch(REVOKE_URL, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Authorization: `Bearer ${token}`, }, }) if (!response.ok) { const errorText = await response.text() throw new Error(`Token revocation failed: ${response.status} ${errorText}`) } } /** * Get account information for the authenticated user. */ async getAccountInfo(token: string): Promise<AccountInfo> { const response = await fetch(USERINFO_URL, { headers: { Authorization: `Bearer ${token}` }, }) if (!response.ok) { const errorText = await response.text() throw new Error(`Failed to get account info: ${response.status} ${errorText}`) } const data: UserInfoResponse = await response.json() return { id: data.id, email: data.email, name: data.name, picture: data.avatar, } }
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen