chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkAddOrganizationMemberParams,
|
||||
ClerkAddOrganizationMemberResponse,
|
||||
ClerkApiError,
|
||||
ClerkOrganizationMembership,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkAddOrganizationMember')
|
||||
|
||||
export const clerkAddOrganizationMemberTool: ToolConfig<
|
||||
ClerkAddOrganizationMemberParams,
|
||||
ClerkAddOrganizationMemberResponse
|
||||
> = {
|
||||
id: 'clerk_add_organization_member',
|
||||
name: 'Add Organization Member in Clerk',
|
||||
description: 'Add a user as a member of a Clerk organization with a given role',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the organization (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the user to add as a member',
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Role to assign, e.g. org:admin or org:member',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}/memberships`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => ({
|
||||
user_id: params.userId?.trim(),
|
||||
role: params.role,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkOrganizationMembership | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to add organization member in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const membership = data as ClerkOrganizationMembership
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: membership.id,
|
||||
role: membership.role,
|
||||
roleName: membership.role_name ?? null,
|
||||
permissions: membership.permissions ?? [],
|
||||
organizationId: membership.organization.id,
|
||||
userId: membership.public_user_data.user_id,
|
||||
firstName: membership.public_user_data.first_name ?? null,
|
||||
lastName: membership.public_user_data.last_name ?? null,
|
||||
imageUrl: membership.public_user_data.image_url ?? null,
|
||||
identifier: membership.public_user_data.identifier ?? null,
|
||||
username: membership.public_user_data.username ?? null,
|
||||
banned: membership.public_user_data.banned ?? false,
|
||||
publicMetadata: membership.public_metadata ?? {},
|
||||
createdAt: membership.created_at,
|
||||
updatedAt: membership.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Membership ID' },
|
||||
role: { type: 'string', description: 'Member role' },
|
||||
roleName: { type: 'string', description: 'Human-readable role name', optional: true },
|
||||
permissions: {
|
||||
type: 'array',
|
||||
description: 'Permissions granted by the role',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
organizationId: { type: 'string', description: 'Organization ID' },
|
||||
userId: { type: 'string', description: 'Member user ID' },
|
||||
firstName: { type: 'string', description: 'Member first name', optional: true },
|
||||
lastName: { type: 'string', description: 'Member last name', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Member profile image URL', optional: true },
|
||||
identifier: { type: 'string', description: 'Member identifier (e.g., email)', optional: true },
|
||||
username: { type: 'string', description: 'Member username', optional: true },
|
||||
banned: { type: 'boolean', description: 'Whether the member is banned' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkBanUserParams,
|
||||
ClerkBanUserResponse,
|
||||
ClerkUser,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkBanUser')
|
||||
|
||||
export const clerkBanUserTool: ToolConfig<ClerkBanUserParams, ClerkBanUserResponse> = {
|
||||
id: 'clerk_ban_user',
|
||||
name: 'Ban User in Clerk',
|
||||
description: 'Ban a user, preventing them from signing in',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the user to ban (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}/ban`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkUser | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error((data as ClerkApiError).errors?.[0]?.message || 'Failed to ban user in Clerk')
|
||||
}
|
||||
|
||||
const user = data as ClerkUser
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
username: user.username ?? null,
|
||||
firstName: user.first_name ?? null,
|
||||
lastName: user.last_name ?? null,
|
||||
banned: user.banned ?? false,
|
||||
locked: user.locked ?? false,
|
||||
lockoutExpiresInSeconds: user.lockout_expires_in_seconds ?? null,
|
||||
updatedAt: user.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username', optional: true },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
banned: { type: 'boolean', description: 'Whether the user is banned' },
|
||||
locked: { type: 'boolean', description: 'Whether the user is locked' },
|
||||
lockoutExpiresInSeconds: {
|
||||
type: 'number',
|
||||
description: 'Seconds until lockout expires',
|
||||
optional: true,
|
||||
},
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkActorToken,
|
||||
ClerkApiError,
|
||||
ClerkCreateActorTokenParams,
|
||||
ClerkCreateActorTokenResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkCreateActorToken')
|
||||
|
||||
export const clerkCreateActorTokenTool: ToolConfig<
|
||||
ClerkCreateActorTokenParams,
|
||||
ClerkCreateActorTokenResponse
|
||||
> = {
|
||||
id: 'clerk_create_actor_token',
|
||||
name: 'Create Actor Token in Clerk',
|
||||
description:
|
||||
'Create an actor token to impersonate a user (God Mode / act-as-user), e.g. for support tooling',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the user to impersonate',
|
||||
},
|
||||
actor: {
|
||||
type: 'json',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Actor JSON object identifying who is impersonating, must include a "sub" field, e.g. {"sub": "user_support_agent_id"}',
|
||||
},
|
||||
expiresInSeconds: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Seconds until the token expires (default 3600)',
|
||||
},
|
||||
sessionMaxDurationInSeconds: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Max duration in seconds for sessions created with this token (default 1800)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.clerk.com/v1/actor_tokens',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
user_id: params.userId?.trim(),
|
||||
actor: params.actor,
|
||||
}
|
||||
|
||||
if (params.expiresInSeconds !== undefined) body.expires_in_seconds = params.expiresInSeconds
|
||||
if (params.sessionMaxDurationInSeconds !== undefined)
|
||||
body.session_max_duration_in_seconds = params.sessionMaxDurationInSeconds
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkActorToken | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to create actor token in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const actorToken = data as ClerkActorToken
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: actorToken.id,
|
||||
status: actorToken.status,
|
||||
userId: actorToken.user_id,
|
||||
actor: actorToken.actor ?? {},
|
||||
token: actorToken.token ?? null,
|
||||
url: actorToken.url ?? null,
|
||||
createdAt: actorToken.created_at,
|
||||
updatedAt: actorToken.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Actor token ID' },
|
||||
status: { type: 'string', description: 'Actor token status' },
|
||||
userId: { type: 'string', description: 'ID of the impersonated user' },
|
||||
actor: { type: 'json', description: 'Actor object identifying who is impersonating' },
|
||||
token: { type: 'string', description: 'Signed actor token (JWT)', optional: true },
|
||||
url: { type: 'string', description: 'Sign-in URL for the actor token', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkAllowlistIdentifier,
|
||||
ClerkApiError,
|
||||
ClerkCreateAllowlistIdentifierParams,
|
||||
ClerkCreateAllowlistIdentifierResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkCreateAllowlistIdentifier')
|
||||
|
||||
export const clerkCreateAllowlistIdentifierTool: ToolConfig<
|
||||
ClerkCreateAllowlistIdentifierParams,
|
||||
ClerkCreateAllowlistIdentifierResponse
|
||||
> = {
|
||||
id: 'clerk_create_allowlist_identifier',
|
||||
name: 'Create Allowlist Identifier in Clerk',
|
||||
description: 'Add an email, phone number, or web3 wallet to your Clerk instance allowlist',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
identifier: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Email address, phone number, or web3 wallet to allow (wildcards like *@example.com supported for email)',
|
||||
},
|
||||
notify: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to notify the identifier owner by email (default false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.clerk.com/v1/allowlist_identifiers',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
identifier: params.identifier?.trim(),
|
||||
}
|
||||
|
||||
if (params.notify !== undefined) body.notify = params.notify
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkAllowlistIdentifier | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message ||
|
||||
'Failed to create allowlist identifier in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const identifier = data as ClerkAllowlistIdentifier
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: identifier.id,
|
||||
identifier: identifier.identifier,
|
||||
identifierType: identifier.identifier_type,
|
||||
invitationId: identifier.invitation_id ?? null,
|
||||
createdAt: identifier.created_at,
|
||||
updatedAt: identifier.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Allowlist identifier ID' },
|
||||
identifier: { type: 'string', description: 'Email, phone, or web3 wallet identifier' },
|
||||
identifierType: { type: 'string', description: 'Type of identifier' },
|
||||
invitationId: { type: 'string', description: 'Associated invitation ID', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkBlocklistIdentifier,
|
||||
ClerkCreateBlocklistIdentifierParams,
|
||||
ClerkCreateBlocklistIdentifierResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkCreateBlocklistIdentifier')
|
||||
|
||||
export const clerkCreateBlocklistIdentifierTool: ToolConfig<
|
||||
ClerkCreateBlocklistIdentifierParams,
|
||||
ClerkCreateBlocklistIdentifierResponse
|
||||
> = {
|
||||
id: 'clerk_create_blocklist_identifier',
|
||||
name: 'Create Blocklist Identifier in Clerk',
|
||||
description:
|
||||
'Add an email, phone number, or web3 wallet to your Clerk instance blocklist to prevent sign-ups',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
identifier: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email address, phone number, or web3 wallet to block',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.clerk.com/v1/blocklist_identifiers',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => ({
|
||||
identifier: params.identifier?.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkBlocklistIdentifier | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message ||
|
||||
'Failed to create blocklist identifier in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const identifier = data as ClerkBlocklistIdentifier
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: identifier.id,
|
||||
identifier: identifier.identifier,
|
||||
identifierType: identifier.identifier_type,
|
||||
createdAt: identifier.created_at,
|
||||
updatedAt: identifier.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Blocklist identifier ID' },
|
||||
identifier: { type: 'string', description: 'Email, phone, or web3 wallet identifier' },
|
||||
identifierType: { type: 'string', description: 'Type of identifier' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkCreateOrganizationParams,
|
||||
ClerkCreateOrganizationResponse,
|
||||
ClerkOrganization,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkCreateOrganization')
|
||||
|
||||
export const clerkCreateOrganizationTool: ToolConfig<
|
||||
ClerkCreateOrganizationParams,
|
||||
ClerkCreateOrganizationResponse
|
||||
> = {
|
||||
id: 'clerk_create_organization',
|
||||
name: 'Create Organization in Clerk',
|
||||
description: 'Create a new organization in your Clerk application',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name of the organization',
|
||||
},
|
||||
createdBy: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'User ID of the creator who will become admin (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
slug: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Slug identifier for the organization',
|
||||
},
|
||||
maxAllowedMemberships: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum member capacity (0 for unlimited)',
|
||||
},
|
||||
publicMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Public metadata (JSON object)',
|
||||
},
|
||||
privateMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Private metadata (JSON object)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.clerk.com/v1/organizations',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
name: params.name,
|
||||
created_by: params.createdBy,
|
||||
}
|
||||
|
||||
if (params.slug) body.slug = params.slug
|
||||
if (params.maxAllowedMemberships !== undefined)
|
||||
body.max_allowed_memberships = params.maxAllowedMemberships
|
||||
if (params.publicMetadata) body.public_metadata = params.publicMetadata
|
||||
if (params.privateMetadata) body.private_metadata = params.privateMetadata
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkOrganization | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to create organization in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const org = data as ClerkOrganization
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
slug: org.slug ?? null,
|
||||
imageUrl: org.image_url ?? null,
|
||||
hasImage: org.has_image ?? false,
|
||||
membersCount: org.members_count ?? null,
|
||||
pendingInvitationsCount: org.pending_invitations_count ?? null,
|
||||
maxAllowedMemberships: org.max_allowed_memberships ?? 0,
|
||||
adminDeleteEnabled: org.admin_delete_enabled ?? false,
|
||||
createdBy: org.created_by ?? null,
|
||||
createdAt: org.created_at,
|
||||
updatedAt: org.updated_at,
|
||||
publicMetadata: org.public_metadata ?? {},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Created organization ID' },
|
||||
name: { type: 'string', description: 'Organization name' },
|
||||
slug: { type: 'string', description: 'Organization slug', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Organization image URL', optional: true },
|
||||
hasImage: { type: 'boolean', description: 'Whether organization has an image' },
|
||||
membersCount: { type: 'number', description: 'Number of members', optional: true },
|
||||
pendingInvitationsCount: {
|
||||
type: 'number',
|
||||
description: 'Number of pending invitations',
|
||||
optional: true,
|
||||
},
|
||||
maxAllowedMemberships: { type: 'number', description: 'Max allowed memberships' },
|
||||
adminDeleteEnabled: { type: 'boolean', description: 'Whether admin delete is enabled' },
|
||||
createdBy: { type: 'string', description: 'Creator user ID', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkCreateOrganizationInvitationParams,
|
||||
ClerkCreateOrganizationInvitationResponse,
|
||||
ClerkOrganizationInvitation,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkCreateOrganizationInvitation')
|
||||
|
||||
export const clerkCreateOrganizationInvitationTool: ToolConfig<
|
||||
ClerkCreateOrganizationInvitationParams,
|
||||
ClerkCreateOrganizationInvitationResponse
|
||||
> = {
|
||||
id: 'clerk_create_organization_invitation',
|
||||
name: 'Create Organization Invitation in Clerk',
|
||||
description: 'Invite a user by email to join a Clerk organization with a given role',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the organization (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
emailAddress: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email address of the user to invite',
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Role to assign on acceptance, e.g. org:admin or org:member',
|
||||
},
|
||||
inviterUserId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'User ID of the inviter',
|
||||
},
|
||||
redirectUrl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'URL to redirect to after the invitation is accepted',
|
||||
},
|
||||
expiresInDays: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Days until the invitation expires (1-365, default 30)',
|
||||
},
|
||||
publicMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Public metadata (JSON object)',
|
||||
},
|
||||
privateMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Private metadata (JSON object)',
|
||||
},
|
||||
notify: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether Clerk sends the invitation email (default true)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}/invitations`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
email_address: params.emailAddress,
|
||||
role: params.role,
|
||||
}
|
||||
|
||||
if (params.inviterUserId !== undefined) body.inviter_user_id = params.inviterUserId
|
||||
if (params.redirectUrl !== undefined) body.redirect_url = params.redirectUrl
|
||||
if (params.expiresInDays !== undefined) body.expires_in_days = params.expiresInDays
|
||||
if (params.publicMetadata !== undefined) body.public_metadata = params.publicMetadata
|
||||
if (params.privateMetadata !== undefined) body.private_metadata = params.privateMetadata
|
||||
if (params.notify !== undefined) body.notify = params.notify
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkOrganizationInvitation | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message ||
|
||||
'Failed to create organization invitation in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const invitation = data as ClerkOrganizationInvitation
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: invitation.id,
|
||||
emailAddress: invitation.email_address,
|
||||
role: invitation.role,
|
||||
roleName: invitation.role_name ?? null,
|
||||
organizationId: invitation.organization_id,
|
||||
inviterId: invitation.inviter_id ?? null,
|
||||
inviterEmail: invitation.public_inviter_data?.identifier ?? null,
|
||||
inviterFirstName: invitation.public_inviter_data?.first_name ?? null,
|
||||
inviterLastName: invitation.public_inviter_data?.last_name ?? null,
|
||||
status: invitation.status,
|
||||
url: invitation.url ?? null,
|
||||
expiresAt: invitation.expires_at ?? null,
|
||||
publicMetadata: invitation.public_metadata ?? {},
|
||||
createdAt: invitation.created_at,
|
||||
updatedAt: invitation.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Invitation ID' },
|
||||
emailAddress: { type: 'string', description: 'Invited email address' },
|
||||
role: { type: 'string', description: 'Role to assign on acceptance' },
|
||||
roleName: { type: 'string', description: 'Human-readable role name', optional: true },
|
||||
organizationId: { type: 'string', description: 'Organization ID' },
|
||||
inviterId: { type: 'string', description: 'User ID of the inviter', optional: true },
|
||||
inviterEmail: { type: 'string', description: "Inviter's email address", optional: true },
|
||||
inviterFirstName: { type: 'string', description: "Inviter's first name", optional: true },
|
||||
inviterLastName: { type: 'string', description: "Inviter's last name", optional: true },
|
||||
status: { type: 'string', description: 'Invitation status' },
|
||||
url: { type: 'string', description: 'Invitation URL', optional: true },
|
||||
expiresAt: { type: 'number', description: 'Expiration timestamp', optional: true },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkCreateUserParams,
|
||||
ClerkCreateUserResponse,
|
||||
ClerkEmailAddress,
|
||||
ClerkPhoneNumber,
|
||||
ClerkUser,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkCreateUser')
|
||||
|
||||
export const clerkCreateUserTool: ToolConfig<ClerkCreateUserParams, ClerkCreateUserResponse> = {
|
||||
id: 'clerk_create_user',
|
||||
name: 'Create User in Clerk',
|
||||
description: 'Create a new user in your Clerk application',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
emailAddress: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Email addresses for the user (comma-separated for multiple)',
|
||||
},
|
||||
phoneNumber: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Phone numbers for the user (comma-separated for multiple)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Username for the user (must be unique)',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Password for the user (minimum 8 characters)',
|
||||
},
|
||||
firstName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'First name of the user',
|
||||
},
|
||||
lastName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Last name of the user',
|
||||
},
|
||||
externalId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'External system identifier (must be unique)',
|
||||
},
|
||||
publicMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Public metadata (JSON object, readable from frontend)',
|
||||
},
|
||||
privateMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Private metadata (JSON object, backend only)',
|
||||
},
|
||||
unsafeMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Unsafe metadata (JSON object, modifiable from frontend)',
|
||||
},
|
||||
skipPasswordChecks: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Skip password validation checks',
|
||||
},
|
||||
skipPasswordRequirement: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Make password optional',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.clerk.com/v1/users',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {}
|
||||
|
||||
if (params.emailAddress) {
|
||||
const emailStr = params.emailAddress as string
|
||||
body.email_address = emailStr.split(',').map((e) => e.trim())
|
||||
}
|
||||
if (params.phoneNumber) {
|
||||
const phoneStr = params.phoneNumber as string
|
||||
body.phone_number = phoneStr.split(',').map((p) => p.trim())
|
||||
}
|
||||
if (params.username) body.username = params.username.trim()
|
||||
if (params.password) body.password = params.password
|
||||
if (params.firstName) body.first_name = params.firstName.trim()
|
||||
if (params.lastName) body.last_name = params.lastName.trim()
|
||||
if (params.externalId) body.external_id = params.externalId.trim()
|
||||
if (params.publicMetadata) body.public_metadata = params.publicMetadata
|
||||
if (params.privateMetadata) body.private_metadata = params.privateMetadata
|
||||
if (params.unsafeMetadata) body.unsafe_metadata = params.unsafeMetadata
|
||||
if (params.skipPasswordChecks !== undefined)
|
||||
body.skip_password_checks = params.skipPasswordChecks
|
||||
if (params.skipPasswordRequirement !== undefined)
|
||||
body.skip_password_requirement = params.skipPasswordRequirement
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkUser | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to create user in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const user = data as ClerkUser
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
username: user.username ?? null,
|
||||
firstName: user.first_name ?? null,
|
||||
lastName: user.last_name ?? null,
|
||||
imageUrl: user.image_url ?? null,
|
||||
primaryEmailAddressId: user.primary_email_address_id ?? null,
|
||||
primaryPhoneNumberId: user.primary_phone_number_id ?? null,
|
||||
emailAddresses: (user.email_addresses ?? []).map((email: ClerkEmailAddress) => ({
|
||||
id: email.id,
|
||||
emailAddress: email.email_address,
|
||||
verified: email.verification?.status === 'verified',
|
||||
})),
|
||||
phoneNumbers: (user.phone_numbers ?? []).map((phone: ClerkPhoneNumber) => ({
|
||||
id: phone.id,
|
||||
phoneNumber: phone.phone_number,
|
||||
verified: phone.verification?.status === 'verified',
|
||||
})),
|
||||
externalId: user.external_id ?? null,
|
||||
createdAt: user.created_at,
|
||||
updatedAt: user.updated_at,
|
||||
publicMetadata: user.public_metadata ?? {},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Created user ID' },
|
||||
username: { type: 'string', description: 'Username', optional: true },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
primaryEmailAddressId: {
|
||||
type: 'string',
|
||||
description: 'Primary email address ID',
|
||||
optional: true,
|
||||
},
|
||||
primaryPhoneNumberId: {
|
||||
type: 'string',
|
||||
description: 'Primary phone number ID',
|
||||
optional: true,
|
||||
},
|
||||
emailAddresses: {
|
||||
type: 'array',
|
||||
description: 'User email addresses',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Email address ID' },
|
||||
emailAddress: { type: 'string', description: 'Email address' },
|
||||
verified: { type: 'boolean', description: 'Whether email is verified' },
|
||||
},
|
||||
},
|
||||
},
|
||||
phoneNumbers: {
|
||||
type: 'array',
|
||||
description: 'User phone numbers',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Phone number ID' },
|
||||
phoneNumber: { type: 'string', description: 'Phone number' },
|
||||
verified: { type: 'boolean', description: 'Whether phone is verified' },
|
||||
},
|
||||
},
|
||||
},
|
||||
externalId: { type: 'string', description: 'External system ID', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkDeleteAllowlistIdentifierParams,
|
||||
ClerkDeleteAllowlistIdentifierResponse,
|
||||
ClerkDeleteResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkDeleteAllowlistIdentifier')
|
||||
|
||||
export const clerkDeleteAllowlistIdentifierTool: ToolConfig<
|
||||
ClerkDeleteAllowlistIdentifierParams,
|
||||
ClerkDeleteAllowlistIdentifierResponse
|
||||
> = {
|
||||
id: 'clerk_delete_allowlist_identifier',
|
||||
name: 'Delete Allowlist Identifier from Clerk',
|
||||
description: 'Remove an identifier from your Clerk instance allowlist',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
identifierId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the allowlist identifier to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.clerk.com/v1/allowlist_identifiers/${params.identifierId?.trim()}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkDeleteResponse | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message ||
|
||||
'Failed to delete allowlist identifier from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const deleteData = data as ClerkDeleteResponse
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: deleteData.id,
|
||||
object: deleteData.object ?? 'allowlist_identifier',
|
||||
deleted: deleteData.deleted ?? true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Deleted allowlist identifier ID' },
|
||||
object: { type: 'string', description: 'Object type (allowlist_identifier)' },
|
||||
deleted: { type: 'boolean', description: 'Whether the identifier was deleted' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkDeleteBlocklistIdentifierParams,
|
||||
ClerkDeleteBlocklistIdentifierResponse,
|
||||
ClerkDeleteResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkDeleteBlocklistIdentifier')
|
||||
|
||||
export const clerkDeleteBlocklistIdentifierTool: ToolConfig<
|
||||
ClerkDeleteBlocklistIdentifierParams,
|
||||
ClerkDeleteBlocklistIdentifierResponse
|
||||
> = {
|
||||
id: 'clerk_delete_blocklist_identifier',
|
||||
name: 'Delete Blocklist Identifier from Clerk',
|
||||
description: 'Remove an identifier from your Clerk instance blocklist',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
identifierId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the blocklist identifier to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.clerk.com/v1/blocklist_identifiers/${params.identifierId?.trim()}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkDeleteResponse | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message ||
|
||||
'Failed to delete blocklist identifier from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const deleteData = data as ClerkDeleteResponse
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: deleteData.id,
|
||||
object: deleteData.object ?? 'blocklist_identifier',
|
||||
deleted: deleteData.deleted ?? true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Deleted blocklist identifier ID' },
|
||||
object: { type: 'string', description: 'Object type (blocklist_identifier)' },
|
||||
deleted: { type: 'boolean', description: 'Whether the identifier was deleted' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkDeleteOrganizationParams,
|
||||
ClerkDeleteOrganizationResponse,
|
||||
ClerkDeleteResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkDeleteOrganization')
|
||||
|
||||
export const clerkDeleteOrganizationTool: ToolConfig<
|
||||
ClerkDeleteOrganizationParams,
|
||||
ClerkDeleteOrganizationResponse
|
||||
> = {
|
||||
id: 'clerk_delete_organization',
|
||||
name: 'Delete Organization from Clerk',
|
||||
description: 'Delete an organization from your Clerk application',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the organization to delete (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkDeleteResponse | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to delete organization from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const deleteData = data as ClerkDeleteResponse
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: deleteData.id,
|
||||
object: deleteData.object ?? 'organization',
|
||||
deleted: deleteData.deleted ?? true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Deleted organization ID' },
|
||||
object: { type: 'string', description: 'Object type (organization)' },
|
||||
deleted: { type: 'boolean', description: 'Whether the organization was deleted' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkDeleteResponse,
|
||||
ClerkDeleteUserParams,
|
||||
ClerkDeleteUserResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkDeleteUser')
|
||||
|
||||
export const clerkDeleteUserTool: ToolConfig<ClerkDeleteUserParams, ClerkDeleteUserResponse> = {
|
||||
id: 'clerk_delete_user',
|
||||
name: 'Delete User from Clerk',
|
||||
description: 'Delete a user from your Clerk application',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the user to delete (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkDeleteResponse | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to delete user from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const deleteData = data as ClerkDeleteResponse
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: deleteData.id,
|
||||
object: deleteData.object ?? 'user',
|
||||
deleted: deleteData.deleted ?? true,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Deleted user ID' },
|
||||
object: { type: 'string', description: 'Object type (user)' },
|
||||
deleted: { type: 'boolean', description: 'Whether the user was deleted' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkGetJwtTemplateParams,
|
||||
ClerkGetJwtTemplateResponse,
|
||||
ClerkJwtTemplate,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkGetJwtTemplate')
|
||||
|
||||
export const clerkGetJwtTemplateTool: ToolConfig<
|
||||
ClerkGetJwtTemplateParams,
|
||||
ClerkGetJwtTemplateResponse
|
||||
> = {
|
||||
id: 'clerk_get_jwt_template',
|
||||
name: 'Get JWT Template from Clerk',
|
||||
description: 'Retrieve a single custom JWT template by ID from Clerk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
templateId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the JWT template to retrieve',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/jwt_templates/${params.templateId?.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkJwtTemplate | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to get JWT template from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const template = data as ClerkJwtTemplate
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: template.id,
|
||||
name: template.name,
|
||||
claims: template.claims ?? {},
|
||||
lifetime: template.lifetime,
|
||||
allowedClockSkew: template.allowed_clock_skew,
|
||||
customSigningKey: template.custom_signing_key ?? false,
|
||||
signingAlgorithm: template.signing_algorithm,
|
||||
createdAt: template.created_at,
|
||||
updatedAt: template.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'JWT template ID' },
|
||||
name: { type: 'string', description: 'JWT template name' },
|
||||
claims: { type: 'json', description: 'Custom claims defined on the template' },
|
||||
lifetime: { type: 'number', description: 'Token lifetime in seconds' },
|
||||
allowedClockSkew: { type: 'number', description: 'Allowed clock skew in seconds' },
|
||||
customSigningKey: {
|
||||
type: 'boolean',
|
||||
description: 'Whether a custom signing key is configured',
|
||||
},
|
||||
signingAlgorithm: { type: 'string', description: 'Signing algorithm used' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkGetOrganizationParams,
|
||||
ClerkGetOrganizationResponse,
|
||||
ClerkOrganization,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkGetOrganization')
|
||||
|
||||
export const clerkGetOrganizationTool: ToolConfig<
|
||||
ClerkGetOrganizationParams,
|
||||
ClerkGetOrganizationResponse
|
||||
> = {
|
||||
id: 'clerk_get_organization',
|
||||
name: 'Get Organization from Clerk',
|
||||
description: 'Retrieve a single organization by ID or slug from Clerk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The ID or slug of the organization to retrieve (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC or my-org-slug)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkOrganization | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to get organization from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const org = data as ClerkOrganization
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
slug: org.slug ?? null,
|
||||
imageUrl: org.image_url ?? null,
|
||||
hasImage: org.has_image ?? false,
|
||||
membersCount: org.members_count ?? null,
|
||||
pendingInvitationsCount: org.pending_invitations_count ?? null,
|
||||
maxAllowedMemberships: org.max_allowed_memberships ?? 0,
|
||||
adminDeleteEnabled: org.admin_delete_enabled ?? false,
|
||||
createdBy: org.created_by ?? null,
|
||||
createdAt: org.created_at,
|
||||
updatedAt: org.updated_at,
|
||||
publicMetadata: org.public_metadata ?? {},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Organization ID' },
|
||||
name: { type: 'string', description: 'Organization name' },
|
||||
slug: { type: 'string', description: 'Organization slug', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Organization image URL', optional: true },
|
||||
hasImage: { type: 'boolean', description: 'Whether organization has an image' },
|
||||
membersCount: { type: 'number', description: 'Number of members', optional: true },
|
||||
pendingInvitationsCount: {
|
||||
type: 'number',
|
||||
description: 'Number of pending invitations',
|
||||
optional: true,
|
||||
},
|
||||
maxAllowedMemberships: { type: 'number', description: 'Max allowed memberships' },
|
||||
adminDeleteEnabled: { type: 'boolean', description: 'Whether admin delete is enabled' },
|
||||
createdBy: { type: 'string', description: 'Creator user ID', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkGetSessionParams,
|
||||
ClerkGetSessionResponse,
|
||||
ClerkSession,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkGetSession')
|
||||
|
||||
export const clerkGetSessionTool: ToolConfig<ClerkGetSessionParams, ClerkGetSessionResponse> = {
|
||||
id: 'clerk_get_session',
|
||||
name: 'Get Session from Clerk',
|
||||
description: 'Retrieve a single session by ID from Clerk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
sessionId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the session to retrieve (e.g., sess_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/sessions/${params.sessionId?.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkSession | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to get session from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const session = data as ClerkSession
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: session.id,
|
||||
userId: session.user_id,
|
||||
clientId: session.client_id,
|
||||
status: session.status,
|
||||
lastActiveAt: session.last_active_at ?? null,
|
||||
lastActiveOrganizationId: session.last_active_organization_id ?? null,
|
||||
expireAt: session.expire_at ?? null,
|
||||
abandonAt: session.abandon_at ?? null,
|
||||
createdAt: session.created_at,
|
||||
updatedAt: session.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Session ID' },
|
||||
userId: { type: 'string', description: 'User ID' },
|
||||
clientId: { type: 'string', description: 'Client ID' },
|
||||
status: { type: 'string', description: 'Session status' },
|
||||
lastActiveAt: { type: 'number', description: 'Last activity timestamp', optional: true },
|
||||
lastActiveOrganizationId: {
|
||||
type: 'string',
|
||||
description: 'Last active organization ID',
|
||||
optional: true,
|
||||
},
|
||||
expireAt: { type: 'number', description: 'Expiration timestamp', optional: true },
|
||||
abandonAt: { type: 'number', description: 'Abandon timestamp', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkEmailAddress,
|
||||
ClerkGetUserParams,
|
||||
ClerkGetUserResponse,
|
||||
ClerkPhoneNumber,
|
||||
ClerkUser,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkGetUser')
|
||||
|
||||
export const clerkGetUserTool: ToolConfig<ClerkGetUserParams, ClerkGetUserResponse> = {
|
||||
id: 'clerk_get_user',
|
||||
name: 'Get User from Clerk',
|
||||
description: 'Retrieve a single user by their ID from Clerk',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the user to retrieve (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}`,
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkUser | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to get user from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const user = data as ClerkUser
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
username: user.username ?? null,
|
||||
firstName: user.first_name ?? null,
|
||||
lastName: user.last_name ?? null,
|
||||
imageUrl: user.image_url ?? null,
|
||||
hasImage: user.has_image ?? false,
|
||||
primaryEmailAddressId: user.primary_email_address_id ?? null,
|
||||
primaryPhoneNumberId: user.primary_phone_number_id ?? null,
|
||||
primaryWeb3WalletId: user.primary_web3_wallet_id ?? null,
|
||||
emailAddresses: (user.email_addresses ?? []).map((email: ClerkEmailAddress) => ({
|
||||
id: email.id,
|
||||
emailAddress: email.email_address,
|
||||
verified: email.verification?.status === 'verified',
|
||||
})),
|
||||
phoneNumbers: (user.phone_numbers ?? []).map((phone: ClerkPhoneNumber) => ({
|
||||
id: phone.id,
|
||||
phoneNumber: phone.phone_number,
|
||||
verified: phone.verification?.status === 'verified',
|
||||
})),
|
||||
externalId: user.external_id ?? null,
|
||||
passwordEnabled: user.password_enabled ?? false,
|
||||
twoFactorEnabled: user.two_factor_enabled ?? false,
|
||||
totpEnabled: user.totp_enabled ?? false,
|
||||
backupCodeEnabled: user.backup_code_enabled ?? false,
|
||||
banned: user.banned ?? false,
|
||||
locked: user.locked ?? false,
|
||||
deleteSelfEnabled: user.delete_self_enabled ?? false,
|
||||
createOrganizationEnabled: user.create_organization_enabled ?? false,
|
||||
lastSignInAt: user.last_sign_in_at ?? null,
|
||||
lastActiveAt: user.last_active_at ?? null,
|
||||
createdAt: user.created_at,
|
||||
updatedAt: user.updated_at,
|
||||
publicMetadata: user.public_metadata ?? {},
|
||||
privateMetadata: user.private_metadata ?? {},
|
||||
unsafeMetadata: user.unsafe_metadata ?? {},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username', optional: true },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
hasImage: { type: 'boolean', description: 'Whether user has a profile image' },
|
||||
primaryEmailAddressId: {
|
||||
type: 'string',
|
||||
description: 'Primary email address ID',
|
||||
optional: true,
|
||||
},
|
||||
primaryPhoneNumberId: {
|
||||
type: 'string',
|
||||
description: 'Primary phone number ID',
|
||||
optional: true,
|
||||
},
|
||||
primaryWeb3WalletId: { type: 'string', description: 'Primary Web3 wallet ID', optional: true },
|
||||
emailAddresses: {
|
||||
type: 'array',
|
||||
description: 'User email addresses',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Email address ID' },
|
||||
emailAddress: { type: 'string', description: 'Email address' },
|
||||
verified: { type: 'boolean', description: 'Whether email is verified' },
|
||||
},
|
||||
},
|
||||
},
|
||||
phoneNumbers: {
|
||||
type: 'array',
|
||||
description: 'User phone numbers',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Phone number ID' },
|
||||
phoneNumber: { type: 'string', description: 'Phone number' },
|
||||
verified: { type: 'boolean', description: 'Whether phone is verified' },
|
||||
},
|
||||
},
|
||||
},
|
||||
externalId: { type: 'string', description: 'External system ID', optional: true },
|
||||
passwordEnabled: { type: 'boolean', description: 'Whether password is enabled' },
|
||||
twoFactorEnabled: { type: 'boolean', description: 'Whether 2FA is enabled' },
|
||||
totpEnabled: { type: 'boolean', description: 'Whether TOTP is enabled' },
|
||||
backupCodeEnabled: { type: 'boolean', description: 'Whether backup codes are enabled' },
|
||||
banned: { type: 'boolean', description: 'Whether user is banned' },
|
||||
locked: { type: 'boolean', description: 'Whether user is locked' },
|
||||
deleteSelfEnabled: { type: 'boolean', description: 'Whether user can delete themselves' },
|
||||
createOrganizationEnabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether user can create organizations',
|
||||
},
|
||||
lastSignInAt: { type: 'number', description: 'Last sign-in timestamp', optional: true },
|
||||
lastActiveAt: { type: 'number', description: 'Last activity timestamp', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata (readable from frontend)' },
|
||||
privateMetadata: { type: 'json', description: 'Private metadata (backend only)' },
|
||||
unsafeMetadata: { type: 'json', description: 'Unsafe metadata (modifiable from frontend)' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkGetUserOauthTokenParams,
|
||||
ClerkGetUserOauthTokenResponse,
|
||||
ClerkOAuthAccessToken,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkGetUserOauthToken')
|
||||
|
||||
export const clerkGetUserOauthTokenTool: ToolConfig<
|
||||
ClerkGetUserOauthTokenParams,
|
||||
ClerkGetUserOauthTokenResponse
|
||||
> = {
|
||||
id: 'clerk_get_user_oauth_token',
|
||||
name: 'Get User OAuth Access Token from Clerk',
|
||||
description:
|
||||
"Retrieve a user's OAuth access token for a connected external provider (e.g. Google, GitHub, Microsoft) obtained via Clerk SSO",
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the user (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
provider: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'OAuth provider slug, e.g. google, github, microsoft, discord (without the oauth_ prefix)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const providerSlug = params.provider?.trim().replace(/^oauth_/, '')
|
||||
return `https://api.clerk.com/v1/users/${params.userId?.trim()}/oauth_access_tokens/oauth_${providerSlug}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkOAuthAccessToken[] | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message ||
|
||||
'Failed to get user OAuth access token from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const tokens = data as ClerkOAuthAccessToken[]
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
accessTokens: tokens.map((token) => ({
|
||||
externalAccountId: token.external_account_id,
|
||||
token: token.token,
|
||||
expiresAt: token.expires_at ?? null,
|
||||
provider: token.provider,
|
||||
label: token.label ?? null,
|
||||
scopes: token.scopes ?? [],
|
||||
publicMetadata: token.public_metadata ?? {},
|
||||
})),
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
accessTokens: {
|
||||
type: 'array',
|
||||
description: 'OAuth access tokens for the connected provider',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
externalAccountId: { type: 'string', description: 'External account ID' },
|
||||
token: { type: 'string', description: 'OAuth access token' },
|
||||
expiresAt: { type: 'number', description: 'Expiration timestamp', optional: true },
|
||||
provider: { type: 'string', description: 'OAuth provider slug' },
|
||||
label: { type: 'string', description: 'Token label', optional: true },
|
||||
scopes: {
|
||||
type: 'array',
|
||||
description: 'OAuth scopes granted to the token',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
publicMetadata: {
|
||||
type: 'json',
|
||||
description: 'Public metadata associated with the token',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
export { clerkAddOrganizationMemberTool } from './add_organization_member'
|
||||
export { clerkBanUserTool } from './ban_user'
|
||||
export { clerkCreateActorTokenTool } from './create_actor_token'
|
||||
export { clerkCreateAllowlistIdentifierTool } from './create_allowlist_identifier'
|
||||
export { clerkCreateBlocklistIdentifierTool } from './create_blocklist_identifier'
|
||||
export { clerkCreateOrganizationTool } from './create_organization'
|
||||
export { clerkCreateOrganizationInvitationTool } from './create_organization_invitation'
|
||||
export { clerkCreateUserTool } from './create_user'
|
||||
export { clerkDeleteAllowlistIdentifierTool } from './delete_allowlist_identifier'
|
||||
export { clerkDeleteBlocklistIdentifierTool } from './delete_blocklist_identifier'
|
||||
export { clerkDeleteOrganizationTool } from './delete_organization'
|
||||
export { clerkDeleteUserTool } from './delete_user'
|
||||
export { clerkGetJwtTemplateTool } from './get_jwt_template'
|
||||
export { clerkGetOrganizationTool } from './get_organization'
|
||||
export { clerkGetSessionTool } from './get_session'
|
||||
export { clerkGetUserTool } from './get_user'
|
||||
export { clerkGetUserOauthTokenTool } from './get_user_oauth_token'
|
||||
export { clerkListAllowlistIdentifiersTool } from './list_allowlist_identifiers'
|
||||
export { clerkListBlocklistIdentifiersTool } from './list_blocklist_identifiers'
|
||||
export { clerkListJwtTemplatesTool } from './list_jwt_templates'
|
||||
export { clerkListOrganizationInvitationsTool } from './list_organization_invitations'
|
||||
export { clerkListOrganizationMembershipsTool } from './list_organization_memberships'
|
||||
export { clerkListOrganizationsTool } from './list_organizations'
|
||||
export { clerkListSessionsTool } from './list_sessions'
|
||||
export { clerkListUsersTool } from './list_users'
|
||||
export { clerkLockUserTool } from './lock_user'
|
||||
export { clerkRemoveOrganizationMemberTool } from './remove_organization_member'
|
||||
export { clerkRevokeActorTokenTool } from './revoke_actor_token'
|
||||
export { clerkRevokeSessionTool } from './revoke_session'
|
||||
export { clerkUnbanUserTool } from './unban_user'
|
||||
export { clerkUnlockUserTool } from './unlock_user'
|
||||
export { clerkUpdateOrganizationTool } from './update_organization'
|
||||
export { clerkUpdateOrganizationMembershipTool } from './update_organization_membership'
|
||||
export { clerkUpdateUserTool } from './update_user'
|
||||
@@ -0,0 +1,119 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkAllowlistIdentifier,
|
||||
ClerkApiError,
|
||||
ClerkListAllowlistIdentifiersParams,
|
||||
ClerkListAllowlistIdentifiersResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkListAllowlistIdentifiers')
|
||||
|
||||
export const clerkListAllowlistIdentifiersTool: ToolConfig<
|
||||
ClerkListAllowlistIdentifiersParams,
|
||||
ClerkListAllowlistIdentifiersResponse
|
||||
> = {
|
||||
id: 'clerk_list_allowlist_identifiers',
|
||||
name: 'List Allowlist Identifiers from Clerk',
|
||||
description: 'List email/phone/web3-wallet identifiers on your Clerk instance allowlist',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (e.g., 10, 50, 100; range: 1-500, default: 10)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination (e.g., 0, 10, 20)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
if (params.offset) queryParams.append('offset', params.offset.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString
|
||||
? `https://api.clerk.com/v1/allowlist_identifiers?${queryString}`
|
||||
: 'https://api.clerk.com/v1/allowlist_identifiers'
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkAllowlistIdentifier[] | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message ||
|
||||
'Failed to list allowlist identifiers from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const identifiers = (data as ClerkAllowlistIdentifier[]).map((identifier) => ({
|
||||
id: identifier.id,
|
||||
identifier: identifier.identifier,
|
||||
identifierType: identifier.identifier_type,
|
||||
invitationId: identifier.invitation_id ?? null,
|
||||
createdAt: identifier.created_at,
|
||||
updatedAt: identifier.updated_at,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
identifiers,
|
||||
totalCount: identifiers.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
identifiers: {
|
||||
type: 'array',
|
||||
description: 'Array of Clerk allowlist identifier objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Allowlist identifier ID' },
|
||||
identifier: { type: 'string', description: 'Email, phone, or web3 wallet identifier' },
|
||||
identifierType: { type: 'string', description: 'Type of identifier' },
|
||||
invitationId: {
|
||||
type: 'string',
|
||||
description: 'Associated invitation ID',
|
||||
optional: true,
|
||||
},
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of allowlist identifiers' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkBlocklistIdentifier,
|
||||
ClerkListBlocklistIdentifiersParams,
|
||||
ClerkListBlocklistIdentifiersResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkListBlocklistIdentifiers')
|
||||
|
||||
export const clerkListBlocklistIdentifiersTool: ToolConfig<
|
||||
ClerkListBlocklistIdentifiersParams,
|
||||
ClerkListBlocklistIdentifiersResponse
|
||||
> = {
|
||||
id: 'clerk_list_blocklist_identifiers',
|
||||
name: 'List Blocklist Identifiers from Clerk',
|
||||
description: 'List email/phone/web3-wallet identifiers on your Clerk instance blocklist',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.clerk.com/v1/blocklist_identifiers',
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const json: { data: ClerkBlocklistIdentifier[]; total_count: number } | ClerkApiError =
|
||||
await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data: json, status: response.status })
|
||||
throw new Error(
|
||||
(json as ClerkApiError).errors?.[0]?.message ||
|
||||
'Failed to list blocklist identifiers from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const responseData = json as { data: ClerkBlocklistIdentifier[]; total_count: number }
|
||||
|
||||
const identifiers = responseData.data.map((identifier) => ({
|
||||
id: identifier.id,
|
||||
identifier: identifier.identifier,
|
||||
identifierType: identifier.identifier_type,
|
||||
createdAt: identifier.created_at,
|
||||
updatedAt: identifier.updated_at,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
identifiers,
|
||||
totalCount: responseData.total_count ?? identifiers.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
identifiers: {
|
||||
type: 'array',
|
||||
description: 'Array of Clerk blocklist identifier objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Blocklist identifier ID' },
|
||||
identifier: { type: 'string', description: 'Email, phone, or web3 wallet identifier' },
|
||||
identifierType: { type: 'string', description: 'Type of identifier' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of blocklist identifiers' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkJwtTemplate,
|
||||
ClerkListJwtTemplatesParams,
|
||||
ClerkListJwtTemplatesResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkListJwtTemplates')
|
||||
|
||||
export const clerkListJwtTemplatesTool: ToolConfig<
|
||||
ClerkListJwtTemplatesParams,
|
||||
ClerkListJwtTemplatesResponse
|
||||
> = {
|
||||
id: 'clerk_list_jwt_templates',
|
||||
name: 'List JWT Templates from Clerk',
|
||||
description: 'List custom JWT templates configured on your Clerk instance',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.clerk.com/v1/jwt_templates',
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkJwtTemplate[] | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to list JWT templates from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const templates = (data as ClerkJwtTemplate[]).map((template) => ({
|
||||
id: template.id,
|
||||
name: template.name,
|
||||
claims: template.claims ?? {},
|
||||
lifetime: template.lifetime,
|
||||
allowedClockSkew: template.allowed_clock_skew,
|
||||
customSigningKey: template.custom_signing_key ?? false,
|
||||
signingAlgorithm: template.signing_algorithm,
|
||||
createdAt: template.created_at,
|
||||
updatedAt: template.updated_at,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
templates,
|
||||
totalCount: templates.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
templates: {
|
||||
type: 'array',
|
||||
description: 'Array of Clerk JWT template objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'JWT template ID' },
|
||||
name: { type: 'string', description: 'JWT template name' },
|
||||
claims: { type: 'json', description: 'Custom claims defined on the template' },
|
||||
lifetime: { type: 'number', description: 'Token lifetime in seconds' },
|
||||
allowedClockSkew: { type: 'number', description: 'Allowed clock skew in seconds' },
|
||||
customSigningKey: {
|
||||
type: 'boolean',
|
||||
description: 'Whether a custom signing key is configured',
|
||||
},
|
||||
signingAlgorithm: { type: 'string', description: 'Signing algorithm used' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of JWT templates' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkListOrganizationInvitationsParams,
|
||||
ClerkListOrganizationInvitationsResponse,
|
||||
ClerkOrganizationInvitation,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkListOrganizationInvitations')
|
||||
|
||||
export const clerkListOrganizationInvitationsTool: ToolConfig<
|
||||
ClerkListOrganizationInvitationsParams,
|
||||
ClerkListOrganizationInvitationsResponse
|
||||
> = {
|
||||
id: 'clerk_list_organization_invitations',
|
||||
name: 'List Organization Invitations from Clerk',
|
||||
description: 'List pending and past invitations for a Clerk organization',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the organization (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by status: pending, accepted, revoked, or expired',
|
||||
},
|
||||
emailAddress: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by invited email address',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort field (created_at, email_address) with +/- prefix (default: -created_at)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (e.g., 10, 50, 100; range: 1-500, default: 10)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination (e.g., 0, 10, 20)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.status) queryParams.append('status', params.status)
|
||||
if (params.emailAddress) queryParams.append('email_address', params.emailAddress)
|
||||
if (params.orderBy) queryParams.append('order_by', params.orderBy)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
if (params.offset) queryParams.append('offset', params.offset.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const base = `https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}/invitations`
|
||||
return queryString ? `${base}?${queryString}` : base
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const json: { data: ClerkOrganizationInvitation[]; total_count: number } | ClerkApiError =
|
||||
await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data: json, status: response.status })
|
||||
throw new Error(
|
||||
(json as ClerkApiError).errors?.[0]?.message ||
|
||||
'Failed to list organization invitations from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const responseData = json as { data: ClerkOrganizationInvitation[]; total_count: number }
|
||||
|
||||
const invitations = responseData.data.map((invitation) => ({
|
||||
id: invitation.id,
|
||||
emailAddress: invitation.email_address,
|
||||
role: invitation.role,
|
||||
roleName: invitation.role_name ?? null,
|
||||
organizationId: invitation.organization_id,
|
||||
inviterId: invitation.inviter_id ?? null,
|
||||
inviterEmail: invitation.public_inviter_data?.identifier ?? null,
|
||||
inviterFirstName: invitation.public_inviter_data?.first_name ?? null,
|
||||
inviterLastName: invitation.public_inviter_data?.last_name ?? null,
|
||||
status: invitation.status,
|
||||
url: invitation.url ?? null,
|
||||
expiresAt: invitation.expires_at ?? null,
|
||||
publicMetadata: invitation.public_metadata ?? {},
|
||||
createdAt: invitation.created_at,
|
||||
updatedAt: invitation.updated_at,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
invitations,
|
||||
totalCount: responseData.total_count ?? invitations.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
invitations: {
|
||||
type: 'array',
|
||||
description: 'Array of Clerk organization invitation objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Invitation ID' },
|
||||
emailAddress: { type: 'string', description: 'Invited email address' },
|
||||
role: { type: 'string', description: 'Role to assign on acceptance' },
|
||||
roleName: { type: 'string', description: 'Human-readable role name', optional: true },
|
||||
organizationId: { type: 'string', description: 'Organization ID' },
|
||||
inviterId: { type: 'string', description: 'User ID of the inviter', optional: true },
|
||||
inviterEmail: { type: 'string', description: "Inviter's email address", optional: true },
|
||||
inviterFirstName: { type: 'string', description: "Inviter's first name", optional: true },
|
||||
inviterLastName: { type: 'string', description: "Inviter's last name", optional: true },
|
||||
status: { type: 'string', description: 'Invitation status' },
|
||||
url: { type: 'string', description: 'Invitation URL', optional: true },
|
||||
expiresAt: { type: 'number', description: 'Expiration timestamp', optional: true },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of invitations' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkListOrganizationMembershipsParams,
|
||||
ClerkListOrganizationMembershipsResponse,
|
||||
ClerkOrganizationMembership,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkListOrganizationMemberships')
|
||||
|
||||
export const clerkListOrganizationMembershipsTool: ToolConfig<
|
||||
ClerkListOrganizationMembershipsParams,
|
||||
ClerkListOrganizationMembershipsResponse
|
||||
> = {
|
||||
id: 'clerk_list_organization_memberships',
|
||||
name: 'List Organization Memberships from Clerk',
|
||||
description: 'List members of a Clerk organization with optional filtering and pagination',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the organization (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (e.g., 10, 50, 100; range: 1-500, default: 10)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination (e.g., 0, 10, 20)',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort field (e.g., created_at) with +/- prefix for direction',
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by role, comma-separated for multiple (e.g., org:admin,org:member)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
if (params.offset) queryParams.append('offset', params.offset.toString())
|
||||
if (params.orderBy) queryParams.append('order_by', params.orderBy)
|
||||
if (params.role) {
|
||||
params.role.split(',').forEach((role) => {
|
||||
queryParams.append('role', role.trim())
|
||||
})
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
const base = `https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}/memberships`
|
||||
return queryString ? `${base}?${queryString}` : base
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const json: { data: ClerkOrganizationMembership[]; total_count: number } | ClerkApiError =
|
||||
await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data: json, status: response.status })
|
||||
throw new Error(
|
||||
(json as ClerkApiError).errors?.[0]?.message ||
|
||||
'Failed to list organization memberships from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const responseData = json as { data: ClerkOrganizationMembership[]; total_count: number }
|
||||
|
||||
const memberships = responseData.data.map((membership) => ({
|
||||
id: membership.id,
|
||||
role: membership.role,
|
||||
roleName: membership.role_name ?? null,
|
||||
permissions: membership.permissions ?? [],
|
||||
organizationId: membership.organization.id,
|
||||
userId: membership.public_user_data.user_id,
|
||||
firstName: membership.public_user_data.first_name ?? null,
|
||||
lastName: membership.public_user_data.last_name ?? null,
|
||||
imageUrl: membership.public_user_data.image_url ?? null,
|
||||
identifier: membership.public_user_data.identifier ?? null,
|
||||
username: membership.public_user_data.username ?? null,
|
||||
banned: membership.public_user_data.banned ?? false,
|
||||
publicMetadata: membership.public_metadata ?? {},
|
||||
createdAt: membership.created_at,
|
||||
updatedAt: membership.updated_at,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
memberships,
|
||||
totalCount: responseData.total_count ?? memberships.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
memberships: {
|
||||
type: 'array',
|
||||
description: 'Array of Clerk organization membership objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Membership ID' },
|
||||
role: { type: 'string', description: 'Member role' },
|
||||
roleName: { type: 'string', description: 'Human-readable role name', optional: true },
|
||||
permissions: {
|
||||
type: 'array',
|
||||
description: 'Permissions granted by the role',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
organizationId: { type: 'string', description: 'Organization ID' },
|
||||
userId: { type: 'string', description: 'Member user ID' },
|
||||
firstName: { type: 'string', description: 'Member first name', optional: true },
|
||||
lastName: { type: 'string', description: 'Member last name', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Member profile image URL', optional: true },
|
||||
identifier: {
|
||||
type: 'string',
|
||||
description: 'Member identifier (e.g., email)',
|
||||
optional: true,
|
||||
},
|
||||
username: { type: 'string', description: 'Member username', optional: true },
|
||||
banned: { type: 'boolean', description: 'Whether the member is banned' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of memberships' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkListOrganizationsParams,
|
||||
ClerkListOrganizationsResponse,
|
||||
ClerkOrganization,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkListOrganizations')
|
||||
|
||||
export const clerkListOrganizationsTool: ToolConfig<
|
||||
ClerkListOrganizationsParams,
|
||||
ClerkListOrganizationsResponse
|
||||
> = {
|
||||
id: 'clerk_list_organizations',
|
||||
name: 'List Organizations from Clerk',
|
||||
description: 'List all organizations in your Clerk application with optional filtering',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (e.g., 10, 50, 100; range: 1-500, default: 10)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination (e.g., 0, 10, 20)',
|
||||
},
|
||||
includeMembersCount: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Include member count for each organization',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search by organization ID, name, or slug (e.g., Acme Corp or acme-corp)',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort field (name, created_at, members_count) with +/- prefix',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
if (params.offset) queryParams.append('offset', params.offset.toString())
|
||||
if (params.includeMembersCount) queryParams.append('include_members_count', 'true')
|
||||
if (params.query) queryParams.append('query', params.query)
|
||||
if (params.orderBy) queryParams.append('order_by', params.orderBy)
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString
|
||||
? `https://api.clerk.com/v1/organizations?${queryString}`
|
||||
: 'https://api.clerk.com/v1/organizations'
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const json: { data: ClerkOrganization[]; total_count: number } | ClerkApiError =
|
||||
await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data: json, status: response.status })
|
||||
throw new Error(
|
||||
(json as ClerkApiError).errors?.[0]?.message || 'Failed to list organizations from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const responseData = json as { data: ClerkOrganization[]; total_count: number }
|
||||
|
||||
// Transform each organization to extract key fields
|
||||
const organizations = responseData.data.map((org) => ({
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
slug: org.slug ?? null,
|
||||
imageUrl: org.image_url ?? null,
|
||||
hasImage: org.has_image ?? false,
|
||||
membersCount: org.members_count ?? null,
|
||||
pendingInvitationsCount: org.pending_invitations_count ?? null,
|
||||
maxAllowedMemberships: org.max_allowed_memberships ?? 0,
|
||||
adminDeleteEnabled: org.admin_delete_enabled ?? false,
|
||||
createdBy: org.created_by ?? null,
|
||||
createdAt: org.created_at,
|
||||
updatedAt: org.updated_at,
|
||||
publicMetadata: org.public_metadata ?? {},
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
organizations,
|
||||
totalCount: responseData.total_count ?? organizations.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
organizations: {
|
||||
type: 'array',
|
||||
description: 'Array of Clerk organization objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Organization ID' },
|
||||
name: { type: 'string', description: 'Organization name' },
|
||||
slug: { type: 'string', description: 'Organization slug', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Organization image URL', optional: true },
|
||||
hasImage: { type: 'boolean', description: 'Whether organization has an image' },
|
||||
membersCount: { type: 'number', description: 'Number of members', optional: true },
|
||||
pendingInvitationsCount: {
|
||||
type: 'number',
|
||||
description: 'Number of pending invitations',
|
||||
optional: true,
|
||||
},
|
||||
maxAllowedMemberships: { type: 'number', description: 'Max allowed memberships' },
|
||||
adminDeleteEnabled: { type: 'boolean', description: 'Whether admin delete is enabled' },
|
||||
createdBy: { type: 'string', description: 'Creator user ID', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of organizations' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkListSessionsParams,
|
||||
ClerkListSessionsResponse,
|
||||
ClerkSession,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkListSessions')
|
||||
|
||||
export const clerkListSessionsTool: ToolConfig<ClerkListSessionsParams, ClerkListSessionsResponse> =
|
||||
{
|
||||
id: 'clerk_list_sessions',
|
||||
name: 'List Sessions from Clerk',
|
||||
description: 'List sessions for a user or client in your Clerk application',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'User ID to list sessions for (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC; required if clientId not provided)',
|
||||
},
|
||||
clientId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Client ID to list sessions for (required if userId not provided)',
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter by session status (abandoned, active, ended, expired, pending, removed, replaced, revoked)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (e.g., 10, 50, 100; range: 1-500, default: 10)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination (e.g., 0, 10, 20)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.userId) queryParams.append('user_id', params.userId)
|
||||
if (params.clientId) queryParams.append('client_id', params.clientId)
|
||||
if (params.status) queryParams.append('status', params.status)
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
if (params.offset) queryParams.append('offset', params.offset.toString())
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString
|
||||
? `https://api.clerk.com/v1/sessions?${queryString}`
|
||||
: 'https://api.clerk.com/v1/sessions'
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkSession[] | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to list sessions from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const totalCount = Number.parseInt(response.headers.get('x-total-count') || '0', 10)
|
||||
|
||||
const sessions = (data as ClerkSession[]).map((session) => ({
|
||||
id: session.id,
|
||||
userId: session.user_id,
|
||||
clientId: session.client_id,
|
||||
status: session.status,
|
||||
lastActiveAt: session.last_active_at ?? null,
|
||||
lastActiveOrganizationId: session.last_active_organization_id ?? null,
|
||||
expireAt: session.expire_at ?? null,
|
||||
abandonAt: session.abandon_at ?? null,
|
||||
createdAt: session.created_at,
|
||||
updatedAt: session.updated_at,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
sessions,
|
||||
totalCount: totalCount || sessions.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
sessions: {
|
||||
type: 'array',
|
||||
description: 'Array of Clerk session objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Session ID' },
|
||||
userId: { type: 'string', description: 'User ID' },
|
||||
clientId: { type: 'string', description: 'Client ID' },
|
||||
status: { type: 'string', description: 'Session status' },
|
||||
lastActiveAt: {
|
||||
type: 'number',
|
||||
description: 'Last activity timestamp',
|
||||
optional: true,
|
||||
},
|
||||
lastActiveOrganizationId: {
|
||||
type: 'string',
|
||||
description: 'Last active organization ID',
|
||||
optional: true,
|
||||
},
|
||||
expireAt: { type: 'number', description: 'Expiration timestamp', optional: true },
|
||||
abandonAt: { type: 'number', description: 'Abandon timestamp', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of sessions' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkEmailAddress,
|
||||
ClerkListUsersParams,
|
||||
ClerkListUsersResponse,
|
||||
ClerkPhoneNumber,
|
||||
ClerkUser,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkListUsers')
|
||||
|
||||
export const clerkListUsersTool: ToolConfig<ClerkListUsersParams, ClerkListUsersResponse> = {
|
||||
id: 'clerk_list_users',
|
||||
name: 'List Users from Clerk',
|
||||
description: 'List all users in your Clerk application with optional filtering and pagination',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results per page (e.g., 10, 50, 100; range: 1-500, default: 10)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip for pagination (e.g., 0, 10, 20)',
|
||||
},
|
||||
orderBy: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Sort field with optional +/- prefix for direction (default: -created_at)',
|
||||
},
|
||||
emailAddress: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter by email address (e.g., user@example.com or user1@example.com,user2@example.com)',
|
||||
},
|
||||
phoneNumber: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by phone number (comma-separated for multiple)',
|
||||
},
|
||||
externalId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by external ID (comma-separated for multiple)',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter by username (comma-separated for multiple)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Filter by user ID (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC or comma-separated for multiple)',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Search query to match across email, phone, username, and names (e.g., john or john@example.com)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
|
||||
if (params.limit) queryParams.append('limit', params.limit.toString())
|
||||
if (params.offset) queryParams.append('offset', params.offset.toString())
|
||||
if (params.orderBy) queryParams.append('order_by', params.orderBy)
|
||||
if (params.query) queryParams.append('query', params.query)
|
||||
|
||||
// Handle comma-separated array params
|
||||
if (params.emailAddress) {
|
||||
params.emailAddress.split(',').forEach((email) => {
|
||||
queryParams.append('email_address', email.trim())
|
||||
})
|
||||
}
|
||||
if (params.phoneNumber) {
|
||||
params.phoneNumber.split(',').forEach((phone) => {
|
||||
queryParams.append('phone_number', phone.trim())
|
||||
})
|
||||
}
|
||||
if (params.externalId) {
|
||||
params.externalId.split(',').forEach((id) => {
|
||||
queryParams.append('external_id', id.trim())
|
||||
})
|
||||
}
|
||||
if (params.username) {
|
||||
params.username.split(',').forEach((uname) => {
|
||||
queryParams.append('username', uname.trim())
|
||||
})
|
||||
}
|
||||
if (params.userId) {
|
||||
params.userId.split(',').forEach((id) => {
|
||||
queryParams.append('user_id', id.trim())
|
||||
})
|
||||
}
|
||||
|
||||
const queryString = queryParams.toString()
|
||||
return queryString
|
||||
? `https://api.clerk.com/v1/users?${queryString}`
|
||||
: 'https://api.clerk.com/v1/users'
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkUser[] | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to list users from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
// The response is an array of users, total_count is in the header
|
||||
const totalCount = Number.parseInt(response.headers.get('x-total-count') || '0', 10)
|
||||
|
||||
// Transform each user to extract key fields
|
||||
const users = (data as ClerkUser[]).map((user) => ({
|
||||
id: user.id,
|
||||
username: user.username ?? null,
|
||||
firstName: user.first_name ?? null,
|
||||
lastName: user.last_name ?? null,
|
||||
imageUrl: user.image_url ?? null,
|
||||
hasImage: user.has_image ?? false,
|
||||
primaryEmailAddressId: user.primary_email_address_id ?? null,
|
||||
primaryPhoneNumberId: user.primary_phone_number_id ?? null,
|
||||
emailAddresses: (user.email_addresses ?? []).map((email: ClerkEmailAddress) => ({
|
||||
id: email.id,
|
||||
emailAddress: email.email_address,
|
||||
})),
|
||||
phoneNumbers: (user.phone_numbers ?? []).map((phone: ClerkPhoneNumber) => ({
|
||||
id: phone.id,
|
||||
phoneNumber: phone.phone_number,
|
||||
})),
|
||||
externalId: user.external_id ?? null,
|
||||
passwordEnabled: user.password_enabled ?? false,
|
||||
twoFactorEnabled: user.two_factor_enabled ?? false,
|
||||
banned: user.banned ?? false,
|
||||
locked: user.locked ?? false,
|
||||
lastSignInAt: user.last_sign_in_at ?? null,
|
||||
lastActiveAt: user.last_active_at ?? null,
|
||||
createdAt: user.created_at,
|
||||
updatedAt: user.updated_at,
|
||||
publicMetadata: user.public_metadata ?? {},
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
users,
|
||||
totalCount: totalCount || users.length,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
users: {
|
||||
type: 'array',
|
||||
description: 'Array of Clerk user objects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username', optional: true },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
hasImage: { type: 'boolean', description: 'Whether user has a profile image' },
|
||||
primaryEmailAddressId: {
|
||||
type: 'string',
|
||||
description: 'Primary email address ID',
|
||||
optional: true,
|
||||
},
|
||||
primaryPhoneNumberId: {
|
||||
type: 'string',
|
||||
description: 'Primary phone number ID',
|
||||
optional: true,
|
||||
},
|
||||
emailAddresses: {
|
||||
type: 'array',
|
||||
description: 'User email addresses',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Email address ID' },
|
||||
emailAddress: { type: 'string', description: 'Email address' },
|
||||
},
|
||||
},
|
||||
},
|
||||
phoneNumbers: {
|
||||
type: 'array',
|
||||
description: 'User phone numbers',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Phone number ID' },
|
||||
phoneNumber: { type: 'string', description: 'Phone number' },
|
||||
},
|
||||
},
|
||||
},
|
||||
externalId: { type: 'string', description: 'External system ID', optional: true },
|
||||
passwordEnabled: { type: 'boolean', description: 'Whether password is enabled' },
|
||||
twoFactorEnabled: { type: 'boolean', description: 'Whether 2FA is enabled' },
|
||||
banned: { type: 'boolean', description: 'Whether user is banned' },
|
||||
locked: { type: 'boolean', description: 'Whether user is locked' },
|
||||
lastSignInAt: { type: 'number', description: 'Last sign-in timestamp', optional: true },
|
||||
lastActiveAt: { type: 'number', description: 'Last activity timestamp', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
},
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of users matching the query' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkLockUserParams,
|
||||
ClerkLockUserResponse,
|
||||
ClerkUser,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkLockUser')
|
||||
|
||||
export const clerkLockUserTool: ToolConfig<ClerkLockUserParams, ClerkLockUserResponse> = {
|
||||
id: 'clerk_lock_user',
|
||||
name: 'Lock User in Clerk',
|
||||
description: 'Lock a user account, blocking sign-in attempts',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the user to lock (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}/lock`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkUser | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to lock user in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const user = data as ClerkUser
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
username: user.username ?? null,
|
||||
firstName: user.first_name ?? null,
|
||||
lastName: user.last_name ?? null,
|
||||
banned: user.banned ?? false,
|
||||
locked: user.locked ?? false,
|
||||
lockoutExpiresInSeconds: user.lockout_expires_in_seconds ?? null,
|
||||
updatedAt: user.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username', optional: true },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
banned: { type: 'boolean', description: 'Whether the user is banned' },
|
||||
locked: { type: 'boolean', description: 'Whether the user is locked' },
|
||||
lockoutExpiresInSeconds: {
|
||||
type: 'number',
|
||||
description: 'Seconds until lockout expires',
|
||||
optional: true,
|
||||
},
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkOrganizationMembership,
|
||||
ClerkRemoveOrganizationMemberParams,
|
||||
ClerkRemoveOrganizationMemberResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkRemoveOrganizationMember')
|
||||
|
||||
export const clerkRemoveOrganizationMemberTool: ToolConfig<
|
||||
ClerkRemoveOrganizationMemberParams,
|
||||
ClerkRemoveOrganizationMemberResponse
|
||||
> = {
|
||||
id: 'clerk_remove_organization_member',
|
||||
name: 'Remove Organization Member from Clerk',
|
||||
description: 'Remove a member from a Clerk organization',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the organization (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the member to remove',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}/memberships/${params.userId?.trim()}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkOrganizationMembership | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message ||
|
||||
'Failed to remove organization member from Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const membership = data as ClerkOrganizationMembership
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: membership.id,
|
||||
role: membership.role,
|
||||
roleName: membership.role_name ?? null,
|
||||
permissions: membership.permissions ?? [],
|
||||
organizationId: membership.organization.id,
|
||||
userId: membership.public_user_data.user_id,
|
||||
firstName: membership.public_user_data.first_name ?? null,
|
||||
lastName: membership.public_user_data.last_name ?? null,
|
||||
imageUrl: membership.public_user_data.image_url ?? null,
|
||||
identifier: membership.public_user_data.identifier ?? null,
|
||||
username: membership.public_user_data.username ?? null,
|
||||
banned: membership.public_user_data.banned ?? false,
|
||||
publicMetadata: membership.public_metadata ?? {},
|
||||
createdAt: membership.created_at,
|
||||
updatedAt: membership.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Membership ID' },
|
||||
role: { type: 'string', description: 'Member role' },
|
||||
roleName: { type: 'string', description: 'Human-readable role name', optional: true },
|
||||
permissions: {
|
||||
type: 'array',
|
||||
description: 'Permissions granted by the role',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
organizationId: { type: 'string', description: 'Organization ID' },
|
||||
userId: { type: 'string', description: 'Member user ID' },
|
||||
firstName: { type: 'string', description: 'Member first name', optional: true },
|
||||
lastName: { type: 'string', description: 'Member last name', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Member profile image URL', optional: true },
|
||||
identifier: { type: 'string', description: 'Member identifier (e.g., email)', optional: true },
|
||||
username: { type: 'string', description: 'Member username', optional: true },
|
||||
banned: { type: 'boolean', description: 'Whether the member is banned' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkActorToken,
|
||||
ClerkApiError,
|
||||
ClerkRevokeActorTokenParams,
|
||||
ClerkRevokeActorTokenResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkRevokeActorToken')
|
||||
|
||||
export const clerkRevokeActorTokenTool: ToolConfig<
|
||||
ClerkRevokeActorTokenParams,
|
||||
ClerkRevokeActorTokenResponse
|
||||
> = {
|
||||
id: 'clerk_revoke_actor_token',
|
||||
name: 'Revoke Actor Token in Clerk',
|
||||
description: 'Revoke an actor token before it is used or expires',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
actorTokenId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the actor token to revoke',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/actor_tokens/${params.actorTokenId?.trim()}/revoke`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkActorToken | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to revoke actor token in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const actorToken = data as ClerkActorToken
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: actorToken.id,
|
||||
status: actorToken.status,
|
||||
userId: actorToken.user_id,
|
||||
actor: actorToken.actor ?? {},
|
||||
token: actorToken.token ?? null,
|
||||
url: actorToken.url ?? null,
|
||||
createdAt: actorToken.created_at,
|
||||
updatedAt: actorToken.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Actor token ID' },
|
||||
status: { type: 'string', description: 'Actor token status (should be revoked)' },
|
||||
userId: { type: 'string', description: 'ID of the impersonated user' },
|
||||
actor: { type: 'json', description: 'Actor object identifying who is impersonating' },
|
||||
token: { type: 'string', description: 'Signed actor token (JWT)', optional: true },
|
||||
url: { type: 'string', description: 'Sign-in URL for the actor token', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkRevokeSessionParams,
|
||||
ClerkRevokeSessionResponse,
|
||||
ClerkSession,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkRevokeSession')
|
||||
|
||||
export const clerkRevokeSessionTool: ToolConfig<
|
||||
ClerkRevokeSessionParams,
|
||||
ClerkRevokeSessionResponse
|
||||
> = {
|
||||
id: 'clerk_revoke_session',
|
||||
name: 'Revoke Session in Clerk',
|
||||
description: 'Revoke a session to immediately invalidate it',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
sessionId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the session to revoke (e.g., sess_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/sessions/${params.sessionId?.trim()}/revoke`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkSession | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to revoke session in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const session = data as ClerkSession
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: session.id,
|
||||
userId: session.user_id,
|
||||
clientId: session.client_id,
|
||||
status: session.status,
|
||||
lastActiveAt: session.last_active_at ?? null,
|
||||
lastActiveOrganizationId: session.last_active_organization_id ?? null,
|
||||
expireAt: session.expire_at ?? null,
|
||||
abandonAt: session.abandon_at ?? null,
|
||||
createdAt: session.created_at,
|
||||
updatedAt: session.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Session ID' },
|
||||
userId: { type: 'string', description: 'User ID' },
|
||||
clientId: { type: 'string', description: 'Client ID' },
|
||||
status: { type: 'string', description: 'Session status (should be revoked)' },
|
||||
lastActiveAt: { type: 'number', description: 'Last activity timestamp', optional: true },
|
||||
lastActiveOrganizationId: {
|
||||
type: 'string',
|
||||
description: 'Last active organization ID',
|
||||
optional: true,
|
||||
},
|
||||
expireAt: { type: 'number', description: 'Expiration timestamp', optional: true },
|
||||
abandonAt: { type: 'number', description: 'Abandon timestamp', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,89 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkUnbanUserParams,
|
||||
ClerkUnbanUserResponse,
|
||||
ClerkUser,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkUnbanUser')
|
||||
|
||||
export const clerkUnbanUserTool: ToolConfig<ClerkUnbanUserParams, ClerkUnbanUserResponse> = {
|
||||
id: 'clerk_unban_user',
|
||||
name: 'Unban User in Clerk',
|
||||
description: 'Remove a ban from a user, allowing them to sign in again',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the user to unban (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}/unban`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkUser | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to unban user in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const user = data as ClerkUser
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
username: user.username ?? null,
|
||||
firstName: user.first_name ?? null,
|
||||
lastName: user.last_name ?? null,
|
||||
banned: user.banned ?? false,
|
||||
locked: user.locked ?? false,
|
||||
lockoutExpiresInSeconds: user.lockout_expires_in_seconds ?? null,
|
||||
updatedAt: user.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username', optional: true },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
banned: { type: 'boolean', description: 'Whether the user is banned' },
|
||||
locked: { type: 'boolean', description: 'Whether the user is locked' },
|
||||
lockoutExpiresInSeconds: {
|
||||
type: 'number',
|
||||
description: 'Seconds until lockout expires',
|
||||
optional: true,
|
||||
},
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkUnlockUserParams,
|
||||
ClerkUnlockUserResponse,
|
||||
ClerkUser,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkUnlockUser')
|
||||
|
||||
export const clerkUnlockUserTool: ToolConfig<ClerkUnlockUserParams, ClerkUnlockUserResponse> = {
|
||||
id: 'clerk_unlock_user',
|
||||
name: 'Unlock User in Clerk',
|
||||
description: 'Unlock a previously locked user account',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the user to unlock (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}/unlock`,
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkUser | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to unlock user in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const user = data as ClerkUser
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
username: user.username ?? null,
|
||||
firstName: user.first_name ?? null,
|
||||
lastName: user.last_name ?? null,
|
||||
banned: user.banned ?? false,
|
||||
locked: user.locked ?? false,
|
||||
lockoutExpiresInSeconds: user.lockout_expires_in_seconds ?? null,
|
||||
updatedAt: user.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'User ID' },
|
||||
username: { type: 'string', description: 'Username', optional: true },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
banned: { type: 'boolean', description: 'Whether the user is banned' },
|
||||
locked: { type: 'boolean', description: 'Whether the user is locked' },
|
||||
lockoutExpiresInSeconds: {
|
||||
type: 'number',
|
||||
description: 'Seconds until lockout expires',
|
||||
optional: true,
|
||||
},
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkOrganization,
|
||||
ClerkUpdateOrganizationParams,
|
||||
ClerkUpdateOrganizationResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkUpdateOrganization')
|
||||
|
||||
export const clerkUpdateOrganizationTool: ToolConfig<
|
||||
ClerkUpdateOrganizationParams,
|
||||
ClerkUpdateOrganizationResponse
|
||||
> = {
|
||||
id: 'clerk_update_organization',
|
||||
name: 'Update Organization in Clerk',
|
||||
description: 'Update an existing organization in your Clerk application',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the organization to update (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Name of the organization',
|
||||
},
|
||||
slug: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Slug identifier for the organization',
|
||||
},
|
||||
maxAllowedMemberships: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum member capacity (0 for unlimited)',
|
||||
},
|
||||
adminDeleteEnabled: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether admins can delete the organization',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {}
|
||||
|
||||
if (params.name !== undefined) body.name = params.name
|
||||
if (params.slug !== undefined) body.slug = params.slug
|
||||
if (params.maxAllowedMemberships !== undefined)
|
||||
body.max_allowed_memberships = params.maxAllowedMemberships
|
||||
if (params.adminDeleteEnabled !== undefined)
|
||||
body.admin_delete_enabled = params.adminDeleteEnabled
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkOrganization | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to update organization in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const org = data as ClerkOrganization
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: org.id,
|
||||
name: org.name,
|
||||
slug: org.slug ?? null,
|
||||
imageUrl: org.image_url ?? null,
|
||||
hasImage: org.has_image ?? false,
|
||||
membersCount: org.members_count ?? null,
|
||||
pendingInvitationsCount: org.pending_invitations_count ?? null,
|
||||
maxAllowedMemberships: org.max_allowed_memberships ?? 0,
|
||||
adminDeleteEnabled: org.admin_delete_enabled ?? false,
|
||||
createdBy: org.created_by ?? null,
|
||||
createdAt: org.created_at,
|
||||
updatedAt: org.updated_at,
|
||||
publicMetadata: org.public_metadata ?? {},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Organization ID' },
|
||||
name: { type: 'string', description: 'Organization name' },
|
||||
slug: { type: 'string', description: 'Organization slug', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Organization image URL', optional: true },
|
||||
hasImage: { type: 'boolean', description: 'Whether organization has an image' },
|
||||
membersCount: { type: 'number', description: 'Number of members', optional: true },
|
||||
pendingInvitationsCount: {
|
||||
type: 'number',
|
||||
description: 'Number of pending invitations',
|
||||
optional: true,
|
||||
},
|
||||
maxAllowedMemberships: { type: 'number', description: 'Max allowed memberships' },
|
||||
adminDeleteEnabled: { type: 'boolean', description: 'Whether admin delete is enabled' },
|
||||
createdBy: { type: 'string', description: 'Creator user ID', optional: true },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkOrganizationMembership,
|
||||
ClerkUpdateOrganizationMembershipParams,
|
||||
ClerkUpdateOrganizationMembershipResponse,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkUpdateOrganizationMembership')
|
||||
|
||||
export const clerkUpdateOrganizationMembershipTool: ToolConfig<
|
||||
ClerkUpdateOrganizationMembershipParams,
|
||||
ClerkUpdateOrganizationMembershipResponse
|
||||
> = {
|
||||
id: 'clerk_update_organization_membership',
|
||||
name: 'Update Organization Membership in Clerk',
|
||||
description: "Change a member's role within a Clerk organization",
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
organizationId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the organization (e.g., org_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of the member whose role is being changed',
|
||||
},
|
||||
role: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New role to assign, e.g. org:admin or org:member',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://api.clerk.com/v1/organizations/${params.organizationId?.trim()}/memberships/${params.userId?.trim()}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => ({
|
||||
role: params.role,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkOrganizationMembership | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message ||
|
||||
'Failed to update organization membership in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const membership = data as ClerkOrganizationMembership
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: membership.id,
|
||||
role: membership.role,
|
||||
roleName: membership.role_name ?? null,
|
||||
permissions: membership.permissions ?? [],
|
||||
organizationId: membership.organization.id,
|
||||
userId: membership.public_user_data.user_id,
|
||||
firstName: membership.public_user_data.first_name ?? null,
|
||||
lastName: membership.public_user_data.last_name ?? null,
|
||||
imageUrl: membership.public_user_data.image_url ?? null,
|
||||
identifier: membership.public_user_data.identifier ?? null,
|
||||
username: membership.public_user_data.username ?? null,
|
||||
banned: membership.public_user_data.banned ?? false,
|
||||
publicMetadata: membership.public_metadata ?? {},
|
||||
createdAt: membership.created_at,
|
||||
updatedAt: membership.updated_at,
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Membership ID' },
|
||||
role: { type: 'string', description: 'Member role' },
|
||||
roleName: { type: 'string', description: 'Human-readable role name', optional: true },
|
||||
permissions: {
|
||||
type: 'array',
|
||||
description: 'Permissions granted by the role',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
organizationId: { type: 'string', description: 'Organization ID' },
|
||||
userId: { type: 'string', description: 'Member user ID' },
|
||||
firstName: { type: 'string', description: 'Member first name', optional: true },
|
||||
lastName: { type: 'string', description: 'Member last name', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Member profile image URL', optional: true },
|
||||
identifier: { type: 'string', description: 'Member identifier (e.g., email)', optional: true },
|
||||
username: { type: 'string', description: 'Member username', optional: true },
|
||||
banned: { type: 'boolean', description: 'Whether the member is banned' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import type {
|
||||
ClerkApiError,
|
||||
ClerkEmailAddress,
|
||||
ClerkPhoneNumber,
|
||||
ClerkUpdateUserParams,
|
||||
ClerkUpdateUserResponse,
|
||||
ClerkUser,
|
||||
} from '@/tools/clerk/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('ClerkUpdateUser')
|
||||
|
||||
export const clerkUpdateUserTool: ToolConfig<ClerkUpdateUserParams, ClerkUpdateUserResponse> = {
|
||||
id: 'clerk_update_user',
|
||||
name: 'Update User in Clerk',
|
||||
description: 'Update an existing user in your Clerk application',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
secretKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The Clerk Secret Key for API authentication',
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the user to update (e.g., user_2NNEqL2nrIRdJ194ndJqAHwEfxC)',
|
||||
},
|
||||
firstName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'First name of the user',
|
||||
},
|
||||
lastName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Last name of the user',
|
||||
},
|
||||
username: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Username (must be unique)',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New password (minimum 8 characters)',
|
||||
},
|
||||
externalId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'External system identifier',
|
||||
},
|
||||
primaryEmailAddressId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of verified email to set as primary',
|
||||
},
|
||||
primaryPhoneNumberId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'ID of verified phone to set as primary',
|
||||
},
|
||||
publicMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Public metadata (JSON object)',
|
||||
},
|
||||
privateMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Private metadata (JSON object)',
|
||||
},
|
||||
unsafeMetadata: {
|
||||
type: 'json',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Unsafe metadata (JSON object)',
|
||||
},
|
||||
skipPasswordChecks: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Skip password validation checks',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => `https://api.clerk.com/v1/users/${params.userId?.trim()}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
if (!params.secretKey) {
|
||||
throw new Error('Clerk Secret Key is required')
|
||||
}
|
||||
return {
|
||||
Authorization: `Bearer ${params.secretKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {}
|
||||
|
||||
if (params.firstName !== undefined) body.first_name = params.firstName?.trim()
|
||||
if (params.lastName !== undefined) body.last_name = params.lastName?.trim()
|
||||
if (params.username !== undefined) body.username = params.username?.trim()
|
||||
if (params.password !== undefined) body.password = params.password
|
||||
if (params.externalId !== undefined) body.external_id = params.externalId?.trim()
|
||||
if (params.primaryEmailAddressId !== undefined)
|
||||
body.primary_email_address_id = params.primaryEmailAddressId?.trim()
|
||||
if (params.primaryPhoneNumberId !== undefined)
|
||||
body.primary_phone_number_id = params.primaryPhoneNumberId?.trim()
|
||||
if (params.publicMetadata !== undefined) body.public_metadata = params.publicMetadata
|
||||
if (params.privateMetadata !== undefined) body.private_metadata = params.privateMetadata
|
||||
if (params.unsafeMetadata !== undefined) body.unsafe_metadata = params.unsafeMetadata
|
||||
if (params.skipPasswordChecks !== undefined)
|
||||
body.skip_password_checks = params.skipPasswordChecks
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data: ClerkUser | ClerkApiError = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Clerk API request failed', { data, status: response.status })
|
||||
throw new Error(
|
||||
(data as ClerkApiError).errors?.[0]?.message || 'Failed to update user in Clerk'
|
||||
)
|
||||
}
|
||||
|
||||
const user = data as ClerkUser
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
id: user.id,
|
||||
username: user.username ?? null,
|
||||
firstName: user.first_name ?? null,
|
||||
lastName: user.last_name ?? null,
|
||||
imageUrl: user.image_url ?? null,
|
||||
primaryEmailAddressId: user.primary_email_address_id ?? null,
|
||||
primaryPhoneNumberId: user.primary_phone_number_id ?? null,
|
||||
emailAddresses: (user.email_addresses ?? []).map((email: ClerkEmailAddress) => ({
|
||||
id: email.id,
|
||||
emailAddress: email.email_address,
|
||||
verified: email.verification?.status === 'verified',
|
||||
})),
|
||||
phoneNumbers: (user.phone_numbers ?? []).map((phone: ClerkPhoneNumber) => ({
|
||||
id: phone.id,
|
||||
phoneNumber: phone.phone_number,
|
||||
verified: phone.verification?.status === 'verified',
|
||||
})),
|
||||
externalId: user.external_id ?? null,
|
||||
banned: user.banned ?? false,
|
||||
locked: user.locked ?? false,
|
||||
createdAt: user.created_at,
|
||||
updatedAt: user.updated_at,
|
||||
publicMetadata: user.public_metadata ?? {},
|
||||
success: true,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
id: { type: 'string', description: 'Updated user ID' },
|
||||
username: { type: 'string', description: 'Username', optional: true },
|
||||
firstName: { type: 'string', description: 'First name', optional: true },
|
||||
lastName: { type: 'string', description: 'Last name', optional: true },
|
||||
imageUrl: { type: 'string', description: 'Profile image URL', optional: true },
|
||||
primaryEmailAddressId: {
|
||||
type: 'string',
|
||||
description: 'Primary email address ID',
|
||||
optional: true,
|
||||
},
|
||||
primaryPhoneNumberId: {
|
||||
type: 'string',
|
||||
description: 'Primary phone number ID',
|
||||
optional: true,
|
||||
},
|
||||
emailAddresses: {
|
||||
type: 'array',
|
||||
description: 'User email addresses',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Email address ID' },
|
||||
emailAddress: { type: 'string', description: 'Email address' },
|
||||
verified: { type: 'boolean', description: 'Whether email is verified' },
|
||||
},
|
||||
},
|
||||
},
|
||||
phoneNumbers: {
|
||||
type: 'array',
|
||||
description: 'User phone numbers',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', description: 'Phone number ID' },
|
||||
phoneNumber: { type: 'string', description: 'Phone number' },
|
||||
verified: { type: 'boolean', description: 'Whether phone is verified' },
|
||||
},
|
||||
},
|
||||
},
|
||||
externalId: { type: 'string', description: 'External system ID', optional: true },
|
||||
banned: { type: 'boolean', description: 'Whether user is banned' },
|
||||
locked: { type: 'boolean', description: 'Whether user is locked' },
|
||||
createdAt: { type: 'number', description: 'Creation timestamp' },
|
||||
updatedAt: { type: 'number', description: 'Last update timestamp' },
|
||||
publicMetadata: { type: 'json', description: 'Public metadata' },
|
||||
success: { type: 'boolean', description: 'Operation success status' },
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user