[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F9EFSS5F32VMFFYVVGKHWR":3,"skill-same:01M6F9EFSS5F32VMFFYVVGKHWR":80},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":57,"commitSha":58,"skillDirectory":59,"forksCount":60,"repositoryHtmlUrl":61,"repositoryHomepageUrl":62,"ownerHtmlUrl":63,"ownerBlogUrl":6,"ownerDescription":64,"updatedAt":65,"install":66,"resources":79},"01M6F9EFSS5F32VMFFYVVGKHWR","customerio-multi-env-setup",null,"Configure Customer.io multi-environment setup with workspace isolation.\nUse when setting up dev\u002Fstaging\u002Fprod workspaces, environment-aware\nclients, or Kubernetes config overlays.\nTrigger: \"customer.io environments\", \"customer.io staging\",\n\"customer.io dev prod\", \"customer.io workspace isolation\".","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fjeremylongshore\u002Fclaude-code-plugins-plus-skills\u002Fcustomerio-multi-env-setup\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000011","运维与部署","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"customerio multi env setup","setup","multi","env setup","env","edit","customer-io","io","workspaces","multi-environment","up","openclaw","write","read","configure","Bash","kubectl","code","config","customer","2026-11-10 10:12:30",false,"---\nname: customerio-multi-env-setup\ndescription: |\n  Configure Customer.io multi-environment setup with workspace isolation.\n  Use when setting up dev\u002Fstaging\u002Fprod workspaces, environment-aware\n  clients, or Kubernetes config overlays.\n  Trigger: \"customer.io environments\", \"customer.io staging\",\n  \"customer.io dev prod\", \"customer.io workspace isolation\".\nallowed-tools: Read, Write, Edit, Bash(npm:*), Bash(kubectl:*), Glob, Grep\nversion: 1.0.0\nlicense: MIT\nauthor: Jeremy Longshore \u003Cjeremy@intentsolutions.io>\ncompatible-with: claude-code, codex, openclaw\ntags: [saas, customer-io, multi-environment, kubernetes]\n\n---\n# Customer.io Multi-Environment Setup\n\n## Overview\n\nConfigure isolated Customer.io environments for dev, staging, and production: separate workspaces per environment, typed configuration with validation, environment-aware client wrappers, Kubernetes ConfigMap overlays, and data isolation verification.\n\n## Prerequisites\n\n- Customer.io account with multiple workspaces (create at fly.customer.io)\n- Environment variable management (dotenv, secrets manager)\n- CI\u002FCD pipeline for per-environment deployment\n\n## Workspace Strategy\n\n| Environment | Workspace Name | Purpose | Dry Run | Data |\n|-------------|---------------|---------|---------|------|\n| Local dev | `myapp-dev` | Individual developer testing | Optional | Fake\u002Ftest data |\n| CI | `myapp-ci` | Automated test runs | No | Auto-cleaned test data |\n| Staging | `myapp-staging` | Pre-production validation | No | Subset of real data |\n| Production | `myapp-prod` | Live users | No | Real user data |\n\nEach workspace has its own Site ID, Track API Key, and App API Key. Create workspaces at Settings > Workspace Settings.\n\n## Instructions\n\n### Step 1: Typed Environment Configuration\n\n```typescript\n\u002F\u002F config\u002Fcustomerio.ts\nimport { RegionUS, RegionEU } from \"customerio-node\";\n\ntype CioEnvironment = \"development\" | \"ci\" | \"staging\" | \"production\";\n\ninterface CioEnvConfig {\n  siteId: string;\n  trackApiKey: string;\n  appApiKey: string;\n  region: typeof RegionUS | typeof RegionEU;\n  dryRun: boolean;\n  logLevel: \"debug\" | \"info\" | \"warn\" | \"error\";\n  eventPrefix: string;     \u002F\u002F Prefix events in non-prod to prevent confusion\n}\n\nfunction validateConfig(config: CioEnvConfig, env: CioEnvironment): void {\n  if (!config.siteId) throw new Error(`Missing CUSTOMERIO_SITE_ID for ${env}`);\n  if (!config.trackApiKey) throw new Error(`Missing CUSTOMERIO_TRACK_API_KEY for ${env}`);\n  if (env === \"production\" && config.dryRun) {\n    throw new Error(\"Production cannot be in dry-run mode\");\n  }\n  if (env === \"production\" && config.eventPrefix) {\n    throw new Error(\"Production must not use event prefix\");\n  }\n}\n\nexport function loadCioConfig(): CioEnvConfig {\n  const env = (process.env.NODE_ENV ?? \"development\") as CioEnvironment;\n  const region = process.env.CUSTOMERIO_REGION === \"eu\" ? RegionEU : RegionUS;\n\n  const config: CioEnvConfig = {\n    siteId: process.env.CUSTOMERIO_SITE_ID ?? \"\",\n    trackApiKey: process.env.CUSTOMERIO_TRACK_API_KEY ?? \"\",\n    appApiKey: process.env.CUSTOMERIO_APP_API_KEY ?? \"\",\n    region,\n    dryRun: process.env.CUSTOMERIO_DRY_RUN === \"true\",\n    logLevel: (process.env.CUSTOMERIO_LOG_LEVEL as any) ?? (env === \"production\" ? \"warn\" : \"debug\"),\n    eventPrefix: process.env.CUSTOMERIO_EVENT_PREFIX ?? (env === \"production\" ? \"\" : `${env}_`),\n  };\n\n  validateConfig(config, env);\n  return config;\n}\n```\n\n### Step 2: Environment-Aware Client\n\n```typescript\n\u002F\u002F lib\u002Fcustomerio-env.ts\nimport { TrackClient, APIClient } from \"customerio-node\";\nimport { loadCioConfig } from \"..\u002Fconfig\u002Fcustomerio\";\n\nconst config = loadCioConfig();\n\nexport class EnvAwareCioClient {\n  private track: TrackClient | null;\n  private app: APIClient | null;\n\n  constructor() {\n    if (config.dryRun) {\n      this.track = null;\n      this.app = null;\n    } else {\n      this.track = new TrackClient(config.siteId, config.trackApiKey, {\n        region: config.region,\n      });\n      this.app = config.appApiKey\n        ? new APIClient(config.appApiKey, { region: config.region })\n        : null;\n    }\n  }\n\n  async identify(userId: string, attrs: Record\u003Cstring, any>): Promise\u003Cvoid> {\n    const prefixedId = config.eventPrefix\n      ? `${config.eventPrefix}${userId}`\n      : userId;\n\n    \u002F\u002F Tag with environment for debugging\n    const envAttrs = {\n      ...attrs,\n      _cio_env: process.env.NODE_ENV,\n    };\n\n    if (config.dryRun) {\n      if (config.logLevel === \"debug\") {\n        console.log(`[CIO DRY RUN] identify: ${prefixedId}`, envAttrs);\n      }\n      return;\n    }\n\n    await this.track!.identify(prefixedId, envAttrs);\n  }\n\n  async track(userId: string, name: string, data?: Record\u003Cstring, any>): Promise\u003Cvoid> {\n    const prefixedId = config.eventPrefix\n      ? `${config.eventPrefix}${userId}`\n      : userId;\n    const prefixedName = config.eventPrefix\n      ? `${config.eventPrefix}${name}`\n      : name;\n\n    if (config.dryRun) {\n      if (config.logLevel === \"debug\") {\n        console.log(`[CIO DRY RUN] track: ${prefixedId} ${prefixedName}`, data);\n      }\n      return;\n    }\n\n    await this.track!.track(prefixedId, { name: prefixedName, data });\n  }\n\n  getAppClient(): APIClient {\n    if (!this.app) {\n      throw new Error(\"App API not available (dry-run or missing key)\");\n    }\n    return this.app;\n  }\n}\n```\n\n### Step 3: Environment Files\n\n```bash\n# .env.development\nNODE_ENV=development\nCUSTOMERIO_SITE_ID=dev-workspace-site-id\nCUSTOMERIO_TRACK_API_KEY=dev-track-key\nCUSTOMERIO_APP_API_KEY=dev-app-key\nCUSTOMERIO_REGION=us\nCUSTOMERIO_DRY_RUN=false\nCUSTOMERIO_EVENT_PREFIX=dev_\nCUSTOMERIO_LOG_LEVEL=debug\n\n# .env.staging\nNODE_ENV=staging\nCUSTOMERIO_SITE_ID=staging-workspace-site-id\nCUSTOMERIO_TRACK_API_KEY=staging-track-key\nCUSTOMERIO_APP_API_KEY=staging-app-key\nCUSTOMERIO_REGION=us\nCUSTOMERIO_DRY_RUN=false\nCUSTOMERIO_EVENT_PREFIX=staging_\nCUSTOMERIO_LOG_LEVEL=info\n\n# .env.production (or use secrets manager)\nNODE_ENV=production\nCUSTOMERIO_SITE_ID=prod-workspace-site-id\nCUSTOMERIO_TRACK_API_KEY=prod-track-key\nCUSTOMERIO_APP_API_KEY=prod-app-key\nCUSTOMERIO_REGION=us\nCUSTOMERIO_DRY_RUN=false\nCUSTOMERIO_EVENT_PREFIX=\nCUSTOMERIO_LOG_LEVEL=warn\n```\n\n### Step 4: Kubernetes ConfigMap Overlays\n\n```yaml\n# k8s\u002Fbase\u002Fconfigmap.yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: customerio-config\ndata:\n  CUSTOMERIO_REGION: \"us\"\n  CUSTOMERIO_LOG_LEVEL: \"info\"\n\n---\n# k8s\u002Foverlays\u002Fdevelopment\u002Fconfigmap-patch.yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: customerio-config\ndata:\n  CUSTOMERIO_DRY_RUN: \"true\"\n  CUSTOMERIO_EVENT_PREFIX: \"dev_\"\n  CUSTOMERIO_LOG_LEVEL: \"debug\"\n\n---\n# k8s\u002Foverlays\u002Fstaging\u002Fconfigmap-patch.yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: customerio-config\ndata:\n  CUSTOMERIO_DRY_RUN: \"false\"\n  CUSTOMERIO_EVENT_PREFIX: \"staging_\"\n  CUSTOMERIO_LOG_LEVEL: \"info\"\n\n---\n# k8s\u002Foverlays\u002Fproduction\u002Fconfigmap-patch.yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: customerio-config\ndata:\n  CUSTOMERIO_DRY_RUN: \"false\"\n  CUSTOMERIO_EVENT_PREFIX: \"\"\n  CUSTOMERIO_LOG_LEVEL: \"warn\"\n```\n\n### Step 5: Data Isolation Verification\n\n```typescript\n\u002F\u002F scripts\u002Fverify-isolation.ts\nimport { TrackClient, RegionUS } from \"customerio-node\";\n\nasync function verifyIsolation() {\n  const envs = [\"development\", \"staging\", \"production\"];\n  const testId = `isolation-test-${Date.now()}`;\n\n  for (const env of envs) {\n    const siteId = process.env[`CIO_${env.toUpperCase()}_SITE_ID`];\n    const apiKey = process.env[`CIO_${env.toUpperCase()}_TRACK_KEY`];\n    if (!siteId || !apiKey) {\n      console.log(`[SKIP] ${env}: credentials not configured`);\n      continue;\n    }\n\n    const client = new TrackClient(siteId, apiKey, { region: RegionUS });\n    try {\n      await client.identify(testId, {\n        email: `${testId}@isolation-test.example.com`,\n        _test_env: env,\n      });\n      console.log(`[OK] ${env}: identify succeeded (separate workspace)`);\n\n      \u002F\u002F Clean up\n      await client.suppress(testId);\n      await client.destroy(testId);\n    } catch (err: any) {\n      console.log(`[FAIL] ${env}: ${err.statusCode} ${err.message}`);\n    }\n  }\n}\n\nverifyIsolation();\n```\n\n### Step 6: CI\u002FCD Environment Promotion\n\n```yaml\n# .github\u002Fworkflows\u002Fpromote.yml\nname: Promote to Environment\non:\n  workflow_dispatch:\n    inputs:\n      target_env:\n        description: \"Target environment\"\n        required: true\n        type: choice\n        options: [staging, production]\n\njobs:\n  promote:\n    runs-on: ubuntu-latest\n    environment: ${{ inputs.target_env }}\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: actions\u002Fsetup-node@v4\n        with: { node-version: 20 }\n      - run: npm ci\n\n      - name: Smoke test target environment\n        env:\n          CUSTOMERIO_SITE_ID: ${{ secrets.CIO_SITE_ID }}\n          CUSTOMERIO_TRACK_API_KEY: ${{ secrets.CIO_TRACK_API_KEY }}\n        run: npx tsx scripts\u002Fverify-customerio.ts\n\n      - name: Deploy\n        run: echo \"Deploy to ${{ inputs.target_env }}\"\n```\n\n## Error Handling\n\n| Issue | Solution |\n|-------|----------|\n| Wrong workspace credentials | Config validation throws on startup — check error message |\n| Cross-env data leak | Event prefix prevents accidental production triggers |\n| Production in dry-run | Config validator explicitly blocks this combination |\n| Missing env-specific secret | Kubernetes ExternalSecrets or CI secret scoping |\n\n## Resources\n\n- [Customer.io Workspaces](https:\u002F\u002Fdocs.customer.io\u002Faccounts-and-workspaces\u002Fmanaging-credentials\u002F)\n- [Account Regions](https:\u002F\u002Fdocs.customer.io\u002Faccounts-and-workspaces\u002Fdata-centers\u002F)\n\n## Next Steps\n\nAfter multi-env setup, proceed to `customerio-observability` for monitoring.\n\n","# Customer.io Multi-Environment Setup\n\n## Overview\n\nConfigure isolated Customer.io environments for dev, staging, and production: separate workspaces per environment, typed configuration with validation, environment-aware client wrappers, Kubernetes ConfigMap overlays, and data isolation verification.\n\n## Prerequisites\n\n- Customer.io account with multiple workspaces (create at fly.customer.io)\n- Environment variable management (dotenv, secrets manager)\n- CI\u002FCD pipeline for per-environment deployment\n\n## Workspace Strategy\n\n| Environment | Workspace Name | Purpose | Dry Run | Data |\n|-------------|---------------|---------|---------|------|\n| Local dev | `myapp-dev` | Individual developer testing | Optional | Fake\u002Ftest data |\n| CI | `myapp-ci` | Automated test runs | No | Auto-cleaned test data |\n| Staging | `myapp-staging` | Pre-production validation | No | Subset of real data |\n| Production | `myapp-prod` | Live users | No | Real user data |\n\nEach workspace has its own Site ID, Track API Key, and App API Key. Create workspaces at Settings > Workspace Settings.\n\n## Instructions\n\n### Step 1: Typed Environment Configuration\n\n```typescript\n\u002F\u002F config\u002Fcustomerio.ts\nimport { RegionUS, RegionEU } from \"customerio-node\";\n\ntype CioEnvironment = \"development\" | \"ci\" | \"staging\" | \"production\";\n\ninterface CioEnvConfig {\n  siteId: string;\n  trackApiKey: string;\n  appApiKey: string;\n  region: typeof RegionUS | typeof RegionEU;\n  dryRun: boolean;\n  logLevel: \"debug\" | \"info\" | \"warn\" | \"error\";\n  eventPrefix: string;     \u002F\u002F Prefix events in non-prod to prevent confusion\n}\n\nfunction validateConfig(config: CioEnvConfig, env: CioEnvironment): void {\n  if (!config.siteId) throw new Error(`Missing CUSTOMERIO_SITE_ID for ${env}`);\n  if (!config.trackApiKey) throw new Error(`Missing CUSTOMERIO_TRACK_API_KEY for ${env}`);\n  if (env === \"production\" && config.dryRun) {\n    throw new Error(\"Production cannot be in dry-run mode\");\n  }\n  if (env === \"production\" && config.eventPrefix) {\n    throw new Error(\"Production must not use event prefix\");\n  }\n}\n\nexport function loadCioConfig(): CioEnvConfig {\n  const env = (process.env.NODE_ENV ?? \"development\") as CioEnvironment;\n  const region = process.env.CUSTOMERIO_REGION === \"eu\" ? RegionEU : RegionUS;\n\n  const config: CioEnvConfig = {\n    siteId: process.env.CUSTOMERIO_SITE_ID ?? \"\",\n    trackApiKey: process.env.CUSTOMERIO_TRACK_API_KEY ?? \"\",\n    appApiKey: process.env.CUSTOMERIO_APP_API_KEY ?? \"\",\n    region,\n    dryRun: process.env.CUSTOMERIO_DRY_RUN === \"true\",\n    logLevel: (process.env.CUSTOMERIO_LOG_LEVEL as any) ?? (env === \"production\" ? \"warn\" : \"debug\"),\n    eventPrefix: process.env.CUSTOMERIO_EVENT_PREFIX ?? (env === \"production\" ? \"\" : `${env}_`),\n  };\n\n  validateConfig(config, env);\n  return config;\n}\n```\n\n### Step 2: Environment-Aware Client\n\n```typescript\n\u002F\u002F lib\u002Fcustomerio-env.ts\nimport { TrackClient, APIClient } from \"customerio-node\";\nimport { loadCioConfig } from \"..\u002Fconfig\u002Fcustomerio\";\n\nconst config = loadCioConfig();\n\nexport class EnvAwareCioClient {\n  private track: TrackClient | null;\n  private app: APIClient | null;\n\n  constructor() {\n    if (config.dryRun) {\n      this.track = null;\n      this.app = null;\n    } else {\n      this.track = new TrackClient(config.siteId, config.trackApiKey, {\n        region: config.region,\n      });\n      this.app = config.appApiKey\n        ? new APIClient(config.appApiKey, { region: config.region })\n        : null;\n    }\n  }\n\n  async identify(userId: string, attrs: Record\u003Cstring, any>): Promise\u003Cvoid> {\n    const prefixedId = config.eventPrefix\n      ? `${config.eventPrefix}${userId}`\n      : userId;\n\n    \u002F\u002F Tag with environment for debugging\n    const envAttrs = {\n      ...attrs,\n      _cio_env: process.env.NODE_ENV,\n    };\n\n    if (config.dryRun) {\n      if (config.logLevel === \"debug\") {\n        console.log(`[CIO DRY RUN] identify: ${prefixedId}`, envAttrs);\n      }\n      return;\n    }\n\n    await this.track!.identify(prefixedId, envAttrs);\n  }\n\n  async track(userId: string, name: string, data?: Record\u003Cstring, any>): Promise\u003Cvoid> {\n    const prefixedId = config.eventPrefix\n      ? `${config.eventPrefix}${userId}`\n      : userId;\n    const prefixedName = config.eventPrefix\n      ? `${config.eventPrefix}${name}`\n      : name;\n\n    if (config.dryRun) {\n      if (config.logLevel === \"debug\") {\n        console.log(`[CIO DRY RUN] track: ${prefixedId} ${prefixedName}`, data);\n      }\n      return;\n    }\n\n    await this.track!.track(prefixedId, { name: prefixedName, data });\n  }\n\n  getAppClient(): APIClient {\n    if (!this.app) {\n      throw new Error(\"App API not available (dry-run or missing key)\");\n    }\n    return this.app;\n  }\n}\n```\n\n### Step 3: Environment Files\n\n```bash\n# .env.development\nNODE_ENV=development\nCUSTOMERIO_SITE_ID=dev-workspace-site-id\nCUSTOMERIO_TRACK_API_KEY=dev-track-key\nCUSTOMERIO_APP_API_KEY=dev-app-key\nCUSTOMERIO_REGION=us\nCUSTOMERIO_DRY_RUN=false\nCUSTOMERIO_EVENT_PREFIX=dev_\nCUSTOMERIO_LOG_LEVEL=debug\n\n# .env.staging\nNODE_ENV=staging\nCUSTOMERIO_SITE_ID=staging-workspace-site-id\nCUSTOMERIO_TRACK_API_KEY=staging-track-key\nCUSTOMERIO_APP_API_KEY=staging-app-key\nCUSTOMERIO_REGION=us\nCUSTOMERIO_DRY_RUN=false\nCUSTOMERIO_EVENT_PREFIX=staging_\nCUSTOMERIO_LOG_LEVEL=info\n\n# .env.production (or use secrets manager)\nNODE_ENV=production\nCUSTOMERIO_SITE_ID=prod-workspace-site-id\nCUSTOMERIO_TRACK_API_KEY=prod-track-key\nCUSTOMERIO_APP_API_KEY=prod-app-key\nCUSTOMERIO_REGION=us\nCUSTOMERIO_DRY_RUN=false\nCUSTOMERIO_EVENT_PREFIX=\nCUSTOMERIO_LOG_LEVEL=warn\n```\n\n### Step 4: Kubernetes ConfigMap Overlays\n\n```yaml\n# k8s\u002Fbase\u002Fconfigmap.yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: customerio-config\ndata:\n  CUSTOMERIO_REGION: \"us\"\n  CUSTOMERIO_LOG_LEVEL: \"info\"\n\n---\n# k8s\u002Foverlays\u002Fdevelopment\u002Fconfigmap-patch.yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: customerio-config\ndata:\n  CUSTOMERIO_DRY_RUN: \"true\"\n  CUSTOMERIO_EVENT_PREFIX: \"dev_\"\n  CUSTOMERIO_LOG_LEVEL: \"debug\"\n\n---\n# k8s\u002Foverlays\u002Fstaging\u002Fconfigmap-patch.yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: customerio-config\ndata:\n  CUSTOMERIO_DRY_RUN: \"false\"\n  CUSTOMERIO_EVENT_PREFIX: \"staging_\"\n  CUSTOMERIO_LOG_LEVEL: \"info\"\n\n---\n# k8s\u002Foverlays\u002Fproduction\u002Fconfigmap-patch.yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: customerio-config\ndata:\n  CUSTOMERIO_DRY_RUN: \"false\"\n  CUSTOMERIO_EVENT_PREFIX: \"\"\n  CUSTOMERIO_LOG_LEVEL: \"warn\"\n```\n\n### Step 5: Data Isolation Verification\n\n```typescript\n\u002F\u002F scripts\u002Fverify-isolation.ts\nimport { TrackClient, RegionUS } from \"customerio-node\";\n\nasync function verifyIsolation() {\n  const envs = [\"development\", \"staging\", \"production\"];\n  const testId = `isolation-test-${Date.now()}`;\n\n  for (const env of envs) {\n    const siteId = process.env[`CIO_${env.toUpperCase()}_SITE_ID`];\n    const apiKey = process.env[`CIO_${env.toUpperCase()}_TRACK_KEY`];\n    if (!siteId || !apiKey) {\n      console.log(`[SKIP] ${env}: credentials not configured`);\n      continue;\n    }\n\n    const client = new TrackClient(siteId, apiKey, { region: RegionUS });\n    try {\n      await client.identify(testId, {\n        email: `${testId}@isolation-test.example.com`,\n        _test_env: env,\n      });\n      console.log(`[OK] ${env}: identify succeeded (separate workspace)`);\n\n      \u002F\u002F Clean up\n      await client.suppress(testId);\n      await client.destroy(testId);\n    } catch (err: any) {\n      console.log(`[FAIL] ${env}: ${err.statusCode} ${err.message}`);\n    }\n  }\n}\n\nverifyIsolation();\n```\n\n### Step 6: CI\u002FCD Environment Promotion\n\n```yaml\n# .github\u002Fworkflows\u002Fpromote.yml\nname: Promote to Environment\non:\n  workflow_dispatch:\n    inputs:\n      target_env:\n        description: \"Target environment\"\n        required: true\n        type: choice\n        options: [staging, production]\n\njobs:\n  promote:\n    runs-on: ubuntu-latest\n    environment: ${{ inputs.target_env }}\n    steps:\n      - uses: actions\u002Fcheckout@v4\n      - uses: actions\u002Fsetup-node@v4\n        with: { node-version: 20 }\n      - run: npm ci\n\n      - name: Smoke test target environment\n        env:\n          CUSTOMERIO_SITE_ID: ${{ secrets.CIO_SITE_ID }}\n          CUSTOMERIO_TRACK_API_KEY: ${{ secrets.CIO_TRACK_API_KEY }}\n        run: npx tsx scripts\u002Fverify-customerio.ts\n\n      - name: Deploy\n        run: echo \"Deploy to ${{ inputs.target_env }}\"\n```\n\n## Error Handling\n\n| Issue | Solution |\n|-------|----------|\n| Wrong workspace credentials | Config validation throws on startup — check error message |\n| Cross-env data leak | Event prefix prevents accidental production triggers |\n| Production in dry-run | Config validator explicitly blocks this combination |\n| Missing env-specific secret | Kubernetes ExternalSecrets or CI secret scoping |\n\n## Resources\n\n- [Customer.io Workspaces](https:\u002F\u002Fdocs.customer.io\u002Faccounts-and-workspaces\u002Fmanaging-credentials\u002F)\n- [Account Regions](https:\u002F\u002Fdocs.customer.io\u002Faccounts-and-workspaces\u002Fdata-centers\u002F)\n\n## Next Steps\n\nAfter multi-env setup, proceed to `customerio-observability` for monitoring.\n\n",{"name":5,"tags":49,"author":52,"license":15,"version":53,"description":54,"allowed-tools":55,"compatible-with":56},[50,30,33,51],"saas","kubernetes","Jeremy Longshore \u003Cjeremy@intentsolutions.io>","1.0.0","Configure Customer.io multi-environment setup with workspace isolation.\nUse when setting up dev\u002Fstaging\u002Fprod workspaces, environment-aware\nclients, or Kubernetes config overlays.\nTrigger: \"customer.io environments\", \"customer.io staging\",\n\"customer.io dev prod\", \"customer.io workspace isolation\".\n","Read, Write, Edit, Bash(npm:*), Bash(kubectl:*), Glob, Grep","claude-code, codex, openclaw","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002Fjeremylongshore\u002Fclaude-code-plugins-plus-skills\u002Fcustomerio-multi-env-setup",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":67,"prompt":77,"zipAvailable":78},[68,71,74],{"tool":69,"command":70},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill customerio-multi-env-setup",{"tool":72,"command":73},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill customerio-multi-env-setup",{"tool":75,"command":76},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill customerio-multi-env-setup","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fjeremylongshore\u002Fclaude-code-plugins-plus-skills\u002Fcustomerio-multi-env-setup\u002FSKILL.md",true,[],[81,93,103,112,118,125],{"skillNo":82,"name":83,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":84,"categories":85,"keywords":89,"publishedAt":44},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[86],{"categoryNo":87,"categoryName":88,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[90,91,92],"model","model hierarchy","model hierarchy skill",{"skillNo":94,"name":95,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":96,"categories":97,"keywords":99,"publishedAt":44},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[98],{"categoryNo":87,"categoryName":88,"taxonomyType":21,"taxonomyTypeDesc":22},[100,101,102],"MCP","zoom","zoom mcp",{"skillNo":104,"name":105,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":106,"categories":107,"keywords":108,"publishedAt":44},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[109,110,111,101],"sdk","zoom apps sdk","apps",{"skillNo":113,"name":114,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":115,"categories":116,"keywords":117,"publishedAt":44},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[114],{"skillNo":119,"name":120,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":121,"categories":122,"keywords":124,"publishedAt":44},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[123],{"categoryNo":87,"categoryName":88,"taxonomyType":21,"taxonomyTypeDesc":22},[120],{"skillNo":126,"name":127,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":128,"categories":129,"keywords":130,"publishedAt":44},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[127]]