Claude Code Plugins

Community-maintained marketplace

Feedback

integration-generator

@dafthunk-com/dafthunk
33
0

Generate new OAuth integration providers for Dafthunk with backend providers, type definitions, frontend configurations, and integration nodes

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name integration-generator
description Generate new OAuth integration providers for Dafthunk with backend providers, type definitions, frontend configurations, and integration nodes

Integration Generator

Generate OAuth integration providers for Dafthunk: research provider APIs, create backend OAuth provider, add type definitions, configure frontend, and optionally create integration nodes.

Step 1: Research Provider and Register OAuth App

Research the OAuth flow:

  • Use WebSearch/WebFetch to find official OAuth documentation
  • Look for "OAuth 2.0", "Developer Apps", or "API Authentication" sections
  • Identify authorization, token, and user info endpoints
  • Note required scopes and token format (refresh support, expiration)
  • Check token response structure and user info fields

Register OAuth application:

  • Create a developer account on the provider's platform
  • Register a new OAuth application/client
  • Set redirect URI to: http://localhost:3001/oauth/{provider-id}/connect (dev)
  • Note the generated Client ID and Client Secret
  • Configure required scopes/permissions

Present for confirmation:

Based on {Provider} OAuth documentation:

**Provider**: {Provider Name}
**ID**: `{provider-id}` (kebab-case)
**Endpoints**:
- Authorization: https://...
- Token: https://...
- User Info: https://...

**Scopes**: scope1, scope2
**Token Refresh**: Supported/Not supported
**Token Expiration**: {expires_in seconds} / Never expires

**OAuth App Registered**:
- Client ID: {id}
- Client Secret: {secret}
- Redirect URI: http://localhost:3001/oauth/{provider-id}/connect

Ready to implement?

Step 2: Add Backend Types

File: apps/api/src/oauth/types.ts

/**
 * {Provider} OAuth token response
 */
export interface {Provider}Token {
  access_token: string;
  refresh_token?: string;
  expires_in?: number;
  token_type: string;
  scope?: string;
}

/**
 * {Provider} user information
 */
export interface {Provider}User {
  id: string | number;
  email?: string;
  name?: string;
  // Add provider-specific fields
}

Step 3: Create Provider Class

File: apps/api/src/oauth/providers/{Provider}Provider.ts

import { OAuthProvider } from "../OAuthProvider";
import type { {Provider}Token, {Provider}User } from "../types";

export class {Provider}Provider extends OAuthProvider<{Provider}Token, {Provider}User> {
  readonly name = "{provider-id}";
  readonly displayName = "{Provider Name}";
  readonly authorizationEndpoint = "https://...";
  readonly tokenEndpoint = "https://...";
  readonly userInfoEndpoint = "https://...";
  readonly scopes = ["scope1", "scope2"];

  readonly refreshEnabled = true;  // false if no refresh support
  readonly refreshEndpoint = "https://...";  // only if refreshEnabled

  protected formatIntegrationName(user: {Provider}User): string {
    return user.name || user.email || "User";
  }

  protected formatUserMetadata(user: {Provider}User): Record<string, any> {
    return {
      userId: user.id,
      email: user.email,
      name: user.name,
    };
  }

  protected extractAccessToken(token: {Provider}Token): string {
    return token.access_token;
  }

  protected extractRefreshToken(token: {Provider}Token): string | undefined {
    return token.refresh_token;
  }

  protected extractExpiresAt(token: {Provider}Token): Date | undefined {
    return token.expires_in
      ? new Date(Date.now() + token.expires_in * 1000)
      : undefined;
  }
}

Step 4: Register Backend Provider

File: apps/api/src/oauth/registry.ts

import { {Provider}Provider } from "./providers/{Provider}Provider";

const providers = {
  // ... existing providers
  "{provider-id}": new {Provider}Provider(),
} as const;

Step 5: Update Shared Types

File: packages/types/src/integration.ts

Add to IntegrationProvider union and OAUTH_PROVIDERS array:

export type IntegrationProvider =
  | "existing-provider"
  | "{provider-id}"
  | "other-provider";

export const OAUTH_PROVIDERS: OAuthProvider[] = [
  // ... existing providers
  {
    id: "{provider-id}",
    name: "{Provider Name}",
    description: "Connect your {Provider} account to...",
    supportsOAuth: true,
    oauthEndpoint: "/oauth/{provider-id}/connect",
  },
];

Step 6: Create Frontend Provider

File: apps/web/src/integrations/providers/{provider-id}.ts

import {IconName} from "lucide-react/icons/{icon-name}";
import type { ProviderConfig } from "../types";

export const {provider}Provider: ProviderConfig = {
  id: "{provider-id}",
  name: "{Provider Name}",
  description: "Connect your {Provider} account...",
  icon: {IconName},
  supportsOAuth: true,
  oauthEndpoint: "/oauth/{provider-id}/connect",
  successMessage: "{Provider} integration connected successfully",
};

Step 7: Register Frontend Provider

File: apps/web/src/integrations/providers/index.ts

import { {provider}Provider } from "./{provider-id}";

export const PROVIDER_REGISTRY: Record<IntegrationProvider, ProviderConfig> = {
  // ... existing providers
  "{provider-id}": {provider}Provider,
};

Step 8: Configure Environment

File: apps/api/.dev.vars

INTEGRATION_{PROVIDER}_CLIENT_ID=your_client_id
INTEGRATION_{PROVIDER}_CLIENT_SECRET=your_client_secret

Step 9: Update Integration Routes

File: apps/api/src/routes/integrations.ts

Add provider availability check:

if (
  env.INTEGRATION_{PROVIDER}_CLIENT_ID &&
  env.INTEGRATION_{PROVIDER}_CLIENT_SECRET
) {
  availableProviders.push("{provider-id}");
}

Step 10: Test OAuth Flow

pnpm typecheck
pnpm dev

Navigate to integrations page, click "Connect {Provider}", complete OAuth flow, verify integration appears in list.

Optional: Create Integration Nodes

File: apps/api/src/nodes/{provider-id}/{action}-{provider-id}-node.ts

import { NodeExecution, NodeType } from "@dafthunk/types";
import { ExecutableNode, NodeContext } from "../types";

export class {Action}{Provider}Node extends ExecutableNode {
  public static readonly nodeType: NodeType = {
    id: "{action}-{provider-id}",
    name: "{Action} ({Provider})",
    type: "{action}-{provider-id}",
    description: "{Action description}",
    tags: ["{Category}", "{Provider}"],
    icon: "icon-name",
    inputs: [
      {
        name: "integrationId",
        type: "string",
        description: "{Provider} integration to use",
        hidden: true,
        required: true,
      },
      // Add action-specific inputs
    ],
    outputs: [
      // Add action-specific outputs
    ],
  };

  async execute(context: NodeContext): Promise<NodeExecution> {
    try {
      const { integrationId } = context.inputs;

      // Get integration (auto-refreshes token)
      const integration = await context.getIntegration(integrationId);

      // Call provider API
      const response = await fetch("https://api.{provider}.com/...", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${integration.token}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ /* request */ }),
      });

      if (!response.ok) {
        return this.createErrorResult(`API call failed: ${await response.text()}`);
      }

      const data = await response.json();
      return this.createSuccessResult({ /* outputs */ });
    } catch (error) {
      return this.createErrorResult(
        error instanceof Error ? error.message : "Unknown error"
      );
    }
  }
}

Register in apps/api/src/nodes/cloudflare-node-registry.ts:

import { {Action}{Provider}Node } from "./{provider-id}/{action}-{provider-id}-node";

this.registerImplementation({Action}{Provider}Node);

Optional: Create Frontend Widget

File: apps/web/src/components/workflow/widgets/integration-selector.tsx

export const {provider}Widget = createIntegrationWidget("{provider-id}", [
  "{action1}-{provider-id}",
  "{action2}-{provider-id}",
]);

Register in apps/web/src/components/workflow/widgets/index.ts:

import { {provider}Widget } from "./integration-selector";

export const widgetRegistry = {
  ...{provider}Widget,
};

Common Patterns

HTTP Basic Auth (e.g., Reddit):

protected getTokenHeaders(clientId: string, clientSecret: string): Record<string, string> {
  const credentials = btoa(`${clientId}:${clientSecret}`);
  return {
    Authorization: `Basic ${credentials}`,
    "Content-Type": "application/x-www-form-urlencoded",
  };
}

No Token Expiration (e.g., GitHub):

readonly refreshEnabled = false;

protected extractExpiresAt(token: {Provider}Token): Date | undefined {
  return undefined;
}

Custom Auth Parameters (e.g., Google):

protected customizeAuthUrl(url: URL): void {
  url.searchParams.set("access_type", "offline");
}

Summary

After completion, list:

  • Files created (backend types, provider, frontend config)
  • Environment variables required
  • OAuth flow test results
  • Optional nodes/widgets created