Skip to content

Commit 4ca7a2e

Browse files
authored
feat: Add Clerk integration plugin (#76)
* feat: Add Clerk integration plugin Adds a complete Clerk integration with 4 workflow actions: - Get User: Fetch user details by ID - Create User: Create new users with email, names - Update User: Update existing user properties - Delete User: Remove users from Clerk Features: - Full plugin implementation following the plugin architecture - Integration form with secret key configuration - Connection testing via Clerk API - Code generation templates for workflow export - Proper credential handling via fetchCredentials * fix: add user-agent to test clerk function + fix formatting * fix: use ClerkCredentials type in delete-user step * fix: address PR review comments - Revert AGENTS.md to upstream version - Remove plugins/clerk/codegen/ folder (auto-generated) - Remove template variable hint from AI prompt (auto-generated) * fix: align Clerk plugin with project conventions - Remove codegen imports (auto-generated) - Add outputFields to all actions - Add maxRetries = 0 to all step functions * fix: address PR review feedback for Clerk plugin - Add encodeURIComponent to userId in URL paths (security) - Return explicit errors on invalid JSON metadata instead of silent fallback - Extract ClerkUser type to shared types.ts file
1 parent 43b830a commit 4ca7a2e

File tree

12 files changed

+825
-0
lines changed

12 files changed

+825
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Visit [http://localhost:3000](http://localhost:3000) to get started.
8181
<!-- PLUGINS:START - Do not remove. Auto-generated by discover-plugins -->
8282
- **AI Gateway**: Generate Text, Generate Image
8383
- **Blob**: Put Blob, List Blobs
84+
- **Clerk**: Get User, Create User, Update User, Delete User
8485
- **fal.ai**: Generate Image, Generate Video, Upscale Image, Remove Background, Image to Image
8586
- **Firecrawl**: Scrape URL, Search Web
8687
- **GitHub**: Create Issue, List Issues, Get Issue, Update Issue

lib/workflow-codegen-sdk.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,84 @@ export function generateWorkflowSDKCode(
393393
"apiKey: process.env.V0_API_KEY!",
394394
];
395395
}
396+
397+
function buildClerkGetUserParams(config: Record<string, unknown>): string[] {
398+
return [
399+
`userId: \`${convertTemplateToJS((config.userId as string) || "")}\``,
400+
];
401+
}
402+
403+
function buildClerkCreateUserParams(
404+
config: Record<string, unknown>
405+
): string[] {
406+
const params = [
407+
`emailAddress: \`${convertTemplateToJS((config.emailAddress as string) || "")}\``,
408+
];
409+
if (config.password) {
410+
params.push(
411+
`password: \`${convertTemplateToJS(config.password as string)}\``
412+
);
413+
}
414+
if (config.firstName) {
415+
params.push(
416+
`firstName: \`${convertTemplateToJS(config.firstName as string)}\``
417+
);
418+
}
419+
if (config.lastName) {
420+
params.push(
421+
`lastName: \`${convertTemplateToJS(config.lastName as string)}\``
422+
);
423+
}
424+
if (config.publicMetadata) {
425+
params.push(
426+
`publicMetadata: \`${convertTemplateToJS(config.publicMetadata as string)}\``
427+
);
428+
}
429+
if (config.privateMetadata) {
430+
params.push(
431+
`privateMetadata: \`${convertTemplateToJS(config.privateMetadata as string)}\``
432+
);
433+
}
434+
return params;
435+
}
436+
437+
function buildClerkUpdateUserParams(
438+
config: Record<string, unknown>
439+
): string[] {
440+
const params = [
441+
`userId: \`${convertTemplateToJS((config.userId as string) || "")}\``,
442+
];
443+
if (config.firstName) {
444+
params.push(
445+
`firstName: \`${convertTemplateToJS(config.firstName as string)}\``
446+
);
447+
}
448+
if (config.lastName) {
449+
params.push(
450+
`lastName: \`${convertTemplateToJS(config.lastName as string)}\``
451+
);
452+
}
453+
if (config.publicMetadata) {
454+
params.push(
455+
`publicMetadata: \`${convertTemplateToJS(config.publicMetadata as string)}\``
456+
);
457+
}
458+
if (config.privateMetadata) {
459+
params.push(
460+
`privateMetadata: \`${convertTemplateToJS(config.privateMetadata as string)}\``
461+
);
462+
}
463+
return params;
464+
}
465+
466+
function buildClerkDeleteUserParams(
467+
config: Record<string, unknown>
468+
): string[] {
469+
return [
470+
`userId: \`${convertTemplateToJS((config.userId as string) || "")}\``,
471+
];
472+
}
473+
396474
function buildStepInputParams(
397475
actionType: string,
398476
config: Record<string, unknown>
@@ -410,6 +488,11 @@ export function generateWorkflowSDKCode(
410488
Search: () => buildFirecrawlParams(actionType, config),
411489
"Create Chat": () => buildV0CreateChatParams(config),
412490
"Send Message": () => buildV0SendMessageParams(config),
491+
// Clerk
492+
"Get User": () => buildClerkGetUserParams(config),
493+
"Create User": () => buildClerkCreateUserParams(config),
494+
"Update User": () => buildClerkUpdateUserParams(config),
495+
"Delete User": () => buildClerkDeleteUserParams(config),
413496
};
414497

415498
const builder = paramBuilders[actionType];

plugins/clerk/credentials.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type ClerkCredentials = {
2+
CLERK_SECRET_KEY?: string;
3+
};

plugins/clerk/icon.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export function ClerkIcon({ className }: { className?: string }) {
2+
return (
3+
<svg
4+
className={className}
5+
role="img"
6+
viewBox="0 0 128 128"
7+
xmlns="http://www.w3.org/2000/svg"
8+
>
9+
<circle cx="64" cy="64" r="20" fill="currentColor" />
10+
<path
11+
fill="currentColor"
12+
fillOpacity="0.6"
13+
d="M99.5716 10.788C101.571 12.1272 101.742 14.9444 100.04 16.646L85.4244 31.2618C84.1035 32.5828 82.0542 32.7914 80.3915 31.9397C75.4752 29.421 69.9035 28 64 28C44.1177 28 28 44.1177 28 64C28 69.9035 29.421 75.4752 31.9397 80.3915C32.7914 82.0542 32.5828 84.1035 31.2618 85.4244L16.646 100.04C14.9444 101.742 12.1272 101.571 10.788 99.5716C3.97411 89.3989 0 77.1635 0 64C0 28.6538 28.6538 0 64 0C77.1635 0 89.3989 3.97411 99.5716 10.788Z"
14+
/>
15+
<path
16+
fill="currentColor"
17+
d="M100.04 111.354C101.742 113.056 101.571 115.873 99.5717 117.212C89.3989 124.026 77.1636 128 64 128C50.8364 128 38.6011 124.026 28.4283 117.212C26.4289 115.873 26.2581 113.056 27.9597 111.354L42.5755 96.7382C43.8965 95.4172 45.9457 95.2085 47.6084 96.0603C52.5248 98.579 58.0964 100 64 100C69.9036 100 75.4753 98.579 80.3916 96.0603C82.0543 95.2085 84.1036 95.4172 85.4245 96.7382L100.04 111.354Z"
18+
/>
19+
</svg>
20+
);
21+
}

plugins/clerk/index.ts

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import type { IntegrationPlugin } from "../registry";
2+
import { registerIntegration } from "../registry";
3+
import { ClerkIcon } from "./icon";
4+
5+
const clerkPlugin: IntegrationPlugin = {
6+
type: "clerk",
7+
label: "Clerk",
8+
description: "User authentication and management",
9+
10+
icon: ClerkIcon,
11+
12+
formFields: [
13+
{
14+
id: "clerkSecretKey",
15+
label: "Secret Key",
16+
type: "password",
17+
placeholder: "sk_live_... or sk_test_...",
18+
configKey: "clerkSecretKey",
19+
envVar: "CLERK_SECRET_KEY",
20+
helpText: "Get your secret key from ",
21+
helpLink: {
22+
text: "Clerk Dashboard",
23+
url: "https://dashboard.clerk.com",
24+
},
25+
},
26+
],
27+
28+
testConfig: {
29+
getTestFunction: async () => {
30+
const { testClerk } = await import("./test");
31+
return testClerk;
32+
},
33+
},
34+
35+
actions: [
36+
{
37+
slug: "get-user",
38+
label: "Get User",
39+
description: "Fetch a user by ID from Clerk",
40+
category: "Clerk",
41+
stepFunction: "clerkGetUserStep",
42+
stepImportPath: "get-user",
43+
outputFields: [
44+
{ field: "user.id", description: "User ID" },
45+
{ field: "user.first_name", description: "First name" },
46+
{ field: "user.last_name", description: "Last name" },
47+
],
48+
configFields: [
49+
{
50+
key: "userId",
51+
label: "User ID",
52+
type: "template-input",
53+
placeholder: "user_... or {{NodeName.userId}}",
54+
example: "user_2abc123",
55+
required: true,
56+
},
57+
],
58+
},
59+
{
60+
slug: "create-user",
61+
label: "Create User",
62+
description: "Create a new user in Clerk",
63+
category: "Clerk",
64+
stepFunction: "clerkCreateUserStep",
65+
stepImportPath: "create-user",
66+
outputFields: [
67+
{ field: "user.id", description: "User ID" },
68+
{ field: "user.first_name", description: "First name" },
69+
{ field: "user.last_name", description: "Last name" },
70+
],
71+
configFields: [
72+
{
73+
key: "emailAddress",
74+
label: "Email Address",
75+
type: "template-input",
76+
placeholder: "user@example.com or {{NodeName.email}}",
77+
example: "user@example.com",
78+
required: true,
79+
},
80+
{
81+
key: "firstName",
82+
label: "First Name",
83+
type: "template-input",
84+
placeholder: "John or {{NodeName.firstName}}",
85+
example: "John",
86+
},
87+
{
88+
key: "lastName",
89+
label: "Last Name",
90+
type: "template-input",
91+
placeholder: "Doe or {{NodeName.lastName}}",
92+
example: "Doe",
93+
},
94+
{
95+
key: "password",
96+
label: "Password",
97+
type: "template-input",
98+
placeholder: "Password (min 8 chars) or leave empty",
99+
example: "securepassword123",
100+
},
101+
{
102+
label: "Metadata",
103+
type: "group",
104+
defaultExpanded: false,
105+
fields: [
106+
{
107+
key: "publicMetadata",
108+
label: "Public Metadata (JSON)",
109+
type: "template-textarea",
110+
placeholder: '{"role": "admin"} or {{NodeName.metadata}}',
111+
rows: 3,
112+
},
113+
{
114+
key: "privateMetadata",
115+
label: "Private Metadata (JSON)",
116+
type: "template-textarea",
117+
placeholder: '{"internal_id": "123"}',
118+
rows: 3,
119+
},
120+
],
121+
},
122+
],
123+
},
124+
{
125+
slug: "update-user",
126+
label: "Update User",
127+
description: "Update an existing user in Clerk",
128+
category: "Clerk",
129+
stepFunction: "clerkUpdateUserStep",
130+
stepImportPath: "update-user",
131+
outputFields: [
132+
{ field: "user.id", description: "User ID" },
133+
{ field: "user.first_name", description: "First name" },
134+
{ field: "user.last_name", description: "Last name" },
135+
],
136+
configFields: [
137+
{
138+
key: "userId",
139+
label: "User ID",
140+
type: "template-input",
141+
placeholder: "user_... or {{NodeName.user.id}}",
142+
example: "user_2abc123",
143+
required: true,
144+
},
145+
{
146+
key: "firstName",
147+
label: "First Name",
148+
type: "template-input",
149+
placeholder: "Jane or {{NodeName.firstName}}",
150+
},
151+
{
152+
key: "lastName",
153+
label: "Last Name",
154+
type: "template-input",
155+
placeholder: "Doe or {{NodeName.lastName}}",
156+
},
157+
{
158+
label: "Metadata",
159+
type: "group",
160+
defaultExpanded: false,
161+
fields: [
162+
{
163+
key: "publicMetadata",
164+
label: "Public Metadata (JSON)",
165+
type: "template-textarea",
166+
placeholder: '{"role": "admin"} or {{NodeName.metadata}}',
167+
rows: 3,
168+
},
169+
{
170+
key: "privateMetadata",
171+
label: "Private Metadata (JSON)",
172+
type: "template-textarea",
173+
placeholder: '{"internal_id": "123"}',
174+
rows: 3,
175+
},
176+
],
177+
},
178+
],
179+
},
180+
{
181+
slug: "delete-user",
182+
label: "Delete User",
183+
description: "Delete a user from Clerk",
184+
category: "Clerk",
185+
stepFunction: "clerkDeleteUserStep",
186+
stepImportPath: "delete-user",
187+
outputFields: [{ field: "deleted", description: "Deletion success" }],
188+
configFields: [
189+
{
190+
key: "userId",
191+
label: "User ID",
192+
type: "template-input",
193+
placeholder: "user_... or {{NodeName.user.id}}",
194+
example: "user_2abc123",
195+
required: true,
196+
},
197+
],
198+
},
199+
],
200+
};
201+
202+
// Auto-register on import
203+
registerIntegration(clerkPlugin);
204+
205+
export default clerkPlugin;

0 commit comments

Comments
 (0)