Token导航 LogoToken导航

Agent Skill · 前端设计

feature-flags(特征标志)

feature-flags 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

feature-flags 是前端设计类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描已通过,按安装量在前端设计类的 10,216 个 Skill 里排第 6,192。

总安装 541周安装 23GitHub Stars 777下载 190核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:feature-flags(特征标志)
来源仓库:https://github.com/dadbodgeoff/drift
仓库路径:skills/feature-flags
安装命令:
npx skills add https://github.com/dadbodgeoff/drift --skill feature-flags
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/dadbodgeoff/drift --skill feature-flags

这个 Skill 是做什么的

前端设计类 Skill 关注页面结构、视觉规范和组件写法,输出通常是可运行的界面代码。

feature-flags 用于渐进发布与 A/B 测试,适合在 Codex、Claude、Cursor、Gemini CLI 中需要控制功能可用性或灰度上线时使用。它支持布尔、百分比与用户列表等多种 flag 类型,提供 kill switch 与环境隔离能力。

使用时需管理平台状态与回滚机制,避免配置漂移;建议通过自动化测试验证分支逻辑。安装前请确认配置存储方式,注意是否会读取外部服务,确保 flag 决策过程可观测。适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

来源仓库内路径:skills/feature-flags

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/dadbodgeoff/drift --skill feature-flags;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

feature-flags 怎么安装?

在终端执行 npx skills add https://github.com/dadbodgeoff/drift --skill feature-flags。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

feature-flags 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

feature-flags 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

feature-flags 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

feature-flags 用的人多吗?

公开数据是累计安装 541 次,近一周安装 23 次,下载 190 次,GitHub 777 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

feature-flags 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理前端设计类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

前端设计类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
frontend-design

前端设计 · Codex、Claude

8,823,360126,072只读MIT通过
vercel-composition-patterns

Vercel 组合模式 · Codex、Claude

3,858,81625,893需要联网MIT通过
vercel-react-native-skills

Vercel React native skills 前端 · Codex、Claude

2,468,66425,878只读MIT通过
adapt

adapt 搜索 · Codex、Claude

2,027,04023,125操作浏览器未明确通过
bolder

bolder 分析 · Codex、Claude

2,004,28823,130只读未明确通过
feature-flags

特征标志 · 本页

541777需要联网未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

Feature Flags

Control feature availability without deployments.

When to Use This Skill

  • Gradual feature rollouts (1% → 10% → 100%)
  • A/B testing different implementations
  • Kill switches for problematic features
  • Beta features for specific users
  • Environment-specific features

Flag Types

TypeUse CaseExample
BooleanSimple on/offnew_checkout_enabled
PercentageGradual rolloutnew_ui: 25%
User ListBeta testersbeta_users: [id1, id2]
SegmentUser groupspremium_feature: tier=pro

TypeScript Implementation

Flag Configuration

// feature-flags.ts
interface FlagConfig {
  enabled: boolean;
  percentage?: number;        // 0-100 for gradual rollout
  allowedUsers?: string[];    // Specific user IDs
  allowedSegments?: string[]; // User segments (e.g., 'beta', 'pro')
  metadata?: Record<string, unknown>;
}

interface FeatureFlagsConfig {
  flags: Record<string, FlagConfig>;
  defaultEnabled: boolean;
}

interface UserContext {
  id: string;
  segments?: string[];
  attributes?: Record<string, unknown>;
}

class FeatureFlags {
  private config: FeatureFlagsConfig;

  constructor(config: FeatureFlagsConfig) {
    this.config = config;
  }

  isEnabled(flagName: string, user?: UserContext): boolean {
    const flag = this.config.flags[flagName];

    if (!flag) {
      return this.config.defaultEnabled;
    }

    if (!flag.enabled) {
      return false;
    }

    // Check user allowlist
    if (flag.allowedUsers?.length && user) {
      if (flag.allowedUsers.includes(user.id)) {
        return true;
      }
    }

    // Check segment allowlist
    if (flag.allowedSegments?.length && user?.segments) {
      const hasSegment = flag.allowedSegments.some(
        segment => user.segments?.includes(segment)
      );
      if (hasSegment) {
        return true;
      }
    }

    // Check percentage rollout
    if (flag.percentage !== undefined && user) {
      return this.isInPercentage(user.id, flagName, flag.percentage);
    }

    // If no specific rules, use enabled status
    return flag.enabled && !flag.percentage && !flag.allowedUsers?.length;
  }

  private isInPercentage(userId: string, flagName: string, percentage: number): boolean {
    // Consistent hashing - same user always gets same result
    const hash = this.hashString(`${userId}:${flagName}`);
    const bucket = hash % 100;
    return bucket < percentage;
  }

  private hashString(str: string): number {
    let hash = 0;
    for (let i = 0; i < str.length; i++) {
      const char = str.charCodeAt(i);
      hash = ((hash << 5) - hash) + char;
      hash = hash & hash; // Convert to 32-bit integer
    }
    return Math.abs(hash);
  }

  getFlag(flagName: string): FlagConfig | undefined {
    return this.config.flags[flagName];
  }

  getAllFlags(): Record<string, FlagConfig> {
    return { ...this.config.flags };
  }

  // For client-side: get all flags evaluated for a user
  evaluateAll(user?: UserContext): Record<string, boolean> {
    const result: Record<string, boolean> = {};
    for (const flagName of Object.keys(this.config.flags)) {
      result[flagName] = this.isEnabled(flagName, user);
    }
    return result;
  }
}

export { FeatureFlags, FlagConfig, FeatureFlagsConfig, UserContext };

Dynamic Flag Loading

// flag-loader.ts
import { Redis } from 'ioredis';
import { FeatureFlags, FeatureFlagsConfig } from './feature-flags';

class DynamicFeatureFlags {
  private flags: FeatureFlags;
  private redis: Redis;
  private refreshInterval: NodeJS.Timeout;

  constructor(redis: Redis, refreshMs = 30000) {
    this.redis = redis;
    this.flags = new FeatureFlags({ flags: {}, defaultEnabled: false });

    // Refresh flags periodically
    this.refreshInterval = setInterval(() => this.refresh(), refreshMs);
    this.refresh();
  }

  async refresh(): Promise<void> {
    try {
      const configJson = await this.redis.get('feature_flags');
      if (configJson) {
        const config: FeatureFlagsConfig = JSON.parse(configJson);
        this.flags = new FeatureFlags(config);
      }
    } catch (error) {
      console.error('Failed to refresh feature flags:', error);
    }
  }

  isEnabled(flagName: string, user?: UserContext): boolean {
    return this.flags.isEnabled(flagName, user);
  }

  evaluateAll(user?: UserContext): Record<string, boolean> {
    return this.flags.evaluateAll(user);
  }

  async setFlag(flagName: string, config: FlagConfig): Promise<void> {
    const currentConfig = await this.getConfig();
    currentConfig.flags[flagName] = config;
    await this.redis.set('feature_flags', JSON.stringify(currentConfig));
    await this.refresh();
  }

  async deleteFlag(flagName: string): Promise<void> {
    const currentConfig = await this.getConfig();
    delete currentConfig.flags[flagName];
    await this.redis.set('feature_flags', JSON.stringify(currentConfig));
    await this.refresh();
  }

  private async getConfig(): Promise<FeatureFlagsConfig> {
    const configJson = await this.redis.get('feature_flags');
    return configJson
      ? JSON.parse(configJson)
      : { flags: {}, defaultEnabled: false };
  }

  close(): void {
    clearInterval(this.refreshInterval);
  }
}

export { DynamicFeatureFlags };

Express Middleware

// feature-flag-middleware.ts
import { Request, Response, NextFunction } from 'express';
import { DynamicFeatureFlags, UserContext } from './flag-loader';

declare global {
  namespace Express {
    interface Request {
      featureFlags: Record<string, boolean>;
      isFeatureEnabled: (flag: string) => boolean;
    }
  }
}

function featureFlagMiddleware(flags: DynamicFeatureFlags) {
  return (req: Request, res: Response, next: NextFunction) => {
    // Build user context from request
    const user: UserContext | undefined = req.user ? {
      id: req.user.id,
      segments: [req.user.tier, ...(req.user.tags || [])],
      attributes: {
        email: req.user.email,
        createdAt: req.user.createdAt,
      },
    } : undefined;

    // Evaluate all flags for this user
    req.featureFlags = flags.evaluateAll(user);

    // Helper function
    req.isFeatureEnabled = (flag: string) => req.featureFlags[flag] ?? false;

    next();
  };
}

export { featureFlagMiddleware };

Usage in Routes

// routes.ts
app.get('/checkout', (req, res) => {
  if (req.isFeatureEnabled('new_checkout')) {
    return res.render('checkout-v2');
  }
  return res.render('checkout');
});

app.get('/api/features', (req, res) => {
  // Send evaluated flags to frontend
  res.json({ flags: req.featureFlags });
});

Python Implementation

# feature_flags.py
import hashlib
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
import json
import redis

@dataclass
class FlagConfig:
    enabled: bool
    percentage: Optional[int] = None
    allowed_users: List[str] = field(default_factory=list)
    allowed_segments: List[str] = field(default_factory=list)
    metadata: Dict[str, Any] = field(default_factory=dict)

@dataclass
class UserContext:
    id: str
    segments: List[str] = field(default_factory=list)
    attributes: Dict[str, Any] = field(default_factory=dict)

class FeatureFlags:
    def __init__(self, flags: Dict[str, FlagConfig], default_enabled: bool = False):
        self.flags = flags
        self.default_enabled = default_enabled

    def is_enabled(self, flag_name: str, user: Optional[UserContext] = None) -> bool:
        flag = self.flags.get(flag_name)

        if not flag:
            return self.default_enabled

        if not flag.enabled:
            return False

        # Check user allowlist
        if flag.allowed_users and user:
            if user.id in flag.allowed_users:
                return True

        # Check segment allowlist
        if flag.allowed_segments and user and user.segments:
            if any(seg in flag.allowed_segments for seg in user.segments):
                return True

        # Check percentage rollout
        if flag.percentage is not None and user:
            return self._is_in_percentage(user.id, flag_name, flag.percentage)

        # Default to enabled if no specific rules
        return flag.enabled and not flag.percentage and not flag.allowed_users

    def _is_in_percentage(self, user_id: str, flag_name: str, percentage: int) -> bool:
        hash_input = f"{user_id}:{flag_name}".encode()
        hash_value = int(hashlib.md5(hash_input).hexdigest(), 16)
        bucket = hash_value % 100
        return bucket < percentage

    def evaluate_all(self, user: Optional[UserContext] = None) -> Dict[str, bool]:
        return {
            name: self.is_enabled(name, user)
            for name in self.flags
        }

class DynamicFeatureFlags:
    def __init__(self, redis_client: redis.Redis, key: str = "feature_flags"):
        self.redis = redis_client
        self.key = key
        self._flags: Optional[FeatureFlags] = None
        self.refresh()

    def refresh(self) -> None:
        try:
            data = self.redis.get(self.key)
            if data:
                config = json.loads(data)
                flags = {
                    name: FlagConfig(**flag_config)
                    for name, flag_config in config.get("flags", {}).items()
                }
                self._flags = FeatureFlags(
                    flags=flags,
                    default_enabled=config.get("default_enabled", False)
                )
        except Exception as e:
            print(f"Failed to refresh flags: {e}")

    def is_enabled(self, flag_name: str, user: Optional[UserContext] = None) -> bool:
        if not self._flags:
            return False
        return self._flags.is_enabled(flag_name, user)

    def evaluate_all(self, user: Optional[UserContext] = None) -> Dict[str, bool]:
        if not self._flags:
            return {}
        return self._flags.evaluate_all(user)

    def set_flag(self, flag_name: str, config: FlagConfig) -> None:
        data = self.redis.get(self.key)
        current = json.loads(data) if data else {"flags": {}, "default_enabled": False}
        current["flags"][flag_name] = {
            "enabled": config.enabled,
            "percentage": config.percentage,
            "allowed_users": config.allowed_users,
            "allowed_segments": config.allowed_segments,
            "metadata": config.metadata,
        }
        self.redis.set(self.key, json.dumps(current))
        self.refresh()

FastAPI Integration

# fastapi_flags.py
from fastapi import Request, Depends
from functools import lru_cache

@lru_cache()
def get_feature_flags() -> DynamicFeatureFlags:
    return DynamicFeatureFlags(redis_client)

def get_user_context(request: Request) -> Optional[UserContext]:
    user = getattr(request.state, "user", None)
    if not user:
        return None
    return UserContext(
        id=user.id,
        segments=[user.tier] + (user.tags or []),
        attributes={"email": user.email},
    )

@app.get("/checkout")
async def checkout(
    flags: DynamicFeatureFlags = Depends(get_feature_flags),
    user: Optional[UserContext] = Depends(get_user_context),
):
    if flags.is_enabled("new_checkout", user):
        return {"template": "checkout-v2"}
    return {"template": "checkout"}

@app.get("/api/features")
async def get_features(
    flags: DynamicFeatureFlags = Depends(get_feature_flags),
    user: Optional[UserContext] = Depends(get_user_context),
):
    return {"flags": flags.evaluate_all(user)}

React Integration

// FeatureFlagProvider.tsx
import { createContext, useContext, useEffect, useState } from 'react';

interface FeatureFlagContextValue {
  flags: Record<string, boolean>;
  isEnabled: (flag: string) => boolean;
  loading: boolean;
}

const FeatureFlagContext = createContext<FeatureFlagContextValue>({
  flags: {},
  isEnabled: () => false,
  loading: true,
});

export function FeatureFlagProvider({ children }: { children: React.ReactNode }) {
  const [flags, setFlags] = useState<Record<string, boolean>>({});
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/features')
      .then(res => res.json())
      .then(data => {
        setFlags(data.flags);
        setLoading(false);
      });
  }, []);

  const isEnabled = (flag: string) => flags[flag] ?? false;

  return (
    <FeatureFlagContext.Provider value={{ flags, isEnabled, loading }}>
      {children}
    </FeatureFlagContext.Provider>
  );
}

export function useFeatureFlag(flag: string): boolean {
  const { isEnabled } = useContext(FeatureFlagContext);
  return isEnabled(flag);
}

// Usage
function CheckoutButton() {
  const newCheckout = useFeatureFlag('new_checkout');

  if (newCheckout) {
    return <NewCheckoutButton />;
  }
  return <LegacyCheckoutButton />;
}

Admin API

// admin-routes.ts
router.get('/admin/flags', async (req, res) => {
  const flags = await featureFlags.getAllFlags();
  res.json({ flags });
});

router.put('/admin/flags/:name', async (req, res) => {
  const { name } = req.params;
  const config: FlagConfig = req.body;

  await featureFlags.setFlag(name, config);
  res.json({ success: true });
});

router.delete('/admin/flags/:name', async (req, res) => {
  const { name } = req.params;
  await featureFlags.deleteFlag(name);
  res.json({ success: true });
});

// Gradual rollout helper
router.post('/admin/flags/:name/rollout', async (req, res) => {
  const { name } = req.params;
  const { percentage } = req.body;

  const current = await featureFlags.getFlag(name);
  if (!current) {
    return res.status(404).json({ error: 'Flag not found' });
  }

  await featureFlags.setFlag(name, {
    ...current,
    percentage,
  });

  res.json({ success: true, percentage });
});

Best Practices

  1. Use consistent hashing: Same user always gets same result
  2. Default to disabled: New flags should be off by default
  3. Clean up old flags: Remove flags after full rollout
  4. Log flag evaluations: Track which users see which features
  5. Cache flag config: Don't hit Redis on every request

Common Mistakes

  • Random percentage (user sees different result each request)
  • Not cleaning up old flags (config bloat)
  • Hardcoding flag names (use constants)
  • Not testing both paths
  • Forgetting to remove flag checks after rollout

平台分布

Codex

38.68%
按下载量换算73

Claude

28.78%
按下载量换算55

Cursor

17.72%
按下载量换算34

Gemini CLI

9.63%
按下载量换算18

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills