All skills
launchdarkly avatar

/projects

@ef54971 official

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.

Use this Skill: https://skilld.dev/gh/launchdarkly/agent-skills/projects

This session only. Nothing lands on disk.

referencesadmin-tooling.md

≈3.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

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

# 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:

pip install click tabulate

Usage:

# Create project
python cli/ldprojects.py create "My Agent" 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 Agent 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

# 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/<key>')
def project_detail(key):
    project = pm.get_project(key)
    return render_template('project.html', project=project)

@app.route('/projects/<key>/keys/<env>')
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:

<!DOCTYPE html>
<html>
<head>
    <title>LaunchDarkly Projects Admin</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        .project { border: 1px solid #ddd; padding: 15px; margin: 10px 0; }
        .tags { color: #666; font-size: 0.9em; }
    </style>
</head>
<body>
    <h1>LaunchDarkly Projects</h1>
    
    <h2>Create New Project</h2>
    <form id="createForm">
        <input type="text" name="name" placeholder="Project Name" required>
        <input type="text" name="key" placeholder="project-key" required>
        <input type="text" name="tags" placeholder="tag1,tag2">
        <button type="submit">Create</button>
    </form>
    
    <h2>Existing Projects</h2>
    {% for project in projects %}
    <div class="project">
        <h3><a href="/projects/{{ project.key }}">{{ project.name }}</a></h3>
        <p><strong>Key:</strong> {{ project.key }}</p>
        <p class="tags"><strong>Tags:</strong> {{ project.tags|join(', ') }}</p>
    </div>
    {% endfor %}
    
    <script>
        document.getElementById('createForm').onsubmit = async (e) => {
            e.preventDefault();
            const formData = new FormData(e.target);
            const data = {
                name: formData.get('name'),
                key: formData.get('key'),
                tags: formData.get('tags').split(',').filter(t => t.trim())
            };
            
            const resp = await fetch('/projects', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(data)
            });
            
            if (resp.ok) {
                location.reload();
            } else {
                alert('Failed to create project');
            }
        };
    </script>
</body>
</html>

Run the dashboard:

pip install flask
python admin/app.py
# Visit http://localhost:5000

Monitoring & Alerting

Monitor project creation and usage:

# 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:

# 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:

# 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:

# 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

Source: SKILL.md on GitHub

2 alerts2d3 checks · Risk HIGH
  • Gen Agent Trust Hub2d

    The skill facilitates LaunchDarkly project setup but performs high-risk operations, including instructions to automatically extract credentials from sensitive agent configuration files (~/.claude/config.json) and system environment variables. It also generates and executes local scripts to verify integrations, creating a potential path for local code execution.

  • Socket2d

    1 alert: gptAnomaly

  • Snyk2d

    Risk: HIGH · 1 issue

Signed by skilld at ef54971. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated 3 days ago
metadata
{
  "author": "launchdarkly",
  "version": "0.4.0"
}
Other metadata
compatibility
Requires LaunchDarkly API access token with projects:write permission or LaunchDarkly MCP server.
  • Python
  • Go
  • Infrastructure
  • launchdarkly
  • project-setup
  • api-integration
  • nodejs
  • configuration-management
  • feature-flags
  • secrets-management

README badge

README badge for launchdarkly/agent-skills/projects

Guides setup of LaunchDarkly projects in a codebase by exploring the stack, assessing architecture, and choosing the right implementation path. Supports Python, Node.js, Go, and polyglot setups, with reference guides for quick setup, environment configuration, project cloning, and infrastructure automation.

Generated from the current SKILL.md.

What permissions do I need to set up LaunchDarkly projects?
Your API access token must have the projects:write permission, or you can use a LaunchDarkly MCP server configured in your environment instead.
Can the skill automatically detect my LaunchDarkly API key?
Yes. The skill checks for LAUNCHDARKLY_API_KEY, LAUNCHDARKLY_API_TOKEN, or LD_API_KEY environment variables, and also checks the Claude MCP config at ~/.claude/config.json before prompting you for the key.
What project key format is required?
Project keys must start with a letter, use only lowercase letters, numbers, and hyphens, and cannot contain uppercase letters, underscores, or dots.
Does this skill create the projects, or just guide me?
The skill guides you through the setup process and follows reference implementations for your tech stack (Python, Node.js, Go, etc.), but you choose which reference path to execute based on your architecture.
What should I do after projects are created?
After setup, verify the project exists and SDK keys are valid, then proceed to the configs-create skill to create feature flags or the sdk skill to integrate into your application.

Generated from the current SKILL.md. These answers refresh after source changes.