diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 12d290d..da85524 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,5 +1,6 @@ { "name": "launchdarkly-ai-tooling", + "description": "Official LaunchDarkly plugins for Claude Code", "owner": { "name": "LaunchDarkly", "email": "support@launchdarkly.com", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 9d3418e..ee42452 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "launchdarkly", - "version": "1.0.0", + "version": "1.0.1", "description": "LaunchDarkly agent skills and MCP servers for feature flag management, AgentControl, and metrics", "author": { "name": "LaunchDarkly", @@ -9,7 +9,8 @@ "homepage": "https://launchdarkly.com", "repository": "https://github.com/launchdarkly/ai-tooling", "license": "Apache-2.0", - "logo": "logo.svg", + "privacyPolicyUrl": "https://launchdarkly.com/policies/privacy/", + "icon": "logo.svg", "keywords": [ "launchdarkly", "feature-flags", @@ -19,5 +20,21 @@ "rollout", "cleanup", "mcp" + ], + "skills": [ + "./skills/agentcontrol/configs-create", + "./skills/agentcontrol/configs-targeting", + "./skills/agentcontrol/configs-update", + "./skills/agentcontrol/configs-variations", + "./skills/feature-flags/launchdarkly-flag-cleanup", + "./skills/feature-flags/launchdarkly-flag-create", + "./skills/feature-flags/launchdarkly-flag-discovery", + "./skills/feature-flags/launchdarkly-flag-targeting", + "./skills/metrics/launchdarkly-metric-choose", + "./skills/metrics/launchdarkly-metric-create", + "./skills/metrics/launchdarkly-metric-instrument", + "./skills/agentcontrol/online-evals", + "./skills/agentcontrol/projects", + "./skills/agentcontrol/tools" ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 47f8736..3563dd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,5 +4,6 @@ All notable changes to this repository will be documented in this file. ## Unreleased +- Claude Code plugin 1.0.1: resolve plugin directory scanner findings. Replace the flat `skills/` symlinks with an explicit `skills` list in `.claude-plugin/plugin.json`, add `icon` and `privacyPolicyUrl`, and stop skills from auto-detecting API tokens in environment variables or `~/.claude/config.json` (prefer the MCP server's OAuth; ask the user for a token when a REST call is unavoidable). - Refine `experiments/launchdarkly-experiment-setup` skill to match the LaunchDarkly REST API tool shapes: nested `iteration` on create, `mutableFieldsByStatus`-aware `update-experiment`, and the new `save-and-start-experiment-iteration` + `stop-experiment-iteration` tools. - Initial public release of LaunchDarkly agent skills diff --git a/skills/agentcontrol/built-in-metrics/references/langchain-tracking.md b/skills/agentcontrol/built-in-metrics/references/langchain-tracking.md index 5412c2f..32d5454 100644 --- a/skills/agentcontrol/built-in-metrics/references/langchain-tracking.md +++ b/skills/agentcontrol/built-in-metrics/references/langchain-tracking.md @@ -30,12 +30,12 @@ max_search_results = ai_config.model.get_custom("max_search_results") or 10 **MCP caveat — two paths, pick one.** The LaunchDarkly MCP `update-ai-config-variation` tool does not currently expose the top-level `custom` field on a variation. You have two options: -*Option A — PATCH via REST API.* Cleanest shape (value lands at `model.custom` where the Python/Node SDKs expose it via `get_custom(...)` / `custom` accessors) but requires a separate `LD_API_KEY` with write scope: +*Option A — PATCH via REST API.* Cleanest shape (value lands at `model.custom` where the Python/Node SDKs expose it via `get_custom(...)` / `custom` accessors) but requires an API access token with write scope (ask the user for it; do not read it from env vars or config files): ```bash curl -X PATCH \ "https://app.launchdarkly.com/api/v2/projects/$PROJECT/ai-configs/$CONFIG_KEY/variations/$VARIATION_ID" \ - -H "Authorization: $LD_API_KEY" \ + -H "Authorization: {api_token}" \ -H "Content-Type: application/json" \ -d '{"patch":[{"op":"add","path":"/model/custom","value":{"max_search_results":10}}]}' ``` diff --git a/skills/agentcontrol/built-in-metrics/references/metrics-api.md b/skills/agentcontrol/built-in-metrics/references/metrics-api.md index 4fa2ddb..1b45dcd 100644 --- a/skills/agentcontrol/built-in-metrics/references/metrics-api.md +++ b/skills/agentcontrol/built-in-metrics/references/metrics-api.md @@ -27,7 +27,7 @@ import os def get_ai_config_metrics(project_key: str, config_key: str, env: str = "production", hours: int = 24): """Get config metrics for the last N hours.""" - API_TOKEN = os.environ.get("LAUNCHDARKLY_API_TOKEN") + API_TOKEN = "{api_token}" # token the user provided for this session now = int(time.time()) start = now - (hours * 3600) diff --git a/skills/agentcontrol/configs-create/SKILL.md b/skills/agentcontrol/configs-create/SKILL.md index c0da8e4..73a31f3 100644 --- a/skills/agentcontrol/configs-create/SKILL.md +++ b/skills/agentcontrol/configs-create/SKILL.md @@ -164,14 +164,14 @@ Print this checklist verbatim to the user after Step 4, then wait for confirmati Get the variation ID (use `get-ai-config` MCP, or): ```bash curl -s "https://app.launchdarkly.com/api/v2/projects/$PROJECT/ai-configs/$CONFIG_KEY/targeting?env=$ENV" \ - -H "Authorization: $LD_API_KEY" -H "LD-API-Version: beta" \ + -H "Authorization: {api_token}" -H "LD-API-Version: beta" \ | jq '.variations[] | {key, _id}' ``` Flip the fallthrough to point at it: ```bash curl -X PATCH "https://app.launchdarkly.com/api/v2/projects/$PROJECT/ai-configs/$CONFIG_KEY/targeting?env=$ENV" \ - -H "Authorization: $LD_API_KEY" \ + -H "Authorization: {api_token}" \ -H "Content-Type: application/json; domain-model=launchdarkly.semanticpatch" \ -H "LD-API-Version: beta" \ -d '{"instructions":[{"kind":"updateFallthroughVariationOrRollout","variationId":""}]}' diff --git a/skills/agentcontrol/configs-targeting/SKILL.md b/skills/agentcontrol/configs-targeting/SKILL.md index 2338fb3..a57cdc8 100644 --- a/skills/agentcontrol/configs-targeting/SKILL.md +++ b/skills/agentcontrol/configs-targeting/SKILL.md @@ -18,11 +18,9 @@ Configure targeting rules for configs to control which variations serve to diffe - Project key and environment key - Existing config with variations (use `configs-create` skill) -## API Key Detection +## API Access -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 +Prefer the LaunchDarkly MCP server tools: they authenticate via OAuth and need no token. If a step has no MCP equivalent and you must call the REST API directly, ask the user to paste an API access token for this session and substitute it for `{api_token}` in the examples below. Do not search environment variables, `.env` files, or agent/MCP config files for credentials. ## Core Concepts diff --git a/skills/agentcontrol/custom-metrics/SKILL.md b/skills/agentcontrol/custom-metrics/SKILL.md index 30e7f79..3c57878 100644 --- a/skills/agentcontrol/custom-metrics/SKILL.md +++ b/skills/agentcontrol/custom-metrics/SKILL.md @@ -18,39 +18,9 @@ Full lifecycle management of custom business metrics: create metric definitions - LaunchDarkly API token with `writer` role for metric management - Understanding of built-in agent metrics (see `built-in-metrics`) -## API Key Detection +## API Access -Before prompting the user for an API key, try to detect it automatically: - -1. **Check Claude MCP config** - Read `~/.claude/config.json` and look for `mcpServers.launchdarkly.env.LAUNCHDARKLY_API_KEY` -2. **Check environment variables** - Look for `LAUNCHDARKLY_API_KEY`, `LAUNCHDARKLY_API_TOKEN`, or `LD_API_KEY` -3. **Prompt user** - Only if detection fails, ask the user for their API key - -```python -import os -import json -from pathlib import Path - -def get_launchdarkly_api_key(): - """Auto-detect LaunchDarkly API key from Claude config or environment.""" - # 1. Check Claude MCP config - claude_config = Path.home() / ".claude" / "config.json" - if claude_config.exists(): - try: - config = json.load(open(claude_config)) - api_key = config.get("mcpServers", {}).get("launchdarkly", {}).get("env", {}).get("LAUNCHDARKLY_API_KEY") - if api_key: - return api_key - except (json.JSONDecodeError, IOError): - pass - - # 2. Check environment variables - for var in ["LAUNCHDARKLY_API_KEY", "LAUNCHDARKLY_API_TOKEN", "LD_API_KEY"]: - if os.environ.get(var): - return os.environ[var] - - return None -``` +Prefer the LaunchDarkly MCP server tools: they authenticate via OAuth and need no token. If a step has no MCP equivalent and you must call the REST API directly, ask the user to paste an API access token for this session and substitute it for `{api_token}` in the examples below. Do not search environment variables, `.env` files, or agent/MCP config files for credentials. ## Metrics Lifecycle Overview @@ -86,7 +56,7 @@ def create_metric( description: str = None ): """Create a new metric definition in LaunchDarkly.""" - API_TOKEN = os.environ.get("LAUNCHDARKLY_API_TOKEN") + API_TOKEN = "{api_token}" # token the user provided for this session url = f"https://app.launchdarkly.com/api/v2/metrics/{project_key}" @@ -229,7 +199,7 @@ def track_revenue(ld_client, user_id: str, revenue: float, source: str): ```python def get_metric(project_key: str, metric_key: str): """Get a single metric definition.""" - API_TOKEN = os.environ.get("LAUNCHDARKLY_API_TOKEN") + API_TOKEN = "{api_token}" # token the user provided for this session url = f"https://app.launchdarkly.com/api/v2/metrics/{project_key}/{metric_key}" @@ -258,7 +228,7 @@ def get_metric(project_key: str, metric_key: str): ```python def list_metrics(project_key: str, limit: int = 20): """List all metrics in a project.""" - API_TOKEN = os.environ.get("LAUNCHDARKLY_API_TOKEN") + API_TOKEN = "{api_token}" # token the user provided for this session url = f"https://app.launchdarkly.com/api/v2/metrics/{project_key}" @@ -291,7 +261,7 @@ def update_metric(project_key: str, metric_key: str, updates: list): updates: List of patch operations, e.g.: [{"op": "replace", "path": "/name", "value": "New Name"}] """ - API_TOKEN = os.environ.get("LAUNCHDARKLY_API_TOKEN") + API_TOKEN = "{api_token}" # token the user provided for this session url = f"https://app.launchdarkly.com/api/v2/metrics/{project_key}/{metric_key}" @@ -330,7 +300,7 @@ def rename_metric(project_key: str, metric_key: str, new_name: str, new_descript ```python def delete_metric(project_key: str, metric_key: str): """Delete a metric from the project.""" - API_TOKEN = os.environ.get("LAUNCHDARKLY_API_TOKEN") + API_TOKEN = "{api_token}" # token the user provided for this session url = f"https://app.launchdarkly.com/api/v2/metrics/{project_key}/{metric_key}" @@ -359,7 +329,7 @@ from ldclient.config import Config import ldclient # Setup -API_TOKEN = os.environ.get("LAUNCHDARKLY_API_TOKEN") +API_TOKEN = "{api_token}" # token the user provided for this session SDK_KEY = os.environ.get("LAUNCHDARKLY_SDK_KEY") PROJECT_KEY = "support-ai" diff --git a/skills/agentcontrol/migrate/references/agent-mode-frameworks.md b/skills/agentcontrol/migrate/references/agent-mode-frameworks.md index 027f3ae..74a4e66 100644 --- a/skills/agentcontrol/migrate/references/agent-mode-frameworks.md +++ b/skills/agentcontrol/migrate/references/agent-mode-frameworks.md @@ -78,7 +78,7 @@ Mirror the same shape on the LaunchDarkly variation. **MCP caveat.** The `update ```bash curl -X PATCH \ "https://app.launchdarkly.com/api/v2/projects/$PROJECT/ai-configs/$CONFIG_KEY/variations/$VARIATION_ID" \ - -H "Authorization: $LD_API_KEY" \ + -H "Authorization: {api_token}" \ -H "Content-Type: application/json" \ -d '{"patch":[{"op":"add","path":"/model/custom","value":{"max_search_results":10}}]}' ``` diff --git a/skills/agentcontrol/online-evals/SKILL.md b/skills/agentcontrol/online-evals/SKILL.md index b7fb9b2..afabed7 100644 --- a/skills/agentcontrol/online-evals/SKILL.md +++ b/skills/agentcontrol/online-evals/SKILL.md @@ -18,11 +18,9 @@ Attach judges to config variations for automatic quality scoring using LLM-as-a- - Existing config with variations (use `configs-create` skill) - For automatic metric recording and the consolidated judge-result API: Python AI SDK v0.20.0+ or Node.js AI SDK v0.20.0+ -## API Key Detection +## API Access -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 +Prefer the LaunchDarkly MCP server tools: they authenticate via OAuth and need no token. If a step has no MCP equivalent and you must call the REST API directly, ask the user to paste an API access token for this session and substitute it for `{api_token}` in the examples below. Do not search environment variables, `.env` files, or agent/MCP config files for credentials. ## Core Concepts diff --git a/skills/agentcontrol/projects/SKILL.md b/skills/agentcontrol/projects/SKILL.md index acb28bf..5470ace 100644 --- a/skills/agentcontrol/projects/SKILL.md +++ b/skills/agentcontrol/projects/SKILL.md @@ -24,13 +24,9 @@ You're using a skill that will guide you through setting up LaunchDarkly project 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 +## API Access -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 +Prefer the LaunchDarkly MCP server tools: they authenticate via OAuth and need no token. If a step has no MCP equivalent and you must call the REST API directly, ask the user to paste an API access token for this session and substitute it for `{api_token}` in the examples below. Do not search environment variables, `.env` files, or agent/MCP config files for credentials. See [Quick Start](references/quick-start.md) for API usage patterns. diff --git a/skills/configs-create b/skills/configs-create deleted file mode 120000 index c55a91b..0000000 --- a/skills/configs-create +++ /dev/null @@ -1 +0,0 @@ -agentcontrol/configs-create \ No newline at end of file diff --git a/skills/configs-targeting b/skills/configs-targeting deleted file mode 120000 index 4cf3e85..0000000 --- a/skills/configs-targeting +++ /dev/null @@ -1 +0,0 @@ -agentcontrol/configs-targeting \ No newline at end of file diff --git a/skills/configs-update b/skills/configs-update deleted file mode 120000 index 6dd07da..0000000 --- a/skills/configs-update +++ /dev/null @@ -1 +0,0 @@ -agentcontrol/configs-update \ No newline at end of file diff --git a/skills/configs-variations b/skills/configs-variations deleted file mode 120000 index 79453ca..0000000 --- a/skills/configs-variations +++ /dev/null @@ -1 +0,0 @@ -agentcontrol/configs-variations \ No newline at end of file diff --git a/skills/launchdarkly-flag-cleanup b/skills/launchdarkly-flag-cleanup deleted file mode 120000 index 4264903..0000000 --- a/skills/launchdarkly-flag-cleanup +++ /dev/null @@ -1 +0,0 @@ -feature-flags/launchdarkly-flag-cleanup \ No newline at end of file diff --git a/skills/launchdarkly-flag-create b/skills/launchdarkly-flag-create deleted file mode 120000 index d5688c6..0000000 --- a/skills/launchdarkly-flag-create +++ /dev/null @@ -1 +0,0 @@ -feature-flags/launchdarkly-flag-create \ No newline at end of file diff --git a/skills/launchdarkly-flag-discovery b/skills/launchdarkly-flag-discovery deleted file mode 120000 index c289226..0000000 --- a/skills/launchdarkly-flag-discovery +++ /dev/null @@ -1 +0,0 @@ -feature-flags/launchdarkly-flag-discovery \ No newline at end of file diff --git a/skills/launchdarkly-flag-targeting b/skills/launchdarkly-flag-targeting deleted file mode 120000 index c7a825d..0000000 --- a/skills/launchdarkly-flag-targeting +++ /dev/null @@ -1 +0,0 @@ -feature-flags/launchdarkly-flag-targeting \ No newline at end of file diff --git a/skills/launchdarkly-metric-choose b/skills/launchdarkly-metric-choose deleted file mode 120000 index dacc0b2..0000000 --- a/skills/launchdarkly-metric-choose +++ /dev/null @@ -1 +0,0 @@ -metrics/launchdarkly-metric-choose \ No newline at end of file diff --git a/skills/launchdarkly-metric-create b/skills/launchdarkly-metric-create deleted file mode 120000 index 83e5600..0000000 --- a/skills/launchdarkly-metric-create +++ /dev/null @@ -1 +0,0 @@ -metrics/launchdarkly-metric-create \ No newline at end of file diff --git a/skills/launchdarkly-metric-instrument b/skills/launchdarkly-metric-instrument deleted file mode 120000 index 6f7e816..0000000 --- a/skills/launchdarkly-metric-instrument +++ /dev/null @@ -1 +0,0 @@ -metrics/launchdarkly-metric-instrument \ No newline at end of file diff --git a/skills/online-evals b/skills/online-evals deleted file mode 120000 index 5774aaa..0000000 --- a/skills/online-evals +++ /dev/null @@ -1 +0,0 @@ -agentcontrol/online-evals \ No newline at end of file diff --git a/skills/projects b/skills/projects deleted file mode 120000 index 79b64fa..0000000 --- a/skills/projects +++ /dev/null @@ -1 +0,0 @@ -agentcontrol/projects \ No newline at end of file diff --git a/skills/tools b/skills/tools deleted file mode 120000 index 8c65647..0000000 --- a/skills/tools +++ /dev/null @@ -1 +0,0 @@ -agentcontrol/tools \ No newline at end of file