| name | twinmind-multi-env-setup |
| description | Set up TwinMind across multiple environments (dev, staging, production).
Use when configuring environment-specific settings, managing multiple API keys,
or implementing environment promotion workflows.
Trigger with phrases like "twinmind environments", "twinmind multi-env",
"twinmind staging setup", "twinmind dev vs prod".
|
| allowed-tools | Read, Write, Edit |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
TwinMind Multi-Environment Setup
Overview
Configure TwinMind for development, staging, and production environments with proper isolation and promotion workflows.
Prerequisites
- Multiple TwinMind accounts or API keys per environment
- Understanding of environment management
- CI/CD pipeline for deployments
Instructions
Step 1: Environment Configuration Structure
export type Environment = 'development' | 'staging' | 'production';
export interface EnvironmentConfig {
name: Environment;
twinmind: {
apiKey: string;
baseUrl: string;
webhookSecret: string;
model: 'ear-2' | 'ear-3' | 'ear-3-custom';
features: {
diarization: boolean;
autoSummary: boolean;
actionItems: boolean;
debug: boolean;
};
};
limits: {
rateLimit: number;
concurrentJobs: number;
dailyQuota?: number;
};
integrations: {
slack?: { webhook: string; channel: string };
calendar?: { enabled: boolean };
email?: { enabled: boolean };
};
}
const configs: Record<Environment, Partial<EnvironmentConfig>> = {
development: {
name: 'development',
twinmind: {
apiKey: process.env.TWINMIND_API_KEY_DEV!,
baseUrl: 'https://api.twinmind.com/v1',
webhookSecret: process.env.TWINMIND_WEBHOOK_SECRET_DEV!,
model: 'ear-2',
features: {
diarization: false,
autoSummary: true,
actionItems: true,
debug: true,
},
},
limits: {
rateLimit: 30,
concurrentJobs: 1,
dailyQuota: 10,
},
integrations: {
slack: { webhook: process.env.SLACK_WEBHOOK_DEV!, channel: '#dev-alerts' },
calendar: { enabled: false },
email: { enabled: false },
},
},
staging: {
name: 'staging',
twinmind: {
apiKey: process.env.TWINMIND_API_KEY_STAGING!,
baseUrl: 'https://api.twinmind.com/v1',
webhookSecret: process.env.TWINMIND_WEBHOOK_SECRET_STAGING!,
model: 'ear-3',
features: {
diarization: true,
autoSummary: true,
actionItems: true,
debug: true,
},
},
limits: {
rateLimit: 60,
concurrentJobs: 2,
dailyQuota: 50,
},
integrations: {
slack: { webhook: process.env.SLACK_WEBHOOK_STAGING!, channel: '#staging-alerts' },
calendar: { enabled: true },
email: { enabled: false },
},
},
production: {
name: 'production',
twinmind: {
apiKey: process.env.TWINMIND_API_KEY_PROD!,
baseUrl: 'https://api.twinmind.com/v1',
webhookSecret: process.env.TWINMIND_WEBHOOK_SECRET_PROD!,
model: 'ear-3',
features: {
diarization: true,
autoSummary: true,
actionItems: true,
debug: false,
},
},
limits: {
rateLimit: 300,
concurrentJobs: 10,
},
integrations: {
slack: { webhook: process.env.SLACK_WEBHOOK_PROD!, channel: '#production-alerts' },
calendar: { enabled: true },
email: { enabled: true },
},
},
};
export function getEnvironmentConfig(): EnvironmentConfig {
const env = (process.env.NODE_ENV || 'development') as Environment;
const config = configs[env];
if (!config) {
throw new Error(`Unknown environment: ${env}`);
}
return config as EnvironmentConfig;
}
Step 2: Environment Variable Files
NODE_ENV=development
TWINMIND_API_KEY_DEV=tm_sk_dev_xxx
TWINMIND_WEBHOOK_SECRET_DEV=whsec_dev_xxx
SLACK_WEBHOOK_DEV=https://hooks.slack.com/services/xxx
NODE_ENV=staging
TWINMIND_API_KEY_STAGING=tm_sk_staging_xxx
TWINMIND_WEBHOOK_SECRET_STAGING=whsec_staging_xxx
SLACK_WEBHOOK_STAGING=https://hooks.slack.com/services/xxx
NODE_ENV=production
TWINMIND_API_KEY_PROD=tm_sk_prod_xxx
TWINMIND_WEBHOOK_SECRET_PROD=whsec_prod_xxx
SLACK_WEBHOOK_PROD=https://hooks.slack.com/services/xxx
NODE_ENV=development
TWINMIND_API_KEY_DEV=
TWINMIND_WEBHOOK_SECRET_DEV=
Step 3: Environment-Aware Client Factory
import { TwinMindClient } from './client';
import { getEnvironmentConfig, Environment } from '../config/environments';
const clients = new Map<Environment, TwinMindClient>();
export function getTwinMindClient(env?: Environment): TwinMindClient {
const config = getEnvironmentConfig();
const targetEnv = env || config.name;
if (!clients.has(targetEnv)) {
const envConfig = configs[targetEnv];
clients.set(targetEnv, new TwinMindClient({
apiKey: envConfig.twinmind.apiKey,
baseUrl: envConfig.twinmind.baseUrl,
timeout: 30000,
debug: envConfig.twinmind.features.debug,
}));
}
return clients.get(targetEnv)!;
}
export function (): {
(env) {
clients.(env);
} {
clients.();
}
}
Step 4: Environment Validation
import { Environment } from '../src/config/environments';
interface ValidationResult {
environment: string;
valid: boolean;
issues: string[];
}
async function validateEnvironment(env: Environment): Promise<ValidationResult> {
const issues: string[] = [];
const requiredVars = [
`TWINMIND_API_KEY_${env.toUpperCase()}`,
`TWINMIND_WEBHOOK_SECRET_${env.toUpperCase()}`,
];
for (const varName of requiredVars) {
if (!process.env[varName]) {
issues.push(`Missing required variable: ${varName}`);
}
}
const apiKey = process.env[`TWINMIND_API_KEY_${env.toUpperCase()}`];
if (apiKey && !apiKey.startsWith('tm_sk_')) {
issues.push('API key should start with "tm_sk_"');
}
(apiKey) {
{
response = (, {
: { : },
});
(!response.) {
issues.();
}
} (: ) {
issues.();
}
}
{
: env,
: issues. === ,
issues,
};
}
(): <> {
: [] = [, , ];
.();
( env environments) {
result = (env);
.();
(result.. > ) {
result..( .());
}
.();
}
}
();
Step 5: Promotion Workflow
import * as fs from 'fs';
import * as path from 'path';
interface PromotionResult {
success: boolean;
fromEnv: string;
toEnv: string;
changes: string[];
}
async function promoteConfig(
fromEnv: 'development' | 'staging',
toEnv: 'staging' | 'production'
): Promise<PromotionResult> {
const changes: string[] = [];
const sourceConfigPath = path.join(__dirname, `../config/twinmind.${fromEnv}.json`);
const sourceConfig = JSON.parse(fs.readFileSync(sourceConfigPath, 'utf-8'));
const targetConfigPath = path.join(__dirname, `../config/twinmind.${toEnv}.json`);
const targetConfig = JSON.(fs.(targetConfigPath, ));
promotableKeys = [, , ];
( key promotableKeys) {
(.(sourceConfig[key]) !== .(targetConfig[key])) {
changes.();
targetConfig[key] = sourceConfig[key];
}
}
(changes. > ) {
fs.(targetConfigPath, .(targetConfig, , ));
}
{
: ,
fromEnv,
toEnv,
changes,
};
}
(): <> {
args = process..();
fromEnv = args[] | ;
toEnv = args[] | ;
(!fromEnv || !toEnv) {
.();
.();
process.();
}
result = (fromEnv, toEnv);
.();
.();
(result.. > ) {
.();
result..( .());
} {
.();
}
}
();
Step 6: Feature Flags per Environment
import { getEnvironmentConfig, Environment } from '../config/environments';
export interface FeatureFlags {
enableDiarization: boolean;
enableAutoSummary: boolean;
enableActionItems: boolean;
enableRealTimeTranscription: boolean;
enableEmailFollowUps: boolean;
maxAudioDurationMinutes: number;
enableBetaFeatures: boolean;
}
const defaultFlags: FeatureFlags = {
enableDiarization: false,
enableAutoSummary: true,
enableActionItems: true,
enableRealTimeTranscription: false,
enableEmailFollowUps: false,
maxAudioDurationMinutes: 60,
enableBetaFeatures: false,
};
const environmentFlags: Record<Environment, Partial<FeatureFlags>> = {
development: {
enableBetaFeatures: true,
maxAudioDurationMinutes: 10,
},
: {
: ,
: ,
: ,
},
: {
: ,
: ,
: ,
: ,
: ,
},
};
(): {
config = ();
{
...defaultFlags,
...environmentFlags[config.],
};
}
(): {
flags = ();
(flags[feature]);
}
Output
- Environment configuration structure
- Environment variable templates
- Client factory with environment awareness
- Environment validation script
- Config promotion workflow
- Feature flags per environment
Environment Matrix
| Setting | Development | Staging | Production |
|---|
| Model | ear-2 | ear-3 | ear-3 |
| Diarization | Off | On | On |
| Debug mode | On | On | Off |
| Rate limit | 30/min | 60/min | 300/min |
| Email sending | Off | Off | On |
| Daily quota | 10 hrs | 50 hrs | Unlimited |
Error Handling
| Issue | Cause | Solution |
|---|
| Wrong API key used | Env mismatch | Validate before deploy |
| Feature not available | Wrong environment | Check feature flags |
| Config not loading | Missing file | Verify config paths |
| Promotion failed | Schema mismatch | Validate configs first |
Resources
Next Steps
For monitoring setup, see twinmind-observability.