diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index 86ccbef282..b3c96ada69 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -2152,6 +2152,17 @@ export function LangsmithIcon(props: SVGProps) { ) } +export function LaunchDarklyIcon(props: SVGProps) { + return ( + + + + ) +} + export function LemlistIcon(props: SVGProps) { return ( diff --git a/apps/docs/components/ui/icon-mapping.ts b/apps/docs/components/ui/icon-mapping.ts index 4e9791b02d..19e227354a 100644 --- a/apps/docs/components/ui/icon-mapping.ts +++ b/apps/docs/components/ui/icon-mapping.ts @@ -91,6 +91,7 @@ import { KalshiIcon, KetchIcon, LangsmithIcon, + LaunchDarklyIcon, LemlistIcon, LinearIcon, LinkedInIcon, @@ -270,6 +271,7 @@ export const blockTypeToIconMap: Record = { ketch: KetchIcon, knowledge: PackageSearchIcon, langsmith: LangsmithIcon, + launchdarkly: LaunchDarklyIcon, lemlist: LemlistIcon, linear: LinearIcon, linkedin: LinkedInIcon, diff --git a/apps/docs/content/docs/en/tools/launchdarkly.mdx b/apps/docs/content/docs/en/tools/launchdarkly.mdx new file mode 100644 index 0000000000..3cadbcb7f2 --- /dev/null +++ b/apps/docs/content/docs/en/tools/launchdarkly.mdx @@ -0,0 +1,388 @@ +--- +title: LaunchDarkly +description: Manage feature flags with LaunchDarkly. +--- + +import { BlockInfoCard } from "@/components/ui/block-info-card" + + + +{/* MANUAL-CONTENT-START:intro */} +[LaunchDarkly](https://launchdarkly.com/) is a feature management platform that enables teams to safely deploy, control, and measure their software features at scale. + +With the LaunchDarkly integration in Sim, you can: + +- **Feature flag management** — List, create, update, toggle, and delete feature flags programmatically. Toggle flags on or off in specific environments using LaunchDarkly's semantic patch API. +- **Flag status monitoring** — Check whether a flag is active, inactive, new, or launched in a given environment. Track the last time a flag was evaluated. +- **Project and environment management** — List all projects and their environments to understand your LaunchDarkly organization structure. +- **User segments** — List user segments within a project and environment to understand how your audience is organized for targeting. +- **Team visibility** — List account members and their roles for auditing and access management workflows. +- **Audit log** — Retrieve recent audit log entries to track who changed what, when. Filter entries by resource type for targeted monitoring. + +In Sim, the LaunchDarkly integration enables your agents to automate feature flag operations as part of their workflows. This allows for automation scenarios such as toggling flags on/off based on deployment pipeline events, monitoring flag status and alerting on stale or unused flags, auditing flag changes by querying the audit log after deployments, syncing flag metadata with your project management tools, and listing all feature flags across projects for governance. + +## Authentication + +This integration uses a LaunchDarkly API key. You can create personal access tokens or service tokens in the LaunchDarkly dashboard under **Account Settings > Authorization**. The API key is passed directly in the `Authorization` header (no `Bearer` prefix). + +## Need Help? + +If you encounter issues with the LaunchDarkly integration, contact us at [help@sim.ai](mailto:help@sim.ai) +{/* MANUAL-CONTENT-END */} + + +## Usage Instructions + +Integrate LaunchDarkly into your workflow. List, create, update, toggle, and delete feature flags. Manage projects, environments, segments, members, and audit logs. Requires API Key. + + + +## Tools + +### `launchdarkly_create_flag` + +Create a new feature flag in a LaunchDarkly project. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | LaunchDarkly API key | +| `projectKey` | string | Yes | The project key to create the flag in | +| `name` | string | Yes | Human-readable name for the feature flag | +| `key` | string | Yes | Unique key for the feature flag \(used in code\) | +| `description` | string | No | Description of the feature flag | +| `tags` | string | No | Comma-separated list of tags | +| `temporary` | boolean | No | Whether the flag is temporary \(default true\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `key` | string | The unique key of the feature flag | +| `name` | string | The human-readable name of the feature flag | +| `kind` | string | The type of flag \(boolean or multivariate\) | +| `description` | string | Description of the feature flag | +| `temporary` | boolean | Whether the flag is temporary | +| `archived` | boolean | Whether the flag is archived | +| `deprecated` | boolean | Whether the flag is deprecated | +| `creationDate` | number | Unix timestamp in milliseconds when the flag was created | +| `tags` | array | Tags applied to the flag | +| `variations` | array | The variations for this feature flag | +| ↳ `value` | string | The variation value | +| ↳ `name` | string | The variation name | +| ↳ `description` | string | The variation description | +| `maintainerId` | string | The ID of the member who maintains this flag | + +### `launchdarkly_delete_flag` + +Delete a feature flag from a LaunchDarkly project. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | LaunchDarkly API key | +| `projectKey` | string | Yes | The project key | +| `flagKey` | string | Yes | The feature flag key to delete | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `deleted` | boolean | Whether the flag was successfully deleted | + +### `launchdarkly_get_audit_log` + +List audit log entries from your LaunchDarkly account. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | LaunchDarkly API key | +| `limit` | number | No | Maximum number of entries to return \(default 10, max 20\) | +| `spec` | string | No | Filter expression \(e.g., "resourceType:flag"\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `entries` | array | List of audit log entries | +| ↳ `id` | string | The audit log entry ID | +| ↳ `date` | number | Unix timestamp in milliseconds | +| ↳ `kind` | string | The type of action performed | +| ↳ `name` | string | The name of the resource acted on | +| ↳ `description` | string | Full description of the action | +| ↳ `shortDescription` | string | Short description of the action | +| ↳ `memberEmail` | string | Email of the member who performed the action | +| ↳ `targetName` | string | Name of the target resource | +| ↳ `targetKind` | string | Kind of the target resource | +| `totalCount` | number | Total number of audit log entries | + +### `launchdarkly_get_flag` + +Get a single feature flag by key from a LaunchDarkly project. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | LaunchDarkly API key | +| `projectKey` | string | Yes | The project key | +| `flagKey` | string | Yes | The feature flag key | +| `environmentKey` | string | No | Filter flag configuration to a specific environment | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `key` | string | The unique key of the feature flag | +| `name` | string | The human-readable name of the feature flag | +| `kind` | string | The type of flag \(boolean or multivariate\) | +| `description` | string | Description of the feature flag | +| `temporary` | boolean | Whether the flag is temporary | +| `archived` | boolean | Whether the flag is archived | +| `deprecated` | boolean | Whether the flag is deprecated | +| `creationDate` | number | Unix timestamp in milliseconds when the flag was created | +| `tags` | array | Tags applied to the flag | +| `variations` | array | The variations for this feature flag | +| ↳ `value` | string | The variation value | +| ↳ `name` | string | The variation name | +| ↳ `description` | string | The variation description | +| `maintainerId` | string | The ID of the member who maintains this flag | +| `on` | boolean | Whether the flag is on in the requested environment \(null if no single environment was specified\) | + +### `launchdarkly_get_flag_status` + +Get the status of a feature flag across environments (active, inactive, launched, etc.). + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | LaunchDarkly API key | +| `projectKey` | string | Yes | The project key | +| `flagKey` | string | Yes | The feature flag key | +| `environmentKey` | string | Yes | The environment key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `name` | string | The flag status \(new, active, inactive, launched\) | +| `lastRequested` | string | Timestamp of the last evaluation | +| `defaultVal` | string | The default variation value | + +### `launchdarkly_list_environments` + +List environments in a LaunchDarkly project. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | LaunchDarkly API key | +| `projectKey` | string | Yes | The project key to list environments for | +| `limit` | number | No | Maximum number of environments to return \(default 20\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `environments` | array | List of environments | +| ↳ `id` | string | The environment ID | +| ↳ `key` | string | The unique environment key | +| ↳ `name` | string | The environment name | +| ↳ `color` | string | The color assigned to this environment | +| ↳ `apiKey` | string | The server-side SDK key for this environment | +| ↳ `mobileKey` | string | The mobile SDK key for this environment | +| ↳ `tags` | array | Tags applied to the environment | +| `totalCount` | number | Total number of environments | + +### `launchdarkly_list_flags` + +List feature flags in a LaunchDarkly project. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | LaunchDarkly API key | +| `projectKey` | string | Yes | The project key to list flags for | +| `environmentKey` | string | No | Filter flag configurations to a specific environment | +| `tag` | string | No | Filter flags by tag name | +| `limit` | number | No | Maximum number of flags to return \(default 20\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `flags` | array | List of feature flags | +| ↳ `key` | string | The unique key of the feature flag | +| ↳ `name` | string | The human-readable name of the feature flag | +| ↳ `kind` | string | The type of flag \(boolean or multivariate\) | +| ↳ `description` | string | Description of the feature flag | +| ↳ `temporary` | boolean | Whether the flag is temporary | +| ↳ `archived` | boolean | Whether the flag is archived | +| ↳ `deprecated` | boolean | Whether the flag is deprecated | +| ↳ `creationDate` | number | Unix timestamp in milliseconds when the flag was created | +| ↳ `tags` | array | Tags applied to the flag | +| ↳ `variations` | array | The variations for this feature flag | +| ↳ `value` | string | The variation value | +| ↳ `name` | string | The variation name | +| ↳ `description` | string | The variation description | +| ↳ `maintainerId` | string | The ID of the member who maintains this flag | +| `totalCount` | number | Total number of flags | + +### `launchdarkly_list_members` + +List account members in your LaunchDarkly organization. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | LaunchDarkly API key | +| `limit` | number | No | Maximum number of members to return \(default 20\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `members` | array | List of account members | +| ↳ `id` | string | The member ID | +| ↳ `email` | string | The member email address | +| ↳ `firstName` | string | The member first name | +| ↳ `lastName` | string | The member last name | +| ↳ `role` | string | The member role \(reader, writer, admin, owner\) | +| ↳ `lastSeen` | number | Unix timestamp of last activity | +| ↳ `creationDate` | number | Unix timestamp when the member was created | +| ↳ `verified` | boolean | Whether the member email is verified | +| `totalCount` | number | Total number of members | + +### `launchdarkly_list_projects` + +List all projects in your LaunchDarkly account. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | LaunchDarkly API key | +| `limit` | number | No | Maximum number of projects to return \(default 20\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `projects` | array | List of projects | +| ↳ `id` | string | The project ID | +| ↳ `key` | string | The unique project key | +| ↳ `name` | string | The project name | +| ↳ `tags` | array | Tags applied to the project | +| `totalCount` | number | Total number of projects | + +### `launchdarkly_list_segments` + +List user segments in a LaunchDarkly project and environment. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | LaunchDarkly API key | +| `projectKey` | string | Yes | The project key | +| `environmentKey` | string | Yes | The environment key | +| `limit` | number | No | Maximum number of segments to return \(default 20\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `segments` | array | List of user segments | +| ↳ `key` | string | The unique segment key | +| ↳ `name` | string | The segment name | +| ↳ `description` | string | The segment description | +| ↳ `tags` | array | Tags applied to the segment | +| ↳ `creationDate` | number | Unix timestamp in milliseconds when the segment was created | +| ↳ `unbounded` | boolean | Whether this is an unbounded \(big\) segment | +| ↳ `included` | array | User keys explicitly included in the segment | +| ↳ `excluded` | array | User keys explicitly excluded from the segment | +| `totalCount` | number | Total number of segments | + +### `launchdarkly_toggle_flag` + +Toggle a feature flag on or off in a specific LaunchDarkly environment. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | LaunchDarkly API key | +| `projectKey` | string | Yes | The project key | +| `flagKey` | string | Yes | The feature flag key to toggle | +| `environmentKey` | string | Yes | The environment key to toggle the flag in | +| `enabled` | boolean | Yes | Whether to turn the flag on \(true\) or off \(false\) | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `key` | string | The unique key of the feature flag | +| `name` | string | The human-readable name of the feature flag | +| `kind` | string | The type of flag \(boolean or multivariate\) | +| `description` | string | Description of the feature flag | +| `temporary` | boolean | Whether the flag is temporary | +| `archived` | boolean | Whether the flag is archived | +| `deprecated` | boolean | Whether the flag is deprecated | +| `creationDate` | number | Unix timestamp in milliseconds when the flag was created | +| `tags` | array | Tags applied to the flag | +| `variations` | array | The variations for this feature flag | +| ↳ `value` | string | The variation value | +| ↳ `name` | string | The variation name | +| ↳ `description` | string | The variation description | +| `maintainerId` | string | The ID of the member who maintains this flag | +| `on` | boolean | Whether the flag is now on in the target environment | + +### `launchdarkly_update_flag` + +Update a feature flag metadata (name, description, tags, temporary, archived) using semantic patch. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `apiKey` | string | Yes | LaunchDarkly API key | +| `projectKey` | string | Yes | The project key | +| `flagKey` | string | Yes | The feature flag key to update | +| `updateName` | string | No | New name for the flag | +| `updateDescription` | string | No | New description for the flag | +| `addTags` | string | No | Comma-separated tags to add | +| `removeTags` | string | No | Comma-separated tags to remove | +| `archive` | boolean | No | Set to true to archive, false to restore | +| `comment` | string | No | Optional comment explaining the update | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `key` | string | The unique key of the feature flag | +| `name` | string | The human-readable name of the feature flag | +| `kind` | string | The type of flag \(boolean or multivariate\) | +| `description` | string | Description of the feature flag | +| `temporary` | boolean | Whether the flag is temporary | +| `archived` | boolean | Whether the flag is archived | +| `deprecated` | boolean | Whether the flag is deprecated | +| `creationDate` | number | Unix timestamp in milliseconds when the flag was created | +| `tags` | array | Tags applied to the flag | +| `variations` | array | The variations for this feature flag | +| ↳ `value` | string | The variation value | +| ↳ `name` | string | The variation name | +| ↳ `description` | string | The variation description | +| `maintainerId` | string | The ID of the member who maintains this flag | + + diff --git a/apps/docs/content/docs/en/tools/meta.json b/apps/docs/content/docs/en/tools/meta.json index bd5b47ea46..7dc864de27 100644 --- a/apps/docs/content/docs/en/tools/meta.json +++ b/apps/docs/content/docs/en/tools/meta.json @@ -87,6 +87,7 @@ "ketch", "knowledge", "langsmith", + "launchdarkly", "lemlist", "linear", "linkedin", diff --git a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts index 4e370b807c..9cb3bf6ac7 100644 --- a/apps/sim/app/(landing)/integrations/data/icon-mapping.ts +++ b/apps/sim/app/(landing)/integrations/data/icon-mapping.ts @@ -91,6 +91,7 @@ import { KalshiIcon, KetchIcon, LangsmithIcon, + LaunchDarklyIcon, LemlistIcon, LinearIcon, LinkedInIcon, @@ -270,6 +271,7 @@ export const blockTypeToIconMap: Record = { ketch: KetchIcon, knowledge: PackageSearchIcon, langsmith: LangsmithIcon, + launchdarkly: LaunchDarklyIcon, lemlist: LemlistIcon, linear: LinearIcon, linkedin: LinkedInIcon, diff --git a/apps/sim/app/(landing)/integrations/data/integrations.json b/apps/sim/app/(landing)/integrations/data/integrations.json index 6c6120a545..20fd48a83d 100644 --- a/apps/sim/app/(landing)/integrations/data/integrations.json +++ b/apps/sim/app/(landing)/integrations/data/integrations.json @@ -6339,6 +6339,73 @@ "integrationType": "developer-tools", "tags": ["monitoring", "llm", "data-analytics"] }, + { + "type": "launchdarkly", + "slug": "launchdarkly", + "name": "LaunchDarkly", + "description": "Manage feature flags with LaunchDarkly.", + "longDescription": "Integrate LaunchDarkly into your workflow. List, create, update, toggle, and delete feature flags. Manage projects, environments, segments, members, and audit logs. Requires API Key.", + "bgColor": "#191919", + "iconName": "LaunchDarklyIcon", + "docsUrl": "https://docs.sim.ai/tools/launchdarkly", + "operations": [ + { + "name": "List Flags", + "description": "List feature flags in a LaunchDarkly project." + }, + { + "name": "Get Flag", + "description": "Get a single feature flag by key from a LaunchDarkly project." + }, + { + "name": "Create Flag", + "description": "Create a new feature flag in a LaunchDarkly project." + }, + { + "name": "Update Flag", + "description": "Update a feature flag metadata (name, description, tags, temporary, archived) using semantic patch." + }, + { + "name": "Toggle Flag", + "description": "Toggle a feature flag on or off in a specific LaunchDarkly environment." + }, + { + "name": "Delete Flag", + "description": "Delete a feature flag from a LaunchDarkly project." + }, + { + "name": "Get Flag Status", + "description": "Get the status of a feature flag across environments (active, inactive, launched, etc.)." + }, + { + "name": "List Projects", + "description": "List all projects in your LaunchDarkly account." + }, + { + "name": "List Environments", + "description": "List environments in a LaunchDarkly project." + }, + { + "name": "List Segments", + "description": "List user segments in a LaunchDarkly project and environment." + }, + { + "name": "List Members", + "description": "List account members in your LaunchDarkly organization." + }, + { + "name": "Get Audit Log", + "description": "List audit log entries from your LaunchDarkly account." + } + ], + "operationCount": 12, + "triggers": [], + "triggerCount": 0, + "authType": "api-key", + "category": "tools", + "integrationType": "developer-tools", + "tags": ["feature-flags", "ci-cd"] + }, { "type": "lemlist", "slug": "lemlist", diff --git a/apps/sim/blocks/blocks/launchdarkly.ts b/apps/sim/blocks/blocks/launchdarkly.ts new file mode 100644 index 0000000000..dbc78c214a --- /dev/null +++ b/apps/sim/blocks/blocks/launchdarkly.ts @@ -0,0 +1,343 @@ +import { LaunchDarklyIcon } from '@/components/icons' +import type { BlockConfig } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' + +export const LaunchDarklyBlock: BlockConfig = { + type: 'launchdarkly', + name: 'LaunchDarkly', + description: 'Manage feature flags with LaunchDarkly.', + longDescription: + 'Integrate LaunchDarkly into your workflow. List, create, update, toggle, and delete feature flags. Manage projects, environments, segments, members, and audit logs. Requires API Key.', + docsLink: 'https://docs.sim.ai/tools/launchdarkly', + category: 'tools', + integrationType: IntegrationType.DeveloperTools, + tags: ['feature-flags', 'ci-cd'], + bgColor: '#191919', + icon: LaunchDarklyIcon, + authMode: AuthMode.ApiKey, + + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'List Flags', id: 'list_flags' }, + { label: 'Get Flag', id: 'get_flag' }, + { label: 'Create Flag', id: 'create_flag' }, + { label: 'Update Flag', id: 'update_flag' }, + { label: 'Toggle Flag', id: 'toggle_flag' }, + { label: 'Delete Flag', id: 'delete_flag' }, + { label: 'Get Flag Status', id: 'get_flag_status' }, + { label: 'List Projects', id: 'list_projects' }, + { label: 'List Environments', id: 'list_environments' }, + { label: 'List Segments', id: 'list_segments' }, + { label: 'List Members', id: 'list_members' }, + { label: 'Get Audit Log', id: 'get_audit_log' }, + ], + value: () => 'list_flags', + }, + { + id: 'apiKey', + title: 'API Key', + type: 'short-input', + placeholder: 'Enter your LaunchDarkly API key', + password: true, + required: true, + }, + + // Project key — needed for all except list_projects, list_members, get_audit_log + { + id: 'projectKey', + title: 'Project Key', + type: 'short-input', + placeholder: 'my-project', + condition: { + field: 'operation', + value: ['list_projects', 'list_members', 'get_audit_log'], + not: true, + }, + required: { + field: 'operation', + value: ['list_projects', 'list_members', 'get_audit_log'], + not: true, + }, + }, + + // Flag key — needed for get_flag, toggle_flag, delete_flag, update_flag, get_flag_status + { + id: 'flagKey', + title: 'Flag Key', + type: 'short-input', + placeholder: 'my-feature-flag', + condition: { + field: 'operation', + value: ['get_flag', 'toggle_flag', 'delete_flag', 'update_flag', 'get_flag_status'], + }, + required: { + field: 'operation', + value: ['get_flag', 'toggle_flag', 'delete_flag', 'update_flag', 'get_flag_status'], + }, + }, + + // Environment key — optional for list_flags/get_flag, required for toggle_flag/get_flag_status/list_segments + { + id: 'environmentKey', + title: 'Environment Key', + type: 'short-input', + placeholder: 'production', + condition: { + field: 'operation', + value: ['list_flags', 'get_flag', 'toggle_flag', 'get_flag_status', 'list_segments'], + }, + required: { + field: 'operation', + value: ['toggle_flag', 'get_flag_status', 'list_segments'], + }, + }, + + // Enabled toggle — for toggle_flag only + { + id: 'enabled', + title: 'Enable Flag', + type: 'dropdown', + options: [ + { label: 'On', id: 'true' }, + { label: 'Off', id: 'false' }, + ], + value: () => 'true', + condition: { field: 'operation', value: 'toggle_flag' }, + }, + + // Create flag fields + { + id: 'flagName', + title: 'Flag Name', + type: 'short-input', + placeholder: 'My Feature Flag', + condition: { field: 'operation', value: 'create_flag' }, + required: { field: 'operation', value: 'create_flag' }, + }, + { + id: 'newFlagKey', + title: 'Flag Key', + type: 'short-input', + placeholder: 'my-feature-flag', + condition: { field: 'operation', value: 'create_flag' }, + required: { field: 'operation', value: 'create_flag' }, + }, + { + id: 'description', + title: 'Description', + type: 'long-input', + placeholder: 'Description of the feature flag', + condition: { field: 'operation', value: 'create_flag' }, + }, + { + id: 'tags', + title: 'Tags', + type: 'short-input', + placeholder: 'tag1, tag2', + condition: { field: 'operation', value: 'create_flag' }, + mode: 'advanced', + }, + { + id: 'temporary', + title: 'Temporary', + type: 'dropdown', + options: [ + { label: 'Yes', id: 'true' }, + { label: 'No', id: 'false' }, + ], + value: () => 'true', + condition: { field: 'operation', value: 'create_flag' }, + mode: 'advanced', + }, + + // Update flag fields + { + id: 'updateName', + title: 'New Name', + type: 'short-input', + placeholder: 'Updated flag name', + condition: { field: 'operation', value: 'update_flag' }, + }, + { + id: 'updateDescription', + title: 'New Description', + type: 'long-input', + placeholder: 'Updated description', + condition: { field: 'operation', value: 'update_flag' }, + }, + { + id: 'addTags', + title: 'Add Tags', + type: 'short-input', + placeholder: 'tag1, tag2', + condition: { field: 'operation', value: 'update_flag' }, + mode: 'advanced', + }, + { + id: 'removeTags', + title: 'Remove Tags', + type: 'short-input', + placeholder: 'old-tag1, old-tag2', + condition: { field: 'operation', value: 'update_flag' }, + mode: 'advanced', + }, + { + id: 'archive', + title: 'Archive/Restore', + type: 'dropdown', + options: [ + { label: 'No Change', id: '' }, + { label: 'Archive', id: 'true' }, + { label: 'Restore', id: 'false' }, + ], + value: () => '', + condition: { field: 'operation', value: 'update_flag' }, + mode: 'advanced', + }, + { + id: 'comment', + title: 'Comment', + type: 'short-input', + placeholder: 'Reason for update', + condition: { field: 'operation', value: 'update_flag' }, + mode: 'advanced', + }, + + // Audit log filter + { + id: 'spec', + title: 'Filter', + type: 'short-input', + placeholder: 'resourceType:flag', + condition: { field: 'operation', value: 'get_audit_log' }, + mode: 'advanced', + }, + + // Tag filter for list_flags + { + id: 'tag', + title: 'Filter by Tag', + type: 'short-input', + placeholder: 'tag-name', + condition: { field: 'operation', value: 'list_flags' }, + mode: 'advanced', + }, + + // Limit — for list operations and audit log + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: '20', + condition: { + field: 'operation', + value: [ + 'list_flags', + 'list_projects', + 'list_environments', + 'list_segments', + 'list_members', + 'get_audit_log', + ], + }, + mode: 'advanced', + }, + ], + + tools: { + access: [ + 'launchdarkly_create_flag', + 'launchdarkly_delete_flag', + 'launchdarkly_get_audit_log', + 'launchdarkly_get_flag', + 'launchdarkly_get_flag_status', + 'launchdarkly_list_environments', + 'launchdarkly_list_flags', + 'launchdarkly_list_members', + 'launchdarkly_list_projects', + 'launchdarkly_list_segments', + 'launchdarkly_toggle_flag', + 'launchdarkly_update_flag', + ], + config: { + tool: (params) => { + const operation = params.operation || 'list_flags' + return `launchdarkly_${operation}` + }, + params: (params) => { + const { operation, flagName, newFlagKey, ...rest } = params + + if (operation === 'create_flag') { + rest.name = flagName + rest.key = newFlagKey + } + + if (operation === 'toggle_flag') { + rest.enabled = rest.enabled === 'true' + } + + if (rest.temporary !== undefined) { + rest.temporary = rest.temporary === 'true' + } + + if (rest.archive !== undefined) { + if (rest.archive === 'true') rest.archive = true + else if (rest.archive === 'false') rest.archive = false + else rest.archive = undefined + } + + if (rest.limit) { + rest.limit = Number(rest.limit) + } + + return rest + }, + }, + }, + + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + apiKey: { type: 'string', description: 'LaunchDarkly API key' }, + projectKey: { type: 'string', description: 'Project key' }, + flagKey: { type: 'string', description: 'Feature flag key' }, + environmentKey: { type: 'string', description: 'Environment key' }, + enabled: { type: 'string', description: 'Whether to enable or disable the flag' }, + flagName: { type: 'string', description: 'Human-readable name for the flag' }, + newFlagKey: { type: 'string', description: 'Unique key for the new flag' }, + description: { type: 'string', description: 'Flag description' }, + tags: { type: 'string', description: 'Comma-separated tags' }, + temporary: { type: 'string', description: 'Whether the flag is temporary' }, + updateName: { type: 'string', description: 'New name for update operation' }, + updateDescription: { type: 'string', description: 'New description for update operation' }, + addTags: { type: 'string', description: 'Comma-separated tags to add' }, + removeTags: { type: 'string', description: 'Comma-separated tags to remove' }, + archive: { type: 'string', description: 'Archive or restore flag' }, + comment: { type: 'string', description: 'Comment for the update' }, + spec: { type: 'string', description: 'Audit log filter expression' }, + tag: { type: 'string', description: 'Filter flags by tag' }, + limit: { type: 'string', description: 'Maximum number of results' }, + }, + + outputs: { + flags: { type: 'json', description: 'List of feature flags' }, + totalCount: { type: 'number', description: 'Total number of results' }, + key: { type: 'string', description: 'Feature flag key' }, + name: { type: 'string', description: 'Feature flag or status name' }, + kind: { type: 'string', description: 'Flag type (boolean or multivariate)' }, + description: { type: 'string', description: 'Flag description' }, + temporary: { type: 'boolean', description: 'Whether the flag is temporary' }, + archived: { type: 'boolean', description: 'Whether the flag is archived' }, + on: { type: 'boolean', description: 'Whether the flag is on in the environment' }, + deleted: { type: 'boolean', description: 'Whether the flag was deleted' }, + projects: { type: 'json', description: 'List of projects' }, + environments: { type: 'json', description: 'List of environments' }, + segments: { type: 'json', description: 'List of segments' }, + members: { type: 'json', description: 'List of members' }, + entries: { type: 'json', description: 'List of audit log entries' }, + lastRequested: { type: 'string', description: 'Last time the flag was evaluated' }, + }, +} diff --git a/apps/sim/blocks/registry.ts b/apps/sim/blocks/registry.ts index 1f888b7a03..7b6f5c8102 100644 --- a/apps/sim/blocks/registry.ts +++ b/apps/sim/blocks/registry.ts @@ -95,6 +95,7 @@ import { KalshiBlock, KalshiV2Block } from '@/blocks/blocks/kalshi' import { KetchBlock } from '@/blocks/blocks/ketch' import { KnowledgeBlock } from '@/blocks/blocks/knowledge' import { LangsmithBlock } from '@/blocks/blocks/langsmith' +import { LaunchDarklyBlock } from '@/blocks/blocks/launchdarkly' import { LemlistBlock } from '@/blocks/blocks/lemlist' import { LinearBlock } from '@/blocks/blocks/linear' import { LinkedInBlock } from '@/blocks/blocks/linkedin' @@ -320,6 +321,7 @@ export const registry: Record = { ketch: KetchBlock, knowledge: KnowledgeBlock, langsmith: LangsmithBlock, + launchdarkly: LaunchDarklyBlock, lemlist: LemlistBlock, linear: LinearBlock, linkedin: LinkedInBlock, diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index cfed6eeb7b..614c686ec5 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -89,6 +89,7 @@ export type IntegrationTag = | 'forms' | 'link-management' | 'events' + | 'feature-flags' // Authentication modes for sub-blocks and summaries export enum AuthMode { diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index 86ccbef282..b3c96ada69 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -2152,6 +2152,17 @@ export function LangsmithIcon(props: SVGProps) { ) } +export function LaunchDarklyIcon(props: SVGProps) { + return ( + + + + ) +} + export function LemlistIcon(props: SVGProps) { return ( diff --git a/apps/sim/tools/launchdarkly/create_flag.ts b/apps/sim/tools/launchdarkly/create_flag.ts new file mode 100644 index 0000000000..c5cb71e681 --- /dev/null +++ b/apps/sim/tools/launchdarkly/create_flag.ts @@ -0,0 +1,124 @@ +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 = { + 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, + }, + 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, + }, + } + }, + + outputs: FLAG_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/launchdarkly/delete_flag.ts b/apps/sim/tools/launchdarkly/delete_flag.ts new file mode 100644 index 0000000000..2ec772ecb0 --- /dev/null +++ b/apps/sim/tools/launchdarkly/delete_flag.ts @@ -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' }, + }, +} diff --git a/apps/sim/tools/launchdarkly/get_audit_log.ts b/apps/sim/tools/launchdarkly/get_audit_log.ts new file mode 100644 index 0000000000..0c31ebd37b --- /dev/null +++ b/apps/sim/tools/launchdarkly/get_audit_log.ts @@ -0,0 +1,95 @@ +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: 'Filter expression (e.g., "resourceType:flag")', + }, + }, + + 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) => { + const member = item.member as Record | undefined + const target = item.target as Record | 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' }, + }, +} diff --git a/apps/sim/tools/launchdarkly/get_flag.ts b/apps/sim/tools/launchdarkly/get_flag.ts new file mode 100644 index 0000000000..68c7f01584 --- /dev/null +++ b/apps/sim/tools/launchdarkly/get_flag.ts @@ -0,0 +1,119 @@ +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) => { + 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, + on: null, + }, + error: error.message, + } + } + + const data = await response.json() + + const environments = data.environments as Record> | undefined + let on: boolean | null = null + if (environments) { + 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, + on, + }, + } + }, + + outputs: { + ...FLAG_OUTPUT_PROPERTIES, + on: { + type: 'boolean', + description: + 'Whether the flag is on in the requested environment (null if no single environment was specified)', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/launchdarkly/get_flag_status.ts b/apps/sim/tools/launchdarkly/get_flag_status.ts new file mode 100644 index 0000000000..584de06e0a --- /dev/null +++ b/apps/sim/tools/launchdarkly/get_flag_status.ts @@ -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, +} diff --git a/apps/sim/tools/launchdarkly/index.ts b/apps/sim/tools/launchdarkly/index.ts new file mode 100644 index 0000000000..b134cb4d06 --- /dev/null +++ b/apps/sim/tools/launchdarkly/index.ts @@ -0,0 +1,38 @@ +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 type { + LaunchDarklyCreateFlagParams, + LaunchDarklyCreateFlagResponse, + LaunchDarklyDeleteFlagParams, + LaunchDarklyDeleteFlagResponse, + LaunchDarklyGetAuditLogParams, + LaunchDarklyGetAuditLogResponse, + LaunchDarklyGetFlagParams, + LaunchDarklyGetFlagResponse, + LaunchDarklyGetFlagStatusParams, + LaunchDarklyGetFlagStatusResponse, + LaunchDarklyListEnvironmentsParams, + LaunchDarklyListEnvironmentsResponse, + LaunchDarklyListFlagsParams, + LaunchDarklyListFlagsResponse, + LaunchDarklyListMembersParams, + LaunchDarklyListMembersResponse, + LaunchDarklyListProjectsParams, + LaunchDarklyListProjectsResponse, + LaunchDarklyListSegmentsParams, + LaunchDarklyListSegmentsResponse, + LaunchDarklyToggleFlagParams, + LaunchDarklyToggleFlagResponse, + LaunchDarklyUpdateFlagParams, + LaunchDarklyUpdateFlagResponse, +} from '@/tools/launchdarkly/types' +export { launchDarklyUpdateFlagTool } from '@/tools/launchdarkly/update_flag' diff --git a/apps/sim/tools/launchdarkly/list_environments.ts b/apps/sim/tools/launchdarkly/list_environments.ts new file mode 100644 index 0000000000..aa1647553a --- /dev/null +++ b/apps/sim/tools/launchdarkly/list_environments.ts @@ -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) => ({ + 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' }, + }, +} diff --git a/apps/sim/tools/launchdarkly/list_flags.ts b/apps/sim/tools/launchdarkly/list_flags.ts new file mode 100644 index 0000000000..bd4b208cd4 --- /dev/null +++ b/apps/sim/tools/launchdarkly/list_flags.ts @@ -0,0 +1,106 @@ +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) => ({ + 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>) ?? [], + maintainerId: item.maintainerId ?? 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' }, + }, +} diff --git a/apps/sim/tools/launchdarkly/list_members.ts b/apps/sim/tools/launchdarkly/list_members.ts new file mode 100644 index 0000000000..969098b6fe --- /dev/null +++ b/apps/sim/tools/launchdarkly/list_members.ts @@ -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) => ({ + 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' }, + }, +} diff --git a/apps/sim/tools/launchdarkly/list_projects.ts b/apps/sim/tools/launchdarkly/list_projects.ts new file mode 100644 index 0000000000..e06a63a27b --- /dev/null +++ b/apps/sim/tools/launchdarkly/list_projects.ts @@ -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) => ({ + 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' }, + }, +} diff --git a/apps/sim/tools/launchdarkly/list_segments.ts b/apps/sim/tools/launchdarkly/list_segments.ts new file mode 100644 index 0000000000..c86e45082e --- /dev/null +++ b/apps/sim/tools/launchdarkly/list_segments.ts @@ -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) => ({ + 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' }, + }, +} diff --git a/apps/sim/tools/launchdarkly/toggle_flag.ts b/apps/sim/tools/launchdarkly/toggle_flag.ts new file mode 100644 index 0000000000..fc4bdc429a --- /dev/null +++ b/apps/sim/tools/launchdarkly/toggle_flag.ts @@ -0,0 +1,125 @@ +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, + on: null, + }, + error: error.message, + } + } + + const data = await response.json() + + const environments = data.environments as Record> | 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, + on, + }, + } + }, + + outputs: { + ...FLAG_OUTPUT_PROPERTIES, + on: { + type: 'boolean', + description: 'Whether the flag is now on in the target environment', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/launchdarkly/types.ts b/apps/sim/tools/launchdarkly/types.ts new file mode 100644 index 0000000000..beeea949cb --- /dev/null +++ b/apps/sim/tools/launchdarkly/types.ts @@ -0,0 +1,362 @@ +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' }, + 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, + }, +} as const satisfies Record + +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 + +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 + +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: 'Kind of the target resource', optional: true }, +} as const satisfies Record + +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 + +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 + +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 + +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 +} + +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 + } +} diff --git a/apps/sim/tools/launchdarkly/update_flag.ts b/apps/sim/tools/launchdarkly/update_flag.ts new file mode 100644 index 0000000000..fe750ac4e0 --- /dev/null +++ b/apps/sim/tools/launchdarkly/update_flag.ts @@ -0,0 +1,165 @@ +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 a 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> = [] + + 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 = { 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, + }, + 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, + }, + } + }, + + outputs: FLAG_OUTPUT_PROPERTIES, +} diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index 36bb3e2739..113bff549d 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -1290,6 +1290,20 @@ import { knowledgeUpsertDocumentTool, } from '@/tools/knowledge' import { langsmithCreateRunsBatchTool, langsmithCreateRunTool } from '@/tools/langsmith' +import { + launchDarklyCreateFlagTool, + launchDarklyDeleteFlagTool, + launchDarklyGetAuditLogTool, + launchDarklyGetFlagStatusTool, + launchDarklyGetFlagTool, + launchDarklyListEnvironmentsTool, + launchDarklyListFlagsTool, + launchDarklyListMembersTool, + launchDarklyListProjectsTool, + launchDarklyListSegmentsTool, + launchDarklyToggleFlagTool, + launchDarklyUpdateFlagTool, +} from '@/tools/launchdarkly' import { lemlistGetActivitiesTool, lemlistGetLeadTool, lemlistSendEmailTool } from '@/tools/lemlist' import { linearAddLabelToIssueTool, @@ -4255,6 +4269,18 @@ export const tools: Record = { linear_list_project_statuses: linearListProjectStatusesTool, langsmith_create_run: langsmithCreateRunTool, langsmith_create_runs_batch: langsmithCreateRunsBatchTool, + launchdarkly_create_flag: launchDarklyCreateFlagTool, + launchdarkly_delete_flag: launchDarklyDeleteFlagTool, + launchdarkly_get_audit_log: launchDarklyGetAuditLogTool, + launchdarkly_get_flag: launchDarklyGetFlagTool, + launchdarkly_get_flag_status: launchDarklyGetFlagStatusTool, + launchdarkly_list_environments: launchDarklyListEnvironmentsTool, + launchdarkly_list_flags: launchDarklyListFlagsTool, + launchdarkly_list_members: launchDarklyListMembersTool, + launchdarkly_list_projects: launchDarklyListProjectsTool, + launchdarkly_list_segments: launchDarklyListSegmentsTool, + launchdarkly_toggle_flag: launchDarklyToggleFlagTool, + launchdarkly_update_flag: launchDarklyUpdateFlagTool, lemlist_get_activities: lemlistGetActivitiesTool, lemlist_get_lead: lemlistGetLeadTool, lemlist_send_email: lemlistSendEmailTool,