diff --git a/README.md b/README.md index d97001f..93bd5ee 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Agent Skills are modular, text-based playbooks that teach an agent how to perfor | `feature-flags/launchdarkly-flag-create` | Create new feature flags in a way that fits existing codebase patterns | | `feature-flags/launchdarkly-flag-targeting` | Control targeting, rollouts, rules, and cross-environment config | | `feature-flags/launchdarkly-flag-cleanup` | Safely remove flags from code using LaunchDarkly as the source of truth | +| `feature-flags/launchdarkly-flag-recreate` | Recreate a misconfigured flag when the key or variation type was set incorrectly | ### AI Configs diff --git a/skills.json b/skills.json index 5eefd12..a75f3ef 100644 --- a/skills.json +++ b/skills.json @@ -2,38 +2,51 @@ "skills": [ { "name": "aiconfig-create", - "description": "Guide for setting up AI configuration in your application. Helps you choose between agent vs completion mode, select the right approach for your stack, and create AI Configs that make sense for your use case.", + "description": "Create and configure AI Configs in LaunchDarkly. Helps you choose between agent vs completion mode, create the config, add variations with models and prompts, and verify the setup.", "path": "skills/ai-configs/aiconfig-create", - "version": "0.2.0", - "compatibility": "Requires LaunchDarkly API access token with ai-configs:write permission or LaunchDarkly MCP server." + "version": "1.0.0-experimental", + "license": "Apache-2.0", + "compatibility": "Requires the remotely hosted LaunchDarkly MCP server" }, { "name": "aiconfig-projects", - "description": "Guide for setting up LaunchDarkly projects in your codebase. Helps you assess your stack, choose the right approach, and integrate project management that makes sense for your architecture.", + "description": "Set up LaunchDarkly projects for your application. Helps you create projects, retrieve SDK keys, and understand how projects organize your AI Configs and feature flags.", "path": "skills/ai-configs/aiconfig-projects", - "version": "0.4.0", - "compatibility": "Requires LaunchDarkly API access token with projects:write permission or LaunchDarkly MCP server." + "version": "1.0.0-experimental", + "license": "Apache-2.0", + "compatibility": "Requires the remotely hosted LaunchDarkly MCP server" }, { "name": "aiconfig-tools", - "description": "Guide for giving your AI agents capabilities through tools. Helps you identify what your AI needs to do, create tool definitions, and attach them in a way that makes sense for your framework.", + "description": "Give your AI agents capabilities through tools (function calling). Helps you identify what your AI needs to do, create tool definitions, and attach them to AI Config variations.", "path": "skills/ai-configs/aiconfig-tools", - "version": "0.2.0", - "compatibility": "Requires LaunchDarkly API token with ai-tool permissions." + "version": "1.0.0-experimental", + "license": "Apache-2.0", + "compatibility": "Requires the remotely hosted LaunchDarkly MCP server" }, { "name": "aiconfig-update", "description": "Update, archive, and delete LaunchDarkly AI Configs and their variations. Use when you need to modify config properties, change model parameters, update instructions or messages, archive unused configs, or permanently remove them.", "path": "skills/ai-configs/aiconfig-update", - "version": "0.2.0", - "compatibility": "Requires LaunchDarkly project with AI Configs enabled and API access token." + "version": "1.0.0-experimental", + "license": "Apache-2.0", + "compatibility": "Requires the remotely hosted LaunchDarkly MCP server" }, { "name": "aiconfig-variations", - "description": "Guide for experimenting with AI configurations. Helps you test different models, prompts, and parameters to find what works best through systematic experimentation.", + "description": "Experiment with AI configurations by creating and managing variations. Helps you test different models, prompts, and parameters to find what works best through systematic experimentation.", "path": "skills/ai-configs/aiconfig-variations", - "version": "0.2.0", - "compatibility": "Requires LaunchDarkly API access token with ai-configs:write permission." + "version": "1.0.0-experimental", + "license": "Apache-2.0", + "compatibility": "Requires the remotely hosted LaunchDarkly MCP server" + }, + { + "name": "build-mcp-tools", + "description": "Guide for designing and building MCP tools using Gram Functions. Covers philosophy, codebase architecture, tool complexity patterns, and a step-by-step workflow for creating tools that return the most meaningful information to agents.", + "path": "skills/tooling/build-mcp-tools", + "version": "1.0.0-experimental", + "license": "Apache-2.0", + "compatibility": "This codebase uses @gram-ai/functions with TypeScript" }, { "name": "create-skill", @@ -100,6 +113,14 @@ "mcp" ] }, + { + "name": "launchdarkly-flag-recreate", + "description": "Recreate a misconfigured LaunchDarkly feature flag when the key or variation type was set wrong. Use when a flag has a typo in its key, the wrong kind (boolean vs multivariate), or incorrect variation values that cannot be changed after creation.", + "path": "skills/feature-flags/launchdarkly-flag-recreate", + "version": "0.1.0", + "license": "Apache-2.0", + "compatibility": "Requires the remotely hosted LaunchDarkly MCP server" + }, { "name": "launchdarkly-flag-targeting", "description": "Control LaunchDarkly feature flag targeting including toggling flags on/off, percentage rollouts, targeting rules, individual targets, and copying flag configurations between environments. Use when the user wants to change who sees a flag, roll out to a percentage, add targeting rules, or promote config between environments.", diff --git a/skills/ai-configs/aiconfig-create/README.md b/skills/ai-configs/aiconfig-create/README.md index 29ca8d6..c4e00a7 100644 --- a/skills/ai-configs/aiconfig-create/README.md +++ b/skills/ai-configs/aiconfig-create/README.md @@ -6,9 +6,9 @@ An Agent Skill for creating AI Configs in LaunchDarkly. Guides choosing agent vs This skill teaches agents how to: - Understand the use case and choose agent vs completion mode -- Create AI Configs via the two-step API process (config then variations) -- Set up model configuration with the correct modelConfigKey -- Verify creation via API fetch +- Create AI Configs using MCP tools (`setup-ai-config` for one-step, or `create-ai-config` + `create-ai-config-variation` for more control) +- Set up model configuration with the correct `modelConfigKey` format +- Verify creation via the tool response or `get-ai-config` ## Installation (Local) @@ -16,8 +16,7 @@ Copy `skills/ai-configs/aiconfig-create/` into your agent client's skills path. ## Prerequisites -- LaunchDarkly API access token with `ai-configs:write` permission or MCP server -- LaunchDarkly project (use `aiconfig-projects` skill if needed) +This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment. ## Usage @@ -34,9 +33,7 @@ Set up an AI config for content generation using Claude ``` aiconfig-create/ ├── SKILL.md -├── README.md -└── references/ - └── api-quickstart.md +└── README.md ``` ## Related diff --git a/skills/ai-configs/aiconfig-create/SKILL.md b/skills/ai-configs/aiconfig-create/SKILL.md index 845f491..ed9457f 100644 --- a/skills/ai-configs/aiconfig-create/SKILL.md +++ b/skills/ai-configs/aiconfig-create/SKILL.md @@ -1,43 +1,42 @@ --- name: aiconfig-create -description: Guide for setting up AI configuration in your application. Helps you choose between agent vs completion mode, select the right approach for your stack, and create AI Configs that make sense for your use case. -compatibility: Requires LaunchDarkly API access token with ai-configs:write permission or LaunchDarkly MCP server. +description: "Create and configure AI Configs in LaunchDarkly. Helps you choose between agent vs completion mode, create the config, add variations with models and prompts, and verify the setup." +license: Apache-2.0 +compatibility: Requires the remotely hosted LaunchDarkly MCP server metadata: author: launchdarkly - version: "0.2.0" + version: "1.0.0-experimental" --- # Create AI Config -You're using a skill that will guide you through setting up AI configuration in your application. Your job is to explore the codebase to understand the use case and stack, choose agent vs completion mode, create the config following the right path, and verify it works. +You're using a skill that will guide you through creating an AI Config in LaunchDarkly. Your job is to understand the use case, choose the right mode, create the config and its variations, and verify everything is set up correctly. ## Prerequisites -- LaunchDarkly API access token with `ai-configs:write` permission or MCP server -- LaunchDarkly project (use `aiconfig-projects` skill if needed) +This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment. -## Core Principles +**Primary MCP tool:** +- `setup-ai-config` -- create a config with its first variation in one step (recommended) -1. **Understand the Use Case First**: Know what you're building before choosing a mode -2. **Choose the Right Mode**: Agent mode vs completion mode depends on your framework and needs -3. **Two-Step Creation**: Create config first, then create variations (model, prompts, parameters) -4. **Verify via API**: The agent fetches the config to confirm it was created correctly +**Alternative MCP tools (for more control):** +- `create-ai-config` -- create just the config shell (key, name, mode) +- `create-ai-config-variation` -- add a variation with model, prompts, and parameters +- `get-ai-config` -- verify the config was created correctly -## API Key Detection - -1. **Check environment variables** — `LAUNCHDARKLY_API_KEY`, `LAUNCHDARKLY_API_TOKEN`, `LD_API_KEY` -2. **Check MCP config** — Claude: `~/.claude/config.json` → `mcpServers.launchdarkly.env.LAUNCHDARKLY_API_KEY` -3. **Prompt user** — Only if detection fails +**Optional MCP tools (enhance workflow):** +- `list-ai-configs` -- browse existing configs to understand naming conventions +- `create-project` -- create a project if one doesn't exist yet ## Workflow -### Step 1: Understand Your Use Case +### Step 1: Understand the Use Case Before creating, identify what you're building: - **What framework?** LangGraph, LangChain, CrewAI, OpenAI SDK, Anthropic SDK, custom -- **What does the AI need?** Just text, or tools/function calling? -- **Agent or completion?** See decision below +- **What does the AI need?** Just text generation, or tools/function calling? +- **Agent or completion?** See the decision matrix below ### Step 2: Choose Agent vs Completion Mode @@ -49,66 +48,85 @@ Before creating, identify what you're building: | Full control of message structure | **Completion** | | One-off text generation | **Completion** | -**Both modes support tools.** Agent mode: single `instructions` string. Completion mode: full `messages` array. +**Both modes support tools.** Agent mode uses a single `instructions` string. Completion mode uses a full `messages` array with roles. + +### Step 3: Create the Config (Recommended: One Step) + +Use `setup-ai-config` to create the config and its first variation in one call. This is the recommended approach — it handles creation, variation setup, and verification automatically. + +**Config fields:** +- `key` -- unique identifier (lowercase, hyphens) +- `name` -- human-readable name +- `mode` -- `"agent"` or `"completion"` +- Optional: `description`, `tags` + +**Variation fields:** +- `variationKey`, `variationName` -- identifiers for the first variation +- `modelConfigKey` -- must be `Provider.model-id` format (e.g., `OpenAI.gpt-4o`, `Anthropic.claude-sonnet-4-5`) +- `modelName` -- the model identifier (e.g., `gpt-4o`) + +**For agent mode**, provide: +- `instructions` -- a string with the agent's system instructions -### Step 3: Create the Config +**For completion mode**, provide: +- `messages` -- an array of `{role, content}` objects (system, user, assistant) -Follow [API Quick Start](references/api-quickstart.md) for curl examples: +**Optional:** +- `parameters` -- model parameters like `{temperature: 0.7, maxTokens: 2000}` -1. **Create config** — `POST /projects/{projectKey}/ai-configs` (key, name, mode) -2. **Create variation** — `POST /projects/{projectKey}/ai-configs/{configKey}/variations` (instructions or messages, modelConfigKey, model.parameters) -3. **Attach tools** — After creation, PATCH variation to add tools (see `aiconfig-tools` skill) +The tool returns the full verified config detail with the variation attached. + +### Step 3 (Alternative): Two-Step Creation + +If you need more control (e.g., custom headers, conditional logic), use the individual tools: + +1. `create-ai-config` -- create the config shell +2. `create-ai-config-variation` -- add the variation +3. `get-ai-config` -- verify the result ### Step 4: Verify -After creation, verify the config: +If you used `setup-ai-config`, verification is automatic — the response includes the full config with variations. Check: -1. **Fetch via API:** - ```bash - curl -X GET "https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}" \ - -H "Authorization: {api_token}" -H "LD-API-Version: beta" - ``` +1. Config exists with the correct mode +2. Variation has a model assigned (not "NO MODEL") +3. Instructions or messages are present +4. Parameters are set -2. **Confirm:** - - Config exists with correct mode - - Variations have model names (not "NO MODEL") - - modelConfigKey is set - - Parameters are present +**Report results:** +- Config created with correct structure +- Variation has model assigned +- Flag any missing model or parameters +- Provide config URL: `https://app.launchdarkly.com/projects/{projectKey}/ai-configs/{configKey}` -3. **Report results:** - - ✓ Config created with correct structure - - ✓ Variations have models assigned - - ⚠️ Flag any missing model or parameters - - Provide config URL: `https://app.launchdarkly.com/projects/{projectKey}/ai-configs/{configKey}` +## modelConfigKey Format -## Important Notes +Required for models to display in the UI. Format: `{Provider}.{model-id}` -- **modelConfigKey** must be `{Provider}.{model-id}` (e.g., `OpenAI.gpt-4o`) for models to show in UI -- **Tools** must be created first (`aiconfig-tools` skill), then attached via PATCH -- **Tools endpoint** is `/ai-tools`, NOT `/ai-configs/tools` +- `OpenAI.gpt-4o` +- `OpenAI.gpt-4o-mini` +- `Anthropic.claude-sonnet-4-5` +- `Anthropic.claude-3-5-sonnet` + +The `create-ai-config-variation` tool validates this format and rejects invalid values. ## Edge Cases | Situation | Action | |-----------|--------| | Config already exists | Ask if user wants to update instead | -| Variation shows "NO MODEL" | PATCH variation with modelConfigKey and model | -| Invalid modelConfigKey | Use values from model-configs API | +| Variation shows "NO MODEL" | Use `update-ai-config-variation` to set modelConfigKey | +| Need to attach tools | Create tools first (`aiconfig-tools` skill), then update the variation | ## What NOT to Do - Don't create configs without understanding the use case - Don't skip the two-step process (config then variation) -- Don't try to attach tools during initial creation -- Don't forget modelConfigKey (models won't show) +- Don't try to attach tools during initial creation -- update the variation afterward +- Don't forget modelConfigKey (models won't show in the UI) ## Related Skills -- `aiconfig-tools` — Create tools before attaching -- `aiconfig-variations` — Add more variations for experimentation -- `aiconfig-update` — Modify configs based on learnings - -## References - -- [API Quick Start](references/api-quickstart.md) -- [LaunchDarkly AI Configs Docs](https://docs.launchdarkly.com/home/ai-configs) +- `aiconfig-tools` -- Create tools before attaching +- `aiconfig-variations` -- Add more variations for experimentation +- `aiconfig-update` -- Modify configs based on learnings diff --git a/skills/ai-configs/aiconfig-create/references/api-quickstart.md b/skills/ai-configs/aiconfig-create/references/api-quickstart.md deleted file mode 100644 index 85290fb..0000000 --- a/skills/ai-configs/aiconfig-create/references/api-quickstart.md +++ /dev/null @@ -1,121 +0,0 @@ -# API Quick Start - -Create AI Configs using the LaunchDarkly API. - -## Two-Step Process - -LaunchDarkly requires creating the config first, then adding variations. This ensures model configuration (`modelConfigKey`) is properly set and variations display correctly in the UI. Creating everything in one call can result in variations showing "NO MODEL" or missing parameters. - -1. **Create the config** — Basic metadata (key, name, mode) -2. **Create variations** — Model, prompts/instructions, parameters for each variation - -## Create Config - -```bash -curl -X POST \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -H "LD-API-Version: beta" \ - -d '{ - "key": "support-agent", - "name": "Customer Support Agent", - "mode": "agent" - }' -``` - -## Create Variation (Agent Mode) - -```bash -curl -X POST \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/variations \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -H "LD-API-Version: beta" \ - -d '{ - "key": "default", - "name": "Default Configuration", - "instructions": "You are a helpful customer support agent.", - "modelConfigKey": "OpenAI.gpt-4o", - "model": { - "modelName": "gpt-4o", - "parameters": { - "temperature": 0.7, - "maxTokens": 2000 - } - } - }' -``` - -## Create Variation (Completion Mode) - -```bash -curl -X POST \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/variations \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -H "LD-API-Version: beta" \ - -d '{ - "key": "default", - "name": "Default Configuration", - "messages": [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "{{user_prompt}}"} - ], - "modelConfigKey": "Anthropic.claude-sonnet-4-5", - "model": { - "modelName": "claude-sonnet-4-5", - "parameters": { - "temperature": 0.8, - "maxTokens": 4000 - } - } - }' -``` - -## modelConfigKey Format - -Required for models to show in UI: `{Provider}.{model-id}` - -- `OpenAI.gpt-4o` -- `OpenAI.gpt-4o-mini` -- `Anthropic.claude-sonnet-4-5` -- `Anthropic.claude-3-5-sonnet` - -## Attach Tools (After Creation) - -Tools cannot be attached during config creation. PATCH the variation: - -```bash -curl -X PATCH \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/variations/{variationKey} \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -H "LD-API-Version: beta" \ - -d '{ - "model": { - "parameters": { - "tools": [ - {"key": "search-database", "version": 1} - ] - } - } - }' -``` - -## Verify Config - -```bash -curl -X GET \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey} \ - -H "Authorization: api-xxxxx" \ - -H "LD-API-Version: beta" -``` - -## List Models - -```bash -curl -X GET \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/model-configs \ - -H "Authorization: api-xxxxx" -``` diff --git a/skills/ai-configs/aiconfig-projects/README.md b/skills/ai-configs/aiconfig-projects/README.md index 2c65b01..5ded663 100644 --- a/skills/ai-configs/aiconfig-projects/README.md +++ b/skills/ai-configs/aiconfig-projects/README.md @@ -1,34 +1,25 @@ # LaunchDarkly AI Config Projects Skill -An Agent Skill for setting up LaunchDarkly project management in a codebase. Guides exploration of the stack, assessment of the right approach, and integration that fits the architecture. +An Agent Skill for setting up LaunchDarkly projects. Guides creating projects, retrieving SDK keys, and understanding how projects organize AI Configs and feature flags. ## Overview This skill teaches agents how to: -- Explore the codebase to understand the tech stack and patterns -- Assess what project setup approach makes sense -- Choose the right implementation path (by language, use case, or tooling) -- Create projects and save SDK keys via API or MCP -- Verify the setup via API fetch and SDK integration test +- Understand what LaunchDarkly projects are and how they organize resources +- Create projects using the `create-project` MCP tool +- Retrieve existing projects and SDK keys via `get-project` +- Follow project key naming best practices ## Installation (Local) -For now, install by placing this skill directory where your agent client loads skills. - -Examples: - -- **Generic**: copy `skills/ai-configs/aiconfig-projects/` into your client's skills path +Copy `skills/ai-configs/aiconfig-projects/` into your agent client's skills path. ## Prerequisites -**Choose one:** -- LaunchDarkly API access token with `projects:write` permission -- LaunchDarkly MCP server configured in your environment +This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment. ## Usage -Once installed, the skill activates automatically when you ask about project setup: - ``` Set up a LaunchDarkly project for our AI configs ``` @@ -37,26 +28,12 @@ Set up a LaunchDarkly project for our AI configs Create a project for our customer support agent ``` -``` -Add LaunchDarkly project management to this codebase -``` - ## Structure ``` aiconfig-projects/ ├── SKILL.md -├── README.md -└── references/ - ├── quick-start.md - ├── python-setup.md - ├── nodejs-setup.md - ├── go-setup.md - ├── env-config.md - ├── project-cloning.md - ├── iac-automation.md - ├── admin-tooling.md - └── multi-language-setup.md +└── README.md ``` ## Related diff --git a/skills/ai-configs/aiconfig-projects/SKILL.md b/skills/ai-configs/aiconfig-projects/SKILL.md index 7c9ffd1..dc37f46 100644 --- a/skills/ai-configs/aiconfig-projects/SKILL.md +++ b/skills/ai-configs/aiconfig-projects/SKILL.md @@ -1,237 +1,124 @@ --- name: aiconfig-projects -description: Guide for setting up LaunchDarkly projects in your codebase. Helps you assess your stack, choose the right approach, and integrate project management that makes sense for your architecture. -compatibility: Requires LaunchDarkly API access token with projects:write permission or LaunchDarkly MCP server. +description: "Set up LaunchDarkly projects for your application. Helps you create projects, retrieve SDK keys, and understand how projects organize your AI Configs and feature flags." +license: Apache-2.0 +compatibility: Requires the remotely hosted LaunchDarkly MCP server metadata: author: launchdarkly - version: "0.4.0" + version: "1.0.0-experimental" --- # LaunchDarkly Projects Setup -You're using a skill that will guide you through setting up LaunchDarkly project management in a codebase. Your job is to explore the codebase to understand the stack and patterns, assess what approach makes sense, choose the right implementation path from the references, execute the setup, and verify it works. +You're using a skill that will guide you through setting up a LaunchDarkly project. Your job is to understand what projects are, create one that fits the use case, retrieve the SDK keys, and verify the setup. ## Prerequisites -**Choose one:** -- LaunchDarkly API access token with `projects:write` permission -- LaunchDarkly MCP server configured in your environment +This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment. -## Core Principles - -1. **Understand First**: Explore the codebase to understand the stack and patterns. -2. **Choose the Right Fit**: Select an approach that matches your architecture. -3. **Follow Conventions**: Respect existing code style and structure. -4. **Verify Integration**: Confirm the setup works — the agent performs checks and reports results. - -## API Key Detection - -Before prompting the user for an API key, try to detect it automatically: - -1. **Check environment variables** — Look for `LAUNCHDARKLY_API_KEY`, `LAUNCHDARKLY_API_TOKEN`, or `LD_API_KEY` -2. **Check MCP config** — If using Claude, read `~/.claude/config.json` for `mcpServers.launchdarkly.env.LAUNCHDARKLY_API_KEY` -3. **Prompt user** — Only if detection fails, ask the user for their API key - -See [Quick Start](references/quick-start.md) for API usage patterns. +**Required MCP tools:** +- `create-project` -- create a new LaunchDarkly project +- `get-project` -- retrieve a project with its environments and SDK keys ## What Are Projects? Projects are LaunchDarkly's top-level organizational containers that hold: - All your AI Configs -- Feature flags and segments +- Feature flags and segments - Multiple environments (Production and Test created by default) Think of projects as separate applications, services, or teams that need their own isolated set of configurations. -## Project Setup Workflow - -### Step 1: Explore the Codebase - -Before implementing anything, understand the existing architecture: - -1. **Identify the tech stack:** - - What language(s)? (Python, Node.js, Go, Java, etc.) - - What framework(s)? (FastAPI, Express, Spring Boot, etc.) - - Is there an existing LaunchDarkly integration? - -2. **Check environment management:** - - How are environment variables stored? (.env files, secrets manager, config files) - - Where is configuration loaded? (startup scripts, config modules) - - Are there existing LaunchDarkly SDK keys? - -3. **Look for patterns:** - - Are there existing API clients or service modules? - - How is external API integration typically done? - - Is there a CLI, scripts directory, or admin tooling? - -4. **Understand the use case:** - - Is this a new project being set up? - - Adding to an existing LaunchDarkly integration? - - Part of a multi-service architecture? - - Need for project cloning across regions/teams? +## Workflow -### Step 2: Assess the Situation +### Step 1: Assess the Situation -Based on your exploration, determine the right approach: +Determine the right approach based on your use case: -| Scenario | Recommended Path | -|----------|------------------| -| New project, no LaunchDarkly integration | **Quick Setup** - Create project and save SDK keys | -| Existing LaunchDarkly usage | **Add to Existing** - Create new project or use existing | -| Multiple services/microservices | **Multi-Project** - Create projects per service | -| Multi-region or multi-tenant | **Project Cloning** - Clone template project | -| Infrastructure-as-Code (IaC) setup | **Automated Setup** - Script-based creation | -| Need project management tooling | **CLI/Admin Tools** - Build project management utilities | +| Scenario | Approach | +|----------|----------| +| New application, no LaunchDarkly integration | Create a new project | +| Existing LaunchDarkly usage | Use `get-project` to check if the right project exists | +| Multiple services/microservices | Create a project per service | +| Multi-region or multi-tenant | Create a project per region/tenant | -### Step 3: Choose Your Implementation Path +### Step 2: Create or Retrieve the Project -Select the reference guide that matches your stack and use case: +**New project** -- Use `create-project` with: +- `name` -- human-readable name +- `key` -- unique identifier (lowercase, hyphens, must start with a letter) +- Optional: `tags` for organization -**By Language/Stack:** -- [Python Implementation](references/python-setup.md) - For Python applications (FastAPI, Django, Flask) -- [Node.js/TypeScript Implementation](references/nodejs-setup.md) - For Node.js/Express/NestJS applications -- [Go Implementation](references/go-setup.md) - For Go services -- [Multi-Language Setup](references/multi-language-setup.md) - For polyglot architectures +**Existing project** -- Use `get-project` to retrieve it and its SDK keys. -**By Use Case:** -- [Quick Start](references/quick-start.md) - Create first project and get SDK keys -- [Environment Configuration](references/env-config.md) - Save SDK keys to .env, secrets, or config -- [Project Cloning](references/project-cloning.md) - Clone projects for regions/teams -- [IaC/Automation](references/iac-automation.md) - Terraform, scripts, CI/CD integration -- [Admin Tooling](references/admin-tooling.md) - Build CLI or admin utilities +### Step 3: Retrieve SDK Keys -### Step 4: Implement the Integration +The response from `create-project` or `get-project` includes environments with SDK keys. Each environment has: +- `sdkKey` -- for server-side SDKs +- `mobileKey` -- for mobile/client-side SDKs -Follow the chosen reference guide to implement project management. Key considerations: +Store these keys following your codebase's secrets management pattern (environment variables, secrets manager, etc.). -1. **API Authentication:** - - Store API token securely - - Follow existing secrets management patterns - - Never commit tokens to version control +### Step 4: Verify -2. **Project Naming:** - - Use consistent, descriptive names - - Follow existing naming conventions - - Project keys: lowercase, hyphens, start with letter +Use `get-project` to confirm: +1. Project exists with the correct name and key +2. Environments are present (Production, Test at minimum) +3. SDK keys are available -3. **SDK Key Management:** - - Extract and store SDK keys for each environment - - Use the same pattern as other secrets in your codebase - - Consider separate keys for test/staging/production - -4. **Error Handling:** - - Handle existing projects gracefully (409 conflict) - - Provide clear error messages - - Don't fail silently - -### Step 5: Verify the Setup - -After creating the project, verify it works: - -1. **Fetch via API to confirm it exists:** - ```bash - curl -X GET "https://app.launchdarkly.com/api/v2/projects/{projectKey}?expand=environments" \ - -H "Authorization: {api_token}" - ``` - Confirm the response includes the project, environments, and SDK keys. - -2. **Test SDK integration:** - Run a quick verification to ensure the SDK key works: - ```python - from ldclient import set_config, Config - set_config(Config("{sdk_key}")) - # SDK initializes successfully - ``` - -3. **Report results:** - - ✓ Project exists and has environments - - ✓ SDK keys are present and valid - - ✓ SDK can initialize (or flag any issues) +**Report results:** +- Project exists and has environments +- SDK keys are present +- Provide next steps (create AI Configs, set up SDK integration) ## Project Key Best Practices Project keys must follow these rules: +- Lowercase letters and hyphens only +- Must start with a letter +- No underscores, dots, or uppercase -``` -✓ Good examples: - - "support-ai" - - "chat-bot-v2" - - "internal-tools" - -✗ Bad examples: - - "Support_AI" # No uppercase or underscores - - "123-project" # Must start with letter - - "my.project" # No dots allowed -``` +Good examples: `support-ai`, `chat-bot-v2`, `internal-tools` -**Naming Recommendations:** +**Naming recommendations:** - Keep keys short but descriptive - Use team/service/purpose as naming scheme - Be consistent across your organization ## Common Organization Patterns -### By Team -``` -platform-ai → Platform Team AI -customer-ai → Customer Success Team AI -internal-ai → Internal Tools Team AI -``` - -### By Application/Service -``` -mobile-ai → Mobile App AI Configs -web-ai → Web App AI Configs -api-ai → API Service AI Configs -``` - -### By Region/Deployment -``` -ai-us → US Region -ai-eu → Europe Region -ai-apac → Asia-Pacific Region -``` +**By Team:** +- `platform-ai`, `customer-ai`, `internal-ai` + +**By Application/Service:** +- `mobile-ai`, `web-ai`, `api-ai` + +**By Region/Deployment:** +- `ai-us`, `ai-eu`, `ai-apac` ## Edge Cases | Situation | Action | |-----------|--------| -| Project already exists | Check if it's the right one; use it or create with different key | +| Project already exists (409) | Use `get-project` to retrieve existing | | Need multiple projects | Create separately for each service/region/team | -| Shared configs across services | Use same project, separate by SDK context | -| Token lacks permissions | Request `projects:write` or use MCP server | -| Project name conflict | Keys must be unique, names can be similar | +| Token lacks permissions | Check that the MCP server has `projects:write` permission | ## What NOT to Do - Don't create projects without understanding the use case first -- Don't commit API tokens or SDK keys to version control +- Don't commit SDK keys to version control - Don't use production SDK keys in test/development environments - Don't create duplicate projects unnecessarily -- Don't skip the exploration phase ## Next Steps After setting up projects: - -1. **Create AI Configs** - Use the `aiconfig-create` skill -2. **Set up SDK Integration** - Use the `aiconfig-sdk` skill -3. **Configure Targeting** - Use the `aiconfig-targeting` skill +1. **Create AI Configs** -- Use the `aiconfig-create` skill +2. **Configure targeting** -- Use flag targeting skills +3. **Set up SDK integration** -- Use the SDK keys in your application ## Related Skills -- `aiconfig-create` - Create AI Configs in projects -- `aiconfig-sdk` - Integrate SDK in your application -- `aiconfig-targeting` - Configure AI Config targeting -- `aiconfig-variations` - Manage config variations - -## References - -- [Python Implementation](references/python-setup.md) -- [Node.js Implementation](references/nodejs-setup.md) -- [Go Implementation](references/go-setup.md) -- [Quick Start Guide](references/quick-start.md) -- [Environment Configuration](references/env-config.md) -- [Project Cloning](references/project-cloning.md) -- [IaC/Automation](references/iac-automation.md) -- [Admin Tooling](references/admin-tooling.md) +- `aiconfig-create` -- Create AI Configs in projects +- `aiconfig-variations` -- Manage config variations diff --git a/skills/ai-configs/aiconfig-projects/references/admin-tooling.md b/skills/ai-configs/aiconfig-projects/references/admin-tooling.md deleted file mode 100644 index ec5abb3..0000000 --- a/skills/ai-configs/aiconfig-projects/references/admin-tooling.md +++ /dev/null @@ -1,503 +0,0 @@ -# Admin Tooling - -Build CLI tools and admin utilities for project management at scale. - -## Use Cases - -- **Bulk operations:** Create/manage many projects at once -- **Auditing:** Report on project usage and configuration -- **Maintenance:** Clean up unused projects -- **Onboarding:** Help teams set up their projects -- **Automation:** Integrate with CI/CD and IaC - -## Full-Featured CLI - -Build a comprehensive CLI tool: - -### Python with Click - -```python -# cli/ldprojects.py -import click -import json -from tabulate import tabulate -from launchdarkly.projects import ProjectManager - -pm = ProjectManager() - -@click.group() -@click.option('--api-token', envvar='LAUNCHDARKLY_API_TOKEN', help='LaunchDarkly API token') -@click.pass_context -def cli(ctx, api_token): - """LaunchDarkly project management CLI.""" - ctx.obj = ProjectManager(api_token) - -@cli.command() -@click.argument('name') -@click.argument('key') -@click.option('--tags', '-t', multiple=True, help='Project tags') -@click.option('--save-env/--no-save-env', default=True, help='Save SDK keys to .env') -@click.pass_obj -def create(pm, name, key, tags, save_env): - """Create a new project.""" - try: - project = pm.create_project(name, key, list(tags)) - click.echo(f"✓ Created: {project['name']} ({project['key']})") - - if save_env: - from launchdarkly.env_config import save_sdk_key_to_env - save_sdk_key_to_env(key, "production") - click.echo(f"✓ Saved SDK keys to .env") - except Exception as e: - click.echo(f"✗ Error: {e}", err=True) - raise click.Abort() - -@cli.command() -@click.option('--format', '-f', type=click.Choice(['table', 'json', 'csv']), default='table') -@click.option('--filter-tag', help='Filter by tag') -@click.pass_obj -def list(pm, format, filter_tag): - """List all projects.""" - try: - projects = pm.list_projects() - - if filter_tag: - projects = [p for p in projects if filter_tag in p.get('tags', [])] - - if format == 'json': - click.echo(json.dumps(projects, indent=2)) - elif format == 'csv': - click.echo("key,name,tags") - for p in projects: - tags = ','.join(p.get('tags', [])) - click.echo(f"{p['key']},{p['name']},{tags}") - else: # table - rows = [[p['key'], p['name'], ', '.join(p.get('tags', []))] for p in projects] - click.echo(tabulate(rows, headers=['Key', 'Name', 'Tags'])) - except Exception as e: - click.echo(f"✗ Error: {e}", err=True) - raise click.Abort() - -@cli.command() -@click.argument('project_key') -@click.option('--env', default='production', help='Environment') -@click.option('--show-key/--mask-key', default=False, help='Show full key') -@click.pass_obj -def get_key(pm, project_key, env, show_key): - """Get SDK key for a project.""" - try: - sdk_key = pm.get_sdk_key(project_key, env) - if sdk_key: - if show_key: - click.echo(sdk_key) - else: - click.echo(f"{sdk_key[:10]}...{sdk_key[-4:]}") - else: - click.echo(f"✗ Environment '{env}' not found", err=True) - raise click.Abort() - except Exception as e: - click.echo(f"✗ Error: {e}", err=True) - raise click.Abort() - -@cli.command() -@click.argument('source_key') -@click.argument('new_key') -@click.argument('new_name') -@click.option('--tags', '-t', multiple=True, help='Additional tags') -@click.pass_obj -def clone(pm, source_key, new_key, new_name, tags): - """Clone an existing project.""" - try: - from launchdarkly.cloning import clone_project - project = clone_project(source_key, new_name, new_key, list(tags)) - click.echo(f"✓ Cloned {source_key} → {new_key}") - except Exception as e: - click.echo(f"✗ Error: {e}", err=True) - raise click.Abort() - -@cli.command() -@click.argument('project_key') -@click.option('--environments', '-e', multiple=True, default=['production', 'test']) -@click.option('--output', '-o', type=click.File('w'), default='-') -@click.pass_obj -def export_keys(pm, project_key, environments, output): - """Export SDK keys for all environments.""" - try: - keys = {} - for env in environments: - sdk_key = pm.get_sdk_key(project_key, env) - if sdk_key: - keys[env] = sdk_key - - output.write(json.dumps(keys, indent=2)) - output.write('\n') - click.echo(f"✓ Exported keys for {project_key}", err=True) - except Exception as e: - click.echo(f"✗ Error: {e}", err=True) - raise click.Abort() - -@cli.command() -@click.argument('csv_file', type=click.File('r')) -@click.option('--dry-run/--execute', default=True) -@click.pass_obj -def bulk_create(pm, csv_file, dry_run): - """Create multiple projects from CSV file.""" - import csv - - reader = csv.DictReader(csv_file) - for row in reader: - key = row['key'] - name = row['name'] - tags = row.get('tags', '').split(',') if row.get('tags') else [] - - if dry_run: - click.echo(f"Would create: {key} ({name})") - else: - try: - project = pm.create_project(name, key, tags) - click.echo(f"✓ Created: {key}") - except Exception as e: - click.echo(f"✗ Failed to create {key}: {e}", err=True) - -@cli.command() -@click.pass_obj -def audit(pm): - """Audit all projects and show statistics.""" - try: - projects = pm.list_projects() - - # Gather statistics - total = len(projects) - tags_count = {} - for p in projects: - for tag in p.get('tags', []): - tags_count[tag] = tags_count.get(tag, 0) + 1 - - click.echo(f"\n📊 Project Audit Report\n") - click.echo(f"Total projects: {total}") - click.echo(f"\nTag distribution:") - for tag, count in sorted(tags_count.items(), key=lambda x: x[1], reverse=True): - click.echo(f" {tag}: {count}") - - except Exception as e: - click.echo(f"✗ Error: {e}", err=True) - raise click.Abort() - -if __name__ == '__main__': - cli() -``` - -**Install dependencies:** -```bash -pip install click tabulate -``` - -**Usage:** -```bash -# Create project -python cli/ldprojects.py create "My AI" my-ai -t ai-configs -t production - -# List projects -python cli/ldprojects.py list -python cli/ldprojects.py list --format json -python cli/ldprojects.py list --filter-tag ai-configs - -# Get SDK key -python cli/ldprojects.py get-key my-ai -python cli/ldprojects.py get-key my-ai --env test --show-key - -# Clone project -python cli/ldprojects.py clone template-ai new-ai "New AI Project" - -# Export keys -python cli/ldprojects.py export-keys my-ai -o keys.json - -# Bulk create -python cli/ldprojects.py bulk-create projects.csv --execute - -# Audit -python cli/ldprojects.py audit -``` - -## Web Admin Dashboard - -Build a simple web dashboard: - -### Flask Dashboard - -```python -# admin/app.py -from flask import Flask, render_template, request, redirect, jsonify -from launchdarkly.projects import ProjectManager - -app = Flask(__name__) -pm = ProjectManager() - -@app.route('/') -def index(): - projects = pm.list_projects() - return render_template('index.html', projects=projects) - -@app.route('/projects', methods=['POST']) -def create_project(): - data = request.json - project = pm.create_project( - name=data['name'], - key=data['key'], - tags=data.get('tags', []) - ) - return jsonify(project) - -@app.route('/projects/') -def project_detail(key): - project = pm.get_project(key) - return render_template('project.html', project=project) - -@app.route('/projects//keys/') -def get_sdk_key(key, env): - sdk_key = pm.get_sdk_key(key, env) - return jsonify({'sdkKey': sdk_key}) - -if __name__ == '__main__': - app.run(debug=True, port=5000) -``` - -**templates/index.html:** -```html - - - - LaunchDarkly Projects Admin - - - -

LaunchDarkly Projects

- -

Create New Project

-
- - - - -
- -

Existing Projects

- {% for project in projects %} -
-

{{ project.name }}

-

Key: {{ project.key }}

-

Tags: {{ project.tags|join(', ') }}

-
- {% endfor %} - - - - -``` - -**Run the dashboard:** -```bash -pip install flask -python admin/app.py -# Visit http://localhost:5000 -``` - -## Monitoring & Alerting - -Monitor project creation and usage: - -```python -# monitoring/project_monitor.py -import time -from datetime import datetime -from launchdarkly.projects import ProjectManager - -class ProjectMonitor: - """Monitor LaunchDarkly projects for changes.""" - - def __init__(self): - self.pm = ProjectManager() - self.known_projects = set() - - def check_for_new_projects(self): - """Check for newly created projects.""" - projects = self.pm.list_projects() - current_keys = {p['key'] for p in projects} - - new_projects = current_keys - self.known_projects - if new_projects: - self.on_new_projects(new_projects, projects) - - self.known_projects = current_keys - - def on_new_projects(self, new_keys, all_projects): - """Handle new projects.""" - for key in new_keys: - project = next(p for p in all_projects if p['key'] == key) - print(f"[{datetime.now()}] New project detected: {project['name']} ({key})") - # Send alert, log to DB, etc. - - def run(self, interval=60): - """Run monitor continuously.""" - print(f"Starting project monitor (interval: {interval}s)") - while True: - try: - self.check_for_new_projects() - except Exception as e: - print(f"Error: {e}") - time.sleep(interval) - -if __name__ == '__main__': - monitor = ProjectMonitor() - monitor.run() -``` - -## Backup & Recovery - -Backup project configurations: - -```python -# backup/project_backup.py -import json -from datetime import datetime -from launchdarkly.projects import ProjectManager - -def backup_all_projects(output_file=None): - """Backup all projects to JSON file.""" - pm = ProjectManager() - projects = pm.list_projects() - - if not output_file: - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - output_file = f"projects_backup_{timestamp}.json" - - with open(output_file, 'w') as f: - json.dump(projects, f, indent=2) - - print(f"✓ Backed up {len(projects)} projects to {output_file}") - return output_file - -def restore_projects(backup_file): - """Restore projects from backup (creates if missing).""" - pm = ProjectManager() - - with open(backup_file, 'r') as f: - projects = json.load(f) - - for project in projects: - try: - pm.create_project( - name=project['name'], - key=project['key'], - tags=project.get('tags', []) - ) - print(f"✓ Restored: {project['key']}") - except Exception as e: - print(f"✗ Failed to restore {project['key']}: {e}") - -# Usage -backup_all_projects() -# restore_projects('projects_backup_20260205_120000.json') -``` - -## Integration with Terraform - -Export projects to Terraform format: - -```python -# terraform/export_terraform.py -def export_to_terraform(project_keys=None): - """Export projects as Terraform configuration.""" - pm = ProjectManager() - projects = pm.list_projects() - - if project_keys: - projects = [p for p in projects if p['key'] in project_keys] - - tf_config = [] - for project in projects: - resource_name = project['key'].replace('-', '_') - tags = ', '.join(f'"{tag}"' for tag in project.get('tags', [])) - - tf_config.append(f''' -resource "launchdarkly_project" "{resource_name}" {{ - key = "{project['key']}" - name = "{project['name']}" - tags = [{tags}] -}} -''') - - output = '\n'.join(tf_config) - with open('projects.tf', 'w') as f: - f.write(output) - - print(f"✓ Exported {len(projects)} projects to projects.tf") - -# Usage -export_to_terraform() -``` - -## Slack Integration - -Send notifications to Slack: - -```python -# integrations/slack_notifier.py -import requests - -def notify_slack(webhook_url, message): - """Send notification to Slack.""" - requests.post(webhook_url, json={'text': message}) - -def notify_project_created(project, webhook_url): - """Notify Slack when project is created.""" - message = f"🎉 New LaunchDarkly project created: *{project['name']}* (`{project['key']}`)" - notify_slack(webhook_url, message) - -# Usage in CLI -@cli.command() -@click.argument('name') -@click.argument('key') -@click.option('--slack-webhook', envvar='SLACK_WEBHOOK_URL') -@click.pass_obj -def create_with_notification(pm, name, key, slack_webhook): - """Create project and notify Slack.""" - project = pm.create_project(name, key, []) - click.echo(f"✓ Created: {project['name']}") - - if slack_webhook: - notify_project_created(project, slack_webhook) - click.echo("✓ Notified Slack") -``` - -## Next Steps - -- [Set up IaC automation](iac-automation.md) -- [Configure project cloning](project-cloning.md) -- [Manage SDK keys](env-config.md) diff --git a/skills/ai-configs/aiconfig-projects/references/env-config.md b/skills/ai-configs/aiconfig-projects/references/env-config.md deleted file mode 100644 index 337ca7f..0000000 --- a/skills/ai-configs/aiconfig-projects/references/env-config.md +++ /dev/null @@ -1,368 +0,0 @@ -# Environment Configuration - -Patterns for saving SDK keys to your codebase's configuration system. - -## Overview - -After creating a project, you need to save the SDK keys so your application can use them. The approach depends on your existing configuration pattern. - -## Common Patterns - -### 1. .env Files - -Most common pattern for local development and simple deployments. - -#### Python -```python -def save_sdk_key_to_env( - project_key: str, - environment: str = "production", - env_file: str = ".env", - var_name: str = "LAUNCHDARKLY_SDK_KEY" -): - """Save SDK key to .env file.""" - # Get the SDK key - pm = ProjectManager() - sdk_key = pm.get_sdk_key(project_key, environment) - - if not sdk_key: - raise ValueError(f"Could not get SDK key for {project_key}/{environment}") - - # Read existing .env content - env_content = {} - if os.path.exists(env_file): - with open(env_file, "r") as f: - for line in f: - line = line.strip() - if line and not line.startswith("#") and "=" in line: - key, value = line.split("=", 1) - env_content[key] = value - - # Update or add the SDK key - env_content[var_name] = sdk_key - - # Write back to .env - with open(env_file, "w") as f: - for key, value in env_content.items(): - f.write(f"{key}={value}\n") - - print(f"✓ Saved {var_name} to {env_file}") -``` - -#### Node.js/TypeScript -```typescript -import * as fs from 'fs'; -import * as path from 'path'; - -async function saveSdkKeyToEnv( - projectKey: string, - environment: string = 'production', - envFile: string = '.env', - varName: string = 'LAUNCHDARKLY_SDK_KEY' -): Promise { - const pm = new ProjectManager(); - const sdkKey = await pm.getSdkKey(projectKey, environment); - - if (!sdkKey) { - throw new Error(`Could not get SDK key for ${projectKey}/${environment}`); - } - - // Read existing .env content - const envContent: Record = {}; - if (fs.existsSync(envFile)) { - const content = fs.readFileSync(envFile, 'utf-8'); - content.split('\n').forEach((line) => { - const trimmed = line.trim(); - if (trimmed && !trimmed.startsWith('#') && trimmed.includes('=')) { - const [key, ...valueParts] = trimmed.split('='); - envContent[key] = valueParts.join('='); - } - }); - } - - // Update or add the SDK key - envContent[varName] = sdkKey; - - // Write back to .env - const lines = Object.entries(envContent).map(([key, value]) => `${key}=${value}`); - fs.writeFileSync(envFile, lines.join('\n') + '\n'); - - console.log(`✓ Saved ${varName} to ${envFile}`); -} -``` - -#### Usage -```bash -# Python -python -c "from launchdarkly.projects import save_sdk_key_to_env; save_sdk_key_to_env('my-project')" - -# Node.js -node -e "require('./src/launchdarkly/env-config').saveSdkKeyToEnv('my-project')" -``` - -### 2. Multiple Environments - -Save keys for multiple environments: - -```python -# Save both production and test keys -save_sdk_key_to_env("my-project", "production", var_name="LD_SDK_KEY_PROD") -save_sdk_key_to_env("my-project", "test", var_name="LD_SDK_KEY_TEST") -``` - -**.env result:** -```bash -LD_SDK_KEY_PROD=sdk-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -LD_SDK_KEY_TEST=sdk-yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy -``` - -### 3. Secrets Manager Integration - -For cloud deployments, integrate with secrets managers. - -#### AWS Secrets Manager -```python -import boto3 -import json - -def save_to_aws_secrets(project_key: str, environment: str, secret_name: str): - """Save SDK key to AWS Secrets Manager.""" - pm = ProjectManager() - sdk_key = pm.get_sdk_key(project_key, environment) - - client = boto3.client('secretsmanager') - - try: - # Get existing secret - response = client.get_secret_value(SecretId=secret_name) - secrets = json.loads(response['SecretString']) - except client.exceptions.ResourceNotFoundException: - secrets = {} - - # Update with new key - secrets['LAUNCHDARKLY_SDK_KEY'] = sdk_key - - # Save back - client.put_secret_value( - SecretId=secret_name, - SecretString=json.dumps(secrets) - ) - - print(f"✓ Saved SDK key to AWS Secrets Manager: {secret_name}") - -# Usage -save_to_aws_secrets("my-project", "production", "myapp/production") -``` - -#### GCP Secret Manager -```python -from google.cloud import secretmanager - -def save_to_gcp_secrets(project_key: str, environment: str, secret_id: str, gcp_project: str): - """Save SDK key to GCP Secret Manager.""" - pm = ProjectManager() - sdk_key = pm.get_sdk_key(project_key, environment) - - client = secretmanager.SecretManagerServiceClient() - parent = f"projects/{gcp_project}/secrets/{secret_id}" - - # Add new version - response = client.add_secret_version( - request={ - "parent": parent, - "payload": {"data": sdk_key.encode("UTF-8")}, - } - ) - - print(f"✓ Saved SDK key to GCP Secret Manager: {response.name}") -``` - -#### Azure Key Vault -```python -from azure.keyvault.secrets import SecretClient -from azure.identity import DefaultAzureCredential - -def save_to_azure_keyvault(project_key: str, environment: str, vault_url: str, secret_name: str): - """Save SDK key to Azure Key Vault.""" - pm = ProjectManager() - sdk_key = pm.get_sdk_key(project_key, environment) - - credential = DefaultAzureCredential() - client = SecretClient(vault_url=vault_url, credential=credential) - - client.set_secret(secret_name, sdk_key) - - print(f"✓ Saved SDK key to Azure Key Vault: {secret_name}") -``` - -### 4. Kubernetes Secrets - -For Kubernetes deployments: - -```python -import base64 -import yaml - -def create_k8s_secret(project_key: str, environment: str, namespace: str = "default"): - """Generate Kubernetes secret manifest.""" - pm = ProjectManager() - sdk_key = pm.get_sdk_key(project_key, environment) - - # Encode SDK key - encoded_key = base64.b64encode(sdk_key.encode()).decode() - - secret = { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "launchdarkly-sdk-key", - "namespace": namespace - }, - "type": "Opaque", - "data": { - "sdk-key": encoded_key - } - } - - # Write to file - with open("k8s-secret.yaml", "w") as f: - yaml.dump(secret, f) - - print("✓ Created k8s-secret.yaml") - print("Apply with: kubectl apply -f k8s-secret.yaml") -``` - -### 5. Configuration Files - -For applications using config files (YAML, JSON, TOML): - -#### YAML Config -```python -import yaml - -def save_to_yaml_config(project_key: str, environment: str, config_file: str = "config.yaml"): - """Save SDK key to YAML config file.""" - pm = ProjectManager() - sdk_key = pm.get_sdk_key(project_key, environment) - - # Read existing config - config = {} - if os.path.exists(config_file): - with open(config_file, "r") as f: - config = yaml.safe_load(f) or {} - - # Update LaunchDarkly section - if "launchdarkly" not in config: - config["launchdarkly"] = {} - - config["launchdarkly"]["sdk_key"] = sdk_key - config["launchdarkly"]["project_key"] = project_key - config["launchdarkly"]["environment"] = environment - - # Write back - with open(config_file, "w") as f: - yaml.dump(config, f, default_flow_style=False) - - print(f"✓ Saved SDK key to {config_file}") -``` - -#### JSON Config -```typescript -import * as fs from 'fs'; - -async function saveToJsonConfig( - projectKey: string, - environment: string, - configFile: string = 'config.json' -): Promise { - const pm = new ProjectManager(); - const sdkKey = await pm.getSdkKey(projectKey, environment); - - // Read existing config - let config: any = {}; - if (fs.existsSync(configFile)) { - config = JSON.parse(fs.readFileSync(configFile, 'utf-8')); - } - - // Update LaunchDarkly section - config.launchdarkly = { - sdkKey, - projectKey, - environment, - }; - - // Write back - fs.writeFileSync(configFile, JSON.stringify(config, null, 2)); - - console.log(`✓ Saved SDK key to ${configFile}`); -} -``` - -## Security Best Practices - -### 1. Never Commit SDK Keys -Add to `.gitignore`: -```gitignore -# Environment files -.env -.env.local -.env.production -.env.test - -# Config files with secrets -config/secrets.yaml -config/production.json -``` - -### 2. Use Different Keys Per Environment -```python -# Development -save_sdk_key_to_env("my-project", "test", ".env.development") - -# Production (deploy separately) -save_sdk_key_to_env("my-project", "production", ".env.production") -``` - -### 3. Rotate Keys Regularly -```python -def rotate_sdk_key(project_key: str, environment: str): - """ - Note: This requires creating a new SDK key via API. - The LaunchDarkly API doesn't support key rotation directly. - You would need to create a new environment or reset the key in the UI. - """ - print("⚠️ SDK key rotation must be done via LaunchDarkly UI") - print(f" Go to: Project Settings → Environments → {environment} → Reset SDK Key") -``` - -### 4. Least Privilege Access -- API tokens for project creation: `projects:write` -- Application SDK keys: read-only by default -- Separate keys for test vs production - -## Verification - -After saving SDK keys, verify they work: - -```python -def verify_sdk_key(sdk_key: str): - """Verify SDK key works by testing connection.""" - import ldclient - from ldclient.config import Config - - config = Config(sdk_key) - client = ldclient.get() - - if client.is_initialized(): - print("✓ SDK key is valid and working") - return True - else: - print("✗ SDK key failed to initialize") - return False -``` - -## Next Steps - -- [Integrate SDK in your application](../aiconfig-sdk/SKILL.md) -- [Set up project cloning](project-cloning.md) -- [Build automation scripts](iac-automation.md) diff --git a/skills/ai-configs/aiconfig-projects/references/go-setup.md b/skills/ai-configs/aiconfig-projects/references/go-setup.md deleted file mode 100644 index 8b7df49..0000000 --- a/skills/ai-configs/aiconfig-projects/references/go-setup.md +++ /dev/null @@ -1,491 +0,0 @@ -# Go Project Setup - -Implementation patterns for Go applications using the LaunchDarkly API. - -## Prerequisites - -```bash -go get github.com/joho/godotenv -``` - -## Basic Project Manager - -Create a package for project operations: - -```go -// pkg/launchdarkly/projects.go -package launchdarkly - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "os" -) - -const BaseURL = "https://app.launchdarkly.com/api/v2" - -type ProjectManager struct { - apiToken string - client *http.Client -} - -type Project struct { - Name string `json:"name"` - Key string `json:"key"` - Tags []string `json:"tags,omitempty"` - Environments *Environments `json:"environments,omitempty"` -} - -type Environments struct { - Items []Environment `json:"items"` -} - -type Environment struct { - Name string `json:"name"` - Key string `json:"key"` - APIKey string `json:"apiKey"` -} - -func NewProjectManager(apiToken string) *ProjectManager { - if apiToken == "" { - apiToken = os.Getenv("LAUNCHDARKLY_API_TOKEN") - } - - return &ProjectManager{ - apiToken: apiToken, - client: &http.Client{}, - } -} - -func (pm *ProjectManager) CreateProject(name, key string, tags []string) (*Project, error) { - payload := map[string]interface{}{ - "name": name, - "key": key, - } - if tags != nil { - payload["tags"] = tags - } - - body, err := json.Marshal(payload) - if err != nil { - return nil, fmt.Errorf("marshal payload: %w", err) - } - - req, err := http.NewRequest("POST", fmt.Sprintf("%s/projects", BaseURL), bytes.NewReader(body)) - if err != nil { - return nil, fmt.Errorf("create request: %w", err) - } - - req.Header.Set("Authorization", pm.apiToken) - req.Header.Set("Content-Type", "application/json") - - resp, err := pm.client.Do(req) - if err != nil { - return nil, fmt.Errorf("do request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusConflict { - // Project exists, fetch it - return pm.GetProject(key) - } - - if resp.StatusCode != http.StatusCreated { - body, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, body) - } - - var project Project - if err := json.NewDecoder(resp.Body).Decode(&project); err != nil { - return nil, fmt.Errorf("decode response: %w", err) - } - - return &project, nil -} - -func (pm *ProjectManager) GetProject(projectKey string) (*Project, error) { - req, err := http.NewRequest("GET", fmt.Sprintf("%s/projects/%s?expand=environments", BaseURL, projectKey), nil) - if err != nil { - return nil, fmt.Errorf("create request: %w", err) - } - - req.Header.Set("Authorization", pm.apiToken) - - resp, err := pm.client.Do(req) - if err != nil { - return nil, fmt.Errorf("do request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("project not found: %d", resp.StatusCode) - } - - var project Project - if err := json.NewDecoder(resp.Body).Decode(&project); err != nil { - return nil, fmt.Errorf("decode response: %w", err) - } - - return &project, nil -} - -func (pm *ProjectManager) GetSDKKey(projectKey, environment string) (string, error) { - project, err := pm.GetProject(projectKey) - if err != nil { - return "", err - } - - if project.Environments == nil { - return "", fmt.Errorf("no environments found") - } - - for _, env := range project.Environments.Items { - if env.Key == environment { - return env.APIKey, nil - } - } - - return "", fmt.Errorf("environment '%s' not found", environment) -} - -func (pm *ProjectManager) ListProjects() ([]Project, error) { - req, err := http.NewRequest("GET", fmt.Sprintf("%s/projects", BaseURL), nil) - if err != nil { - return nil, fmt.Errorf("create request: %w", err) - } - - req.Header.Set("Authorization", pm.apiToken) - - resp, err := pm.client.Do(req) - if err != nil { - return nil, fmt.Errorf("do request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to list projects: %d", resp.StatusCode) - } - - var result struct { - Items []Project `json:"items"` - } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return nil, fmt.Errorf("decode response: %w", err) - } - - return result.Items, nil -} -``` - -## Usage Examples - -### Create a Project - -```go -package main - -import ( - "fmt" - "log" - - "yourmodule/pkg/launchdarkly" -) - -func main() { - pm := launchdarkly.NewProjectManager("") - - project, err := pm.CreateProject( - "Customer AI Service", - "customer-ai", - []string{"ai-configs", "production"}, - ) - if err != nil { - log.Fatal(err) - } - - fmt.Printf("✓ Created project: %s (%s)\n", project.Name, project.Key) -} -``` - -### Get SDK Key - -```go -func main() { - pm := launchdarkly.NewProjectManager("") - - // Get production SDK key - sdkKey, err := pm.GetSDKKey("customer-ai", "production") - if err != nil { - log.Fatal(err) - } - - fmt.Printf("Production SDK Key: %s\n", sdkKey) -} -``` - -### List Projects - -```go -func main() { - pm := launchdarkly.NewProjectManager("") - - projects, err := pm.ListProjects() - if err != nil { - log.Fatal(err) - } - - fmt.Println("Projects:") - for _, project := range projects { - fmt.Printf(" - %s (%s)\n", project.Name, project.Key) - } -} -``` - -## HTTP Server Integration - -Integrate into an HTTP server: - -```go -// cmd/server/main.go -package main - -import ( - "log" - "net/http" - "os" - - "yourmodule/pkg/launchdarkly" -) - -func main() { - // Initialize LaunchDarkly project - pm := launchdarkly.NewProjectManager(os.Getenv("LAUNCHDARKLY_API_TOKEN")) - - project, err := pm.CreateProject( - "Go API Service", - "go-api-service", - []string{"api", "ai-configs"}, - ) - if err != nil { - log.Fatalf("Failed to initialize LaunchDarkly: %v", err) - } - - // Get SDK key - sdkKey, err := pm.GetSDKKey("go-api-service", "production") - if err != nil { - log.Fatalf("Failed to get SDK key: %v", err) - } - - // Store SDK key for SDK initialization - os.Setenv("LAUNCHDARKLY_SDK_KEY", sdkKey) - - log.Printf("✓ LaunchDarkly project ready: %s\n", project.Key) - - // Start server - http.HandleFunc("/health", healthHandler) - log.Fatal(http.ListenAndServe(":8080", nil)) -} - -func healthHandler(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("OK")) -} -``` - -## CLI Tool - -Create a CLI for project management: - -```go -// cmd/ldprojects/main.go -package main - -import ( - "flag" - "fmt" - "log" - "os" - "strings" - - "yourmodule/pkg/launchdarkly" -) - -func main() { - createCmd := flag.NewFlagSet("create", flag.ExitOnError) - createName := createCmd.String("name", "", "Project name") - createKey := createCmd.String("key", "", "Project key") - createTags := createCmd.String("tags", "", "Comma-separated tags") - - listCmd := flag.NewFlagSet("list", flag.ExitOnError) - - getKeyCmd := flag.NewFlagSet("get-key", flag.ExitOnError) - getKeyProject := getKeyCmd.String("project", "", "Project key") - getKeyEnv := getKeyCmd.String("env", "production", "Environment") - - if len(os.Args) < 2 { - fmt.Println("Usage: ldprojects [create|list|get-key] [options]") - os.Exit(1) - } - - pm := launchdarkly.NewProjectManager("") - - switch os.Args[1] { - case "create": - createCmd.Parse(os.Args[2:]) - if *createName == "" || *createKey == "" { - log.Fatal("name and key are required") - } - - var tags []string - if *createTags != "" { - tags = strings.Split(*createTags, ",") - } - - project, err := pm.CreateProject(*createName, *createKey, tags) - if err != nil { - log.Fatal(err) - } - - fmt.Printf("✓ Created: %s (%s)\n", project.Name, project.Key) - - case "list": - listCmd.Parse(os.Args[2:]) - - projects, err := pm.ListProjects() - if err != nil { - log.Fatal(err) - } - - fmt.Println("Projects:") - for _, project := range projects { - fmt.Printf(" - %s (%s)\n", project.Name, project.Key) - } - - case "get-key": - getKeyCmd.Parse(os.Args[2:]) - if *getKeyProject == "" { - log.Fatal("project is required") - } - - sdkKey, err := pm.GetSDKKey(*getKeyProject, *getKeyEnv) - if err != nil { - log.Fatal(err) - } - - fmt.Println(sdkKey) - - default: - fmt.Println("Unknown command:", os.Args[1]) - os.Exit(1) - } -} -``` - -**Usage:** -```bash -go run cmd/ldprojects/main.go create -name "My AI" -key my-ai -tags ai-configs,production -go run cmd/ldprojects/main.go list -go run cmd/ldprojects/main.go get-key -project my-ai -env production -``` - -## Error Handling - -Add comprehensive error handling: - -```go -type LaunchDarklyError struct { - StatusCode int - Message string -} - -func (e *LaunchDarklyError) Error() string { - return fmt.Sprintf("LaunchDarkly API error (%d): %s", e.StatusCode, e.Message) -} - -func (pm *ProjectManager) CreateProject(name, key string, tags []string) (*Project, error) { - // ... request setup ... - - resp, err := pm.client.Do(req) - if err != nil { - return nil, fmt.Errorf("request failed: %w", err) - } - defer resp.Body.Close() - - switch resp.StatusCode { - case http.StatusCreated: - var project Project - if err := json.NewDecoder(resp.Body).Decode(&project); err != nil { - return nil, fmt.Errorf("decode response: %w", err) - } - return &project, nil - - case http.StatusConflict: - return pm.GetProject(key) - - case http.StatusUnauthorized: - return nil, &LaunchDarklyError{resp.StatusCode, "Invalid API token"} - - case http.StatusForbidden: - return nil, &LaunchDarklyError{resp.StatusCode, "Insufficient permissions (need projects:write)"} - - default: - body, _ := io.ReadAll(resp.Body) - return nil, &LaunchDarklyError{resp.StatusCode, string(body)} - } -} -``` - -## Testing - -Mock the HTTP client for testing: - -```go -// pkg/launchdarkly/projects_test.go -package launchdarkly - -import ( - "net/http" - "net/http/httptest" - "testing" -) - -func TestCreateProject(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - t.Errorf("Expected POST, got %s", r.Method) - } - - w.WriteHeader(http.StatusCreated) - w.Write([]byte(`{"name":"Test","key":"test","tags":[]}`)) - })) - defer server.Close() - - pm := &ProjectManager{ - apiToken: "test-token", - client: server.Client(), - } - - // Override BaseURL for test - oldBaseURL := BaseURL - BaseURL = server.URL - defer func() { BaseURL = oldBaseURL }() - - project, err := pm.CreateProject("Test", "test", nil) - if err != nil { - t.Fatalf("CreateProject failed: %v", err) - } - - if project.Key != "test" { - t.Errorf("Expected key 'test', got '%s'", project.Key) - } -} -``` - -## Next Steps - -- [Save SDK keys to .env](env-config.md) -- [Set up project cloning](project-cloning.md) -- [Build automation tools](admin-tooling.md) diff --git a/skills/ai-configs/aiconfig-projects/references/iac-automation.md b/skills/ai-configs/aiconfig-projects/references/iac-automation.md deleted file mode 100644 index 13e547a..0000000 --- a/skills/ai-configs/aiconfig-projects/references/iac-automation.md +++ /dev/null @@ -1,526 +0,0 @@ -# Infrastructure as Code (IaC) Automation - -Automate project management using IaC tools and CI/CD pipelines. - -## Terraform - -### LaunchDarkly Terraform Provider - -Install and configure the LaunchDarkly Terraform provider: - -```hcl -# terraform/main.tf -terraform { - required_providers { - launchdarkly = { - source = "launchdarkly/launchdarkly" - version = "~> 2.0" - } - } -} - -provider "launchdarkly" { - access_token = var.launchdarkly_access_token -} -``` - -### Define Projects - -```hcl -# terraform/projects.tf -variable "launchdarkly_access_token" { - description = "LaunchDarkly API access token" - type = string - sensitive = true -} - -resource "launchdarkly_project" "customer_ai" { - key = "customer-ai" - name = "Customer AI Service" - tags = ["ai-configs", "production", "terraform"] -} - -resource "launchdarkly_project" "platform_ai" { - key = "platform-ai" - name = "Platform AI Service" - tags = ["ai-configs", "production", "terraform"] -} - -# Output SDK keys -output "customer_ai_sdk_key_production" { - value = launchdarkly_project.customer_ai.environments[0].api_key - sensitive = true -} - -output "customer_ai_sdk_key_test" { - value = launchdarkly_project.customer_ai.environments[1].api_key - sensitive = true -} -``` - -### Custom Environments - -```hcl -resource "launchdarkly_project" "my_project" { - key = "my-project" - name = "My Project" - - environments = [ - { - key = "production" - name = "Production" - color = "FF0000" - }, - { - key = "staging" - name = "Staging" - color = "FFA500" - }, - { - key = "development" - name = "Development" - color = "00FF00" - } - ] -} -``` - -### Apply Terraform - -```bash -# Initialize -terraform init - -# Plan changes -terraform plan -var="launchdarkly_access_token=$LAUNCHDARKLY_API_TOKEN" - -# Apply -terraform apply -var="launchdarkly_access_token=$LAUNCHDARKLY_API_TOKEN" - -# Get SDK key -terraform output -raw customer_ai_sdk_key_production -``` - -### Save SDK Keys to Files - -```hcl -# Save SDK keys to local files (for development only) -resource "local_file" "sdk_key_production" { - content = launchdarkly_project.customer_ai.environments[0].api_key - filename = "${path.module}/.env.production" - - # Don't commit these files! - provisioner "local-exec" { - command = "echo '.env.production' >> .gitignore" - } -} -``` - -## GitHub Actions - -Automate project creation in CI/CD: - -### Create Project on Deploy - -```yaml -# .github/workflows/setup-launchdarkly.yml -name: Setup LaunchDarkly Project - -on: - workflow_dispatch: - inputs: - project_key: - description: 'Project key' - required: true - project_name: - description: 'Project name' - required: true - -jobs: - setup: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Install dependencies - run: | - pip install requests python-dotenv - - - name: Create LaunchDarkly Project - env: - LAUNCHDARKLY_API_TOKEN: ${{ secrets.LAUNCHDARKLY_API_TOKEN }} - run: | - python scripts/create_project.py \ - --name "${{ github.event.inputs.project_name }}" \ - --key "${{ github.event.inputs.project_key }}" \ - --tags "github-actions,automated" - - - name: Save SDK Keys to Secrets - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PROJECT_KEY: ${{ github.event.inputs.project_key }} - run: | - SDK_KEY=$(python scripts/get_sdk_key.py $PROJECT_KEY production) - gh secret set LAUNCHDARKLY_SDK_KEY --body "$SDK_KEY" -``` - -### Automated Project Creation on New Service - -```yaml -# .github/workflows/new-service.yml -name: New Service Setup - -on: - create: - branches: - - 'service/*' - -jobs: - setup-project: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Extract service name - id: service - run: | - BRANCH_NAME="${{ github.ref }}" - SERVICE_NAME="${BRANCH_NAME#refs/heads/service/}" - echo "name=$SERVICE_NAME" >> $GITHUB_OUTPUT - echo "key=${SERVICE_NAME//_/-}" >> $GITHUB_OUTPUT - - - name: Create LaunchDarkly Project - env: - LAUNCHDARKLY_API_TOKEN: ${{ secrets.LAUNCHDARKLY_API_TOKEN }} - run: | - python scripts/create_project.py \ - --name "${{ steps.service.outputs.name }} Service" \ - --key "${{ steps.service.outputs.key }}-service" \ - --tags "service,automated" -``` - -## GitLab CI - -```yaml -# .gitlab-ci.yml -stages: - - setup - - deploy - -setup-launchdarkly: - stage: setup - image: python:3.11 - script: - - pip install requests - - | - python -c " - from launchdarkly.projects import ProjectManager - pm = ProjectManager('$LAUNCHDARKLY_API_TOKEN') - project = pm.create_project( - name='$CI_PROJECT_NAME', - key='$CI_PROJECT_NAME', - tags=['gitlab-ci', '$CI_ENVIRONMENT_NAME'] - ) - sdk_key = pm.get_sdk_key('$CI_PROJECT_NAME', 'production') - print(f'SDK_KEY={sdk_key}') - " > .env.production - artifacts: - paths: - - .env.production - expire_in: 1 day - only: - - main -``` - -## CircleCI - -```yaml -# .circleci/config.yml -version: 2.1 - -jobs: - setup-launchdarkly: - docker: - - image: cimg/python:3.11 - steps: - - checkout - - run: - name: Install dependencies - command: pip install requests - - run: - name: Create LaunchDarkly project - command: | - python scripts/create_project.py \ - --name "$CIRCLE_PROJECT_REPONAME" \ - --key "$CIRCLE_PROJECT_REPONAME" \ - --tags "circleci,automated" - - run: - name: Save SDK key - command: | - SDK_KEY=$(python scripts/get_sdk_key.py $CIRCLE_PROJECT_REPONAME production) - echo "export LAUNCHDARKLY_SDK_KEY='$SDK_KEY'" >> $BASH_ENV - -workflows: - setup: - jobs: - - setup-launchdarkly -``` - -## Ansible - -Manage projects with Ansible: - -```yaml -# playbooks/setup-launchdarkly.yml ---- -- name: Setup LaunchDarkly Projects - hosts: localhost - vars: - launchdarkly_api_token: "{{ lookup('env', 'LAUNCHDARKLY_API_TOKEN') }}" - projects: - - name: "Customer AI Service" - key: "customer-ai" - tags: ["ai-configs", "production"] - - name: "Platform AI Service" - key: "platform-ai" - tags: ["ai-configs", "production"] - - tasks: - - name: Create LaunchDarkly projects - uri: - url: "https://app.launchdarkly.com/api/v2/projects" - method: POST - headers: - Authorization: "{{ launchdarkly_api_token }}" - Content-Type: "application/json" - body_format: json - body: - name: "{{ item.name }}" - key: "{{ item.key }}" - tags: "{{ item.tags }}" - status_code: [201, 409] - loop: "{{ projects }}" - register: project_results - - - name: Get SDK keys - uri: - url: "https://app.launchdarkly.com/api/v2/projects/{{ item.key }}?expand=environments" - method: GET - headers: - Authorization: "{{ launchdarkly_api_token }}" - loop: "{{ projects }}" - register: sdk_keys - - - name: Save SDK keys to .env - copy: - content: | - LAUNCHDARKLY_SDK_KEY={{ item.json.environments.items[0].apiKey }} - dest: ".env.{{ item.item.key }}" - loop: "{{ sdk_keys.results }}" - no_log: true -``` - -**Run playbook:** -```bash -ansible-playbook playbooks/setup-launchdarkly.yml -``` - -## Pulumi - -Infrastructure as code with Pulumi: - -### Python - -```python -# __main__.py -import pulumi -import pulumi_launchdarkly as launchdarkly - -# Create projects -customer_ai = launchdarkly.Project( - "customer-ai", - key="customer-ai", - name="Customer AI Service", - tags=["ai-configs", "production", "pulumi"] -) - -platform_ai = launchdarkly.Project( - "platform-ai", - key="platform-ai", - name="Platform AI Service", - tags=["ai-configs", "production", "pulumi"] -) - -# Export SDK keys -pulumi.export("customer_ai_sdk_key_prod", customer_ai.environments[0]["api_key"]) -pulumi.export("customer_ai_sdk_key_test", customer_ai.environments[1]["api_key"]) -``` - -### TypeScript - -```typescript -// index.ts -import * as pulumi from "@pulumi/pulumi"; -import * as launchdarkly from "@pulumi/launchdarkly"; - -// Create projects -const customerAi = new launchdarkly.Project("customer-ai", { - key: "customer-ai", - name: "Customer AI Service", - tags: ["ai-configs", "production", "pulumi"], -}); - -const platformAi = new launchdarkly.Project("platform-ai", { - key: "platform-ai", - name: "Platform AI Service", - tags: ["ai-configs", "production", "pulumi"], -}); - -// Export SDK keys -export const customerAiSdkKeyProd = customerAi.environments[0].apiKey; -export const customerAiSdkKeyTest = customerAi.environments[1].apiKey; -``` - -**Deploy:** -```bash -pulumi up -pulumi stack output customerAiSdkKeyProd -``` - -## Docker Compose - -Initialize projects in Docker setup: - -```yaml -# docker-compose.yml -version: '3.8' - -services: - setup-launchdarkly: - image: python:3.11-slim - environment: - - LAUNCHDARKLY_API_TOKEN=${LAUNCHDARKLY_API_TOKEN} - volumes: - - ./scripts:/scripts - - ./.env.production:/output/.env - command: > - sh -c " - pip install requests && - python /scripts/create_project.py --name 'My Service' --key my-service && - python /scripts/save_sdk_key.py my-service production > /output/.env - " - - app: - build: . - depends_on: - - setup-launchdarkly - env_file: - - .env.production -``` - -## Kubernetes Operator - -Custom operator to manage projects: - -```yaml -# k8s/launchdarkly-project.yaml -apiVersion: launchdarkly.com/v1 -kind: Project -metadata: - name: customer-ai -spec: - key: customer-ai - name: Customer AI Service - tags: - - ai-configs - - production - - kubernetes - secretName: launchdarkly-sdk-keys -``` - -**Operator implementation (Python):** -```python -# operator/controller.py -import kopf -from launchdarkly.projects import ProjectManager -from kubernetes import client, config - -@kopf.on.create('launchdarkly.com', 'v1', 'projects') -def create_project(spec, name, namespace, **kwargs): - """Handle Project creation.""" - pm = ProjectManager() - - # Create project - project = pm.create_project( - name=spec['name'], - key=spec['key'], - tags=spec.get('tags', []) - ) - - # Get SDK keys - sdk_key_prod = pm.get_sdk_key(spec['key'], 'production') - sdk_key_test = pm.get_sdk_key(spec['key'], 'test') - - # Create Kubernetes Secret - config.load_incluster_config() - v1 = client.CoreV1Api() - - secret = client.V1Secret( - metadata=client.V1ObjectMeta( - name=spec.get('secretName', f"{name}-sdk-keys"), - namespace=namespace - ), - string_data={ - 'sdk-key-production': sdk_key_prod, - 'sdk-key-test': sdk_key_test - } - ) - - v1.create_namespaced_secret(namespace, secret) - - return {'message': f'Created project {spec["key"]}'} -``` - -## Make/Taskfile - -Simple automation with Make: - -```makefile -# Makefile -.PHONY: create-project list-projects get-key - -create-project: - @python scripts/create_project.py \ - --name "$(NAME)" \ - --key "$(KEY)" \ - --tags "$(TAGS)" - -list-projects: - @python scripts/list_projects.py - -get-key: - @python scripts/get_sdk_key.py $(PROJECT) $(ENV) - -setup-env: - @python scripts/save_sdk_key.py $(PROJECT) production > .env.production - @echo "✓ Saved SDK key to .env.production" -``` - -**Usage:** -```bash -make create-project NAME="My AI" KEY=my-ai TAGS=ai-configs,production -make list-projects -make get-key PROJECT=my-ai ENV=production -make setup-env PROJECT=my-ai -``` - -## Next Steps - -- [Build admin tooling](admin-tooling.md) -- [Configure project cloning](project-cloning.md) -- [Manage environment configuration](env-config.md) diff --git a/skills/ai-configs/aiconfig-projects/references/multi-language-setup.md b/skills/ai-configs/aiconfig-projects/references/multi-language-setup.md deleted file mode 100644 index 4d86de2..0000000 --- a/skills/ai-configs/aiconfig-projects/references/multi-language-setup.md +++ /dev/null @@ -1,528 +0,0 @@ -# Multi-Language Setup - -Guidance for polyglot architectures with multiple languages/frameworks. - -## Overview - -In a microservices or polyglot architecture, you may have services in different languages that need to share LaunchDarkly projects or maintain separate projects per service. - -## Architecture Patterns - -### Pattern 1: Shared Project, Multiple Services - -One project, different services consume from different environments or contexts. - -**When to use:** -- All services are part of the same application -- Want centralized config management -- Services share the same AI configs - -``` -Project: "my-app" -├── Service A (Python) → Uses "my-app" production SDK key -├── Service B (Node.js) → Uses "my-app" production SDK key -└── Service C (Go) → Uses "my-app" production SDK key -``` - -### Pattern 2: Project Per Service - -Each service has its own project. - -**When to use:** -- Services are independent -- Different teams own different services -- Need isolation between services - -``` -Project: "service-a" → Service A (Python) -Project: "service-b" → Service B (Node.js) -Project: "service-c" → Service C (Go) -``` - -### Pattern 3: Hybrid - -Shared projects for related services, separate for others. - -``` -Project: "frontend-services" -├── Web App (React) -└── Mobile App (React Native) - -Project: "backend-services" -├── API Gateway (Node.js) -└── Auth Service (Go) - -Project: "ml-services" -├── Recommendation Engine (Python) -└── Model Serving (Python) -``` - -## Centralized Project Management - -Create a central service for project management that other services can use: - -### Project Management API - -```python -# project-manager-service/app.py -from flask import Flask, jsonify, request -from launchdarkly.projects import ProjectManager - -app = Flask(__name__) -pm = ProjectManager() - -@app.route('/projects', methods=['POST']) -def create_project(): - """Central API to create projects for any service.""" - data = request.json - project = pm.create_project( - name=data['name'], - key=data['key'], - tags=data.get('tags', []) - ) - return jsonify(project) - -@app.route('/projects//keys/') -def get_sdk_key(key, env): - """Get SDK key for any service.""" - sdk_key = pm.get_sdk_key(key, env) - return jsonify({'sdkKey': sdk_key}) - -if __name__ == '__main__': - app.run(host='0.0.0.0', port=8080) -``` - -### Service Consumption - -Each service calls the central API: - -**Python Service:** -```python -import requests - -def get_launchdarkly_sdk_key(): - resp = requests.get( - 'http://project-manager:8080/projects/my-service/keys/production' - ) - return resp.json()['sdkKey'] -``` - -**Node.js Service:** -```typescript -async function getLaunchDarklySdkKey(): Promise { - const resp = await fetch( - 'http://project-manager:8080/projects/my-service/keys/production' - ); - const data = await resp.json(); - return data.sdkKey; -} -``` - -**Go Service:** -```go -func getLaunchDarklySdkKey() (string, error) { - resp, err := http.Get("http://project-manager:8080/projects/my-service/keys/production") - if err != nil { - return "", err - } - defer resp.Body.Close() - - var result struct { - SdkKey string `json:"sdkKey"` - } - json.NewDecoder(resp.Body).Decode(&result) - return result.SdkKey, nil -} -``` - -## Shared Configuration Repository - -Maintain a central config repo that all services reference: - -``` -config-repo/ -├── launchdarkly/ -│ ├── projects.yaml # Project definitions -│ ├── sdk-keys/ -│ │ ├── production/ -│ │ │ ├── service-a.key -│ │ │ ├── service-b.key -│ │ │ └── service-c.key -│ │ └── test/ -│ │ ├── service-a.key -│ │ ├── service-b.key -│ │ └── service-c.key -│ └── scripts/ -│ ├── create_projects.py -│ └── sync_keys.sh -``` - -**projects.yaml:** -```yaml -projects: - - name: Service A - key: service-a - tags: [python, backend] - services: - - name: service-a - language: python - - - name: Service B - key: service-b - tags: [nodejs, api] - services: - - name: service-b - language: nodejs - - - name: Service C - key: service-c - tags: [go, gateway] - services: - - name: service-c - language: go -``` - -**Sync script:** -```bash -#!/bin/bash -# scripts/sync_keys.sh - -for env in production test; do - for service in service-a service-b service-c; do - sdk_key=$(python scripts/get_sdk_key.py $service $env) - echo "$sdk_key" > "sdk-keys/$env/$service.key" - echo "✓ Synced $service/$env" - done -done -``` - -## Service Templates - -Create templates for each language: - -### Python Template -```python -# templates/python/launchdarkly_setup.py -import os -from launchdarkly.projects import ProjectManager - -def setup_project(service_name: str): - """Setup LaunchDarkly project for Python service.""" - pm = ProjectManager() - - project = pm.create_project( - name=f"{service_name} Service", - key=service_name, - tags=["python", "service"] - ) - - sdk_key = pm.get_sdk_key(service_name, "production") - - # Save to .env - with open(".env", "w") as f: - f.write(f"LAUNCHDARKLY_SDK_KEY={sdk_key}\n") - - print(f"✓ Setup complete for {service_name}") -``` - -### Node.js Template -```typescript -// templates/nodejs/launchdarkly-setup.ts -import { ProjectManager } from './launchdarkly/projects'; -import * as fs from 'fs'; - -async function setupProject(serviceName: string): Promise { - const pm = new ProjectManager(); - - const project = await pm.createProject({ - name: `${serviceName} Service`, - key: serviceName, - tags: ['nodejs', 'service'], - }); - - const sdkKey = await pm.getSdkKey(serviceName, 'production'); - - // Save to .env - fs.writeFileSync('.env', `LAUNCHDARKLY_SDK_KEY=${sdkKey}\n`); - - console.log(`✓ Setup complete for ${serviceName}`); -} -``` - -### Go Template -```go -// templates/go/launchdarkly_setup.go -package main - -import ( - "fmt" - "os" - "yourmodule/pkg/launchdarkly" -) - -func setupProject(serviceName string) error { - pm := launchdarkly.NewProjectManager("") - - project, err := pm.CreateProject( - fmt.Sprintf("%s Service", serviceName), - serviceName, - []string{"go", "service"}, - ) - if err != nil { - return err - } - - sdkKey, err := pm.GetSDKKey(serviceName, "production") - if err != nil { - return err - } - - // Save to .env - f, err := os.Create(".env") - if err != nil { - return err - } - defer f.Close() - - fmt.Fprintf(f, "LAUNCHDARKLY_SDK_KEY=%s\n", sdkKey) - - fmt.Printf("✓ Setup complete for %s\n", serviceName) - return nil -} -``` - -## Monorepo Setup - -For monorepos with multiple services: - -``` -monorepo/ -├── services/ -│ ├── api/ (Node.js) -│ ├── worker/ (Python) -│ └── gateway/ (Go) -├── packages/ -│ └── launchdarkly-setup/ -│ ├── python/ -│ │ └── setup.py -│ ├── nodejs/ -│ │ └── setup.ts -│ └── go/ -│ └── setup.go -└── scripts/ - └── setup-all-projects.sh -``` - -**setup-all-projects.sh:** -```bash -#!/bin/bash - -echo "Setting up LaunchDarkly projects for all services..." - -cd services/api -npm run setup-launchdarkly - -cd ../worker -python scripts/setup_launchdarkly.py - -cd ../gateway -go run scripts/setup_launchdarkly.go - -echo "✓ All services configured" -``` - -## Environment Variable Convention - -Standardize environment variable names across languages: - -```bash -# Common convention -LAUNCHDARKLY_SDK_KEY= -LAUNCHDARKLY_SDK_KEY_TEST= -LAUNCHDARKLY_PROJECT_KEY= -LAUNCHDARKLY_ENVIRONMENT= -``` - -**Loading in each language:** - -**Python:** -```python -import os -sdk_key = os.environ['LAUNCHDARKLY_SDK_KEY'] -``` - -**Node.js:** -```typescript -const sdkKey = process.env.LAUNCHDARKLY_SDK_KEY; -``` - -**Go:** -```go -sdkKey := os.Getenv("LAUNCHDARKLY_SDK_KEY") -``` - -**Ruby:** -```ruby -sdk_key = ENV['LAUNCHDARKLY_SDK_KEY'] -``` - -**Java:** -```java -String sdkKey = System.getenv("LAUNCHDARKLY_SDK_KEY"); -``` - -## Container/K8s ConfigMap - -Share SDK keys via Kubernetes ConfigMap: - -```yaml -# k8s/launchdarkly-config.yaml -apiVersion: v1 -kind: ConfigMap -metadata: - name: launchdarkly-config -data: - project-key: "my-app" - environment: "production" - ---- -apiVersion: v1 -kind: Secret -metadata: - name: launchdarkly-secrets -type: Opaque -stringData: - sdk-key: "sdk-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -``` - -**Mount in all services:** -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: service-a -spec: - template: - spec: - containers: - - name: app - env: - - name: LAUNCHDARKLY_SDK_KEY - valueFrom: - secretKeyRef: - name: launchdarkly-secrets - key: sdk-key - - name: LAUNCHDARKLY_PROJECT_KEY - valueFrom: - configMapKeyRef: - name: launchdarkly-config - key: project-key -``` - -## Service Mesh Integration - -For service mesh (Istio, Linkerd), use a sidecar pattern: - -```yaml -# Sidecar that manages LaunchDarkly SDK keys -apiVersion: v1 -kind: Pod -metadata: - name: my-service -spec: - containers: - # Main application - - name: app - image: my-service:latest - env: - - name: LAUNCHDARKLY_SDK_KEY - value: /var/run/secrets/launchdarkly/sdk-key - volumeMounts: - - name: ld-keys - mountPath: /var/run/secrets/launchdarkly - - # Sidecar that fetches/refreshes keys - - name: ld-key-sync - image: ld-key-sync:latest - env: - - name: LAUNCHDARKLY_API_TOKEN - valueFrom: - secretKeyRef: - name: ld-api-token - key: token - - name: PROJECT_KEY - value: my-service - volumeMounts: - - name: ld-keys - mountPath: /var/run/secrets/launchdarkly - - volumes: - - name: ld-keys - emptyDir: {} -``` - -## Best Practices - -### 1. Naming Conventions -``` -Project Key Format: {service-name}-{optional-suffix} - -Examples: - - api-gateway - - user-service - - recommendation-engine -``` - -### 2. Tagging Strategy -```python -tags = [ - "language:python", # or nodejs, go, etc. - "team:platform", # owning team - "environment:prod", # deployment env - "type:service" # or frontend, worker, etc. -] -``` - -### 3. Documentation -Maintain a service registry: - -```markdown -# Service Registry - -| Service | Language | Project Key | Team | Status | -|---------|----------|-------------|------|--------| -| API Gateway | Node.js | api-gateway | Platform | Active | -| User Service | Go | user-service | Identity | Active | -| ML Engine | Python | ml-engine | Data Science | Active | -``` - -### 4. Automation -Automate project creation for new services: - -```bash -# scripts/new-service.sh -#!/bin/bash -SERVICE_NAME=$1 -LANGUAGE=$2 - -# Create project -python scripts/create_project.py \ - --name "$SERVICE_NAME Service" \ - --key "$SERVICE_NAME" \ - --tags "language:$LANGUAGE,type:service" - -# Generate template -cp -r "templates/$LANGUAGE" "services/$SERVICE_NAME" - -# Setup SDK key -cd "services/$SERVICE_NAME" -bash setup.sh - -echo "✓ New service $SERVICE_NAME created" -``` - -## Next Steps - -- [Setup environment configuration](env-config.md) -- [Configure project cloning](project-cloning.md) -- [Build admin tooling](admin-tooling.md) diff --git a/skills/ai-configs/aiconfig-projects/references/nodejs-setup.md b/skills/ai-configs/aiconfig-projects/references/nodejs-setup.md deleted file mode 100644 index d8def14..0000000 --- a/skills/ai-configs/aiconfig-projects/references/nodejs-setup.md +++ /dev/null @@ -1,439 +0,0 @@ -# Node.js/TypeScript Project Setup - -Implementation patterns for Node.js and TypeScript applications. - -## Prerequisites - -```bash -npm install axios dotenv -# or -yarn add axios dotenv -``` - -## TypeScript Project Manager - -Create a typed module for project operations: - -```typescript -// src/launchdarkly/projects.ts -import axios, { AxiosInstance } from 'axios'; - -interface Project { - name: string; - key: string; - tags?: string[]; - environments?: { - items: Environment[]; - }; -} - -interface Environment { - name: string; - key: string; - apiKey: string; -} - -interface CreateProjectParams { - name: string; - key: string; - tags?: string[]; -} - -export class ProjectManager { - private client: AxiosInstance; - - constructor(apiToken?: string) { - const token = apiToken || process.env.LAUNCHDARKLY_API_TOKEN; - if (!token) { - throw new Error('LAUNCHDARKLY_API_TOKEN is required'); - } - - this.client = axios.create({ - baseURL: 'https://app.launchdarkly.com/api/v2', - headers: { - Authorization: token, - 'Content-Type': 'application/json', - }, - }); - } - - async createProject(params: CreateProjectParams): Promise { - try { - const response = await this.client.post('/projects', params); - return response.data; - } catch (error: any) { - if (error.response?.status === 409) { - // Project exists, fetch and return it - console.log(`Project '${params.key}' already exists`); - return this.getProject(params.key); - } - throw error; - } - } - - async getProject(projectKey: string): Promise { - const response = await this.client.get(`/projects/${projectKey}`, { - params: { expand: 'environments' }, - }); - return response.data; - } - - async getSdkKey(projectKey: string, environment: string = 'production'): Promise { - const project = await this.getProject(projectKey); - const envItems = project.environments?.items || []; - - const env = envItems.find((e) => e.key === environment); - return env?.apiKey || null; - } - - async listProjects(): Promise { - const response = await this.client.get<{ items: Project[] }>('/projects'); - return response.data.items; - } -} -``` - -## JavaScript (CommonJS) - -For Node.js without TypeScript: - -```javascript -// src/launchdarkly/projects.js -const axios = require('axios'); - -class ProjectManager { - constructor(apiToken) { - const token = apiToken || process.env.LAUNCHDARKLY_API_TOKEN; - if (!token) { - throw new Error('LAUNCHDARKLY_API_TOKEN is required'); - } - - this.client = axios.create({ - baseURL: 'https://app.launchdarkly.com/api/v2', - headers: { - Authorization: token, - 'Content-Type': 'application/json', - }, - }); - } - - async createProject({ name, key, tags = [] }) { - try { - const response = await this.client.post('/projects', { name, key, tags }); - return response.data; - } catch (error) { - if (error.response?.status === 409) { - console.log(`Project '${key}' already exists`); - return this.getProject(key); - } - throw error; - } - } - - async getProject(projectKey) { - const response = await this.client.get(`/projects/${projectKey}`, { - params: { expand: 'environments' }, - }); - return response.data; - } - - async getSdkKey(projectKey, environment = 'production') { - const project = await this.getProject(projectKey); - const envItems = project.environments?.items || []; - - const env = envItems.find((e) => e.key === environment); - return env?.apiKey || null; - } - - async listProjects() { - const response = await this.client.get('/projects'); - return response.data.items; - } -} - -module.exports = { ProjectManager }; -``` - -## Express.js Integration - -Integrate project setup into Express app: - -```typescript -// src/app.ts -import express from 'express'; -import dotenv from 'dotenv'; -import { ProjectManager } from './launchdarkly/projects'; - -dotenv.config(); - -const app = express(); -const pm = new ProjectManager(); - -// Ensure project exists on startup -async function initializeLaunchDarkly() { - try { - const project = await pm.createProject({ - name: 'Express API', - key: 'express-api', - tags: ['api', 'ai-configs'], - }); - - const sdkKey = await pm.getSdkKey('express-api', 'production'); - console.log(`✓ LaunchDarkly project ready: ${project.key}`); - - // Store SDK key for SDK initialization - process.env.LAUNCHDARKLY_SDK_KEY = sdkKey || ''; - } catch (error) { - console.error('Failed to initialize LaunchDarkly:', error); - process.exit(1); - } -} - -// Initialize before starting server -initializeLaunchDarkly().then(() => { - app.listen(3000, () => { - console.log('Server running on port 3000'); - }); -}); -``` - -## NestJS Integration - -For NestJS applications: - -```typescript -// src/launchdarkly/launchdarkly.module.ts -import { Module, OnModuleInit } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { ProjectManager } from './projects'; - -@Module({ - providers: [ProjectManager], - exports: [ProjectManager], -}) -export class LaunchDarklyModule implements OnModuleInit { - constructor( - private readonly pm: ProjectManager, - private readonly config: ConfigService, - ) {} - - async onModuleInit() { - const projectKey = this.config.get('LAUNCHDARKLY_PROJECT_KEY', 'nestjs-app'); - - try { - const project = await this.pm.createProject({ - name: 'NestJS Application', - key: projectKey, - tags: ['nestjs', 'ai-configs'], - }); - - console.log(`✓ LaunchDarkly project ready: ${project.key}`); - } catch (error) { - console.error('LaunchDarkly initialization failed:', error); - } - } -} - - -// src/launchdarkly/projects.ts (Injectable version) -import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import axios, { AxiosInstance } from 'axios'; - -@Injectable() -export class ProjectManager { - private client: AxiosInstance; - - constructor(private config: ConfigService) { - const apiToken = this.config.get('LAUNCHDARKLY_API_TOKEN'); - this.client = axios.create({ - baseURL: 'https://app.launchdarkly.com/api/v2', - headers: { - Authorization: apiToken, - 'Content-Type': 'application/json', - }, - }); - } - - // ... same methods as before -} -``` - -## CLI Tool - -Create a CLI for project management: - -```typescript -// cli/projects.ts -#!/usr/bin/env node -import { Command } from 'commander'; -import { ProjectManager } from '../src/launchdarkly/projects'; - -const program = new Command(); -const pm = new ProjectManager(); - -program - .name('ld-projects') - .description('LaunchDarkly project management CLI'); - -program - .command('create ') - .description('Create a new project') - .option('-t, --tags ', 'Project tags') - .action(async (name: string, key: string, options: { tags?: string[] }) => { - try { - const project = await pm.createProject({ name, key, tags: options.tags }); - console.log(`✓ Created: ${project.name} (${project.key})`); - } catch (error: any) { - console.error('Error:', error.message); - process.exit(1); - } - }); - -program - .command('list') - .description('List all projects') - .action(async () => { - try { - const projects = await pm.listProjects(); - projects.forEach((p) => { - console.log(`- ${p.name} (${p.key})`); - }); - } catch (error: any) { - console.error('Error:', error.message); - process.exit(1); - } - }); - -program - .command('get-key ') - .description('Get SDK key for a project') - .option('-e, --env ', 'Environment', 'production') - .action(async (projectKey: string, options: { env: string }) => { - try { - const sdkKey = await pm.getSdkKey(projectKey, options.env); - if (sdkKey) { - console.log(sdkKey); - } else { - console.error(`Environment '${options.env}' not found`); - process.exit(1); - } - } catch (error: any) { - console.error('Error:', error.message); - process.exit(1); - } - }); - -program.parse(); -``` - -**Usage:** -```bash -npm run ld-projects create "My AI" my-ai -t ai-configs production -npm run ld-projects list -npm run ld-projects get-key my-ai --env production -``` - -## Error Handling - -Add comprehensive error handling: - -```typescript -export class LaunchDarklyError extends Error { - constructor( - message: string, - public statusCode?: number, - public response?: any - ) { - super(message); - this.name = 'LaunchDarklyError'; - } -} - -export class ProjectManager { - async createProject(params: CreateProjectParams): Promise { - try { - const response = await this.client.post('/projects', params); - return response.data; - } catch (error: any) { - if (error.response) { - const status = error.response.status; - if (status === 409) { - return this.getProject(params.key); - } - if (status === 401) { - throw new LaunchDarklyError('Invalid API token', status); - } - if (status === 403) { - throw new LaunchDarklyError( - 'Insufficient permissions (need projects:write)', - status - ); - } - throw new LaunchDarklyError( - `API error: ${error.response.data.message || 'Unknown error'}`, - status, - error.response.data - ); - } - throw new LaunchDarklyError(`Request failed: ${error.message}`); - } - } -} -``` - -## Testing - -Mock with Jest: - -```typescript -// __tests__/projects.test.ts -import axios from 'axios'; -import { ProjectManager } from '../src/launchdarkly/projects'; - -jest.mock('axios'); -const mockedAxios = axios as jest.Mocked; - -describe('ProjectManager', () => { - let pm: ProjectManager; - - beforeEach(() => { - mockedAxios.create.mockReturnValue(mockedAxios as any); - pm = new ProjectManager('test-token'); - }); - - it('should create a project', async () => { - const mockProject = { name: 'Test', key: 'test', tags: [] }; - mockedAxios.post.mockResolvedValue({ data: mockProject }); - - const project = await pm.createProject({ name: 'Test', key: 'test' }); - - expect(project.key).toBe('test'); - expect(mockedAxios.post).toHaveBeenCalledWith('/projects', { - name: 'Test', - key: 'test', - }); - }); - - it('should handle existing project', async () => { - const mockProject = { name: 'Test', key: 'test' }; - mockedAxios.post.mockRejectedValue({ - response: { status: 409 }, - }); - mockedAxios.get.mockResolvedValue({ data: mockProject }); - - const project = await pm.createProject({ name: 'Test', key: 'test' }); - - expect(project.key).toBe('test'); - expect(mockedAxios.get).toHaveBeenCalledWith('/projects/test', { - params: { expand: 'environments' }, - }); - }); -}); -``` - -## Next Steps - -- [Save SDK keys to .env](env-config.md) -- [Set up project cloning](project-cloning.md) -- [Build admin tooling](admin-tooling.md) diff --git a/skills/ai-configs/aiconfig-projects/references/project-cloning.md b/skills/ai-configs/aiconfig-projects/references/project-cloning.md deleted file mode 100644 index c9794be..0000000 --- a/skills/ai-configs/aiconfig-projects/references/project-cloning.md +++ /dev/null @@ -1,437 +0,0 @@ -# Project Cloning - -Patterns for cloning projects across regions, teams, or environments. - -## Use Cases - -- **Multi-region deployments:** Clone project structure for US, EU, APAC regions -- **Multi-tenant applications:** Separate project per customer/tenant -- **Team isolation:** Clone template for different teams -- **Environment parity:** Ensure dev/staging/prod have identical structure - -## Basic Cloning Pattern - -### Python -```python -def clone_project(source_key: str, new_name: str, new_key: str, tags: List[str] = None) -> Dict: - """ - Clone a project's structure (metadata only, not flags/configs). - - Args: - source_key: The project to copy settings from - new_name: Name for the new project - new_key: Unique key for the new project - tags: Optional tags (defaults to source tags + 'cloned') - - Returns: - The newly created project - """ - pm = ProjectManager() - - # Get source project settings - source = pm.get_project(source_key) - if not source: - raise ValueError(f"Source project '{source_key}' not found") - - # Prepare new project with same settings - source_tags = source.get("tags", []) - new_tags = tags or (source_tags + ["cloned"]) - - # Create new project - return pm.create_project( - name=new_name, - key=new_key, - tags=new_tags - ) -``` - -### TypeScript -```typescript -async function cloneProject( - sourceKey: string, - newName: string, - newKey: string, - tags?: string[] -): Promise { - const pm = new ProjectManager(); - - // Get source project settings - const source = await pm.getProject(sourceKey); - if (!source) { - throw new Error(`Source project '${sourceKey}' not found`); - } - - // Prepare new project with same settings - const sourceTags = source.tags || []; - const newTags = tags || [...sourceTags, 'cloned']; - - // Create new project - return pm.createProject({ - name: newName, - key: newKey, - tags: newTags, - }); -} -``` - -## Multi-Region Cloning - -Clone a project for multiple regions: - -```python -def clone_for_regions(base_project: str, regions: List[str]): - """ - Clone a project for multiple regions. - - Example: - clone_for_regions("ai-service", ["us", "eu", "apac"]) - Creates: ai-service-us, ai-service-eu, ai-service-apac - """ - pm = ProjectManager() - base = pm.get_project(base_project) - - if not base: - raise ValueError(f"Base project '{base_project}' not found") - - created_projects = [] - - for region in regions: - new_key = f"{base_project}-{region}" - new_name = f"{base['name']} - {region.upper()}" - - print(f"Creating {new_key}...") - project = clone_project( - source_key=base_project, - new_name=new_name, - new_key=new_key, - tags=base.get("tags", []) + [f"region:{region}"] - ) - - created_projects.append(project) - print(f"✓ Created {new_key}") - - return created_projects - -# Usage -clone_for_regions("customer-ai", ["us", "eu", "apac"]) -``` - -**Result:** -- `customer-ai-us` - Customer AI - US -- `customer-ai-eu` - Customer AI - EU -- `customer-ai-apac` - Customer AI - APAC - -## Multi-Tenant Cloning - -Clone for different tenants/customers: - -```typescript -interface Tenant { - id: string; - name: string; -} - -async function cloneForTenants( - baseProject: string, - tenants: Tenant[] -): Promise { - const pm = new ProjectManager(); - const base = await pm.getProject(baseProject); - - if (!base) { - throw new Error(`Base project '${baseProject}' not found`); - } - - const createdProjects: Project[] = []; - - for (const tenant of tenants) { - const newKey = `${baseProject}-${tenant.id}`; - const newName = `${base.name} - ${tenant.name}`; - - console.log(`Creating ${newKey}...`); - const project = await cloneProject( - baseProject, - newName, - newKey, - [...(base.tags || []), `tenant:${tenant.id}`] - ); - - createdProjects.push(project); - console.log(`✓ Created ${newKey}`); - } - - return createdProjects; -} - -// Usage -const tenants = [ - { id: 'acme', name: 'Acme Corp' }, - { id: 'globex', name: 'Globex Inc' }, - { id: 'initech', name: 'Initech' }, -]; - -cloneForTenants('saas-ai', tenants); -``` - -**Result:** -- `saas-ai-acme` - SaaS AI - Acme Corp -- `saas-ai-globex` - SaaS AI - Globex Inc -- `saas-ai-initech` - SaaS AI - Initech - -## Team-Based Cloning - -Clone template project for multiple teams: - -```python -def clone_for_teams(template_project: str, teams: List[str]): - """ - Clone a template project for multiple teams. - - Example: - clone_for_teams("ai-template", ["platform", "customer", "product"]) - """ - pm = ProjectManager() - template = pm.get_project(template_project) - - if not template: - raise ValueError(f"Template project '{template_project}' not found") - - created_projects = [] - - for team in teams: - new_key = f"{team}-ai" - new_name = f"{team.title()} Team AI" - - print(f"Creating {new_key} for {team} team...") - project = clone_project( - source_key=template_project, - new_name=new_name, - new_key=new_key, - tags=["ai-configs", f"team:{team}"] - ) - - # Save SDK keys for team - save_sdk_key_to_env( - new_key, - "production", - env_file=f".env.{team}", - var_name="LAUNCHDARKLY_SDK_KEY" - ) - - created_projects.append(project) - print(f"✓ Created {new_key}") - - return created_projects - -# Usage -clone_for_teams("ai-template", ["platform", "customer", "product"]) -``` - -## Bulk Cloning with CSV - -Clone from a CSV file with project specifications: - -```python -import csv - -def clone_from_csv(source_key: str, csv_file: str): - """ - Clone projects from CSV file. - - CSV format: - project_key,project_name,tags - mobile-ai-us,Mobile AI US,"mobile,us,production" - mobile-ai-eu,Mobile AI EU,"mobile,eu,production" - """ - pm = ProjectManager() - created_projects = [] - - with open(csv_file, 'r') as f: - reader = csv.DictReader(f) - for row in reader: - key = row['project_key'] - name = row['project_name'] - tags = row.get('tags', '').split(',') if row.get('tags') else [] - - print(f"Creating {key}...") - project = clone_project(source_key, name, key, tags) - created_projects.append(project) - print(f"✓ Created {key}") - - return created_projects - -# Usage -clone_from_csv("ai-template", "projects.csv") -``` - -**projects.csv:** -```csv -project_key,project_name,tags -mobile-ai-us,Mobile AI US,"mobile,us,production" -mobile-ai-eu,Mobile AI EU,"mobile,eu,production" -web-ai-us,Web AI US,"web,us,production" -web-ai-eu,Web AI EU,"web,eu,production" -``` - -## Automated SDK Key Management - -After cloning, automatically save SDK keys: - -```python -def clone_and_configure( - source_key: str, - new_key: str, - new_name: str, - env_file: str = None -): - """Clone project and automatically save SDK keys.""" - # Clone the project - project = clone_project(source_key, new_name, new_key) - print(f"✓ Cloned {source_key} → {new_key}") - - # Save SDK keys for both environments - env_file = env_file or f".env.{new_key}" - - for environment in ["production", "test"]: - var_name = f"LD_SDK_KEY_{environment.upper()}" - save_sdk_key_to_env(new_key, environment, env_file, var_name) - - print(f"✓ Saved SDK keys to {env_file}") - - return project -``` - -## Parallel Cloning - -Clone multiple projects in parallel for speed: - -```python -import concurrent.futures - -def clone_projects_parallel(clones: List[Dict[str, str]], max_workers: int = 5): - """ - Clone multiple projects in parallel. - - Args: - clones: List of dicts with keys: source_key, new_key, new_name - max_workers: Max parallel requests - """ - def clone_single(clone_spec): - return clone_project( - source_key=clone_spec['source_key'], - new_name=clone_spec['new_name'], - new_key=clone_spec['new_key'] - ) - - with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: - futures = {executor.submit(clone_single, spec): spec for spec in clones} - - results = [] - for future in concurrent.futures.as_completed(futures): - spec = futures[future] - try: - project = future.result() - print(f"✓ Cloned {spec['new_key']}") - results.append(project) - except Exception as e: - print(f"✗ Failed to clone {spec['new_key']}: {e}") - - return results - -# Usage -clones = [ - {"source_key": "template", "new_key": "team-a-ai", "new_name": "Team A AI"}, - {"source_key": "template", "new_key": "team-b-ai", "new_name": "Team B AI"}, - {"source_key": "template", "new_key": "team-c-ai", "new_name": "Team C AI"}, -] - -clone_projects_parallel(clones) -``` - -## Cloning with MCP Server - -If using the LaunchDarkly MCP server: - -```typescript -// Note: MCP server may not have clone functionality -// You would create projects individually - -async function cloneWithMCP(sourceKey: string, newKey: string, newName: string) { - // Get source project via MCP - const source = await mcp.getProject(sourceKey); - - // Create new project with same settings - const project = await mcp.createProject({ - name: newName, - key: newKey, - tags: [...(source.tags || []), 'cloned'], - }); - - return project; -} -``` - -## Best Practices - -### 1. Naming Conventions -Use consistent naming across clones: -``` -{base}-{region} → ai-service-us, ai-service-eu -{team}-{service} → platform-ai, customer-ai -{service}-{tenant} → saas-acme, saas-globex -``` - -### 2. Tagging Strategy -Tag clones for easy filtering: -```python -tags = [ - "ai-configs", - f"region:{region}", - f"cloned-from:{source_key}", - f"created:{datetime.now().isoformat()}" -] -``` - -### 3. Documentation -Document cloning relationships: -```python -def clone_with_metadata(source_key: str, new_key: str, new_name: str): - """Clone and document the relationship.""" - project = clone_project(source_key, new_name, new_key) - - # Create a mapping file - with open("project-clones.json", "a") as f: - f.write(json.dumps({ - "source": source_key, - "clone": new_key, - "created_at": datetime.now().isoformat() - }) + "\n") - - return project -``` - -### 4. Verification -Verify clones after creation: -```python -def verify_clones(clones: List[str]): - """Verify all cloned projects exist and have SDK keys.""" - pm = ProjectManager() - - for project_key in clones: - project = pm.get_project(project_key) - if not project: - print(f"✗ {project_key} not found") - continue - - sdk_key = pm.get_sdk_key(project_key, "production") - if sdk_key: - print(f"✓ {project_key} verified") - else: - print(f"⚠️ {project_key} missing SDK key") -``` - -## Next Steps - -- [Save SDK keys for cloned projects](env-config.md) -- [Automate with IaC](iac-automation.md) -- [Build admin tooling](admin-tooling.md) diff --git a/skills/ai-configs/aiconfig-projects/references/python-setup.md b/skills/ai-configs/aiconfig-projects/references/python-setup.md deleted file mode 100644 index 87b4ab9..0000000 --- a/skills/ai-configs/aiconfig-projects/references/python-setup.md +++ /dev/null @@ -1,323 +0,0 @@ -# Python Project Setup - -Implementation patterns for Python applications using the LaunchDarkly API. - -## Prerequisites - -```bash -pip install requests python-dotenv -``` - -## Basic Project Management Module - -Create a reusable module for project operations: - -```python -# launchdarkly/projects.py -import os -import requests -from typing import Optional, Dict, List - -API_TOKEN = os.environ.get("LAUNCHDARKLY_API_TOKEN") -BASE_URL = "https://app.launchdarkly.com/api/v2" - - -class ProjectManager: - """Manage LaunchDarkly projects via API.""" - - def __init__(self, api_token: Optional[str] = None): - self.api_token = api_token or API_TOKEN - self.headers = { - "Authorization": self.api_token, - "Content-Type": "application/json" - } - - def create_project(self, name: str, key: str, tags: Optional[List[str]] = None) -> Optional[Dict]: - """ - Create a new LaunchDarkly project. - - Args: - name: Human-readable project name - key: Unique identifier (lowercase, hyphens only) - tags: Optional list of tags - - Returns: - Project dict if successful, None otherwise - """ - payload = {"name": name, "key": key} - if tags: - payload["tags"] = tags - - response = requests.post( - f"{BASE_URL}/projects", - headers=self.headers, - json=payload - ) - - if response.status_code == 201: - return response.json() - elif response.status_code == 409: - print(f"Project '{key}' already exists") - return self.get_project(key) - else: - print(f"Error: {response.text}") - return None - - def get_project(self, project_key: str) -> Optional[Dict]: - """Get project with environments expanded.""" - response = requests.get( - f"{BASE_URL}/projects/{project_key}", - headers=self.headers, - params={"expand": "environments"} - ) - return response.json() if response.status_code == 200 else None - - def get_sdk_key(self, project_key: str, environment: str = "production") -> Optional[str]: - """Get SDK key for a specific environment.""" - project = self.get_project(project_key) - if not project: - return None - - envs = project.get("environments", {}) - env_items = envs.get("items", []) if isinstance(envs, dict) else envs - - for env in env_items: - if env["key"] == environment: - return env["apiKey"] - - return None - - def list_projects(self) -> List[Dict]: - """List all projects in organization.""" - response = requests.get( - f"{BASE_URL}/projects", - headers=self.headers - ) - return response.json().get("items", []) if response.status_code == 200 else [] -``` - -## Usage Examples - -### Create a Project -```python -from launchdarkly.projects import ProjectManager - -pm = ProjectManager() - -# Create new project -project = pm.create_project( - name="Customer Support AI", - key="support-ai", - tags=["ai-configs", "production"] -) - -if project: - print(f"Created project: {project['key']}") -``` - -### Get SDK Key -```python -pm = ProjectManager() - -# Get production SDK key -sdk_key = pm.get_sdk_key("support-ai", "production") -print(f"Production SDK Key: {sdk_key}") - -# Get test SDK key -test_sdk_key = pm.get_sdk_key("support-ai", "test") -print(f"Test SDK Key: {test_sdk_key}") -``` - -### List Projects -```python -pm = ProjectManager() - -projects = pm.list_projects() -for project in projects: - print(f"- {project['name']} ({project['key']})") -``` - -## FastAPI Integration - -If you're using FastAPI, integrate project management into your app: - -```python -# app/config.py -from pydantic_settings import BaseSettings - -class Settings(BaseSettings): - launchdarkly_api_token: str - launchdarkly_sdk_key: str - - class Config: - env_file = ".env" - -settings = Settings() - - -# app/main.py -from fastapi import FastAPI -from launchdarkly.projects import ProjectManager -from .config import settings - -app = FastAPI() -pm = ProjectManager(api_token=settings.launchdarkly_api_token) - -@app.on_event("startup") -async def startup(): - # Ensure project exists - project = pm.create_project( - name="My API Service", - key="api-service" - ) - if project: - print(f"LaunchDarkly project ready: {project['key']}") -``` - -## Django Integration - -For Django applications: - -```python -# settings.py -import os -from launchdarkly.projects import ProjectManager - -LAUNCHDARKLY_API_TOKEN = os.environ.get("LAUNCHDARKLY_API_TOKEN") -LAUNCHDARKLY_PROJECT_KEY = os.environ.get("LAUNCHDARKLY_PROJECT_KEY", "django-app") - -# Ensure project exists on startup -pm = ProjectManager(api_token=LAUNCHDARKLY_API_TOKEN) -project = pm.create_project( - name="Django Application", - key=LAUNCHDARKLY_PROJECT_KEY -) - -LAUNCHDARKLY_SDK_KEY = pm.get_sdk_key(LAUNCHDARKLY_PROJECT_KEY, "production") -``` - -## CLI Tool - -Create a management CLI for project operations: - -```python -# cli/ld_projects.py -import click -from launchdarkly.projects import ProjectManager - -@click.group() -def cli(): - """LaunchDarkly project management CLI.""" - pass - -@cli.command() -@click.argument("name") -@click.argument("key") -@click.option("--tags", multiple=True, help="Project tags") -def create(name: str, key: str, tags: tuple): - """Create a new project.""" - pm = ProjectManager() - project = pm.create_project(name, key, list(tags)) - if project: - click.echo(f"✓ Created: {project['name']} ({project['key']})") - -@cli.command() -def list(): - """List all projects.""" - pm = ProjectManager() - projects = pm.list_projects() - for project in projects: - click.echo(f"- {project['name']} ({project['key']})") - -@cli.command() -@click.argument("project_key") -@click.option("--env", default="production", help="Environment") -def get_key(project_key: str, env: str): - """Get SDK key for a project environment.""" - pm = ProjectManager() - sdk_key = pm.get_sdk_key(project_key, env) - if sdk_key: - click.echo(sdk_key) - -if __name__ == "__main__": - cli() -``` - -**Usage:** -```bash -python cli/ld_projects.py create "My AI" my-ai --tags ai-configs -python cli/ld_projects.py list -python cli/ld_projects.py get-key my-ai --env production -``` - -## Error Handling - -Add robust error handling for production use: - -```python -class LaunchDarklyError(Exception): - """Base exception for LaunchDarkly operations.""" - pass - -class ProjectManager: - def create_project(self, name: str, key: str, tags: Optional[List[str]] = None) -> Dict: - """Create project with error handling.""" - try: - response = requests.post( - f"{BASE_URL}/projects", - headers=self.headers, - json={"name": name, "key": key, "tags": tags or []}, - timeout=10 - ) - response.raise_for_status() - return response.json() - except requests.exceptions.HTTPError as e: - if e.response.status_code == 409: - # Project exists, return existing - return self.get_project(key) - elif e.response.status_code == 401: - raise LaunchDarklyError("Invalid API token") - elif e.response.status_code == 403: - raise LaunchDarklyError("Insufficient permissions (need projects:write)") - else: - raise LaunchDarklyError(f"API error: {e.response.text}") - except requests.exceptions.RequestException as e: - raise LaunchDarklyError(f"Request failed: {str(e)}") -``` - -## Testing - -Mock the API for unit tests: - -```python -# tests/test_projects.py -import pytest -from unittest.mock import Mock, patch -from launchdarkly.projects import ProjectManager - -@pytest.fixture -def mock_response(): - mock = Mock() - mock.status_code = 201 - mock.json.return_value = { - "name": "Test Project", - "key": "test-project" - } - return mock - -@patch("requests.post") -def test_create_project(mock_post, mock_response): - mock_post.return_value = mock_response - - pm = ProjectManager(api_token="test-token") - project = pm.create_project("Test Project", "test-project") - - assert project["key"] == "test-project" - mock_post.assert_called_once() -``` - -## Next Steps - -- [Save SDK keys to .env](env-config.md) -- [Clone projects for different environments](project-cloning.md) -- [Build admin tooling](admin-tooling.md) diff --git a/skills/ai-configs/aiconfig-projects/references/quick-start.md b/skills/ai-configs/aiconfig-projects/references/quick-start.md deleted file mode 100644 index eb91dec..0000000 --- a/skills/ai-configs/aiconfig-projects/references/quick-start.md +++ /dev/null @@ -1,170 +0,0 @@ -# Quick Start: Create Your First Project - -Basic project creation patterns for getting started quickly. - -## Prerequisites - -- LaunchDarkly API access token with `projects:write` permission -- Set `LAUNCHDARKLY_API_TOKEN` environment variable - -## Basic Project Creation - -### Using the LaunchDarkly API - -**Endpoint:** `POST https://app.launchdarkly.com/api/v2/projects` - -**Required Headers:** -``` -Authorization: {YOUR_API_TOKEN} -Content-Type: application/json -``` - -**Minimal Payload:** -```json -{ - "name": "My AI Project", - "key": "my-ai-project" -} -``` - -**Recommended Payload:** -```json -{ - "name": "Customer Support AI", - "key": "support-ai", - "tags": ["ai-configs", "production"] -} -``` - -## Response Handling - -### Success (201 Created) -```json -{ - "name": "Customer Support AI", - "key": "support-ai", - "environments": { - "items": [ - { - "name": "Production", - "key": "production", - "apiKey": "sdk-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - }, - { - "name": "Test", - "key": "test", - "apiKey": "sdk-yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy" - } - ] - } -} -``` - -**Action:** Extract and save SDK keys for use in your application. - -### Conflict (409) -Project with that key already exists. - -**Action:** Either use the existing project or choose a different key. - -### Error (400, 401, 403) -```json -{ - "code": "invalid_request", - "message": "project key must be lowercase with hyphens" -} -``` - -**Action:** Fix the payload based on error message. - -## Project Key Rules - -Must follow these constraints: -- **Pattern:** `^[a-z][a-z0-9-]*$` -- **Start with:** Lowercase letter -- **Contains only:** Lowercase letters, numbers, hyphens -- **Unique:** Across your entire organization - -### Valid Examples -``` -support-ai -chat-bot-v2 -recommendation-engine -customer-ai-prod -``` - -### Invalid Examples -``` -Support_AI # uppercase and underscore -123-project # starts with number -my.project # contains dot -ai_chatbot # underscore not allowed -``` - -## Extracting SDK Keys - -After creating a project, you'll need the SDK keys to connect your application. - -### Environments Created by Default -- **Production** (key: `production`) -- **Test** (key: `test`) - -### Get SDK Key for an Environment - -**Endpoint:** `GET https://app.launchdarkly.com/api/v2/projects/{projectKey}?expand=environments` - -**Parse Response:** -```json -{ - "environments": { - "items": [ - { - "key": "production", - "apiKey": "sdk-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - } - ] - } -} -``` - -Filter by `environment.key` to find the desired environment's `apiKey`. - -## List Existing Projects - -Before creating a new project, you may want to check what exists. - -**Endpoint:** `GET https://app.launchdarkly.com/api/v2/projects` - -**Response:** -```json -{ - "items": [ - { - "name": "Customer Support AI", - "key": "support-ai", - "tags": ["ai-configs"] - } - ] -} -``` - -## Common Mistakes - -| Mistake | Problem | Fix | -|---------|---------|-----| -| Uppercase in key | Rejected by API | Use lowercase only | -| Spaces in key | Invalid format | Use hyphens instead | -| Key collision | 409 conflict | Choose unique key or use existing | -| Missing API token | 401 unauthorized | Set LAUNCHDARKLY_API_TOKEN | -| Wrong permission | 403 forbidden | Request `projects:write` permission | - -## Next Steps - -After creating your project: - -1. **Save SDK keys** to your environment configuration -2. **Initialize LaunchDarkly SDK** in your application -3. **Create AI Configs** within the project -4. **Test the integration** in test environment first - -See [Environment Configuration](env-config.md) for saving SDK keys to your codebase. diff --git a/skills/ai-configs/aiconfig-tools/README.md b/skills/ai-configs/aiconfig-tools/README.md index 671c2bb..7775346 100644 --- a/skills/ai-configs/aiconfig-tools/README.md +++ b/skills/ai-configs/aiconfig-tools/README.md @@ -6,9 +6,9 @@ An Agent Skill for creating tools (function calling) and attaching them to AI Co This skill teaches agents how to: - Identify what capabilities the AI needs -- Create tool definitions with JSON schemas via the API -- Attach tools to AI Config variations -- Verify tools are properly connected +- Create tool definitions using the `create-ai-tool` MCP tool +- Attach tools to AI Config variations via `update-ai-config-variation` +- Verify tools are properly connected via `get-ai-config` ## Installation (Local) @@ -16,8 +16,7 @@ Copy `skills/ai-configs/aiconfig-tools/` into your agent client's skills path. ## Prerequisites -- LaunchDarkly API token with `/*:ai-tool/*` permission -- Existing AI Config (use `aiconfig-create` skill first) +This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment. ## Usage @@ -34,9 +33,7 @@ Create tools for the content assistant to call our API ``` aiconfig-tools/ ├── SKILL.md -├── README.md -└── references/ - └── api-quickstart.md +└── README.md ``` ## Related diff --git a/skills/ai-configs/aiconfig-tools/SKILL.md b/skills/ai-configs/aiconfig-tools/SKILL.md index a8ff757..f70e273 100644 --- a/skills/ai-configs/aiconfig-tools/SKILL.md +++ b/skills/ai-configs/aiconfig-tools/SKILL.md @@ -1,10 +1,11 @@ --- name: aiconfig-tools -description: Guide for giving your AI agents capabilities through tools. Helps you identify what your AI needs to do, create tool definitions, and attach them in a way that makes sense for your framework. -compatibility: Requires LaunchDarkly API token with ai-tool permissions. +description: "Give your AI agents capabilities through tools (function calling). Helps you identify what your AI needs to do, create tool definitions, and attach them to AI Config variations." +license: Apache-2.0 +compatibility: Requires the remotely hosted LaunchDarkly MCP server metadata: author: launchdarkly - version: "0.2.0" + version: "1.0.0-experimental" --- # AI Config Tools @@ -13,94 +14,101 @@ You're using a skill that will guide you through adding capabilities to your AI ## Prerequisites -- LaunchDarkly API token with `/*:ai-tool/*` permission -- Existing AI Config (use `aiconfig-create` skill first) -- Tools endpoint: `/ai-tools` (NOT `/ai-configs/tools`) +This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment. + +**Required MCP tools:** +- `create-ai-tool` -- create a new tool definition with a schema +- `update-ai-config-variation` -- attach tools to an AI Config variation +- `get-ai-config` -- verify tools are attached to the variation + +**Optional MCP tools:** +- `list-ai-tools` -- browse existing tools in the project +- `get-ai-tool` -- inspect a specific tool's schema ## Core Principles 1. **Start with Capabilities**: Think about what your AI needs to do before creating tools 2. **Framework Matters**: LangGraph/CrewAI often auto-generate schemas; OpenAI SDK needs manual schemas 3. **Create Before Attach**: Tools must exist before you can attach them to variations -4. **Verify**: The agent fetches tools and config to confirm attachment - -## API Key Detection - -1. **Check environment variables** — `LAUNCHDARKLY_API_KEY`, `LAUNCHDARKLY_API_TOKEN`, `LD_API_KEY` -2. **Check MCP config** — Claude config if applicable -3. **Prompt user** — Only if detection fails +4. **Verify**: The agent fetches the config to confirm attachment ## Workflow ### Step 1: Identify Needed Capabilities What should the AI be able to do? - - Query databases, call APIs, perform calculations, send notifications - Check what exists in the codebase (API clients, functions) - Consider framework: LangGraph/LangChain auto-generate schemas; direct SDK needs manual schemas ### Step 2: Create Tools -Follow [API Quick Start](references/api-quickstart.md): - -1. **Create tool** — `POST /projects/{projectKey}/ai-tools` with key, description, schema -2. **Schema format** — Use OpenAI function calling format (type, function.name, function.parameters) -3. **Clear descriptions** — The LLM uses the description to decide when to call +Use `create-ai-tool` with: +- `key` -- unique identifier for the tool +- `description` -- clear description (the LLM uses this to decide when to call the tool) +- `schema` -- OpenAI function calling format: + +```json +{ + "type": "function", + "function": { + "name": "search_database", + "description": "Search for customer records", + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string", "description": "Search query"}, + "limit": {"type": "integer", "default": 10} + }, + "required": ["query"] + } + } +} +``` ### Step 3: Attach to Variation -Tools cannot be attached during config creation. PATCH the variation: +Use `update-ai-config-variation` to attach tools. Pass the tool references in the `parameters` object: -```bash -PATCH /projects/{projectKey}/ai-configs/{configKey}/variations/{variationKey} +```json +{ + "parameters": { + "tools": [ + {"key": "search-database", "version": 1} + ] + } +} ``` -Body: `{"model": {"parameters": {"tools": [{"key": "tool-name", "version": 1}]}}}` - -See [API Quick Start](references/api-quickstart.md) for full curl example. - ### Step 4: Verify -1. **Verify tool exists:** - ```bash - GET /projects/{projectKey}/ai-tools/{toolKey} - ``` +1. Use `get-ai-tool` to confirm the tool exists with a valid schema +2. Use `get-ai-config` to confirm the tool is attached to the variation (check `tools` in the variation's output) -2. **Verify attached to variation:** - ```bash - GET /projects/{projectKey}/ai-configs/{configKey}/variations/{variationKey} - ``` - Check `model.parameters.tools` includes your tool key. - -3. **Report results:** - - ✓ Tool created with valid schema - - ✓ Tool attached to variation - - ⚠️ Flag any issues +**Report results:** +- Tool created with valid schema +- Tool attached to variation +- Flag any issues ## Orchestrator Note -LangGraph, CrewAI, AutoGen often generate schemas from function definitions. You still need to create tools in LaunchDarkly and attach keys to variations so the SDK knows what's available. +LangGraph, CrewAI, and AutoGen often generate schemas from function definitions. You still need to create tools in LaunchDarkly and attach keys to variations so the SDK knows what's available. ## Edge Cases | Situation | Action | |-----------|--------| | Tool already exists (409) | Use existing or create with different key | -| Wrong endpoint | Use `/ai-tools`, not `/ai-configs/tools` | -| Schema invalid | Use OpenAI function format | +| Schema invalid | Use OpenAI function calling format | +| Wrong endpoint assumed | The tools use `/ai-tools`, not `/ai-configs/tools` | ## What NOT to Do -- Don't use `/ai-configs/tools` — it doesn't exist -- Don't try to attach tools during config creation -- Don't skip clear tool descriptions (LLM needs them) +- Don't try to attach tools during config creation -- update the variation afterward +- Don't skip clear tool descriptions (LLM needs them to decide when to call) +- Don't forget to verify attachment after updating the variation ## Related Skills -- `aiconfig-create` — Create config before attaching tools -- `aiconfig-variations` — Manage variations - -## References - -- [API Quick Start](references/api-quickstart.md) +- `aiconfig-create` -- Create config before attaching tools +- `aiconfig-variations` -- Manage variations with different tool sets diff --git a/skills/ai-configs/aiconfig-tools/references/api-quickstart.md b/skills/ai-configs/aiconfig-tools/references/api-quickstart.md deleted file mode 100644 index 9549dc4..0000000 --- a/skills/ai-configs/aiconfig-tools/references/api-quickstart.md +++ /dev/null @@ -1,88 +0,0 @@ -# Tools API Quick Start - -Create and manage tools using the LaunchDarkly API. - -**Endpoint:** `https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-tools` -Do NOT use `/ai-configs/tools` — that endpoint does not exist. - -## Create a Tool - -```bash -curl -X POST \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-tools \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -d '{ - "key": "search-database", - "description": "Search the customer database", - "schema": { - "type": "function", - "function": { - "name": "search_database", - "description": "Search for records", - "parameters": { - "type": "object", - "properties": { - "query": {"type": "string", "description": "Search query"}, - "limit": {"type": "integer", "default": 10} - }, - "required": ["query"] - } - } - } - }' -``` - -## Attach to Variation - -```bash -curl -X PATCH \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/variations/{variationKey} \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -H "LD-API-Version: beta" \ - -d '{ - "model": { - "parameters": { - "tools": [ - {"key": "search-database", "version": 1} - ] - } - } - }' -``` - -## List Tools - -```bash -curl -X GET \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-tools \ - -H "Authorization: api-xxxxx" -``` - -## Get Tool - -```bash -curl -X GET \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-tools/{toolKey} \ - -H "Authorization: api-xxxxx" -``` - -## Schema Format - -Use OpenAI function calling format: - -```json -{ - "type": "function", - "function": { - "name": "function_name", - "description": "What the LLM uses to decide when to call", - "parameters": { - "type": "object", - "properties": { ... }, - "required": [ ... ] - } - } -} -``` diff --git a/skills/ai-configs/aiconfig-update/README.md b/skills/ai-configs/aiconfig-update/README.md index 9f1e00b..b627047 100644 --- a/skills/ai-configs/aiconfig-update/README.md +++ b/skills/ai-configs/aiconfig-update/README.md @@ -5,10 +5,11 @@ An Agent Skill for updating, archiving, and deleting AI Configs and their variat ## Overview This skill teaches agents how to: -- Update config metadata (name, description) -- Modify variation instructions, messages, models, and parameters -- Archive configs (reversible) or delete them (permanent) -- Verify changes via API fetch +- Assess config health using `get-ai-config-health` before making changes +- Update config metadata (name, description, tags) via `update-ai-config` +- Modify variation instructions, messages, models, and parameters via `update-ai-config-variation` +- Archive configs (reversible) or delete them (permanent, irreversible) +- Verify changes via `get-ai-config` ## Installation (Local) @@ -16,8 +17,7 @@ Copy `skills/ai-configs/aiconfig-update/` into your agent client's skills path. ## Prerequisites -- LaunchDarkly API access token with write permissions -- Existing AI Config to modify +This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment. ## Usage @@ -38,9 +38,7 @@ Archive the old chatbot config ``` aiconfig-update/ ├── SKILL.md -├── README.md -└── references/ - └── api-quickstart.md +└── README.md ``` ## Related diff --git a/skills/ai-configs/aiconfig-update/SKILL.md b/skills/ai-configs/aiconfig-update/SKILL.md index f5b1329..b55674b 100644 --- a/skills/ai-configs/aiconfig-update/SKILL.md +++ b/skills/ai-configs/aiconfig-update/SKILL.md @@ -1,10 +1,11 @@ --- name: aiconfig-update -description: Update, archive, and delete LaunchDarkly AI Configs and their variations. Use when you need to modify config properties, change model parameters, update instructions or messages, archive unused configs, or permanently remove them. -compatibility: Requires LaunchDarkly project with AI Configs enabled and API access token. +description: "Update, archive, and delete LaunchDarkly AI Configs and their variations. Use when you need to modify config properties, change model parameters, update instructions or messages, archive unused configs, or permanently remove them." +license: Apache-2.0 +compatibility: Requires the remotely hosted LaunchDarkly MCP server metadata: author: launchdarkly - version: "0.2.0" + version: "1.0.0-experimental" --- # AI Config Update & Lifecycle @@ -13,8 +14,17 @@ You're using a skill that will guide you through updating, archiving, and deleti ## Prerequisites -- Existing AI Config to modify -- LaunchDarkly API access token or MCP server +This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment. + +**Required MCP tools:** +- `get-ai-config-health` -- assess config health before making changes (detects missing models, orphaned tools, empty configs) +- `get-ai-config` -- understand current state before making changes +- `update-ai-config` -- update config metadata (name, description, tags, archive) +- `update-ai-config-variation` -- update variation model, prompts, or parameters + +**Optional MCP tools:** +- `delete-ai-config` -- permanently delete a config (irreversible) +- `delete-ai-config-variation` -- permanently delete a variation (irreversible) ## Core Principles @@ -22,57 +32,58 @@ You're using a skill that will guide you through updating, archiving, and deleti 2. **Verify After Changing**: Fetch the config again to confirm updates were applied 3. **Archive Before Deleting**: Archival is reversible; deletion is not -## API Key Detection +## Workflow -1. **Check environment variables** — `LAUNCHDARKLY_API_KEY`, `LAUNCHDARKLY_API_TOKEN`, `LD_API_KEY` -2. **Check MCP config** — If applicable -3. **Prompt user** — Only if detection fails +### Step 1: Assess Health and Understand Current State -## Workflow +Start with `get-ai-config-health` to get a structured health assessment. This detects: +- Variations with no model (show as "NO MODEL" in the UI) +- Variations with neither instructions nor messages +- Orphaned tool references (tools attached that don't exist in the project) +- Configs with no variations at all -### Step 1: Understand Current State +The health verdict (`healthy`, `warning`, `unhealthy`) helps you prioritize what to fix. -Fetch the config to see what exists before changing anything: -```bash -GET /projects/{projectKey}/ai-configs/{configKey} -``` +Then use `get-ai-config` to review the full detail: +- Current mode (agent or completion) +- Existing variations and their models +- Current instructions or messages +- Attached tools and parameters ### Step 2: Make the Update -Follow [API Quick Start](references/api-quickstart.md): +**Update config metadata** -- Use `update-ai-config`: +- Change name or description +- Add or replace tags +- Archive with `archived: true` (reversible) -- **Update instructions/messages** — PATCH variation -- **Switch model** — PATCH variation with modelConfigKey and model -- **Tune parameters** — PATCH variation with model.parameters -- **Archive config** — PATCH config with `{"archived": true}` -- **Delete** — DELETE config or variation (irreversible) +**Update a variation** -- Use `update-ai-config-variation`: +- Switch model (provide new `modelConfigKey` and `modelName`) +- Change instructions or messages +- Tune parameters (temperature, maxTokens, etc.) +- Attach or detach tools via the parameters object -### Step 4: Verify +**Archive a config** -- Use `update-ai-config` with `archived: true` -1. **Fetch updated config:** - ```bash - GET /projects/{projectKey}/ai-configs/{configKey}/variations/{variationKey} - ``` +**Delete** -- Use `delete-ai-config` or `delete-ai-config-variation` (irreversible, requires `confirm: true`) -2. **Confirm the response shows your updated values** +### Step 3: Verify -3. **Report results:** - - ✓ Update applied successfully - - ✓ Config reflects changes - - ⚠️ Flag any issues or rollback if needed +Use `get-ai-config` to confirm the response shows your updated values. + +**Report results:** +- Update applied successfully +- Config reflects changes +- Flag any issues or rollback if needed ## What NOT to Do -- Don't update production directly without testing -- Don't change multiple things at once +- Don't update production configs without testing in another variation first +- Don't change multiple things at once -- make incremental changes - Don't skip verification - Don't delete without user confirmation ## Related Skills -- `aiconfig-variations` — Create variations to test changes -- `aiconfig-tools` — Update tools - -## References - -- [API Quick Start](references/api-quickstart.md) +- `aiconfig-variations` -- Create variations to test changes side-by-side +- `aiconfig-tools` -- Update tool attachments diff --git a/skills/ai-configs/aiconfig-update/references/api-quickstart.md b/skills/ai-configs/aiconfig-update/references/api-quickstart.md deleted file mode 100644 index ffaea8c..0000000 --- a/skills/ai-configs/aiconfig-update/references/api-quickstart.md +++ /dev/null @@ -1,74 +0,0 @@ -# Update API Quick Start - -Update and delete AI Configs using the LaunchDarkly API. - -## Update Config Metadata - -```bash -curl -X PATCH \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey} \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -H "LD-API-Version: beta" \ - -d '{ - "name": "Updated Name", - "description": "Updated description" - }' -``` - -## Update Variation - -```bash -curl -X PATCH \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/variations/{variationKey} \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -H "LD-API-Version: beta" \ - -d '{ - "instructions": "Updated instructions...", - "model": { - "parameters": { - "temperature": 0.5, - "maxTokens": 1500 - } - } - }' -``` - -## Archive Config - -```bash -curl -X PATCH \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey} \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -H "LD-API-Version: beta" \ - -d '{"archived": true}' -``` - -## Delete Config - -```bash -curl -X DELETE \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey} \ - -H "Authorization: api-xxxxx" \ - -H "LD-API-Version: beta" -``` - -## Delete Variation - -```bash -curl -X DELETE \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/variations/{variationKey} \ - -H "Authorization: api-xxxxx" \ - -H "LD-API-Version: beta" -``` - -## Verify Update - -```bash -curl -X GET \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/variations/{variationKey} \ - -H "Authorization: api-xxxxx" \ - -H "LD-API-Version: beta" -``` diff --git a/skills/ai-configs/aiconfig-variations/README.md b/skills/ai-configs/aiconfig-variations/README.md index 5bb9f69..706ecbf 100644 --- a/skills/ai-configs/aiconfig-variations/README.md +++ b/skills/ai-configs/aiconfig-variations/README.md @@ -6,9 +6,8 @@ An Agent Skill for creating and managing AI Config variations to experiment with This skill teaches agents how to: - Design experiments (model comparison, prompt optimization, parameter tuning) -- Create variations via the API -- Attach tools to variations -- Verify variations exist with correct configuration +- Create variations using `clone-ai-config-variation` (recommended) or `create-ai-config-variation` +- Verify variations exist with correct configuration via `get-ai-config` ## Installation (Local) @@ -16,8 +15,7 @@ Copy `skills/ai-configs/aiconfig-variations/` into your agent client's skills pa ## Prerequisites -- LaunchDarkly API access token with `ai-configs:write` permission -- Existing AI Config (use `aiconfig-create` skill first) +This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment. ## Usage @@ -34,9 +32,7 @@ Create variations to compare Claude vs GPT-4 for our agent ``` aiconfig-variations/ ├── SKILL.md -├── README.md -└── references/ - └── api-quickstart.md +└── README.md ``` ## Related diff --git a/skills/ai-configs/aiconfig-variations/SKILL.md b/skills/ai-configs/aiconfig-variations/SKILL.md index 17627bb..6a64814 100644 --- a/skills/ai-configs/aiconfig-variations/SKILL.md +++ b/skills/ai-configs/aiconfig-variations/SKILL.md @@ -1,10 +1,11 @@ --- name: aiconfig-variations -description: Guide for experimenting with AI configurations. Helps you test different models, prompts, and parameters to find what works best through systematic experimentation. -compatibility: Requires LaunchDarkly API access token with ai-configs:write permission. +description: "Experiment with AI configurations by creating and managing variations. Helps you test different models, prompts, and parameters to find what works best through systematic experimentation." +license: Apache-2.0 +compatibility: Requires the remotely hosted LaunchDarkly MCP server metadata: author: launchdarkly - version: "0.2.0" + version: "1.0.0-experimental" --- # AI Config Variations @@ -13,22 +14,25 @@ You're using a skill that will guide you through testing and optimizing AI confi ## Prerequisites -- Existing AI Config (use `aiconfig-create` first) -- LaunchDarkly API access token or MCP server -- Clear hypothesis about what to test +This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment. + +**Primary MCP tool:** +- `clone-ai-config-variation` -- clone a baseline variation with selective overrides (recommended for experimentation) + +**Alternative MCP tools (for more control):** +- `get-ai-config` -- review existing variations before adding new ones +- `create-ai-config-variation` -- create new variations from scratch + +**Optional MCP tools:** +- `update-ai-config-variation` -- refine a variation after creation +- `delete-ai-config-variation` -- remove variations that didn't work out ## Core Principles 1. **Test One Thing at a Time**: Change model OR prompt OR parameters, not all at once 2. **Have a Hypothesis**: Know what you're trying to improve 3. **Measure Results**: Use metrics to compare variations -4. **Verify via API**: The agent fetches the config to confirm variations exist - -## API Key Detection - -1. **Check environment variables** — `LAUNCHDARKLY_API_KEY`, `LAUNCHDARKLY_API_TOKEN`, `LD_API_KEY` -2. **Check MCP config** — If applicable -3. **Prompt user** — Only if detection fails +4. **Verify via Tool**: The agent fetches the config to confirm variations exist ## Workflow @@ -40,53 +44,49 @@ What's the problem? Cost, quality, speed, accuracy? How will you measure success | Goal | What to Vary | |------|--------------| -| Reduce cost | Cheaper model (e.g., gpt-4o-mini) | -| Improve quality | Better model or prompt | -| Reduce latency | Faster model, lower max_tokens | -| Increase accuracy | Different model (Claude vs GPT-4) | +| Reduce cost | Cheaper model (e.g., `gpt-4o-mini`) | +| Improve quality | Better model or more detailed prompt | +| Reduce latency | Faster model, lower `maxTokens` | +| Increase accuracy | Different model family (Claude vs GPT-4) | -### Step 3: Create Variations +### Step 3: Create Variations (Recommended: Clone with Overrides) -Follow [API Quick Start](references/api-quickstart.md): +Use `clone-ai-config-variation` to duplicate the baseline and override only what you're testing. This ensures everything stays constant except your test variable — the tool reads the source variation, merges your overrides, and creates the new variation. -- `POST /projects/{projectKey}/ai-configs/{configKey}/variations` -- Include modelConfigKey (required for UI) -- Keep everything else constant except what you're testing +Provide: +- `sourceVariationKey` -- the baseline to clone from +- `key` and `name` -- identifiers for the new variation (e.g., `gpt4o-mini-cost-test`) +- Only the fields you want to change (e.g., `modelConfigKey` and `modelName` to test a cheaper model) -### Step 4: Set Up Targeting +The response returns both the source and created variation, so you can immediately verify the diff. -Use `aiconfig-targeting` skill to control distribution (e.g., 50/50 split for A/B test). +### Step 3 (Alternative): Create from Scratch -### Step 5: Verify +If you need full control, use `get-ai-config` to review the current state, then `create-ai-config-variation` with all fields specified manually. -1. **Fetch config:** - ```bash - GET /projects/{projectKey}/ai-configs/{configKey} - ``` +### Step 4: Verify -2. **Confirm variations exist with correct model and parameters** +If you used `clone-ai-config-variation`, the response includes both source and created variations for immediate comparison. Otherwise, use `get-ai-config` to confirm. -3. **Report results:** - - ✓ Variations created - - ✓ Models and parameters correct - - ⚠️ Flag any issues +**Report results:** +- Variations created with correct models and parameters +- Only the intended variable differs between variations +- Flag any issues -## modelConfigKey +## modelConfigKey Format -Required for models to show in UI. Format: `{Provider}.{model-id}` — e.g., `OpenAI.gpt-4o`, `Anthropic.claude-sonnet-4-5`. +Required for models to display in the UI. Format: `{Provider}.{model-id}`: +- `OpenAI.gpt-4o`, `OpenAI.gpt-4o-mini` +- `Anthropic.claude-sonnet-4-5`, `Anthropic.claude-3-5-sonnet` ## What NOT to Do - Don't test too many things at once - Don't forget modelConfigKey - Don't make decisions on small sample sizes +- Don't remove the baseline variation while testing ## Related Skills -- `aiconfig-create` — Create the initial config -- `aiconfig-targeting` — Control who gets which variation -- `aiconfig-update` — Refine based on learnings - -## References - -- [API Quick Start](references/api-quickstart.md) +- `aiconfig-create` -- Create the initial config +- `aiconfig-update` -- Refine based on learnings diff --git a/skills/ai-configs/aiconfig-variations/references/api-quickstart.md b/skills/ai-configs/aiconfig-variations/references/api-quickstart.md deleted file mode 100644 index 526d553..0000000 --- a/skills/ai-configs/aiconfig-variations/references/api-quickstart.md +++ /dev/null @@ -1,112 +0,0 @@ -# Variations API Quick Start - -Create, update, and manage variations using the LaunchDarkly API. - -## Create Variation (Agent Mode) - -```bash -curl -X POST \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/variations \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -H "LD-API-Version: beta" \ - -d '{ - "key": "gpt4o-variant", - "name": "GPT-4o Variant", - "instructions": "You are a helpful assistant.", - "modelConfigKey": "OpenAI.gpt-4o", - "model": { - "modelName": "gpt-4o", - "parameters": { - "temperature": 0.7, - "maxTokens": 1500 - } - } - }' -``` - -## Create Variation (Completion Mode) - -```bash -curl -X POST \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/variations \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -H "LD-API-Version: beta" \ - -d '{ - "key": "claude-variant", - "name": "Claude Variant", - "messages": [ - {"role": "system", "content": "You are helpful."}, - {"role": "user", "content": "{{user_input}}"} - ], - "modelConfigKey": "Anthropic.claude-sonnet-4-5", - "model": { - "modelName": "claude-sonnet-4-5", - "parameters": { - "temperature": 0.8, - "maxTokens": 2000 - } - } - }' -``` - -## Update Variation - -```bash -curl -X PATCH \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/variations/{variationKey} \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -H "LD-API-Version: beta" \ - -d '{ - "instructions": "Updated instructions...", - "model": { - "parameters": { - "temperature": 0.5, - "maxTokens": 1000 - } - } - }' -``` - -## List Variations - -```bash -curl -X GET \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey} \ - -H "Authorization: api-xxxxx" \ - -H "LD-API-Version: beta" -``` - -## Delete Variation - -```bash -curl -X DELETE \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/variations/{variationKey} \ - -H "Authorization: api-xxxxx" \ - -H "LD-API-Version: beta" -``` - -## modelConfigKey Format - -`{Provider}.{model-id}` — e.g., `OpenAI.gpt-4o`, `Anthropic.claude-sonnet-4-5` - -## Attach Tools - -```bash -curl -X PATCH \ - https://app.launchdarkly.com/api/v2/projects/{projectKey}/ai-configs/{configKey}/variations/{variationKey} \ - -H "Authorization: api-xxxxx" \ - -H "Content-Type: application/json" \ - -H "LD-API-Version: beta" \ - -d '{ - "model": { - "parameters": { - "tools": [ - {"key": "search-database", "version": 1} - ] - } - } - }' -``` diff --git a/skills/feature-flags/launchdarkly-flag-create/SKILL.md b/skills/feature-flags/launchdarkly-flag-create/SKILL.md index f4a089f..a639ff5 100644 --- a/skills/feature-flags/launchdarkly-flag-create/SKILL.md +++ b/skills/feature-flags/launchdarkly-flag-create/SKILL.md @@ -68,7 +68,7 @@ Based on what the user needs, choose the appropriate flag configuration. See [Fl **Defaults to apply:** - Set `temporary: true` unless the user explicitly says this is a permanent/long-lived flag. Most flags are release flags that should eventually be cleaned up. -- Generate a `key` from the name if not provided (e.g., "New Checkout Flow" → `new-checkout-flow`), but match the codebase's naming convention if one exists. +- Generate a `key` from the name if not provided (e.g., "New Checkout Flow" -> `new-checkout-flow`), but match the codebase's naming convention if one exists. - Suggest relevant tags based on the feature area, team, or context the user mentions. ### Step 3: Create the Flag in LaunchDarkly diff --git a/skills/feature-flags/launchdarkly-flag-create/references/flag-types.md b/skills/feature-flags/launchdarkly-flag-create/references/flag-types.md index 69bb719..2af4240 100644 --- a/skills/feature-flags/launchdarkly-flag-create/references/flag-types.md +++ b/skills/feature-flags/launchdarkly-flag-create/references/flag-types.md @@ -146,7 +146,7 @@ Tags help organize flags in LaunchDarkly. Suggest tags based on: ## Best Practices for Variations ### Boolean Flags -- Name variations: `true` → "Enabled" / "New behavior", `false` → "Disabled" / "Old behavior" +- Name variations: `true` -> "Enabled" / "New behavior", `false` -> "Disabled" / "Old behavior" - Set `offVariation` to `false` (index 1) ### Multivariate Flags diff --git a/skills/feature-flags/launchdarkly-flag-discovery/SKILL.md b/skills/feature-flags/launchdarkly-flag-discovery/SKILL.md index 2e75560..bd4a3e2 100644 --- a/skills/feature-flags/launchdarkly-flag-discovery/SKILL.md +++ b/skills/feature-flags/launchdarkly-flag-discovery/SKILL.md @@ -68,7 +68,7 @@ Key signals to evaluate: | Signal | What it tells you | |--------|-------------------| -| **Lifecycle state** | Where the flag is in its journey (new → active → launched → inactive) | +| **Lifecycle state** | Where the flag is in its journey (new -> active -> launched -> inactive) | | **Last requested** | When an SDK last evaluated this flag — staleness indicator | | **Targeting complexity** | Number of rules and targets — removal complexity indicator | | **Cross-environment consistency** | Whether the flag behaves the same everywhere | diff --git a/skills/feature-flags/launchdarkly-flag-recreate/README.md b/skills/feature-flags/launchdarkly-flag-recreate/README.md new file mode 100644 index 0000000..0155014 --- /dev/null +++ b/skills/feature-flags/launchdarkly-flag-recreate/README.md @@ -0,0 +1,13 @@ +# LaunchDarkly Flag Recreate + +Recreate a misconfigured feature flag when the key or variation type was set incorrectly. Flag keys and variation kinds are immutable after creation — this skill guides the agent through creating a correct replacement, migrating targeting across environments, and archiving the old flag. + +## When to Use + +- Flag key has a typo +- Flag was created as boolean but should be multivariate (or vice versa) +- Variation values are wrong and can't be corrected + +## How It Works + +See [SKILL.md](SKILL.md) for the full workflow. diff --git a/skills/feature-flags/launchdarkly-flag-recreate/SKILL.md b/skills/feature-flags/launchdarkly-flag-recreate/SKILL.md new file mode 100644 index 0000000..e371ede --- /dev/null +++ b/skills/feature-flags/launchdarkly-flag-recreate/SKILL.md @@ -0,0 +1,238 @@ +--- +name: launchdarkly-flag-recreate +description: "Recreate a misconfigured LaunchDarkly feature flag when the key or variation type was set wrong. Use when a flag has a typo in its key, the wrong kind (boolean vs multivariate), or incorrect variation values that cannot be changed after creation." +license: Apache-2.0 +compatibility: Requires the remotely hosted LaunchDarkly MCP server +metadata: + author: launchdarkly + version: "0.1.0" +--- + +# LaunchDarkly Flag Recreate + +You're using a skill that will guide you through recreating a misconfigured feature flag. Flag keys and variation types are immutable after creation, so when they're wrong, the only fix is to create a new flag with the correct configuration and migrate everything over. Your job is to capture the old flag's full state, create a correct replacement, migrate targeting across environments, update code references, and archive the old flag. + +## Prerequisites + +This skill requires the remotely hosted LaunchDarkly MCP server to be configured in your environment. + +**Required MCP tools:** +- `get-flag` — read the misconfigured flag's full configuration per environment +- `create-flag` — create the replacement flag with the correct key/kind/variations +- `toggle-flag` — set on/off state per environment on the new flag +- `update-rollout` — set fallthrough rules on the new flag +- `update-targeting-rules` — reconstruct custom targeting rules on the new flag +- `update-individual-targets` — reconstruct individual targets on the new flag +- `get-flag-status-across-envs` — check the old flag's status in all environments + +**Optional MCP tools (enhance workflow):** +- `archive-flag` — archive the old flag after migration +- `list-flags` — verify naming conventions in the project +- `update-flag-settings` — copy metadata (tags, description) if needed +- `update-off-variation` — set the off variation when the old flag used a non-default one +- `update-prerequisites` — migrate prerequisites onto the new flag; update dependent flags to point to the new key + +## Core Principles + +1. **Capture Before You Create**: Read the old flag's full configuration before making any changes. You need a complete picture of targeting across all environments. +2. **One Environment at a Time**: Migrate targeting environment by environment. Verify each before moving on. +3. **Don't Rush the Cutover**: The new flag starts OFF. Only toggle it on in an environment after targeting is fully reconstructed there. +4. **Keep the Old Flag Until Verified**: Don't archive or delete the old flag until the new one is confirmed working in all environments. + +## Workflow + +### Step 1: Explore the Old Flag + +Get the full picture of the misconfigured flag before doing anything. + +1. **Get the flag configuration.** Use `get-flag` for each environment the flag is used in. Record: + - Flag key, name, kind, description, tags, temporary status + - Variations (values and names) + - Per-environment: on/off state, fallthrough, rules, individual targets, off variation, prerequisites + +2. **Check status across environments.** Use `get-flag-status-across-envs` to see which environments are active, inactive, or launched. + +3. **Search the codebase for references.** Find all places the old flag key appears: + - SDK evaluation calls + - Constants/enums + - Tests + - Configuration files + +4. **Summarize what you found.** Present the user with: + - The flag's current configuration + - Which environments have active targeting + - How many code references exist + - The complexity of migration (simple if no targeting, complex if rules/targets exist) + +### Step 2: Assess the Correction + +Determine exactly what needs to change and confirm with the user. + +| What's Wrong | What to Fix | +|-------------|-------------| +| Typo in the key | New key with correct spelling; same kind, variations, and metadata | +| Wrong kind (boolean instead of multivariate) | New kind; user must define the correct variations | +| Wrong kind (multivariate instead of boolean) | Switch to boolean; variations become `true`/`false` | +| Wrong variation values | Same kind; new variations with correct values | +| Multiple issues | Fix all at once in the new flag | + +**Confirm with the user:** +- What should the new flag key be? +- If changing kind: what variations should the new flag have? +- If the old flag has targeting rules that reference specific variation indices, how should those map to the new variations? + +### Step 3: Create the Replacement Flag + +Use `create-flag` with the corrected configuration. + +``` +create-flag: + projectKey: + name: + key: + kind: + description: + temporary: + tags: + variations: +``` + +After creation, use `get-flag` on the new flag to verify it was created with the right configuration. The new flag will be OFF in all environments. + +### Step 4: Migrate Targeting + +For each environment that had active targeting on the old flag, reconstruct the targeting on the new flag. Work through one environment at a time. + +**Migration order:** +1. **Non-production environments first** — start with dev/staging to validate the process +2. **Production last** — only after you've verified the pattern works + +**Per environment, in order:** + +1. **Set the fallthrough (default rule).** Use `update-rollout` to match the old flag's fallthrough. If the old flag served a single variation, set `rolloutType: "variation"`. If it used a percentage rollout, set `rolloutType: "percentage"` with matching weights. + +2. **Set the off variation (if non-default).** If the old flag used a different off variation than the default (last variation), use `update-off-variation` to set it on the new flag. When targeting is OFF, the flag serves this variation. + +3. **Add prerequisites (if any).** If the old flag has prerequisites (depends on other flags), use `update-prerequisites` to add the same prerequisites to the new flag. + +4. **Add custom targeting rules.** Use `update-targeting-rules` with `addRule` instructions for each rule from the old flag. Reconstruct clauses and variation assignments. + +5. **Add individual targets.** Use `update-individual-targets` with `addTargets` or `addContextTargets` instructions for each targeting entry. + +6. **Toggle on.** Once targeting is reconstructed, use `toggle-flag` to turn the new flag on in this environment. + +7. **Verify.** Use `get-flag` on the new flag in this environment to confirm the targeting matches the old flag. + +See [Targeting Migration Guide](references/targeting-migration.md) for detailed instructions on reconstructing each type of targeting. + +**Important:** If the variation type changed (boolean to multivariate or vice versa), variation indices will be different. You must map old variation indices to new ones before reconstructing rules and fallthrough. Confirm this mapping with the user. + +### Step 5: Update Code References + +After the new flag is live, update all code references from the old key to the new key. + +1. **Replace the flag key** in all locations found in Step 1. +2. **Update variation handling** if the kind or variation values changed: + - Boolean to multivariate: replace boolean checks with value comparisons + - Multivariate to boolean: replace value comparisons with boolean checks + - Same kind, different values: update any hardcoded variation value references +3. **Update constants/enums** if the flag key is defined in a constants file. +4. **Update tests** to reference the new key and variation values. + +### Step 6: Archive the Old Flag + +Once the new flag is verified and all code references are updated: + +1. **Confirm with the user** before archiving. Verify: + - New flag is ON and serving correctly in all needed environments + - All code references point to the new key + - The old flag is no longer being evaluated (check status) + +2. **Archive the old flag** using `archive-flag`. This is reversible — prefer it over `delete-flag`. + +### Step 7: Verify + +Confirm the full migration is complete: + +1. **New flag exists** with correct key, kind, and variations. Use `get-flag`. +2. **Targeting matches** in all environments. Compare with the configuration captured in Step 1. +3. **Code compiles and lints.** Run the project's build/lint step. +4. **Old flag is archived.** Confirm with `get-flag` (should show archived status or error). +5. **Report results:** + - Old flag key and what was wrong + - New flag key and what was corrected + - Environments migrated + - Code files updated + - Any issues or follow-ups needed + +## Edge Cases + +| Situation | Action | +|-----------|--------| +| Old flag has no targeting (just defaults) | Simple — create new flag and update code references only | +| Old flag has complex rules across many environments | Migrate non-prod first, verify, then prod | +| Old flag has prerequisites (it depends on other flags) | Use `update-prerequisites` to add the same prerequisites to the new flag per environment | +| Old flag is a prerequisite for other flags | Use `update-prerequisites` on each dependent flag to replace the old key with the new key before archiving | +| Variation index mapping changed | Build an explicit old-index → new-index map, confirm with user | +| Old flag has expiring targets | Note them, recreate on the new flag using `manage-expiring-targets` | +| Old flag is actively serving production traffic | Coordinate with user — may need the new flag to go live before the old one goes off | +| Environment requires approval for changes | `toggle-flag` will return `requiresApproval: true` — inform the user | +| Kind changed from boolean to multivariate | All existing `boolVariation()` calls must change to `variation()` with value comparison | +| Only the key is wrong, targeting is complex | Consider if a key typo is tolerable vs the cost of migrating complex targeting | + +## What NOT to Do + +- Don't delete the old flag before the new one is fully verified and serving traffic +- Don't migrate production first — always start with non-production environments +- Don't assume variation indices map 1:1 when the kind or variations changed +- Don't skip confirming the new configuration with the user before creating +- Don't leave the old flag active after migration — archive it to avoid confusion +- Don't batch all environment migrations into one step — go one at a time and verify + +## Tool Stubs + +The following tools are referenced by this skill but may not yet exist. Stub definitions for implementers: + +### `update-off-variation` + +**Description:** Set the off variation for a flag in a specific environment. When targeting is OFF, the flag serves this variation to all contexts. Defaults to the last variation if not set. + +**Input:** + +| Parameter | Type | Required | Description | +|-------------|--------|----------|-----------------------------------------------------| +| projectKey | string | Yes | Project key | +| flagKey | string | Yes | Flag key | +| env | string | Yes | Environment key | +| variationIndex | number | Yes | Zero-based index of the variation to serve when off | +| comment | string | No | Optional change comment | + +**Returns:** Confirmation of the update (key, env, offVariation index). + +**Example call:** +- name: `update-off-variation` +- input: `{ "projectKey": "my-project", "flagKey": "new-flag-key", "env": "production", "variationIndex": 0 }` + +### `update-prerequisites` + +**Description:** Add or remove prerequisites for a flag in a specific environment. Prerequisites are other flags that must evaluate to specific variations before this flag is evaluated. + +**Input:** + +| Parameter | Type | Required | Description | +|-------------|--------|----------|-----------------------------------------------------------------------------| +| projectKey | string | Yes | Project key | +| flagKey | string | Yes | Flag key | +| env | string | Yes | Environment key | +| instructions | array | Yes | Semantic patch instructions: `addPrerequisite` (key, variationId) or `removePrerequisite` (key) | +| comment | string | No | Optional change comment | + +**Returns:** Updated prerequisites list for the flag in that environment. + +**Example call:** +- name: `update-prerequisites` +- input: `{ "projectKey": "my-project", "flagKey": "new-flag-key", "env": "production", "instructions": [{ "kind": "addPrerequisite", "key": "parent-flag", "variationId": "abc123" }] }` + +## References + +- [Targeting Migration Guide](references/targeting-migration.md) — Step-by-step instructions for reconstructing targeting rules, rollouts, and individual targets on the new flag diff --git a/skills/feature-flags/launchdarkly-flag-recreate/references/targeting-migration.md b/skills/feature-flags/launchdarkly-flag-recreate/references/targeting-migration.md new file mode 100644 index 0000000..b2fabcc --- /dev/null +++ b/skills/feature-flags/launchdarkly-flag-recreate/references/targeting-migration.md @@ -0,0 +1,283 @@ +# Targeting Migration Guide + +How to reconstruct targeting from one flag onto another using the LaunchDarkly MCP tools. This is needed when a flag was created with the wrong key or variation type and must be replaced. + +## Overview + +There is no single tool to copy targeting between two different flags. You must read the old flag's targeting configuration and reconstruct it on the new flag using the targeting tools, one environment at a time. + +## Before You Start + +### Build a Variation Map + +If the old and new flags have different variations (different kind, different values, or different ordering), you need a mapping from old variation indices to new ones. + +**Same kind, same variations (key-only fix):** +Indices map 1:1. No translation needed. + +| Old Index | New Index | +|-----------|-----------| +| 0 | 0 | +| 1 | 1 | + +**Boolean to multivariate:** +The old flag had `true` (index 0) and `false` (index 1). Map each to the new variation that represents the equivalent behavior. + +| Old Index | Old Value | New Index | New Value | +|-----------|-----------|-----------|-----------| +| 0 | `true` | ? | Confirm with user | +| 1 | `false` | ? | Confirm with user | + +**Multivariate to boolean:** +Map each old variation to `true` (index 0) or `false` (index 1). Some information loss is expected — confirm which old variations map to which boolean value. + +**Different variation values (same kind):** +Map by semantic meaning, not position. If the old flag had `["small", "large"]` and the new has `["small", "medium", "large"]`, the indices shift. + +### Read the Old Flag + +For each environment, call `get-flag` on the old flag and record: + +``` +get-flag: + projectKey: + flagKey: + env: +``` + +Note these fields from the response: +- `on` — is targeting on or off? +- `fallthrough` — the default rule (variation index or rollout weights) +- `offVariation` — which variation is served when targeting is off +- `rules` — array of custom targeting rules +- `targets` — individual user/context targets +- `prerequisites` — flags this flag depends on + +## Migration Steps (Per Environment) + +### 1. Set the Fallthrough + +The fallthrough (default rule) determines what unmatched contexts receive. + +**Single variation fallthrough:** + +``` +update-rollout: + projectKey: + flagKey: + env: + rolloutType: variation + variationIndex: + comment: "Migrated from " +``` + +**Percentage rollout fallthrough:** + +Translate each weight entry using the variation map. + +``` +update-rollout: + projectKey: + flagKey: + env: + rolloutType: percentage + weights: + - variationIndex: + percent: + - variationIndex: + percent: + comment: "Migrated from " +``` + +### 2. Add Custom Targeting Rules + +For each rule on the old flag, add a corresponding rule on the new flag. Rules are order-sensitive — add them in the same order they appeared on the old flag. + +Each rule has: +- **Clauses**: conditions that match contexts (attribute, op, values) +- **Variation or rollout**: what to serve when the rule matches + +**Single-variation rule:** + +``` +update-targeting-rules: + projectKey: + flagKey: + env: + instructions: + - kind: addRule + clauses: + - attribute: + op: + values: [] + contextKind: + variationId: + description: + comment: "Migrated rule from " +``` + +**Important:** The `addRule` instruction requires a `variationId` (the variation's internal `_id` string), not a `variationIndex`. To get variation IDs, call `get-flag` on the new flag — each variation in the response includes its `_id`. + +**Rollout rule:** + +``` +update-targeting-rules: + projectKey: + flagKey: + env: + instructions: + - kind: addRule + clauses: + - attribute: + op: + values: [] + rolloutWeights: + : + : + rolloutContextKind: + comment: "Migrated rollout rule from " +``` + +### 3. Set the Off Variation + +If the old flag used a non-default off variation (anything other than the last variation), set it on the new flag. When targeting is OFF, the flag serves this variation to all contexts. + +``` +update-off-variation: + projectKey: + flagKey: + env: + variationIndex: + comment: "Migrated from " +``` + +### 4. Add Prerequisites + +If the old flag has prerequisites (it depends on other flags), add them to the new flag. Use `update-prerequisites` with `addPrerequisite` instructions. Map variation indices using the same variation map if the prerequisite flag's variations changed. + +``` +update-prerequisites: + projectKey: + flagKey: + env: + instructions: + - kind: addPrerequisite + key: + variationId: + comment: "Migrated from " +``` + +**Note:** Prerequisites reference other flags by key. If you're recreating a prerequisite flag in the same migration, add the new prerequisite key, not the old one. + +### 5. Add Individual Targets + +Individual targets pin specific users or contexts to a variation. They override all rules. + +**User targets (legacy):** + +``` +update-individual-targets: + projectKey: + flagKey: + env: + instructions: + - kind: addTargets + variationId: + values: [, ] + comment: "Migrated targets from " +``` + +**Context targets:** + +``` +update-individual-targets: + projectKey: + flagKey: + env: + instructions: + - kind: addContextTargets + variationId: + contextKind: + values: [, ] + comment: "Migrated context targets from " +``` + +### 6. Toggle On + +Only after all targeting is reconstructed: + +``` +toggle-flag: + projectKey: + flagKey: + env: + on: true + comment: "Enabling after migration from " +``` + +### 7. Verify + +Call `get-flag` on the new flag in this environment and compare against the old flag's configuration recorded earlier. Check: +- Fallthrough matches (same variation or same rollout percentages) +- Same number of rules with equivalent clauses +- Same individual targets assigned to equivalent variations +- On/off state matches + +## Handling Special Cases + +### Flags That Depend on the Old Flag + +If other flags list the old flag as a prerequisite, those flags need to be updated to reference the new flag key. Use `update-prerequisites` on each dependent flag to remove the old prerequisite and add the new one: + +``` +update-prerequisites: + projectKey: + flagKey: + env: + instructions: + - kind: removePrerequisite + key: + - kind: addPrerequisite + key: + variationId: + comment: "Migrate prerequisite from to " +``` + +### Expiring Targets + +If the old flag has expiring targets, recreate them on the new flag using `manage-expiring-targets`: + +``` +manage-expiring-targets: + projectKey: + flagKey: + env: + action: add + contextKey: + contextKind: + variationId: + expiresAt: +``` + +## Migration Checklist + +For each environment: + +- [ ] Read old flag configuration +- [ ] Map variation indices (if kind or variations changed) +- [ ] Set fallthrough on new flag +- [ ] Set off variation (if old flag used non-default) +- [ ] Add prerequisites (if old flag had any) +- [ ] Add all custom targeting rules (in order) +- [ ] Add all individual targets +- [ ] Add expiring targets (if any) +- [ ] Toggle new flag to match old flag's on/off state +- [ ] Verify new flag configuration matches old flag +- [ ] Update code references from old key to new key + +After all environments: + +- [ ] All code references updated +- [ ] Code compiles and tests pass +- [ ] Update dependent flags' prerequisites to point to new key (if old flag was a prerequisite) +- [ ] Archive old flag diff --git a/skills/feature-flags/launchdarkly-flag-targeting/SKILL.md b/skills/feature-flags/launchdarkly-flag-targeting/SKILL.md index 07f3c9d..4d2a6e5 100644 --- a/skills/feature-flags/launchdarkly-flag-targeting/SKILL.md +++ b/skills/feature-flags/launchdarkly-flag-targeting/SKILL.md @@ -30,10 +30,10 @@ This skill requires the remotely hosted LaunchDarkly MCP server to be configured Before making any targeting changes, understand how LaunchDarkly evaluates flags. This determines what your changes actually do: -1. **Flag is OFF** → Serve the `offVariation` to everyone. Nothing else matters. -2. **Individual targets** → If the context matches a specific target list, serve that variation. Highest priority. -3. **Custom rules** → Evaluate rules top-to-bottom. First matching rule wins. -4. **Default rule (fallthrough)** → If nothing else matched, serve this variation or rollout. +1. **Flag is OFF** -> Serve the `offVariation` to everyone. Nothing else matters. +2. **Individual targets** -> If the context matches a specific target list, serve that variation. Highest priority. +3. **Custom rules** -> Evaluate rules top-to-bottom. First matching rule wins. +4. **Default rule (fallthrough)** -> If nothing else matched, serve this variation or rollout. This means: if you add a targeting rule but the flag is OFF, nobody sees the change. If you set a percentage rollout on the default rule but there's an individual target, that targeted user bypasses the rollout. diff --git a/skills/feature-flags/launchdarkly-flag-targeting/references/targeting-patterns.md b/skills/feature-flags/launchdarkly-flag-targeting/references/targeting-patterns.md index 78e3a83..3fb4246 100644 --- a/skills/feature-flags/launchdarkly-flag-targeting/references/targeting-patterns.md +++ b/skills/feature-flags/launchdarkly-flag-targeting/references/targeting-patterns.md @@ -16,7 +16,7 @@ The simplest targeting change. **Notes:** - Turning a flag OFF makes it serve the `offVariation` to everyone, regardless of rules or targets. -- Turning a flag ON activates the full targeting evaluation (individual targets → rules → default rule). +- Turning a flag ON activates the full targeting evaluation (individual targets -> rules -> default rule). ## Percentage Rollouts (Default Rule) diff --git a/skills/skill-authoring/create-skill/SKILL.md b/skills/skill-authoring/create-skill/SKILL.md index 24eab3c..9181e15 100644 --- a/skills/skill-authoring/create-skill/SKILL.md +++ b/skills/skill-authoring/create-skill/SKILL.md @@ -61,7 +61,12 @@ Based on the user's request and your exploration: - What should the agent explore, assess, and verify? - What references will the skill need? -3. **Plan the workflow.** +3. **Identify required MCP tools.** + - What MCP tools does the skill's workflow depend on? + - Which of those tools already exist? (Check available MCP servers) + - Which tools would need to be built? These will be stubbed out in the skill. + +4. **Plan the workflow.** - Step 1: Explore (what to look for) - Step 2: Assess (decision table or logic) - Step 3: Execute (with references) @@ -92,7 +97,30 @@ See [Frontmatter & Metadata](references/frontmatter.md) for required fields. - `README.md` — short description, link to SKILL.md - `marketplace.json` — if publishing to a marketplace (see existing skills for format) -5. **Update repo docs.** +5. **Stub out tools that don't exist yet.** + If the skill's workflow depends on MCP tools that haven't been built, add a `## Tool Stubs` section at the bottom of SKILL.md (before References). Each stub should define the tool name, description, expected input schema, and what it should return — enough for someone to implement the tool later. + + Format each stub like this: + ``` + ### `tool-name` + + **Description:** What the tool does + + **Input:** + | Parameter | Type | Required | Description | + |-----------|------|----------|-------------| + | param1 | string | Yes | What it is | + + **Returns:** What the tool responds with + + **Example call:** + - name: `tool-name` + - input: `{ "param1": "value" }` + ``` + + This gives tool implementers a clear contract to build against and makes the skill usable as soon as the tools ship. + +6. **Update repo docs.** - Add the skill to the table in `README.md` - If the skill requires specific tools, document them in the skill @@ -144,6 +172,7 @@ Confirm the skill is valid and complete: | Marketplace.json needed | Copy format from `launchdarkly-flag-create/marketplace.json` | | Validation fails | Fix the specific error (often frontmatter or naming) | | Catalog not regenerated | Run `python3 scripts/generate_catalog.py` before commit | +| Skill needs tools that don't exist yet | Add a `## Tool Stubs` section at the bottom of SKILL.md with the tool contract (name, inputs, output) | ## What NOT to Do @@ -152,6 +181,7 @@ Confirm the skill is valid and complete: - Don't forget to run `validate_skills.py` before committing - Don't skip updating README.md and the catalog - Don't use internal-only links or tools unless the skill is internal-only +- Don't reference MCP tools without documenting them — if they don't exist yet, stub them out so implementers know the expected contract ## References