From e74c009e02e8b185f68f73354439712a0945e886 Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Fri, 3 Apr 2026 10:37:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20GrowthBook=20?= =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E6=9C=8D=E5=8A=A1=E5=99=A8=E9=80=82?= =?UTF-8?q?=E9=85=8D=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通过 CLAUDE_GB_ADAPTER_URL/KEY 环境变量连接自定义 GrowthBook 实例, 无配置时所有 feature 读取返回代码默认值。支持 GrowthBook Cloud(非 remoteEval), 含完整文档和 feature key 列表。 Co-Authored-By: Claude Opus 4.6 --- DEV-LOG.md | 17 +++ README.md | 18 +-- docs/internals/growthbook-adapter.mdx | 169 ++++++++++++++++++++++++++ src/constants/keys.ts | 4 + src/services/analytics/growthbook.ts | 26 ++-- 5 files changed, 213 insertions(+), 21 deletions(-) create mode 100644 docs/internals/growthbook-adapter.mdx diff --git a/DEV-LOG.md b/DEV-LOG.md index 9c1c352..3c2a94f 100644 --- a/DEV-LOG.md +++ b/DEV-LOG.md @@ -1,5 +1,22 @@ # DEV-LOG +## GrowthBook 自定义服务器适配器 (2026-04-03) + +GrowthBook 功能开关系统原为 Anthropic 内部构建设计,硬编码 SDK key 和 API 地址,外部构建因 `is1PEventLoggingEnabled()` 门控始终禁用。新增适配器模式,通过环境变量连接自定义 GrowthBook 服务器,无配置时所有 feature 读取返回代码默认值。 + +**修改文件:** + +| 文件 | 变更 | +|------|------| +| `src/constants/keys.ts` | `getGrowthBookClientKey()` 优先读取 `CLAUDE_GB_ADAPTER_KEY` 环境变量 | +| `src/services/analytics/growthbook.ts` | `isGrowthBookEnabled()` 适配器模式下直接返回 `true`,绕过 1P event logging 门控 | +| `src/services/analytics/growthbook.ts` | `getGrowthBookClient()` base URL 优先使用 `CLAUDE_GB_ADAPTER_URL` | +| `docs/internals/growthbook-adapter.mdx` | 新增适配器配置文档,含全部 ~58 个 feature key 列表 | + +**用法:** `CLAUDE_GB_ADAPTER_URL=https://gb.example.com/ CLAUDE_GB_ADAPTER_KEY=sdk-xxx bun run dev` + +--- + ## Datadog 日志端点可配置化 (2026-04-03) 将 Datadog 硬编码的 Anthropic 内部端点改为环境变量驱动,默认禁用。 diff --git a/README.md b/README.md index 18b4c53..d2f7d6b 100644 --- a/README.md +++ b/README.md @@ -11,17 +11,19 @@ - [ ] Biome 格式化可能不会先实施, 避免代码冲突 - [x] 构建流水线完成, 产物 Node/Bun 都可以运行 - [x] V3 会写大量文档, 完善文档站点 -- [ ] V4 会完成大量的测试文件, 以提高稳定性 +- [x] V4 会完成大量的测试文件, 以提高稳定性 - [x] Buddy 小宠物回来啦 - [x] Auto Mode 回归 - [x] 所有 Feature 现在可以通过环境变量配置, 而不是垃圾的 bun --feature +- [x] V5 支持企业级的监控上报功能, 补全缺失的工具, 解除限制 - [x] 移除牢 A 的反蒸馏代码!!! - [x] 补全 web search 能力(用的 Bing 搜索)!!! - [x] 支持 Debug - [x] 关闭自动更新; - - [x] 添加 sentry 错误上报支持 -- [ ] V5 大规模重构石山代码, 全面模块分包 - - [ ] V5 将会为全新分支, 届时 main 分支将会封存为历史版本 + - [x] 添加自定义 sentry 错误上报支持 + - [x] 添加自定义 GrowthBook 支持 (GB 也是开源的, 现在你可以配置一个自定义的遥控平台) +- [ ] V6 大规模重构石山代码, 全面模块分包 + - [ ] V6 将会为全新分支, 届时 main 分支将会封存为历史版本 > 我不知道这个项目还会存在多久, Star + Fork + git clone + .zip 包最稳健; 说白了就是扛旗项目, 看看能走多远 > @@ -30,14 +32,6 @@ > Claude 已经烧了 1000$ 以上, 没钱了, 换成 GLM 继续玩; @zai-org GLM 5.1 非常可以; > -存活记录: - -1. 开源后 36 小时, 9.7k star; 特权模式可以开启了, 新 feature 调控也加上了;加把劲明天就 10k 了; -2. 开源后 48 小时: 突破 7k Star; 测试代码小有成效; -3. 开源后 24 小时: 突破 6k Star, 感谢各位支持. 完成 docs 文档的站点构建, 达到 v3 版本, 后续开始进行测试用例维护, 完成之后可以接受 PR; 看来牢 A 是不想理我们了; -4. 开源后 15 小时: 完成了构建产物的 node 支持, 现在是完全体了; star 快到 3k 了; 等待牢 A 的邮件 -5. 开源后 12 小时: 愚人节, star 破 1k, 并且牢 A 没有发邮件搞这个项目 - ## 快速开始 ### 环境要求 diff --git a/docs/internals/growthbook-adapter.mdx b/docs/internals/growthbook-adapter.mdx new file mode 100644 index 0000000..5277501 --- /dev/null +++ b/docs/internals/growthbook-adapter.mdx @@ -0,0 +1,169 @@ +--- +title: "GrowthBook 适配器 - 自定义 Feature Flag 服务器接入" +description: "通过环境变量连接自定义 GrowthBook 服务器,实现远程 feature flag 控制。无配置时自动回退到代码默认值。" +keywords: ["growthbook", "feature flags", "远程配置", "适配器", "环境变量"] +--- + +## 概述 + +Claude Code 的 GrowthBook 系统支持通过环境变量连接自定义 GrowthBook 服务器,实现远程 feature flag 控制。 + +- **有配置时**:连接你的 GrowthBook 实例,拉取并缓存 feature 值 +- **无配置时**:所有 feature 读取直接返回代码中的默认值,零网络请求 + +## 环境变量 + +| 变量 | 必填 | 说明 | +|---|---|---| +| `CLAUDE_GB_ADAPTER_URL` | 是 | GrowthBook API 地址,如 `https://gb.example.com/` | +| `CLAUDE_GB_ADAPTER_KEY` | 是 | GrowthBook SDK Client Key,如 `sdk-xxxxx` | + +两个变量都设置时启用适配器模式,否则完全跳过 GrowthBook。 + +## 使用方式 + +### 基本用法 + +```bash +CLAUDE_GB_ADAPTER_URL=https://gb.example.com/ \ +CLAUDE_GB_ADAPTER_KEY=sdk-abc123 \ +bun run dev +``` + +### 不使用 GrowthBook(默认行为) + +```bash +bun run dev +# 所有 getFeatureValue_CACHED_MAY_BE_STALE("xxx", defaultValue) 直接返回 defaultValue +``` + +## GrowthBook 服务端配置 + +### 步骤 + +1. **部署 GrowthBook 服务端**(Docker 自托管或 Cloud 版) +2. **创建 Environment**(如 `production`) +3. **创建 SDK Connection**,获得 SDK Key(即 `CLAUDE_GB_ADAPTER_KEY`) +4. **按需添加 Feature**,key 和类型见下方列表 + +### 核心原则 + +- **不配置任何 feature 也能正常运行**——代码中每个调用都提供了默认值 +- 只创建你想远程控制的 feature,其余走代码默认 +- GrowthBook 上配了某个 feature 后,其值会覆盖代码中的默认值 + +## Feature Key 列表 + +### 高频使用 + +| Feature Key | 类型 | 代码默认值 | 用途 | +|---|---|---|---| +| `tengu_hive_evidence` | boolean | `false` | 任务证据系统 | +| `tengu_quartz_lantern` | boolean | `false` | 文件写入/编辑保护 | +| `tengu_auto_background_agents` | boolean | `false` | 自动后台 Agent | +| `tengu_agent_list_attach` | boolean | `false` | Agent 列表附件 | +| `tengu_amber_stoat` | boolean | `true` | 内置 Agents | +| `tengu_slim_subagent_claudemd` | boolean | `true` | 子 Agent CLAUDE.md | +| `tengu_attribution_header` | boolean | `true` | API 归因 Header | +| `tengu_cobalt_harbor` | boolean | `false` | Bridge 模式 | +| `tengu_ccr_bridge` | boolean | `false` | CCR Bridge | +| `tengu_cicada_nap_ms` | number | `0` | 后台刷新节流(毫秒) | +| `tengu_miraculo_the_bard` | boolean | `false` | 启动欢迎信息 | + +### Agent / 工具控制 + +| Feature Key | 类型 | 代码默认值 | 用途 | +|---|---|---|---| +| `tengu_surreal_dali` | boolean | `false` | 远程触发工具 | +| `tengu_glacier_2xr` | boolean | `false` | 工具搜索增强 | +| `tengu_plum_vx3` | boolean | `false` | Web Search 使用 Haiku | +| `tengu_destructive_command_warning` | boolean | `false` | 危险命令警告 | +| `tengu_birch_trellis` | boolean | `true` | Bash 权限控制 | +| `tengu_harbor_permissions` | boolean | `false` | Harbor 权限模式 | + +### Bridge / 远程连接 + +| Feature Key | 类型 | 代码默认值 | 用途 | +|---|---|---|---| +| `tengu_bridge_repl_v2` | boolean | `false` | Bridge REPL v2 | +| `tengu_copper_bridge` | boolean | `false` | Copper Bridge | +| `tengu_ccr_mirror` | boolean | `false` | CCR Mirror | + +### 内存 / 上下文 + +| Feature Key | 类型 | 代码默认值 | 用途 | +|---|---|---|---| +| `tengu_coral_fern` | boolean | `false` | 内存目录功能 | +| `tengu_passport_quail` | boolean | `false` | 内存路径配置 | +| `tengu_slate_thimble` | boolean | `false` | Slate Thimble | +| `tengu_herring_clock` | boolean | `false` | 跳过索引 | +| `tengu_session_memory` | boolean | `false` | 会话内存 | +| `tengu_pebble_leaf_prune` | boolean | `false` | 内存修剪 | + +### UI / 体验 + +| Feature Key | 类型 | 代码默认值 | 用途 | +|---|---|---|---| +| `tengu_terminal_sidebar` | boolean | `false` | 终端侧边栏 | +| `tengu_terminal_panel` | boolean | `false` | 终端面板 | +| `tengu_willow_mode` | boolean | `false` | Willow 模式 | +| `tengu_collage_kaleidoscope` | boolean | `false` | UI 效果 | +| `tengu_chrome_auto_enable` | boolean | `false` | Chrome 自动启用 | +| `tengu_immediate_model_command` | boolean | `false` | 即时模型切换 | +| `tengu_remote_backend` | boolean | `false` | 远程后端 | + +### 配置对象(动态配置) + +| Feature Key | 类型 | 代码默认值 | 用途 | +|---|---|---|---| +| `tengu_file_read_limits` | object | `null` | 文件读取限制配置 | +| `tengu_cobalt_raccoon` | object | `null` | Cobalt 配置 | +| `tengu_cobalt_lantern` | object | `null` | Lantern 配置 | +| `tengu_desktop_upsell` | object | `null` | 桌面版引导 | +| `tengu_marble_sandcastle` | object | `null` | Marble 配置 | +| `tengu_marble_fox` | object | `null` | Marble Fox 配置 | +| `tengu_ultraplan_model` | string | `null` | Ultraplan 模型名 | + +### Gate(布尔门控) + +| Gate Key | 代码默认值 | 用途 | +|---|---|---| +| `tengu_chair_sermon` | `false` | 功能门控 | +| `tengu_scratch` | `false` | Scratch 功能 | +| `tengu_thinkback` | `false` | Thinkback 功能 | +| `tengu_tool_pear` | `false` | Tool Pear 功能 | + +## 读取优先级链 + +每个 feature 的值按以下顺序解析,第一个命中即返回: + +``` +1. CLAUDE_INTERNAL_FC_OVERRIDES 环境变量(JSON 对象覆盖) + ↓ 未命中 +2. growthBookOverrides 配置(~/.claude.json,仅 ant 构建) + ↓ 未命中 +3. 内存缓存(remoteEvalFeatureValues,本次进程从服务器拉取) + ↓ 未命中 +4. 磁盘缓存(~/.claude.json 的 cachedGrowthBookFeatures) + ↓ 未命中 +5. 代码中的 defaultValue 参数 +``` + +## 缓存与刷新机制 + +| 机制 | 说明 | +|---|---| +| **磁盘缓存** | `~/.claude.json` 的 `cachedGrowthBookFeatures` 字段,跨进程持久化 | +| **周期刷新** | 每 6 小时自动从服务器拉取最新值(`setInterval` + `unref`) | +| **初始化超时** | 首次连接超时 5 秒,超时后使用磁盘缓存或默认值 | +| **Auth 变更** | 登录/登出时自动销毁并重建客户端 | + +## 实现细节 + +修改了 2 个文件共 3 处: + +1. **`src/constants/keys.ts`** — `getGrowthBookClientKey()` 优先读取 `CLAUDE_GB_ADAPTER_KEY` +2. **`src/services/analytics/growthbook.ts`** — `isGrowthBookEnabled()` 适配器模式下直接启用 +3. **`src/services/analytics/growthbook.ts`** — base URL 优先使用 `CLAUDE_GB_ADAPTER_URL` + +所有 130+ 个调用方文件无需修改。 diff --git a/src/constants/keys.ts b/src/constants/keys.ts index 773fdd1..33edb85 100644 --- a/src/constants/keys.ts +++ b/src/constants/keys.ts @@ -3,6 +3,10 @@ import { isEnvTruthy } from '../utils/envUtils.js' // Lazy read so ENABLE_GROWTHBOOK_DEV from globalSettings.env (applied after // module load) is picked up. USER_TYPE is a build-time define so it's safe. export function getGrowthBookClientKey(): string { + // 适配器优先:自定义 GrowthBook 服务器 + const adapterKey = process.env.CLAUDE_GB_ADAPTER_KEY + if (adapterKey) return adapterKey + return process.env.USER_TYPE === 'ant' ? isEnvTruthy(process.env.ENABLE_GROWTHBOOK_DEV) ? 'sdk-yZQvlplybuXjYh6L' diff --git a/src/services/analytics/growthbook.ts b/src/services/analytics/growthbook.ts index c71bba8..eead6c9 100644 --- a/src/services/analytics/growthbook.ts +++ b/src/services/analytics/growthbook.ts @@ -420,6 +420,10 @@ function syncRemoteEvalToDisk(): void { * Check if GrowthBook operations should be enabled */ function isGrowthBookEnabled(): boolean { + // 适配器模式:有自定义服务器配置时直接启用 + if (process.env.CLAUDE_GB_ADAPTER_URL && process.env.CLAUDE_GB_ADAPTER_KEY) { + return true + } // GrowthBook depends on 1P event logging. return is1PEventLoggingEnabled() } @@ -495,15 +499,17 @@ const getGrowthBookClient = memoize( const attributes = getUserAttributes() const clientKey = getGrowthBookClientKey() + const baseUrl = + process.env.CLAUDE_GB_ADAPTER_URL + || (process.env.USER_TYPE === 'ant' + ? process.env.CLAUDE_CODE_GB_BASE_URL || 'https://api.anthropic.com/' + : 'https://api.anthropic.com/') + const isAdapterMode = !!(process.env.CLAUDE_GB_ADAPTER_URL && process.env.CLAUDE_GB_ADAPTER_KEY) if (process.env.USER_TYPE === 'ant') { logForDebugging( `GrowthBook: Creating client with clientKey=${clientKey}, attributes: ${jsonStringify(attributes)}`, ) } - const baseUrl = - process.env.USER_TYPE === 'ant' - ? process.env.CLAUDE_CODE_GB_BASE_URL || 'https://api.anthropic.com/' - : 'https://api.anthropic.com/' // Skip auth if trust hasn't been established yet // This prevents executing apiKeyHelper commands before the trust dialog @@ -518,7 +524,8 @@ const getGrowthBookClient = memoize( const authHeaders = hasTrust ? getAuthHeaders() : { headers: {}, error: 'trust not established' } - const hasAuth = !authHeaders.error + // 适配器模式下不需要 auth,GrowthBook Cloud 用 clientKey 即可 + const hasAuth = isAdapterMode || !authHeaders.error clientCreatedWithAuth = hasAuth // Capture in local variable so the init callback operates on THIS client, @@ -527,9 +534,10 @@ const getGrowthBookClient = memoize( apiHost: baseUrl, clientKey, attributes, - remoteEval: true, - // Re-fetch when user ID or org changes (org change = login to different org) - cacheKeyAttributes: ['id', 'organizationUUID'], + // remoteEval only works with Anthropic internal API, GrowthBook Cloud doesn't support it + remoteEval: !isAdapterMode, + // cacheKeyAttributes only valid with remoteEval + ...(!isAdapterMode ? { cacheKeyAttributes: ['id', 'organizationUUID'] } : {}), // Add auth headers if available ...(authHeaders.error ? {} @@ -566,7 +574,7 @@ const getGrowthBookClient = memoize( if (process.env.USER_TYPE === 'ant') { logForDebugging( - `GrowthBook initialized successfully, source: ${result.source}, success: ${result.success}`, + `GrowthBook initialized, source: ${result.source}, success: ${result.success}`, ) }