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,126 @@
|
||||
import type {
|
||||
LaunchDarklyCreateFlagParams,
|
||||
LaunchDarklyCreateFlagResponse,
|
||||
} from '@/tools/launchdarkly/types'
|
||||
import { FLAG_OUTPUT_PROPERTIES } from '@/tools/launchdarkly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const launchDarklyCreateFlagTool: ToolConfig<
|
||||
LaunchDarklyCreateFlagParams,
|
||||
LaunchDarklyCreateFlagResponse
|
||||
> = {
|
||||
id: 'launchdarkly_create_flag',
|
||||
name: 'LaunchDarkly Create Flag',
|
||||
description: 'Create a new feature flag in a LaunchDarkly project.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'LaunchDarkly API key',
|
||||
},
|
||||
projectKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The project key to create the flag in',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Human-readable name for the feature flag',
|
||||
},
|
||||
key: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Unique key for the feature flag (used in code)',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Description of the feature flag',
|
||||
},
|
||||
tags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated list of tags',
|
||||
},
|
||||
temporary: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether the flag is temporary (default true)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://app.launchdarkly.com/api/v2/flags/${encodeURIComponent(params.projectKey.trim())}`,
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Authorization: params.apiKey.trim(),
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: (params) => {
|
||||
const body: Record<string, unknown> = {
|
||||
name: params.name,
|
||||
key: params.key,
|
||||
}
|
||||
if (params.description) body.description = params.description
|
||||
if (params.tags) body.tags = params.tags.split(',').map((t) => t.trim())
|
||||
if (params.temporary !== undefined) body.temporary = params.temporary
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }))
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
key: '',
|
||||
name: '',
|
||||
kind: '',
|
||||
description: null,
|
||||
temporary: false,
|
||||
archived: false,
|
||||
deprecated: false,
|
||||
creationDate: 0,
|
||||
tags: [],
|
||||
variations: [],
|
||||
maintainerId: null,
|
||||
maintainerEmail: null,
|
||||
},
|
||||
error: error.message,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
key: data.key ?? null,
|
||||
name: data.name ?? null,
|
||||
kind: data.kind ?? null,
|
||||
description: data.description ?? null,
|
||||
temporary: data.temporary ?? false,
|
||||
archived: data.archived ?? false,
|
||||
deprecated: data.deprecated ?? false,
|
||||
creationDate: data.creationDate ?? null,
|
||||
tags: data.tags ?? [],
|
||||
variations: data.variations ?? [],
|
||||
maintainerId: data.maintainerId ?? null,
|
||||
maintainerEmail: data._maintainer?.email ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: FLAG_OUTPUT_PROPERTIES,
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import type {
|
||||
LaunchDarklyDeleteFlagParams,
|
||||
LaunchDarklyDeleteFlagResponse,
|
||||
} from '@/tools/launchdarkly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const launchDarklyDeleteFlagTool: ToolConfig<
|
||||
LaunchDarklyDeleteFlagParams,
|
||||
LaunchDarklyDeleteFlagResponse
|
||||
> = {
|
||||
id: 'launchdarkly_delete_flag',
|
||||
name: 'LaunchDarkly Delete Flag',
|
||||
description: 'Delete a feature flag from a LaunchDarkly project.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'LaunchDarkly API key',
|
||||
},
|
||||
projectKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The project key',
|
||||
},
|
||||
flagKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The feature flag key to delete',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://app.launchdarkly.com/api/v2/flags/${encodeURIComponent(params.projectKey.trim())}/${encodeURIComponent(params.flagKey.trim())}`,
|
||||
method: 'DELETE',
|
||||
headers: (params) => ({
|
||||
Authorization: params.apiKey.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }))
|
||||
return { success: false, output: { deleted: false }, error: error.message }
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: { deleted: true },
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
deleted: { type: 'boolean', description: 'Whether the flag was successfully deleted' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
import type {
|
||||
LaunchDarklyGetAuditLogParams,
|
||||
LaunchDarklyGetAuditLogResponse,
|
||||
} from '@/tools/launchdarkly/types'
|
||||
import { AUDIT_LOG_ENTRY_OUTPUT_PROPERTIES } from '@/tools/launchdarkly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const launchDarklyGetAuditLogTool: ToolConfig<
|
||||
LaunchDarklyGetAuditLogParams,
|
||||
LaunchDarklyGetAuditLogResponse
|
||||
> = {
|
||||
id: 'launchdarkly_get_audit_log',
|
||||
name: 'LaunchDarkly Get Audit Log',
|
||||
description: 'List audit log entries from your LaunchDarkly account.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'LaunchDarkly API key',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of entries to return (default 10, max 20)',
|
||||
},
|
||||
spec: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Resource specifier filter (e.g. "proj/*:env/*:flag/*" for all flag changes, or "proj/default:env/production:flag/my-flag" for one flag in one environment)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.limit) queryParams.set('limit', String(params.limit))
|
||||
if (params.spec) queryParams.set('spec', params.spec)
|
||||
const qs = queryParams.toString()
|
||||
return `https://app.launchdarkly.com/api/v2/auditlog${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: params.apiKey.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }))
|
||||
return { success: false, output: { entries: [], totalCount: 0 }, error: error.message }
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const entries = (data.items ?? []).map((item: Record<string, unknown>) => {
|
||||
const member = item.member as Record<string, unknown> | undefined
|
||||
const target = item.target as Record<string, unknown> | undefined
|
||||
return {
|
||||
id: (item._id as string) ?? null,
|
||||
date: item.date ?? null,
|
||||
kind: item.kind ?? null,
|
||||
name: item.name ?? null,
|
||||
description: item.description ?? null,
|
||||
shortDescription: item.shortDescription ?? null,
|
||||
memberEmail: member?.email ?? null,
|
||||
targetName: target?.name ?? null,
|
||||
targetKind: (target?.resources as string[] | undefined)?.[0] ?? null,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
entries,
|
||||
totalCount: (data.totalCount as number) ?? entries.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
entries: {
|
||||
type: 'array',
|
||||
description: 'List of audit log entries',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: AUDIT_LOG_ENTRY_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of audit log entries' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import type {
|
||||
LaunchDarklyGetFlagParams,
|
||||
LaunchDarklyGetFlagResponse,
|
||||
} from '@/tools/launchdarkly/types'
|
||||
import { FLAG_OUTPUT_PROPERTIES } from '@/tools/launchdarkly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const launchDarklyGetFlagTool: ToolConfig<
|
||||
LaunchDarklyGetFlagParams,
|
||||
LaunchDarklyGetFlagResponse
|
||||
> = {
|
||||
id: 'launchdarkly_get_flag',
|
||||
name: 'LaunchDarkly Get Flag',
|
||||
description: 'Get a single feature flag by key from a LaunchDarkly project.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'LaunchDarkly API key',
|
||||
},
|
||||
projectKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The project key',
|
||||
},
|
||||
flagKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The feature flag key',
|
||||
},
|
||||
environmentKey: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter flag configuration to a specific environment',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.environmentKey) queryParams.set('env', params.environmentKey)
|
||||
const qs = queryParams.toString()
|
||||
return `https://app.launchdarkly.com/api/v2/flags/${encodeURIComponent(params.projectKey.trim())}/${encodeURIComponent(params.flagKey.trim())}${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: params.apiKey.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: LaunchDarklyGetFlagParams) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }))
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
key: '',
|
||||
name: '',
|
||||
kind: '',
|
||||
description: null,
|
||||
temporary: false,
|
||||
archived: false,
|
||||
deprecated: false,
|
||||
creationDate: 0,
|
||||
tags: [],
|
||||
variations: [],
|
||||
maintainerId: null,
|
||||
maintainerEmail: null,
|
||||
on: null,
|
||||
},
|
||||
error: error.message,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
const environments = data.environments as Record<string, Record<string, unknown>> | undefined
|
||||
let on: boolean | null = null
|
||||
if (environments) {
|
||||
const envKey = params?.environmentKey?.trim()
|
||||
if (envKey && environments[envKey]) {
|
||||
on = (environments[envKey].on as boolean) ?? null
|
||||
} else if (!envKey) {
|
||||
const envKeys = Object.keys(environments)
|
||||
if (envKeys.length === 1) {
|
||||
on = (environments[envKeys[0]].on as boolean) ?? null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
key: data.key ?? null,
|
||||
name: data.name ?? null,
|
||||
kind: data.kind ?? null,
|
||||
description: data.description ?? null,
|
||||
temporary: data.temporary ?? false,
|
||||
archived: data.archived ?? false,
|
||||
deprecated: data.deprecated ?? false,
|
||||
creationDate: data.creationDate ?? null,
|
||||
tags: data.tags ?? [],
|
||||
variations: data.variations ?? [],
|
||||
maintainerId: data.maintainerId ?? null,
|
||||
maintainerEmail: data._maintainer?.email ?? null,
|
||||
on,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...FLAG_OUTPUT_PROPERTIES,
|
||||
on: {
|
||||
type: 'boolean',
|
||||
description:
|
||||
'Whether the flag is on in the requested environment (null when the flag spans multiple environments and no environment key was provided)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import type {
|
||||
LaunchDarklyGetFlagStatusParams,
|
||||
LaunchDarklyGetFlagStatusResponse,
|
||||
} from '@/tools/launchdarkly/types'
|
||||
import { FLAG_STATUS_OUTPUT_PROPERTIES } from '@/tools/launchdarkly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const launchDarklyGetFlagStatusTool: ToolConfig<
|
||||
LaunchDarklyGetFlagStatusParams,
|
||||
LaunchDarklyGetFlagStatusResponse
|
||||
> = {
|
||||
id: 'launchdarkly_get_flag_status',
|
||||
name: 'LaunchDarkly Get Flag Status',
|
||||
description:
|
||||
'Get the status of a feature flag across environments (active, inactive, launched, etc.).',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'LaunchDarkly API key',
|
||||
},
|
||||
projectKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The project key',
|
||||
},
|
||||
flagKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The feature flag key',
|
||||
},
|
||||
environmentKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The environment key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://app.launchdarkly.com/api/v2/flag-statuses/${encodeURIComponent(params.projectKey.trim())}/${encodeURIComponent(params.environmentKey.trim())}/${encodeURIComponent(params.flagKey.trim())}`,
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: params.apiKey.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }))
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
name: '',
|
||||
lastRequested: null,
|
||||
defaultVal: null,
|
||||
},
|
||||
error: error.message,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
name: data.name ?? null,
|
||||
lastRequested: data.lastRequested ?? null,
|
||||
defaultVal: data.default ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: FLAG_STATUS_OUTPUT_PROPERTIES,
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export { launchDarklyCreateFlagTool } from '@/tools/launchdarkly/create_flag'
|
||||
export { launchDarklyDeleteFlagTool } from '@/tools/launchdarkly/delete_flag'
|
||||
export { launchDarklyGetAuditLogTool } from '@/tools/launchdarkly/get_audit_log'
|
||||
export { launchDarklyGetFlagTool } from '@/tools/launchdarkly/get_flag'
|
||||
export { launchDarklyGetFlagStatusTool } from '@/tools/launchdarkly/get_flag_status'
|
||||
export { launchDarklyListEnvironmentsTool } from '@/tools/launchdarkly/list_environments'
|
||||
export { launchDarklyListFlagsTool } from '@/tools/launchdarkly/list_flags'
|
||||
export { launchDarklyListMembersTool } from '@/tools/launchdarkly/list_members'
|
||||
export { launchDarklyListProjectsTool } from '@/tools/launchdarkly/list_projects'
|
||||
export { launchDarklyListSegmentsTool } from '@/tools/launchdarkly/list_segments'
|
||||
export { launchDarklyToggleFlagTool } from '@/tools/launchdarkly/toggle_flag'
|
||||
export { launchDarklyUpdateFlagTool } from '@/tools/launchdarkly/update_flag'
|
||||
@@ -0,0 +1,92 @@
|
||||
import type {
|
||||
LaunchDarklyListEnvironmentsParams,
|
||||
LaunchDarklyListEnvironmentsResponse,
|
||||
} from '@/tools/launchdarkly/types'
|
||||
import { ENVIRONMENT_OUTPUT_PROPERTIES } from '@/tools/launchdarkly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const launchDarklyListEnvironmentsTool: ToolConfig<
|
||||
LaunchDarklyListEnvironmentsParams,
|
||||
LaunchDarklyListEnvironmentsResponse
|
||||
> = {
|
||||
id: 'launchdarkly_list_environments',
|
||||
name: 'LaunchDarkly List Environments',
|
||||
description: 'List environments in a LaunchDarkly project.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'LaunchDarkly API key',
|
||||
},
|
||||
projectKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The project key to list environments for',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of environments to return (default 20)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.limit) queryParams.set('limit', String(params.limit))
|
||||
const qs = queryParams.toString()
|
||||
return `https://app.launchdarkly.com/api/v2/projects/${encodeURIComponent(params.projectKey.trim())}/environments${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: params.apiKey.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }))
|
||||
return {
|
||||
success: false,
|
||||
output: { environments: [], totalCount: 0 },
|
||||
error: error.message,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const environments = (data.items ?? []).map((item: Record<string, unknown>) => ({
|
||||
id: (item._id as string) ?? null,
|
||||
key: item.key ?? null,
|
||||
name: item.name ?? null,
|
||||
color: item.color ?? null,
|
||||
apiKey: item.apiKey ?? null,
|
||||
mobileKey: item.mobileKey ?? null,
|
||||
tags: (item.tags as string[]) ?? [],
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
environments,
|
||||
totalCount: (data.totalCount as number) ?? environments.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
environments: {
|
||||
type: 'array',
|
||||
description: 'List of environments',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: ENVIRONMENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of environments' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import type {
|
||||
LaunchDarklyListFlagsParams,
|
||||
LaunchDarklyListFlagsResponse,
|
||||
} from '@/tools/launchdarkly/types'
|
||||
import { FLAG_OUTPUT_PROPERTIES } from '@/tools/launchdarkly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const launchDarklyListFlagsTool: ToolConfig<
|
||||
LaunchDarklyListFlagsParams,
|
||||
LaunchDarklyListFlagsResponse
|
||||
> = {
|
||||
id: 'launchdarkly_list_flags',
|
||||
name: 'LaunchDarkly List Flags',
|
||||
description: 'List feature flags in a LaunchDarkly project.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'LaunchDarkly API key',
|
||||
},
|
||||
projectKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The project key to list flags for',
|
||||
},
|
||||
environmentKey: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter flag configurations to a specific environment',
|
||||
},
|
||||
tag: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Filter flags by tag name',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of flags to return (default 20)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.environmentKey) queryParams.set('env', params.environmentKey)
|
||||
if (params.tag) queryParams.set('tag', params.tag)
|
||||
if (params.limit) queryParams.set('limit', String(params.limit))
|
||||
const qs = queryParams.toString()
|
||||
return `https://app.launchdarkly.com/api/v2/flags/${encodeURIComponent(params.projectKey.trim())}${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: params.apiKey.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }))
|
||||
return { success: false, output: { flags: [], totalCount: 0 }, error: error.message }
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const flags = (data.items ?? []).map((item: Record<string, unknown>) => ({
|
||||
key: item.key ?? null,
|
||||
name: item.name ?? null,
|
||||
kind: item.kind ?? null,
|
||||
description: item.description ?? null,
|
||||
temporary: item.temporary ?? false,
|
||||
archived: item.archived ?? false,
|
||||
deprecated: item.deprecated ?? false,
|
||||
creationDate: item.creationDate ?? null,
|
||||
tags: (item.tags as string[]) ?? [],
|
||||
variations: (item.variations as Array<Record<string, unknown>>) ?? [],
|
||||
maintainerId: item.maintainerId ?? null,
|
||||
maintainerEmail: (item._maintainer as Record<string, unknown> | undefined)?.email ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
flags,
|
||||
totalCount: (data.totalCount as number) ?? flags.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
flags: {
|
||||
type: 'array',
|
||||
description: 'List of feature flags',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: FLAG_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of flags' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import type {
|
||||
LaunchDarklyListMembersParams,
|
||||
LaunchDarklyListMembersResponse,
|
||||
} from '@/tools/launchdarkly/types'
|
||||
import { MEMBER_OUTPUT_PROPERTIES } from '@/tools/launchdarkly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const launchDarklyListMembersTool: ToolConfig<
|
||||
LaunchDarklyListMembersParams,
|
||||
LaunchDarklyListMembersResponse
|
||||
> = {
|
||||
id: 'launchdarkly_list_members',
|
||||
name: 'LaunchDarkly List Members',
|
||||
description: 'List account members in your LaunchDarkly organization.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'LaunchDarkly API key',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of members to return (default 20)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.limit) queryParams.set('limit', String(params.limit))
|
||||
const qs = queryParams.toString()
|
||||
return `https://app.launchdarkly.com/api/v2/members${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: params.apiKey.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }))
|
||||
return { success: false, output: { members: [], totalCount: 0 }, error: error.message }
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const members = (data.items ?? []).map((item: Record<string, unknown>) => ({
|
||||
id: (item._id as string) ?? null,
|
||||
email: item.email ?? null,
|
||||
firstName: item.firstName ?? null,
|
||||
lastName: item.lastName ?? null,
|
||||
role: item.role ?? null,
|
||||
lastSeen: item._lastSeen ?? null,
|
||||
creationDate: item.creationDate ?? null,
|
||||
verified: item._verified ?? false,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
members,
|
||||
totalCount: (data.totalCount as number) ?? members.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
members: {
|
||||
type: 'array',
|
||||
description: 'List of account members',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: MEMBER_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of members' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import type {
|
||||
LaunchDarklyListProjectsParams,
|
||||
LaunchDarklyListProjectsResponse,
|
||||
} from '@/tools/launchdarkly/types'
|
||||
import { PROJECT_OUTPUT_PROPERTIES } from '@/tools/launchdarkly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const launchDarklyListProjectsTool: ToolConfig<
|
||||
LaunchDarklyListProjectsParams,
|
||||
LaunchDarklyListProjectsResponse
|
||||
> = {
|
||||
id: 'launchdarkly_list_projects',
|
||||
name: 'LaunchDarkly List Projects',
|
||||
description: 'List all projects in your LaunchDarkly account.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'LaunchDarkly API key',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of projects to return (default 20)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.limit) queryParams.set('limit', String(params.limit))
|
||||
const qs = queryParams.toString()
|
||||
return `https://app.launchdarkly.com/api/v2/projects${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: params.apiKey.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }))
|
||||
return { success: false, output: { projects: [], totalCount: 0 }, error: error.message }
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const projects = (data.items ?? []).map((item: Record<string, unknown>) => ({
|
||||
id: (item._id as string) ?? null,
|
||||
key: item.key ?? null,
|
||||
name: item.name ?? null,
|
||||
tags: (item.tags as string[]) ?? [],
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
projects,
|
||||
totalCount: (data.totalCount as number) ?? projects.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
projects: {
|
||||
type: 'array',
|
||||
description: 'List of projects',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: PROJECT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of projects' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import type {
|
||||
LaunchDarklyListSegmentsParams,
|
||||
LaunchDarklyListSegmentsResponse,
|
||||
} from '@/tools/launchdarkly/types'
|
||||
import { SEGMENT_OUTPUT_PROPERTIES } from '@/tools/launchdarkly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const launchDarklyListSegmentsTool: ToolConfig<
|
||||
LaunchDarklyListSegmentsParams,
|
||||
LaunchDarklyListSegmentsResponse
|
||||
> = {
|
||||
id: 'launchdarkly_list_segments',
|
||||
name: 'LaunchDarkly List Segments',
|
||||
description: 'List user segments in a LaunchDarkly project and environment.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'LaunchDarkly API key',
|
||||
},
|
||||
projectKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The project key',
|
||||
},
|
||||
environmentKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The environment key',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of segments to return (default 20)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const queryParams = new URLSearchParams()
|
||||
if (params.limit) queryParams.set('limit', String(params.limit))
|
||||
const qs = queryParams.toString()
|
||||
return `https://app.launchdarkly.com/api/v2/segments/${encodeURIComponent(params.projectKey.trim())}/${encodeURIComponent(params.environmentKey.trim())}${qs ? `?${qs}` : ''}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Authorization: params.apiKey.trim(),
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }))
|
||||
return { success: false, output: { segments: [], totalCount: 0 }, error: error.message }
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const segments = (data.items ?? []).map((item: Record<string, unknown>) => ({
|
||||
key: item.key ?? null,
|
||||
name: item.name ?? null,
|
||||
description: item.description ?? null,
|
||||
tags: (item.tags as string[]) ?? [],
|
||||
creationDate: item.creationDate ?? null,
|
||||
unbounded: item.unbounded ?? false,
|
||||
included: (item.included as string[]) ?? [],
|
||||
excluded: (item.excluded as string[]) ?? [],
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
segments,
|
||||
totalCount: (data.totalCount as number) ?? segments.length,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
segments: {
|
||||
type: 'array',
|
||||
description: 'List of user segments',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: SEGMENT_OUTPUT_PROPERTIES,
|
||||
},
|
||||
},
|
||||
totalCount: { type: 'number', description: 'Total number of segments' },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import type {
|
||||
LaunchDarklyToggleFlagParams,
|
||||
LaunchDarklyToggleFlagResponse,
|
||||
} from '@/tools/launchdarkly/types'
|
||||
import { FLAG_OUTPUT_PROPERTIES } from '@/tools/launchdarkly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const launchDarklyToggleFlagTool: ToolConfig<
|
||||
LaunchDarklyToggleFlagParams,
|
||||
LaunchDarklyToggleFlagResponse
|
||||
> = {
|
||||
id: 'launchdarkly_toggle_flag',
|
||||
name: 'LaunchDarkly Toggle Flag',
|
||||
description: 'Toggle a feature flag on or off in a specific LaunchDarkly environment.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'LaunchDarkly API key',
|
||||
},
|
||||
projectKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The project key',
|
||||
},
|
||||
flagKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The feature flag key to toggle',
|
||||
},
|
||||
environmentKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The environment key to toggle the flag in',
|
||||
},
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Whether to turn the flag on (true) or off (false)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://app.launchdarkly.com/api/v2/flags/${encodeURIComponent(params.projectKey.trim())}/${encodeURIComponent(params.flagKey.trim())}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: params.apiKey.trim(),
|
||||
'Content-Type': 'application/json; domain-model=launchdarkly.semanticpatch',
|
||||
}),
|
||||
body: (params) => ({
|
||||
environmentKey: params.environmentKey,
|
||||
instructions: [{ kind: params.enabled ? 'turnFlagOn' : 'turnFlagOff' }],
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: LaunchDarklyToggleFlagParams) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }))
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
key: '',
|
||||
name: '',
|
||||
kind: '',
|
||||
description: null,
|
||||
temporary: false,
|
||||
archived: false,
|
||||
deprecated: false,
|
||||
creationDate: 0,
|
||||
tags: [],
|
||||
variations: [],
|
||||
maintainerId: null,
|
||||
maintainerEmail: null,
|
||||
on: null,
|
||||
},
|
||||
error: error.message,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
const environments = data.environments as Record<string, Record<string, unknown>> | undefined
|
||||
let on: boolean | null = null
|
||||
if (environments) {
|
||||
const envKey = params?.environmentKey?.trim()
|
||||
if (envKey && environments[envKey]) {
|
||||
on = (environments[envKey].on as boolean) ?? null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
key: data.key ?? null,
|
||||
name: data.name ?? null,
|
||||
kind: data.kind ?? null,
|
||||
description: data.description ?? null,
|
||||
temporary: data.temporary ?? false,
|
||||
archived: data.archived ?? false,
|
||||
deprecated: data.deprecated ?? false,
|
||||
creationDate: data.creationDate ?? null,
|
||||
tags: data.tags ?? [],
|
||||
variations: data.variations ?? [],
|
||||
maintainerId: data.maintainerId ?? null,
|
||||
maintainerEmail: data._maintainer?.email ?? null,
|
||||
on,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
...FLAG_OUTPUT_PROPERTIES,
|
||||
on: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the flag is now on in the target environment',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
import type { OutputProperty, ToolResponse } from '@/tools/types'
|
||||
|
||||
/**
|
||||
* Shared output property definitions for LaunchDarkly API responses.
|
||||
* Based on LaunchDarkly REST API v2: https://apidocs.launchdarkly.com/
|
||||
*/
|
||||
|
||||
export const FLAG_OUTPUT_PROPERTIES = {
|
||||
key: { type: 'string', description: 'The unique key of the feature flag' },
|
||||
name: { type: 'string', description: 'The human-readable name of the feature flag' },
|
||||
kind: { type: 'string', description: 'The type of flag (boolean or multivariate)' },
|
||||
description: { type: 'string', description: 'Description of the feature flag', optional: true },
|
||||
temporary: { type: 'boolean', description: 'Whether the flag is temporary' },
|
||||
archived: { type: 'boolean', description: 'Whether the flag is archived' },
|
||||
deprecated: { type: 'boolean', description: 'Whether the flag is deprecated' },
|
||||
creationDate: {
|
||||
type: 'number',
|
||||
description: 'Unix timestamp in milliseconds when the flag was created',
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Tags applied to the flag',
|
||||
items: { type: 'string', description: 'Tag name' },
|
||||
},
|
||||
variations: {
|
||||
type: 'array',
|
||||
description: 'The variations for this feature flag',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
value: {
|
||||
type: 'string',
|
||||
description: 'The variation value (any JSON type, shown as text)',
|
||||
},
|
||||
name: { type: 'string', description: 'The variation name', optional: true },
|
||||
description: { type: 'string', description: 'The variation description', optional: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
maintainerId: {
|
||||
type: 'string',
|
||||
description: 'The ID of the member who maintains this flag',
|
||||
optional: true,
|
||||
},
|
||||
maintainerEmail: {
|
||||
type: 'string',
|
||||
description: 'The email of the member who maintains this flag',
|
||||
optional: true,
|
||||
},
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
export const PROJECT_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'string', description: 'The project ID' },
|
||||
key: { type: 'string', description: 'The unique project key' },
|
||||
name: { type: 'string', description: 'The project name' },
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Tags applied to the project',
|
||||
items: { type: 'string', description: 'Tag name' },
|
||||
},
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
export const ENVIRONMENT_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'string', description: 'The environment ID' },
|
||||
key: { type: 'string', description: 'The unique environment key' },
|
||||
name: { type: 'string', description: 'The environment name' },
|
||||
color: { type: 'string', description: 'The color assigned to this environment' },
|
||||
apiKey: { type: 'string', description: 'The server-side SDK key for this environment' },
|
||||
mobileKey: { type: 'string', description: 'The mobile SDK key for this environment' },
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Tags applied to the environment',
|
||||
items: { type: 'string', description: 'Tag name' },
|
||||
},
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
export const AUDIT_LOG_ENTRY_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'string', description: 'The audit log entry ID' },
|
||||
date: { type: 'number', description: 'Unix timestamp in milliseconds' },
|
||||
kind: { type: 'string', description: 'The type of action performed' },
|
||||
name: { type: 'string', description: 'The name of the resource acted on' },
|
||||
description: { type: 'string', description: 'Full description of the action', optional: true },
|
||||
shortDescription: {
|
||||
type: 'string',
|
||||
description: 'Short description of the action',
|
||||
optional: true,
|
||||
},
|
||||
memberEmail: {
|
||||
type: 'string',
|
||||
description: 'Email of the member who performed the action',
|
||||
optional: true,
|
||||
},
|
||||
targetName: { type: 'string', description: 'Name of the target resource', optional: true },
|
||||
targetKind: {
|
||||
type: 'string',
|
||||
description: 'Resource specifier of the target (e.g. proj/default:env/production:flag/my-flag)',
|
||||
optional: true,
|
||||
},
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
export const SEGMENT_OUTPUT_PROPERTIES = {
|
||||
key: { type: 'string', description: 'The unique segment key' },
|
||||
name: { type: 'string', description: 'The segment name' },
|
||||
description: { type: 'string', description: 'The segment description', optional: true },
|
||||
tags: {
|
||||
type: 'array',
|
||||
description: 'Tags applied to the segment',
|
||||
items: { type: 'string', description: 'Tag name' },
|
||||
},
|
||||
creationDate: {
|
||||
type: 'number',
|
||||
description: 'Unix timestamp in milliseconds when the segment was created',
|
||||
},
|
||||
unbounded: { type: 'boolean', description: 'Whether this is an unbounded (big) segment' },
|
||||
included: {
|
||||
type: 'array',
|
||||
description: 'User keys explicitly included in the segment',
|
||||
items: { type: 'string', description: 'User key' },
|
||||
},
|
||||
excluded: {
|
||||
type: 'array',
|
||||
description: 'User keys explicitly excluded from the segment',
|
||||
items: { type: 'string', description: 'User key' },
|
||||
},
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
export const FLAG_STATUS_OUTPUT_PROPERTIES = {
|
||||
name: { type: 'string', description: 'The flag status (new, active, inactive, launched)' },
|
||||
lastRequested: {
|
||||
type: 'string',
|
||||
description: 'Timestamp of the last evaluation',
|
||||
optional: true,
|
||||
},
|
||||
defaultVal: { type: 'string', description: 'The default variation value', optional: true },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
export const MEMBER_OUTPUT_PROPERTIES = {
|
||||
id: { type: 'string', description: 'The member ID' },
|
||||
email: { type: 'string', description: 'The member email address' },
|
||||
firstName: { type: 'string', description: 'The member first name', optional: true },
|
||||
lastName: { type: 'string', description: 'The member last name', optional: true },
|
||||
role: { type: 'string', description: 'The member role (reader, writer, admin, owner)' },
|
||||
lastSeen: { type: 'number', description: 'Unix timestamp of last activity', optional: true },
|
||||
creationDate: { type: 'number', description: 'Unix timestamp when the member was created' },
|
||||
verified: { type: 'boolean', description: 'Whether the member email is verified' },
|
||||
} as const satisfies Record<string, OutputProperty>
|
||||
|
||||
export interface LaunchDarklyListFlagsParams {
|
||||
apiKey: string
|
||||
projectKey: string
|
||||
environmentKey?: string
|
||||
tag?: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface LaunchDarklyGetFlagParams {
|
||||
apiKey: string
|
||||
projectKey: string
|
||||
flagKey: string
|
||||
environmentKey?: string
|
||||
}
|
||||
|
||||
export interface LaunchDarklyCreateFlagParams {
|
||||
apiKey: string
|
||||
projectKey: string
|
||||
name: string
|
||||
key: string
|
||||
description?: string
|
||||
tags?: string
|
||||
temporary?: boolean
|
||||
}
|
||||
|
||||
export interface LaunchDarklyToggleFlagParams {
|
||||
apiKey: string
|
||||
projectKey: string
|
||||
flagKey: string
|
||||
environmentKey: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface LaunchDarklyDeleteFlagParams {
|
||||
apiKey: string
|
||||
projectKey: string
|
||||
flagKey: string
|
||||
}
|
||||
|
||||
export interface LaunchDarklyListProjectsParams {
|
||||
apiKey: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface LaunchDarklyListEnvironmentsParams {
|
||||
apiKey: string
|
||||
projectKey: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface FlagItem {
|
||||
key: string
|
||||
name: string
|
||||
kind: string
|
||||
description: string | null
|
||||
temporary: boolean
|
||||
archived: boolean
|
||||
deprecated: boolean
|
||||
creationDate: number
|
||||
tags: string[]
|
||||
variations: Array<{ value: unknown; name?: string; description?: string }>
|
||||
maintainerId: string | null
|
||||
maintainerEmail: string | null
|
||||
}
|
||||
|
||||
interface ProjectItem {
|
||||
id: string
|
||||
key: string
|
||||
name: string
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
interface EnvironmentItem {
|
||||
id: string
|
||||
key: string
|
||||
name: string
|
||||
color: string
|
||||
apiKey: string
|
||||
mobileKey: string
|
||||
tags: string[]
|
||||
}
|
||||
|
||||
export interface LaunchDarklyListFlagsResponse extends ToolResponse {
|
||||
output: {
|
||||
flags: FlagItem[]
|
||||
totalCount: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface LaunchDarklyGetFlagResponse extends ToolResponse {
|
||||
output: FlagItem & {
|
||||
on: boolean | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface LaunchDarklyCreateFlagResponse extends ToolResponse {
|
||||
output: FlagItem
|
||||
}
|
||||
|
||||
export interface LaunchDarklyToggleFlagResponse extends ToolResponse {
|
||||
output: FlagItem & {
|
||||
on: boolean | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface LaunchDarklyDeleteFlagResponse extends ToolResponse {
|
||||
output: {
|
||||
deleted: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface LaunchDarklyListProjectsResponse extends ToolResponse {
|
||||
output: {
|
||||
projects: ProjectItem[]
|
||||
totalCount: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface LaunchDarklyListEnvironmentsResponse extends ToolResponse {
|
||||
output: {
|
||||
environments: EnvironmentItem[]
|
||||
totalCount: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface LaunchDarklyUpdateFlagParams {
|
||||
apiKey: string
|
||||
projectKey: string
|
||||
flagKey: string
|
||||
updateName?: string
|
||||
updateDescription?: string
|
||||
addTags?: string
|
||||
removeTags?: string
|
||||
archive?: boolean
|
||||
comment?: string
|
||||
}
|
||||
|
||||
export interface LaunchDarklyUpdateFlagResponse extends ToolResponse {
|
||||
output: FlagItem
|
||||
}
|
||||
|
||||
export interface LaunchDarklyGetAuditLogParams {
|
||||
apiKey: string
|
||||
limit?: number
|
||||
spec?: string
|
||||
}
|
||||
|
||||
interface AuditLogEntry {
|
||||
id: string
|
||||
date: number | null
|
||||
kind: string | null
|
||||
name: string | null
|
||||
description: string | null
|
||||
shortDescription: string | null
|
||||
memberEmail: string | null
|
||||
targetName: string | null
|
||||
targetKind: string | null
|
||||
}
|
||||
|
||||
export interface LaunchDarklyGetAuditLogResponse extends ToolResponse {
|
||||
output: {
|
||||
entries: AuditLogEntry[]
|
||||
totalCount: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface LaunchDarklyListSegmentsParams {
|
||||
apiKey: string
|
||||
projectKey: string
|
||||
environmentKey: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface SegmentItem {
|
||||
key: string
|
||||
name: string
|
||||
description: string | null
|
||||
tags: string[]
|
||||
creationDate: number | null
|
||||
unbounded: boolean
|
||||
included: string[]
|
||||
excluded: string[]
|
||||
}
|
||||
|
||||
export interface LaunchDarklyListSegmentsResponse extends ToolResponse {
|
||||
output: {
|
||||
segments: SegmentItem[]
|
||||
totalCount: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface LaunchDarklyGetFlagStatusParams {
|
||||
apiKey: string
|
||||
projectKey: string
|
||||
flagKey: string
|
||||
environmentKey: string
|
||||
}
|
||||
|
||||
export interface LaunchDarklyGetFlagStatusResponse extends ToolResponse {
|
||||
output: {
|
||||
name: string
|
||||
lastRequested: string | null
|
||||
defaultVal: string | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface LaunchDarklyListMembersParams {
|
||||
apiKey: string
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface MemberItem {
|
||||
id: string
|
||||
email: string | null
|
||||
firstName: string | null
|
||||
lastName: string | null
|
||||
role: string | null
|
||||
lastSeen: number | null
|
||||
creationDate: number | null
|
||||
verified: boolean
|
||||
}
|
||||
|
||||
export interface LaunchDarklyListMembersResponse extends ToolResponse {
|
||||
output: {
|
||||
members: MemberItem[]
|
||||
totalCount: number
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import type {
|
||||
LaunchDarklyUpdateFlagParams,
|
||||
LaunchDarklyUpdateFlagResponse,
|
||||
} from '@/tools/launchdarkly/types'
|
||||
import { FLAG_OUTPUT_PROPERTIES } from '@/tools/launchdarkly/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const launchDarklyUpdateFlagTool: ToolConfig<
|
||||
LaunchDarklyUpdateFlagParams,
|
||||
LaunchDarklyUpdateFlagResponse
|
||||
> = {
|
||||
id: 'launchdarkly_update_flag',
|
||||
name: 'LaunchDarkly Update Flag',
|
||||
description:
|
||||
'Update feature flag metadata (name, description, tags, temporary, archived) using semantic patch.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'LaunchDarkly API key',
|
||||
},
|
||||
projectKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The project key',
|
||||
},
|
||||
flagKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The feature flag key to update',
|
||||
},
|
||||
updateName: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New name for the flag',
|
||||
},
|
||||
updateDescription: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'New description for the flag',
|
||||
},
|
||||
addTags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated tags to add',
|
||||
},
|
||||
removeTags: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Comma-separated tags to remove',
|
||||
},
|
||||
archive: {
|
||||
type: 'boolean',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Set to true to archive, false to restore',
|
||||
},
|
||||
comment: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Optional comment explaining the update',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) =>
|
||||
`https://app.launchdarkly.com/api/v2/flags/${encodeURIComponent(params.projectKey.trim())}/${encodeURIComponent(params.flagKey.trim())}`,
|
||||
method: 'PATCH',
|
||||
headers: (params) => ({
|
||||
Authorization: params.apiKey.trim(),
|
||||
'Content-Type': 'application/json; domain-model=launchdarkly.semanticpatch',
|
||||
}),
|
||||
body: (params) => {
|
||||
const instructions: Array<Record<string, unknown>> = []
|
||||
|
||||
if (params.updateName) {
|
||||
instructions.push({ kind: 'updateName', value: params.updateName })
|
||||
}
|
||||
if (params.updateDescription) {
|
||||
instructions.push({ kind: 'updateDescription', value: params.updateDescription })
|
||||
}
|
||||
if (params.addTags) {
|
||||
instructions.push({
|
||||
kind: 'addTags',
|
||||
values: params.addTags.split(',').map((t: string) => t.trim()),
|
||||
})
|
||||
}
|
||||
if (params.removeTags) {
|
||||
instructions.push({
|
||||
kind: 'removeTags',
|
||||
values: params.removeTags.split(',').map((t: string) => t.trim()),
|
||||
})
|
||||
}
|
||||
if (params.archive === true) {
|
||||
instructions.push({ kind: 'archiveFlag' })
|
||||
} else if (params.archive === false) {
|
||||
instructions.push({ kind: 'restoreFlag' })
|
||||
}
|
||||
|
||||
if (instructions.length === 0) {
|
||||
throw new Error(
|
||||
'At least one update field must be provided (updateName, updateDescription, addTags, removeTags, or archive)'
|
||||
)
|
||||
}
|
||||
|
||||
const body: Record<string, unknown> = { instructions }
|
||||
if (params.comment) body.comment = params.comment
|
||||
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => ({ message: response.statusText }))
|
||||
return {
|
||||
success: false,
|
||||
output: {
|
||||
key: '',
|
||||
name: '',
|
||||
kind: '',
|
||||
description: null,
|
||||
temporary: false,
|
||||
archived: false,
|
||||
deprecated: false,
|
||||
creationDate: 0,
|
||||
tags: [],
|
||||
variations: [],
|
||||
maintainerId: null,
|
||||
maintainerEmail: null,
|
||||
},
|
||||
error: error.message,
|
||||
}
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
key: data.key ?? null,
|
||||
name: data.name ?? null,
|
||||
kind: data.kind ?? null,
|
||||
description: data.description ?? null,
|
||||
temporary: data.temporary ?? false,
|
||||
archived: data.archived ?? false,
|
||||
deprecated: data.deprecated ?? false,
|
||||
creationDate: data.creationDate ?? null,
|
||||
tags: data.tags ?? [],
|
||||
variations: data.variations ?? [],
|
||||
maintainerId: data.maintainerId ?? null,
|
||||
maintainerEmail: data._maintainer?.email ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: FLAG_OUTPUT_PROPERTIES,
|
||||
}
|
||||
Reference in New Issue
Block a user