Skip to main content
Glama
environment-value-provider.ts5.28 kB
/** * EnvironmentValueProvider * Implementation for AC 6.4, 6.15 - Environment preparation and performance optimization * * Collects runtime environment values with caching for performance optimization. * Supports custom variable definitions and cross-platform compatibility. * * CRITICAL: No mocks - uses real system calls and environment access. */ import * as os from 'os'; import * as fs from 'fs'; import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); /** * Standard environment values interface */ export interface EnvironmentValues { USER: string; // process.env.USER || os.userInfo().username PWD: string; // process.cwd() HOSTNAME: string; // os.hostname() HOME: string; // os.homedir() LS_OUTPUT: string[]; // fs.readdirSync(process.cwd()) TIMESTAMP: string; // Date.now().toString() [key: string]: any; // Support for custom variables } /** * Configuration for EnvironmentValueProvider */ export interface EnvironmentValueProviderConfig { cacheTimeoutMs?: number; // Cache expiration time (default: no expiration) customVariables?: Record<string, string>; // Custom variable definitions targetDirectory?: string; // Target directory for LS_OUTPUT (default: cwd) failOnCommandError?: boolean; // Fail on custom command execution errors (default: true) } /** * EnvironmentValueProvider class */ export class EnvironmentValueProvider { private cachedValues: EnvironmentValues | null = null; private cacheTimestamp: number = 0; private readonly config: Required<EnvironmentValueProviderConfig>; private sessionTimestamp: string | null = null; // Fix TIMESTAMP stability constructor(config: EnvironmentValueProviderConfig = {}) { this.config = { cacheTimeoutMs: config.cacheTimeoutMs || 0, // 0 means no expiration customVariables: config.customVariables || {}, targetDirectory: config.targetDirectory || process.cwd(), failOnCommandError: config.failOnCommandError !== false }; // Initialize session timestamp that remains stable until explicitly reset this.sessionTimestamp = Date.now().toString(); } /** * Get all environment values with caching * AC 6.4: Pre-execution environment preparation * AC 6.15: Performance optimization through caching */ async getValues(): Promise<EnvironmentValues> { // Check cache validity if (this.cachedValues && this.isCacheValid()) { return this.cachedValues; } // Collect fresh values this.cachedValues = await this.collectValues(); this.cacheTimestamp = Date.now(); return this.cachedValues; } /** * Invalidate cached values * AC 6.15: Cache invalidation for environment changes */ invalidateCache(): void { this.cachedValues = null; this.cacheTimestamp = 0; // DO NOT reset sessionTimestamp - it should remain stable per test session } /** * Check if cache is still valid */ private isCacheValid(): boolean { if (this.config.cacheTimeoutMs === 0) { return true; // No expiration } return Date.now() - this.cacheTimestamp < this.config.cacheTimeoutMs; } /** * Collect all environment values from system */ private async collectValues(): Promise<EnvironmentValues> { const values: EnvironmentValues = { USER: this.getUserName(), PWD: process.cwd(), HOSTNAME: os.hostname(), HOME: os.homedir(), LS_OUTPUT: this.getDirectoryListing(), TIMESTAMP: this.sessionTimestamp! // Use stable session timestamp }; // Add custom variables for (const [key, definition] of Object.entries(this.config.customVariables)) { try { values[key] = await this.resolveCustomVariable(definition); } catch (error) { if (this.config.failOnCommandError) { throw new Error(`Failed to resolve custom variable '${key}': ${error instanceof Error ? error.message : String(error)}`); } else { values[key] = ''; // Default to empty string on failure } } } return values; } /** * Get username with fallback to os.userInfo * AC 6.1: Dynamic username value construction */ private getUserName(): string { return process.env.USER || os.userInfo().username; } /** * Get directory listing for current working directory * AC 6.8: Directory listing dynamic validation */ private getDirectoryListing(): string[] { try { return fs.readdirSync(this.config.targetDirectory); } catch (error) { throw new Error(`Failed to read directory listing for '${this.config.targetDirectory}': ${error instanceof Error ? error.message : String(error)}`); } } /** * Resolve custom variable definition * AC 6.11: Custom variable definition and usage */ private async resolveCustomVariable(definition: string): Promise<string> { if (definition.startsWith('exec:')) { // Execute command and return output const command = definition.substring(5).trim(); const { stdout } = await execAsync(command); return stdout.trim(); } else { // Return literal value return definition; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/LightspeedDMS/ssh-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server