name: integration-development description: Guide for creating new OAuth-based integrations in the Orient codebase. Use when adding external service integrations (APIs like Linear, GitHub, Slack, Notion, etc.), implementing OAuth flows, or creating catalog-based integration manifests. Covers the full integration lifecycle from manifest definition to tool implementation.
Integration Development
This skill provides guidance for creating OAuth-based integrations in Orient.
Integration Architecture
Orient uses a catalog-based architecture for integrations:
packages/integrations/src/
├── catalog/ # Integration definitions
│ ├── linear/
│ │ ├── INTEGRATION.yaml # Manifest
│ │ ├── oauth-config.ts # OAuth configuration
│ │ ├── tools.ts # API client and tools
│ │ └── index.ts # Exports
│ └── github/
│ └── ...
├── types/
│ └── integration.ts # IntegrationManifest types
└── index.ts # Package exports
Quick Start
- Create directory:
packages/integrations/src/catalog/<integration-name>/ - Create
INTEGRATION.yamlmanifest - Create
oauth-config.tsfor OAuth flow - Create
tools.tsfor API client - Create
index.tsfor exports - Update package.json exports
File Templates
INTEGRATION.yaml
See references/manifest-template.md for the complete template.
Key fields:
name: lowercase identifier (e.g.,linear,github)title: display namedescription: 50+ character descriptionversion: semver (e.g.,1.0.0)oauth: authorization and token URLs, scopesrequiredSecrets: CLIENT_ID, CLIENT_SECRET, etc.tools: available API operationsstatus:stable,beta, orexperimental
oauth-config.ts
Required exports:
- Scope constants (e.g.,
LINEAR_SCOPES) - Default scopes array
- Config interface
getAuthUrl()- generate authorization URLexchangeCode()- exchange code for tokensgetUserInfo()- fetch user profilegetConfigFromEnv()- load config from environment
tools.ts
Required exports:
- Type definitions for API responses
- Client class with API methods
- Factory function (e.g.,
createLinearClient())
OAuth Flow Patterns
Standard OAuth 2.0
Most services use standard OAuth 2.0:
// 1. Generate auth URL with state
const authUrl = getAuthUrl(config, scopes, state);
// 2. User authorizes in browser, redirected with code
// 3. Exchange code for tokens
const tokens = await exchangeCode(config, code);
// 4. Use access token for API calls
const client = createClient(tokens.accessToken);
OAuth 2.0 with PKCE
Some services require PKCE (Proof Key for Code Exchange):
// 1. Generate code verifier and challenge
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
// 2. Include challenge in auth URL
const authUrl = `${baseUrl}?code_challenge=${codeChallenge}&code_challenge_method=S256`;
// 3. Include verifier in token exchange
const tokens = await exchangeCode(config, code, codeVerifier);
API Client Patterns
GraphQL APIs (Linear, GitHub GraphQL)
private async query<T>(query: string, variables?: Record<string, unknown>): Promise<T> {
const response = await fetch(this.apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.accessToken}`,
},
body: JSON.stringify({ query, variables }),
});
const result = await response.json();
if (result.errors?.length > 0) {
throw new Error(`GraphQL error: ${result.errors[0].message}`);
}
return result.data;
}
REST APIs (GitHub REST, Slack)
private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const response = await fetch(`${this.apiUrl}${endpoint}`, {
...options,
headers: {
Authorization: `Bearer ${this.accessToken}`,
Accept: 'application/json',
'Content-Type': 'application/json',
...options.headers,
},
});
if (!response.ok) {
const error = await response.json().catch(() => ({ message: response.statusText }));
throw new Error(`API error: ${error.message}`);
}
return response.json();
}
Logging Pattern
Always use the service logger:
import { createServiceLogger } from '@orientbot/core';
const logger = createServiceLogger('integration-name');
async function someOperation() {
const op = logger.startOperation('operationName', { param1: value1 });
try {
const result = await doWork();
op.success('Operation completed', { resultCount: result.length });
return result;
} catch (error) {
op.failure(error instanceof Error ? error : String(error));
throw error;
}
}
Type Transformation
Transform API responses to camelCase:
// API returns snake_case
const apiResponse = {
created_at: '2024-01-15',
pull_request: { html_url: '...' },
};
// Transform to camelCase
const result = {
createdAt: apiResponse.created_at,
pullRequest: { htmlUrl: apiResponse.pull_request.html_url },
};
Required Secrets
Define all required secrets in the manifest:
requiredSecrets:
- name: SERVICE_CLIENT_ID
description: OAuth Client ID from developer settings
category: oauth
required: true
- name: SERVICE_CLIENT_SECRET
description: OAuth Client Secret
category: oauth
required: true
- name: SERVICE_WEBHOOK_SECRET
description: Webhook signing secret (optional)
category: webhook
required: false
Webhook Support
For integrations that support webhooks:
webhooks:
events:
- push
- pull_request
- issues
signatureHeader: X-Hub-Signature-256
signatureAlgorithm: hmac-sha256
Testing Integrations
- OAuth flow: Test with real credentials in development
- API calls: Mock API responses in unit tests
- Token refresh: Test expiration handling
- Error handling: Test API error responses
Common Pitfalls
- Missing scopes: Always request all needed scopes upfront
- Token expiration: Handle refresh tokens if provided
- Rate limiting: Implement backoff for API errors
- Pagination: Handle paginated API responses
- Error messages: Parse API error details for useful messages
References
references/manifest-template.md- Full INTEGRATION.yaml templatereferences/oauth-providers.md- OAuth configuration for common providers