UNPKG

@dollhousemcp/mcp-server

Version:

DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.

897 lines 125 kB
/** * ConfigManager - Centralized configuration management for DollhouseMCP * * Features: * - YAML-based configuration file * - Default values with user overrides * - Migration from environment variables * - Validation and type safety * - Atomic updates with backup * - Privacy-first defaults * - OAuth client ID storage for MCP client integration */ import * as path from 'path'; import * as os from 'os'; import * as yaml from 'js-yaml'; import { logger } from '../utils/logger.js'; import { SecureYamlParser } from '../security/secureYamlParser.js'; import { validatePropertyPath, safeSetProperty, createSafeObject, safeHasOwnProperty } from '../utils/securityUtils.js'; import { env } from './env.js'; /** Validate and brand a port number. Returns undefined if invalid. */ export function validatePort(value) { const num = typeof value === 'string' ? Number(value) : value; if (typeof num !== 'number' || !Number.isFinite(num)) return undefined; const port = Math.floor(num); if (port < 1024 || port > 65535) return undefined; return port; } export class ConfigManager { configDir; configPath; backupPath; config = null; fileOperations; os; /** * Extract console.port from raw YAML config content without full * ConfigManager initialization. Uses FAILSAFE_SCHEMA for security * (no code execution). Returns undefined if not found or invalid. */ static readPortFromYaml(yamlContent) { try { const parsed = yaml.load(yamlContent, { schema: yaml.FAILSAFE_SCHEMA }); const raw = parsed?.console?.port; if (raw === undefined || raw === null) return undefined; const port = Number(raw); return Number.isFinite(port) ? port : undefined; } catch { return undefined; } } constructor(fileOperations, osModule) { this.fileOperations = fileOperations; this.os = osModule; // Initialize paths - use test directory if in test environment // NOTE: Using process.env directly here (not cached env.TEST_CONFIG_DIR) // to allow tests to dynamically override the config directory if (env.NODE_ENV === 'test' && process.env.TEST_CONFIG_DIR) { this.configDir = process.env.TEST_CONFIG_DIR; } else { this.configDir = path.join(this.os.homedir(), '.dollhouse'); } this.configPath = path.join(this.configDir, 'config.yml'); this.backupPath = path.join(this.configDir, 'config.yml.backup'); } /** * Get default configuration */ getDefaultConfig() { return { version: '1.0.0', user: { username: null, email: null, display_name: null }, github: { portfolio: { repository_url: null, repository_name: env.GITHUB_REPOSITORY || 'dollhouse-portfolio', default_branch: 'main', auto_create: true }, auth: { use_oauth: true, token_source: 'environment' } }, sync: { enabled: false, // Privacy first - off by default individual: { require_confirmation: true, show_diff_before_sync: true, track_versions: true, keep_history: 10 }, bulk: { upload_enabled: false, // Requires explicit enablement download_enabled: false, require_preview: true, respect_local_only: true }, privacy: { scan_for_secrets: true, scan_for_pii: true, warn_on_sensitive: true, excluded_patterns: [ '*.secret', '*-private.*', 'credentials/**', 'personal/**' ] } }, collection: { auto_submit: false, // Never auto-submit require_review: true, add_attribution: true }, autoLoad: { enabled: true, // Auto-load baseline memories by default maxTokenBudget: 5000, maxSingleMemoryTokens: undefined, suppressLargeMemoryWarnings: false, memories: [] // Configured via memory files with autoLoad flag }, elements: { auto_activate: {}, default_element_dir: path.join(os.homedir(), '.dollhouse', 'portfolio'), enhanced_index: { enabled: true, limits: { maxTriggersPerElement: 50, maxTriggerLength: 50, maxKeywordsToCheck: 100 }, telemetry: { enabled: false, // Opt-in only sampleRate: 0.1, metricsInterval: 60000 }, resources: { advertise_resources: false, // Default: safe, disabled variants: { summary: false, // ~1,254 tokens - Opt-in required full: false, // ~48,306 tokens - Opt-in required stats: true // ~50 tokens - Safe by default } } } }, display: { persona_indicators: { enabled: true, style: 'minimal', include_emoji: true }, verbose_logging: false, show_progress: true }, wizard: { completed: false, dismissed: false }, license: { tier: 'agpl' }, console: { port: 41715 }, /** * Retention Policy Configuration (Issue #51) * IMPORTANT: Disabled by default - nothing is auto-deleted without explicit consent. * Users must explicitly enable retention enforcement if they want automatic cleanup. */ retentionPolicy: { enabled: false, // DISABLED by default - no auto-deletion enforcement_mode: 'disabled', // Only manual enforcement allowed safety: { require_confirmation: true, // Always require confirmation dry_run_first: true, // Always preview before deleting warn_on_expiring: true, // Warn when entries approaching expiration warning_threshold_days: 7 // Warn 7 days before expiration }, audit: { log_deletions: true, // Log all deletions backup_before_delete: true, // Backup deleted entries backup_retention_days: 30 // Keep backups for 30 days }, defaults: { ttl_days: 30, // Default 30-day TTL (if enabled) max_entries: 1000 // Default max entries per memory } } }; } /** * Initialize configuration */ async initialize() { // Always reload config from disk if it exists, even if we have defaults in memory // This ensures we pick up any manual edits or saved settings try { // Ensure config directory exists with proper permissions (0o700 = owner only) await this.fileOperations.createDirectory(this.configDir); // Set permissions on the directory await this.fileOperations.chmod(this.configDir, 0o700, { source: 'ConfigManager.initialize' }); // Load or create config if (await this.configExists()) { await this.loadConfig(); } else { // Create default config this.config = this.getDefaultConfig(); // Try to migrate from environment variables await this.migrateFromEnvironment(); // Save the config await this.saveConfig(); logger.info('Created new configuration file', { path: this.configPath }); } } catch (error) { logger.error('Failed to initialize configuration', { error: error instanceof Error ? error.message : String(error) }); // Use defaults in memory this.config = this.getDefaultConfig(); } } /** * Load configuration from file */ async loadConfig() { try { const content = await this.fileOperations.readFile(this.configPath, { source: 'ConfigManager.loadConfig' }); /** * IMPORTANT: Parser Selection for Different File Types * * We use DIFFERENT parsers for different file types: * * 1. js-yaml (used here) - For PURE YAML files: * - Configuration files (config.yml) * - Data files without markdown content * - Any .yml or .yaml file that's just YAML * Example format: * ```yaml * version: 1.0.0 * user: * username: johndoe * email: john@example.com * ``` * * 2. SecureYamlParser - For MARKDOWN files with YAML frontmatter: * - Persona files (*.md in personas/) * - Skill files (*.md in skills/) * - Template files (*.md in templates/) * - Any .md file with frontmatter between --- markers * Example format: * ```markdown * --- * name: Creative Writer * description: A creative assistant * --- * # Instructions * You are a creative writer... * ``` * * The config file is PURE YAML, so we use js-yaml directly with FAILSAFE_SCHEMA * for security (prevents code execution via YAML tags). * SECURITY: This is NOT a vulnerability - FAILSAFE_SCHEMA prevents code execution */ let loadedData; try { // Using yaml with FAILSAFE_SCHEMA is secure - prevents code execution loadedData = yaml.load(content, { schema: yaml.FAILSAFE_SCHEMA // Safe schema prevents code execution }); } catch (yamlError) { throw new Error(`Invalid YAML in configuration file: ${yamlError instanceof Error ? yamlError.message : String(yamlError)}`); } if (!loadedData || typeof loadedData !== 'object') { throw new Error('Invalid configuration format'); } logger.debug('Loaded config from file', { username: loadedData.user?.username, email: loadedData.user?.email, syncEnabled: loadedData.sync?.enabled }); this.config = this.mergeWithDefaults(loadedData); // Fix any string booleans that might have been saved incorrectly this.fixConfigTypes(); logger.debug('Configuration loaded successfully', { username: this.config.user.username, syncEnabled: this.config.sync.enabled }); } catch (error) { logger.error('Failed to load configuration', { error: error instanceof Error ? error.message : String(error) }); throw error; } } /** * Check if config file exists */ async configExists() { return this.fileOperations.exists(this.configPath); } /** * Get GitHub OAuth client ID * Environment variable takes precedence over config file */ getGitHubClientId() { // NOTE: DOLLHOUSE_GITHUB_CLIENT_ID is not in centralized env config // as it's an optional feature flag, so we still use process.env here const envClientId = process.env.DOLLHOUSE_GITHUB_CLIENT_ID; if (envClientId) { return envClientId; } // Fall back to config file return this.config?.github?.auth?.client_id || null; } /** * Set GitHub OAuth client ID in config file */ async setGitHubClientId(clientId) { if (!ConfigManager.validateClientId(clientId)) { throw new Error(`Invalid GitHub client ID format. Expected format: Ov23li followed by at least 14 alphanumeric characters (e.g., Ov23liABCDEFGHIJKLMN)`); } if (!this.config) { this.config = this.getDefaultConfig(); } // Ensure github.auth object exists if (!this.config.github) { this.config.github = this.getDefaultConfig().github; } if (!this.config.github.auth) { this.config.github.auth = this.getDefaultConfig().github.auth; } this.config.github.auth.client_id = clientId; await this.saveConfig(); } /** * Get the current configuration */ getConfig() { if (!this.config) { throw new Error('Configuration not initialized'); } return this.config; } /** * Get a specific setting using dot notation */ getSetting(path, defaultValue) { if (!this.config) { return defaultValue; } const keys = path.split('.'); let value = this.config; for (const key of keys) { if (value && typeof value === 'object' && key in value) { value = value[key]; } else { return defaultValue; } } return value; } /** * Update a specific setting using dot notation * SECURITY FIX (PR #895): Added prototype pollution protection * Previously: Direct property assignment allowed __proto__ injection * Now: Validates keys against forbidden properties before assignment */ async updateSetting(path, value) { if (!this.config) { await this.initialize(); } // SECURITY: Validate path to prevent prototype pollution validatePropertyPath(path, 'path'); // Runtime validation for known typed settings (#1840) if (path === 'console.port') { const validated = validatePort(value); if (!validated) { return { success: false, message: `Invalid port: ${value}. Must be an integer between 1024 and 65535.`, }; } value = validated; } const keys = path.split('.'); let current = this.config; const previousValue = this.getSetting(path); // Navigate to the parent object using security utilities for (let i = 0; i < keys.length - 1; i++) { const key = keys[i]; // SECURITY: Use safe property check and create prototype-less objects if (!safeHasOwnProperty(current, key)) { current[key] = createSafeObject(); } current = current[key]; } // Set the value using secure property setter const lastKey = keys[keys.length - 1]; safeSetProperty(current, lastKey, value); // Save the configuration await this.saveConfig(); logger.info('Configuration setting updated', { path, previousValue, newValue: value }); return { success: true, message: `Setting '${path}' updated successfully`, previousValue, newValue: value }; } /** * Validate GitHub OAuth client ID format * Client IDs start with "Ov23li" followed by at least 14 alphanumeric characters * * @param clientId - The client ID to validate * @returns true if valid, false otherwise * * @example * ConfigManager.validateClientId("Ov23liABCDEFGHIJKLMN123456") // true * ConfigManager.validateClientId("invalid") // false * ConfigManager.validateClientId("Ov23li") // false (too short) * ConfigManager.validateClientId("Xv23liABCDEFGHIJKLMN") // false (wrong prefix) */ static validateClientId(clientId) { if (typeof clientId !== 'string' || !clientId) { return false; } // GitHub OAuth client IDs follow the pattern: Ov23li[A-Za-z0-9]{14,} const clientIdPattern = /^Ov23li[A-Za-z0-9]{14,}$/; return clientIdPattern.test(clientId); } /** * Save configuration to file */ async saveConfig() { if (!this.config) { throw new Error('No configuration to save'); } try { // Create backup of existing config if (await this.configExists()) { await this.fileOperations.copyFile(this.configPath, this.backupPath, { source: 'ConfigManager.saveConfig' }); } // Convert to YAML // Note: We use js-yaml's dump() for pure YAML output (no frontmatter markers) // This creates a standard YAML file, not a markdown file with frontmatter const yamlContent = yaml.dump(this.config, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false // Using default schema (not FAILSAFE) for dump to preserve types like booleans }); // Write atomically with proper permissions (0o600 = owner read/write only) const tempPath = `${this.configPath}.tmp`; await this.fileOperations.writeFile(tempPath, yamlContent, { source: 'ConfigManager.saveConfig' }); await this.fileOperations.chmod(tempPath, 0o600, { source: 'ConfigManager.saveConfig' }); await this.fileOperations.renameFile(tempPath, this.configPath); logger.debug('Configuration saved successfully'); // Log audit event for configuration update logger.debug('Configuration update audit', { event: 'CONFIG_UPDATED', source: 'ConfigManager.saveConfig', timestamp: new Date().toISOString() }); } catch (error) { logger.error('Failed to save configuration', { error: error instanceof Error ? error.message : String(error) }); // Try to restore backup if (await this.backupExists()) { await this.fileOperations.copyFile(this.backupPath, this.configPath, { source: 'ConfigManager.saveConfig' }); logger.info('Restored configuration from backup'); } throw error; } } /** * Check if backup exists */ async backupExists() { return this.fileOperations.exists(this.backupPath); } /** * Fix incorrect types in config (e.g., string booleans, string "null") */ fixConfigTypes() { if (!this.config) return; // Helper to convert string "null" to actual null const fixNull = (value) => { if (value === 'null' || value === 'NULL') return null; return value; }; // Helper to convert string booleans to actual booleans const fixBoolean = (value) => { if (typeof value === 'string') { const lower = value.toLowerCase(); if (lower === 'true') return true; if (lower === 'false') return false; } return value; }; // Fix user fields - handle string "null" values if (this.config.user) { this.config.user.username = fixNull(this.config.user.username); this.config.user.email = fixNull(this.config.user.email); this.config.user.display_name = fixNull(this.config.user.display_name); } // Fix sync settings if (this.config.sync) { this.config.sync.enabled = fixBoolean(this.config.sync.enabled); if (this.config.sync.individual) { this.config.sync.individual.require_confirmation = fixBoolean(this.config.sync.individual.require_confirmation); this.config.sync.individual.show_diff_before_sync = fixBoolean(this.config.sync.individual.show_diff_before_sync); this.config.sync.individual.track_versions = fixBoolean(this.config.sync.individual.track_versions); } if (this.config.sync.bulk) { this.config.sync.bulk.upload_enabled = fixBoolean(this.config.sync.bulk.upload_enabled); this.config.sync.bulk.download_enabled = fixBoolean(this.config.sync.bulk.download_enabled); this.config.sync.bulk.require_preview = fixBoolean(this.config.sync.bulk.require_preview); this.config.sync.bulk.respect_local_only = fixBoolean(this.config.sync.bulk.respect_local_only); } if (this.config.sync.privacy) { this.config.sync.privacy.scan_for_secrets = fixBoolean(this.config.sync.privacy.scan_for_secrets); this.config.sync.privacy.scan_for_pii = fixBoolean(this.config.sync.privacy.scan_for_pii); this.config.sync.privacy.warn_on_sensitive = fixBoolean(this.config.sync.privacy.warn_on_sensitive); } } // Fix collection settings if (this.config.collection) { this.config.collection.auto_submit = fixBoolean(this.config.collection.auto_submit); this.config.collection.require_review = fixBoolean(this.config.collection.require_review); this.config.collection.add_attribution = fixBoolean(this.config.collection.add_attribution); } // Fix display settings if (this.config.display) { if (this.config.display.persona_indicators) { this.config.display.persona_indicators.enabled = fixBoolean(this.config.display.persona_indicators.enabled); this.config.display.persona_indicators.include_emoji = fixBoolean(this.config.display.persona_indicators.include_emoji); } this.config.display.verbose_logging = fixBoolean(this.config.display.verbose_logging); this.config.display.show_progress = fixBoolean(this.config.display.show_progress); } // Fix github settings if (this.config.github) { if (this.config.github.portfolio) { this.config.github.portfolio.repository_url = fixNull(this.config.github.portfolio.repository_url); this.config.github.portfolio.auto_create = fixBoolean(this.config.github.portfolio.auto_create); } if (this.config.github.auth) { this.config.github.auth.use_oauth = fixBoolean(this.config.github.auth.use_oauth); // Fix client_id if it's a string "null" if (this.config.github.auth.client_id) { this.config.github.auth.client_id = fixNull(this.config.github.auth.client_id) || undefined; } } } // Fix wizard settings if (this.config.wizard) { this.config.wizard.completed = fixBoolean(this.config.wizard.completed); this.config.wizard.dismissed = fixBoolean(this.config.wizard.dismissed); } // Fix retention policy settings (Issue #51) if (this.config.retentionPolicy) { this.config.retentionPolicy.enabled = fixBoolean(this.config.retentionPolicy.enabled); if (this.config.retentionPolicy.safety) { this.config.retentionPolicy.safety.require_confirmation = fixBoolean(this.config.retentionPolicy.safety.require_confirmation); this.config.retentionPolicy.safety.dry_run_first = fixBoolean(this.config.retentionPolicy.safety.dry_run_first); this.config.retentionPolicy.safety.warn_on_expiring = fixBoolean(this.config.retentionPolicy.safety.warn_on_expiring); } if (this.config.retentionPolicy.audit) { this.config.retentionPolicy.audit.log_deletions = fixBoolean(this.config.retentionPolicy.audit.log_deletions); this.config.retentionPolicy.audit.backup_before_delete = fixBoolean(this.config.retentionPolicy.audit.backup_before_delete); } } } /** * Merge partial config with defaults * * IMPORTANT: This function preserves unknown fields for forward compatibility. * If a future version adds new config fields, older versions won't lose them. */ mergeWithDefaults(partial) { const defaults = this.getDefaultConfig(); // Start with a deep clone of partial to preserve all unknown fields const result = JSON.parse(JSON.stringify(partial)); // Ensure all required fields exist with defaults result.version = result.version || defaults.version; // User section - preserve unknown fields while ensuring required fields result.user = { ...result.user, username: result.user?.username ?? defaults.user.username, email: result.user?.email ?? defaults.user.email, display_name: result.user?.display_name ?? defaults.user.display_name }; // GitHub section - deep merge preserving unknown fields if (!result.github) result.github = {}; result.github.portfolio = { ...defaults.github.portfolio, ...result.github.portfolio }; result.github.auth = { ...defaults.github.auth, ...result.github.auth }; // Sync section - preserve unknown fields at all levels if (!result.sync) result.sync = {}; result.sync.enabled = result.sync.enabled ?? defaults.sync.enabled; result.sync.individual = { ...defaults.sync.individual, ...result.sync.individual }; result.sync.bulk = { ...defaults.sync.bulk, ...result.sync.bulk }; result.sync.privacy = { ...defaults.sync.privacy, ...result.sync.privacy, // Special handling for arrays - use provided or default excluded_patterns: result.sync.privacy?.excluded_patterns || defaults.sync.privacy.excluded_patterns }; // Collection section result.collection = { ...defaults.collection, ...result.collection }; // Elements section if (!result.elements) result.elements = {}; result.elements = { ...result.elements, auto_activate: result.elements.auto_activate || defaults.elements.auto_activate, default_element_dir: result.elements.default_element_dir || defaults.elements.default_element_dir }; // Display section if (!result.display) result.display = {}; result.display.persona_indicators = { ...defaults.display.persona_indicators, ...result.display.persona_indicators }; result.display.verbose_logging = result.display.verbose_logging ?? defaults.display.verbose_logging; result.display.show_progress = result.display.show_progress ?? defaults.display.show_progress; // Wizard section result.wizard = { ...defaults.wizard, ...result.wizard }; // AutoLoad section (Issue: regression from current-server) // This was missing in mcp-server refactor, causing auto-load to fail for existing configs result.autoLoad = { ...defaults.autoLoad, ...result.autoLoad }; // Retention Policy section (Issue #51) // IMPORTANT: Defaults are disabled - nothing auto-deleted without explicit consent if (!result.retentionPolicy) result.retentionPolicy = {}; result.retentionPolicy = { enabled: result.retentionPolicy.enabled ?? defaults.retentionPolicy.enabled, enforcement_mode: result.retentionPolicy.enforcement_mode ?? defaults.retentionPolicy.enforcement_mode, safety: { ...defaults.retentionPolicy.safety, ...result.retentionPolicy.safety }, audit: { ...defaults.retentionPolicy.audit, ...result.retentionPolicy.audit }, defaults: { ...defaults.retentionPolicy.defaults, ...result.retentionPolicy.defaults } }; return result; } /** * Migrate settings from environment variables */ async migrateFromEnvironment() { let migrated = false; // NOTE: These are optional custom env vars not in centralized config // They're used for backwards compatibility migration only // Migrate user settings if (process.env.DOLLHOUSE_USER && !this.config?.user.username) { if (!this.config) this.config = this.getDefaultConfig(); this.config.user.username = process.env.DOLLHOUSE_USER; migrated = true; } if (process.env.DOLLHOUSE_EMAIL && !this.config?.user.email) { if (!this.config) this.config = this.getDefaultConfig(); this.config.user.email = process.env.DOLLHOUSE_EMAIL; migrated = true; } // Migrate portfolio URL if (process.env.DOLLHOUSE_PORTFOLIO_URL && !this.config?.github.portfolio.repository_url) { if (!this.config) this.config = this.getDefaultConfig(); this.config.github.portfolio.repository_url = process.env.DOLLHOUSE_PORTFOLIO_URL; migrated = true; } // Migrate collection auto-submit - use centralized env config if (env.DOLLHOUSE_AUTO_SUBMIT_TO_COLLECTION !== false) { if (!this.config) this.config = this.getDefaultConfig(); this.config.collection.auto_submit = env.DOLLHOUSE_AUTO_SUBMIT_TO_COLLECTION; migrated = true; } if (migrated) { logger.info('Migrated settings from environment variables'); } } /** * Reset configuration to defaults * SECURITY FIX (PR #895): Added prototype pollution protection * Previously: Direct property assignment allowed __proto__ injection * Now: Validates keys against forbidden properties before assignment */ async resetConfig(section) { const defaults = this.getDefaultConfig(); if (section) { // Reset specific section if (!this.config) { this.config = defaults; } else { // SECURITY: Validate section path to prevent prototype pollution validatePropertyPath(section, 'section'); const sectionKeys = section.split('.'); let current = this.config; let defaultSection = defaults; for (let i = 0; i < sectionKeys.length - 1; i++) { current = current[sectionKeys[i]]; defaultSection = defaultSection[sectionKeys[i]]; } const lastKey = sectionKeys[sectionKeys.length - 1]; // SECURITY: Use secure property setter to avoid prototype chain pollution safeSetProperty(current, lastKey, defaultSection[lastKey]); } await this.saveConfig(); return { success: true, message: `Section '${section}' reset to defaults` }; } else { // Reset entire config this.config = defaults; await this.saveConfig(); return { success: true, message: 'Configuration reset to defaults' }; } } /** * Export configuration to file */ async exportConfig(filePath) { if (!this.config) { return { success: false, message: 'No configuration to export' }; } try { const yamlContent = yaml.dump(this.config, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }); await this.fileOperations.writeFile(filePath, yamlContent, { source: 'ConfigManager.exportConfig' }); await this.fileOperations.chmod(filePath, 0o600, { source: 'ConfigManager.exportConfig' }); return { success: true, message: `Configuration exported to ${filePath}` }; } catch (error) { return { success: false, message: `Failed to export configuration: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Import configuration from file */ async importConfig(filePath) { try { const content = await this.fileOperations.readFile(filePath, { source: 'ConfigManager.importConfig' }); // Parse and validate const parsed = SecureYamlParser.parse(content, { maxYamlSize: 64 * 1024, validateContent: false, validateFields: false }); if (!parsed.data || typeof parsed.data !== 'object') { return { success: false, message: 'Invalid configuration format in import file' }; } // Merge with defaults this.config = this.mergeWithDefaults(parsed.data); // Save the imported config await this.saveConfig(); return { success: true, message: `Configuration imported from ${filePath}` }; } catch (error) { return { success: false, message: `Failed to import configuration: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Get formatted config for display */ getFormattedConfig(section) { if (!this.config) { return 'Configuration not initialized'; } let configToShow = this.config; if (section) { configToShow = this.getSetting(section); if (!configToShow) { return `Section '${section}' not found`; } } // Remove sensitive data for display const sanitized = JSON.parse(JSON.stringify(configToShow)); // Don't show tokens if they exist if (sanitized.github?.auth?.token) { sanitized.github.auth.token = '***REDACTED***'; } return yaml.dump(sanitized, { indent: 2, lineWidth: 120, noRefs: true, sortKeys: false }); } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ29uZmlnTWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb25maWcvQ29uZmlnTWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7R0FXRztBQUVILE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBQzdCLE9BQU8sS0FBSyxFQUFFLE1BQU0sSUFBSSxDQUFDO0FBQ3pCLE9BQU8sS0FBSyxJQUFJLE1BQU0sU0FBUyxDQUFDO0FBQ2hDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM1QyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNuRSxPQUFPLEVBQ0wsb0JBQW9CLEVBQ3BCLGVBQWUsRUFDZixnQkFBZ0IsRUFDaEIsa0JBQWtCLEVBQ25CLE1BQU0sMkJBQTJCLENBQUM7QUFDbkMsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLFVBQVUsQ0FBQztBQXlOL0Isc0VBQXNFO0FBQ3RFLE1BQU0sVUFBVSxZQUFZLENBQUMsS0FBYztJQUN6QyxNQUFNLEdBQUcsR0FBRyxPQUFPLEtBQUssS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDO0lBQzlELElBQUksT0FBTyxHQUFHLEtBQUssUUFBUSxJQUFJLENBQUMsTUFBTSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7UUFBRSxPQUFPLFNBQVMsQ0FBQztJQUN2RSxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQzdCLElBQUksSUFBSSxHQUFHLElBQUksSUFBSSxJQUFJLEdBQUcsS0FBSztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQ2xELE9BQU8sSUFBa0IsQ0FBQztBQUM1QixDQUFDO0FBaURELE1BQU0sT0FBTyxhQUFhO0lBQ2hCLFNBQVMsQ0FBUztJQUNsQixVQUFVLENBQVM7SUFDbkIsVUFBVSxDQUFTO0lBQ25CLE1BQU0sR0FBMkIsSUFBSSxDQUFDO0lBQzdCLGNBQWMsQ0FBeUI7SUFDaEQsRUFBRSxDQUFZO0lBRXRCOzs7O09BSUc7SUFDSCxNQUFNLENBQUMsZ0JBQWdCLENBQUMsV0FBbUI7UUFDekMsSUFBSSxDQUFDO1lBQ0gsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsRUFBRSxNQUFNLEVBQUUsSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUErQixDQUFDO1lBQ3RHLE1BQU0sR0FBRyxHQUFHLE1BQU0sRUFBRSxPQUFPLEVBQUUsSUFBSSxDQUFDO1lBQ2xDLElBQUksR0FBRyxLQUFLLFNBQVMsSUFBSSxHQUFHLEtBQUssSUFBSTtnQkFBRSxPQUFPLFNBQVMsQ0FBQztZQUN4RCxNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDekIsT0FBTyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQztRQUNsRCxDQUFDO1FBQUMsTUFBTSxDQUFDO1lBQ1AsT0FBTyxTQUFTLENBQUM7UUFDbkIsQ0FBQztJQUNILENBQUM7SUFFRCxZQUFZLGNBQXNDLEVBQUUsUUFBbUI7UUFDckUsSUFBSSxDQUFDLGNBQWMsR0FBRyxjQUFjLENBQUM7UUFDckMsSUFBSSxDQUFDLEVBQUUsR0FBRyxRQUFRLENBQUM7UUFFbkIsK0RBQStEO1FBQy9ELHlFQUF5RTtRQUN6RSw4REFBOEQ7UUFDOUQsSUFBSSxHQUFHLENBQUMsUUFBUSxLQUFLLE1BQU0sSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLGVBQWUsRUFBRSxDQUFDO1lBQzNELElBQUksQ0FBQyxTQUFTLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxlQUFlLENBQUM7UUFDL0MsQ0FBQzthQUFNLENBQUM7WUFDTixJQUFJLENBQUMsU0FBUyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxPQUFPLEVBQUUsRUFBRSxZQUFZLENBQUMsQ0FBQztRQUM5RCxDQUFDO1FBQ0QsSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsWUFBWSxDQUFDLENBQUM7UUFDMUQsSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUUsbUJBQW1CLENBQUMsQ0FBQztJQUNuRSxDQUFDO0lBRUQ7O09BRUc7SUFDSyxnQkFBZ0I7UUFDdEIsT0FBTztZQUNMLE9BQU8sRUFBRSxPQUFPO1lBQ2hCLElBQUksRUFBRTtnQkFDSixRQUFRLEVBQUUsSUFBSTtnQkFDZCxLQUFLLEVBQUUsSUFBSTtnQkFDWCxZQUFZLEVBQUUsSUFBSTthQUNuQjtZQUNELE1BQU0sRUFBRTtnQkFDTixTQUFTLEVBQUU7b0JBQ1QsY0FBYyxFQUFFLElBQUk7b0JBQ3BCLGVBQWUsRUFBRSxHQUFHLENBQUMsaUJBQWlCLElBQUkscUJBQXFCO29CQUMvRCxjQUFjLEVBQUUsTUFBTTtvQkFDdEIsV0FBVyxFQUFFLElBQUk7aUJBQ2xCO2dCQUNELElBQUksRUFBRTtvQkFDSixTQUFTLEVBQUUsSUFBSTtvQkFDZixZQUFZLEVBQUUsYUFBYTtpQkFDNUI7YUFDRjtZQUNELElBQUksRUFBRTtnQkFDSixPQUFPLEVBQUUsS0FBSyxFQUFFLGlDQUFpQztnQkFDakQsVUFBVSxFQUFFO29CQUNWLG9CQUFvQixFQUFFLElBQUk7b0JBQzFCLHFCQUFxQixFQUFFLElBQUk7b0JBQzNCLGNBQWMsRUFBRSxJQUFJO29CQUNwQixZQUFZLEVBQUUsRUFBRTtpQkFDakI7Z0JBQ0QsSUFBSSxFQUFFO29CQUNKLGNBQWMsRUFBRSxLQUFLLEVBQUUsK0JBQStCO29CQUN0RCxnQkFBZ0IsRUFBRSxLQUFLO29CQUN2QixlQUFlLEVBQUUsSUFBSTtvQkFDckIsa0JBQWtCLEVBQUUsSUFBSTtpQkFDekI7Z0JBQ0QsT0FBTyxFQUFFO29CQUNQLGdCQUFnQixFQUFFLElBQUk7b0JBQ3RCLFlBQVksRUFBRSxJQUFJO29CQUNsQixpQkFBaUIsRUFBRSxJQUFJO29CQUN2QixpQkFBaUIsRUFBRTt3QkFDakIsVUFBVTt3QkFDVixhQUFhO3dCQUNiLGdCQUFnQjt3QkFDaEIsYUFBYTtxQkFDZDtpQkFDRjthQUNGO1lBQ0QsVUFBVSxFQUFFO2dCQUNWLFdBQVcsRUFBRSxLQUFLLEVBQUUsb0JBQW9CO2dCQUN4QyxjQUFjLEVBQUUsSUFBSTtnQkFDcEIsZUFBZSxFQUFFLElBQUk7YUFDdEI7WUFDRCxRQUFRLEVBQUU7Z0JBQ1IsT0FBTyxFQUFFLElBQUksRUFBRSx5Q0FBeUM7Z0JBQ3hELGNBQWMsRUFBRSxJQUFJO2dCQUNwQixxQkFBcUIsRUFBRSxTQUFTO2dCQUNoQywyQkFBMkIsRUFBRSxLQUFLO2dCQUNsQyxRQUFRLEVBQUUsRUFBRSxDQUFDLGlEQUFpRDthQUMvRDtZQUNELFFBQVEsRUFBRTtnQkFDUixhQUFhLEVBQUUsRUFBRTtnQkFDakIsbUJBQW1CLEVBQUUsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLEVBQUUsWUFBWSxFQUFFLFdBQVcsQ0FBQztnQkFDdkUsY0FBYyxFQUFFO29CQUNkLE9BQU8sRUFBRSxJQUFJO29CQUNiLE1BQU0sRUFBRTt3QkFDTixxQkFBcUIsRUFBRSxFQUFFO3dCQUN6QixnQkFBZ0IsRUFBRSxFQUFFO3dCQUNwQixrQkFBa0IsRUFBRSxHQUFHO3FCQUN4QjtvQkFDRCxTQUFTLEVBQUU7d0JBQ1QsT0FBTyxFQUFFLEtBQUssRUFBRyxjQUFjO3dCQUMvQixVQUFVLEVBQUUsR0FBRzt3QkFDZixlQUFlLEVBQUUsS0FBSztxQkFDdkI7b0JBQ0QsU0FBUyxFQUFFO3dCQUNULG1CQUFtQixFQUFFLEtBQUssRUFBRSwwQkFBMEI7d0JBQ3RELFFBQVEsRUFBRTs0QkFDUixPQUFPLEVBQUUsS0FBSyxFQUFHLGtDQUFrQzs0QkFDbkQsSUFBSSxFQUFFLEtBQUssRUFBTSxtQ0FBbUM7NEJBQ3BELEtBQUssRUFBRSxJQUFJLENBQU0sK0JBQStCO3lCQUNqRDtxQkFDRjtpQkFDRjthQUNGO1lBQ0QsT0FBTyxFQUFFO2dCQUNQLGtCQUFrQixFQUFFO29CQUNsQixPQUFPLEVBQUUsSUFBSTtvQkFDYixLQUFLLEVBQUUsU0FBUztvQkFDaEIsYUFBYSxFQUFFLElBQUk7aUJBQ3BCO2dCQUNELGVBQWUsRUFBRSxLQUFLO2dCQUN0QixhQUFhLEVBQUUsSUFBSTthQUNwQjtZQUNELE1BQU0sRUFBRTtnQkFDTixTQUFTLEVBQUUsS0FBSztnQkFDaEIsU0FBUyxFQUFFLEtBQUs7YUFDakI7WUFDRCxPQUFPLEVBQUU7Z0JBQ1AsSUFBSSxFQUFFLE1BQU07YUFDYjtZQUNELE9BQU8sRUFBRTtnQkFDUCxJQUFJLEVBQUUsS0FBSzthQUNaO1lBQ0Q7Ozs7ZUFJRztZQUNILGVBQWUsRUFBRTtnQkFDZixPQUFPLEVBQUUsS0FBSyxFQUFxQix5Q0FBeUM7Z0JBQzVFLGdCQUFnQixFQUFFLFVBQVUsRUFBUSxrQ0FBa0M7Z0JBQ3RFLE1BQU0sRUFBRTtvQkFDTixvQkFBb0IsRUFBRSxJQUFJLEVBQVEsOEJBQThCO29CQUNoRSxhQUFhLEVBQUUsSUFBSSxFQUFlLGlDQUFpQztvQkFDbkUsZ0JBQWdCLEVBQUUsSUFBSSxFQUFZLDJDQUEyQztvQkFDN0Usc0JBQXNCLEVBQUUsQ0FBQyxDQUFTLGdDQUFnQztpQkFDbkU7Z0JBQ0QsS0FBSyxFQUFFO29CQUNMLGFBQWEsRUFBRSxJQUFJLEVBQWUsb0JBQW9CO29CQUN0RCxvQkFBb0IsRUFBRSxJQUFJLEVBQVEseUJBQXlCO29CQUMzRCxxQkFBcUIsRUFBRSxFQUFFLENBQVMsMkJBQTJCO2lCQUM5RDtnQkFDRCxRQUFRLEVBQUU7b0JBQ1IsUUFBUSxFQUFFLEVBQUUsRUFBc0Isa0NBQWtDO29CQUNwRSxXQUFXLEVBQUUsSUFBSSxDQUFpQixpQ0FBaUM7aUJBQ3BFO2FBQ0Y7U0FDRixDQUFDO0lBQ0osQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFVBQVU7UUFDckIsa0ZBQWtGO1FBQ2xGLDZEQUE2RDtRQUU3RCxJQUFJLENBQUM7WUFDSCw4RUFBOEU7WUFDOUUsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUM7WUFDMUQsbUNBQW1DO1lBQ25DLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsRUFBRSxLQUFLLEVBQUU7Z0JBQ3JELE1BQU0sRUFBRSwwQkFBMEI7YUFDbkMsQ0FBQyxDQUFDO1lBRUgsd0JBQXdCO1lBQ3hCLElBQUksTUFBTSxJQUFJLENBQUMsWUFBWSxFQUFFLEVBQUUsQ0FBQztnQkFDOUIsTUFBTSxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7WUFDMUIsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLHdCQUF3QjtnQkFDeEIsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztnQkFFdEMsNENBQTRDO2dCQUM1QyxNQUFNLElBQUksQ0FBQyxzQkFBc0IsRUFBRSxDQUFDO2dCQUVwQyxrQkFBa0I7Z0JBQ2xCLE1BQU0sSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO2dCQUV4QixNQUFNLENBQUMsSUFBSSxDQUFDLGdDQUFnQyxFQUFFO29CQUM1QyxJQUFJLEVBQUUsSUFBSSxDQUFDLFVBQVU7aUJBQ3RCLENBQUMsQ0FBQztZQUNMLENBQUM7UUFDSCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLE1BQU0sQ0FBQyxLQUFLLENBQUMsb0NBQW9DLEVBQUU7Z0JBQ2pELEtBQUssRUFBRSxLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDO2FBQzlELENBQUMsQ0FBQztZQUNILHlCQUF5QjtZQUN6QixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1FBQ3hDLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSyxLQUFLLENBQUMsVUFBVTtRQUN0QixJQUFJLENBQUM7WUFDSCxNQUFNLE9BQU8sR0FBRyxNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUU7Z0JBQ2xFLE1BQU0sRUFBRSwwQkFBMEI7YUFDbkMsQ0FBQyxDQUFDO1lBRUg7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O2VBbUNHO1lBQ0gsSUFBSSxVQUFlLENBQUM7WUFDcEIsSUFBSSxDQUFDO2dCQUNILHNFQUFzRTtnQkFDdEUsVUFBVSxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxFQUFFO29CQUM5QixNQUFNLEVBQUUsSUFBSSxDQUFDLGVBQWUsQ0FBQyxzQ0FBc0M7aUJBQ3BFLENBQUMsQ0FBQztZQUNMLENBQUM7WUFBQyxPQUFPLFNBQVMsRUFBRSxDQUFDO2dCQUNuQixNQUFNLElBQUksS0FBSyxDQUFDLHVDQUF1QyxTQUFTLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQy9ILENBQUM7WUFFRCxJQUFJLENBQUMsVUFBVSxJQUFJLE9BQU8sVUFBVSxLQUFLLFFBQVEsRUFBRSxDQUFDO2dCQUNsRCxNQUFNLElBQUksS0FBSyxDQUFDLDhCQUE4QixDQUFDLENBQUM7WUFDbEQsQ0FBQztZQUNELE1BQU0sQ0FBQyxLQUFLLENBQUMseUJBQXlCLEVBQUU7Z0JBQ3RDLFFBQVEsRUFBRSxVQUFVLENBQUMsSUFBSSxFQUFFLFFBQVE7Z0JBQ25DLEtBQUssRUFBRSxVQUFVLENBQUMsSUFBSSxFQUFFLEtBQUs7Z0JBQzdCLFdBQVcsRUFBRSxVQUFVLENBQUMsSUFBSSxFQUFFLE9BQU87YUFDdEMsQ0FBQyxDQUFDO1lBRUgsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsVUFBVSxDQUFDLENBQUM7WUFFakQsaUVBQWlFO1lBQ2pFLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztZQUV0QixNQUFNLENBQUMsS0FBSyxDQUFDLG1DQUFtQyxFQUFFO2dCQUNoRCxRQUFRLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsUUFBUTtnQkFDbkMsV0FBVyxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU87YUFDdEMsQ0FBQyxDQUFDO1FBRUwsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixNQUFNLENBQUMsS0FBSyxDQUFDLDhCQUE4QixFQUFFO2dCQUMzQyxLQUFLLEVBQUUsS0FBSyxZQUFZLEtBQUssQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQzthQUM5RCxDQUFDLENBQUM7WUFDSCxNQUFNLEtBQUssQ0FBQztRQUNkLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSyxLQUFLLENBQUMsWUFBWTtRQUN4QixPQUFPLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUNyRCxDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksaUJBQWlCO1FBQ3RCLG9FQUFvRTtRQUNwRSxxRUFBcUU7UUFDckUsTUFBTSxXQUFXLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQywwQkFBMEIsQ0FBQztRQUMzRCxJQUFJLFdBQVcsRUFBRSxDQUFDO1lBQ2hCLE9BQU8sV0FBVyxDQUFDO1FBQ3JCLENBQUM7UUFFRCwyQkFBMkI7UUFDM0IsT0FBTyxJQUFJLENBQUMsTUFBTSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsU0FBUyxJQUFJLElBQUksQ0FBQztJQUN0RCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsaUJBQWlCLENBQUMsUUFBZ0I7UUFDN0MsSUFBSSxDQUFDLGFBQWEsQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDO1lBQzlDLE1BQU0sSUFBSSxLQUFLLENBQ2IsdUlBQXVJLENBQ3hJLENBQUM7UUFDSixDQUFDO1FBRUQsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNqQixJQUFJLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1FBQ3hDLENBQUM7UUFFRCxtQ0FBbUM7UUFDbkMsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDeEIsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixFQUFFLENBQUMsTUFBTSxDQUFDO1FBQ3RELENBQUM7UUFDRCxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDN0IsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUM7UUFDaEUsQ0FBQztRQUVELElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxTQUFTLEdBQUcsUUFBUSxDQUFDO1FBQzdDLE1BQU0sSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQzFCLENBQUM7SUFFRDs7T0FFRztJQUNJLFNBQVM7UUFDZCxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ2pCLE1BQU0sSUFBSSxLQUFLLENBQUMsK0JBQStCLENBQUMsQ0FBQztRQUNuRCxDQUFDO1FBQ0QsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDO0lBQ3JCLENBQUM7SUFFRDs7T0FFRztJQUNJLFVBQVUsQ0FBSSxJQUFZLEVBQUUsWUFBZ0I7UUFDakQsSUFBSSxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUNqQixPQUFPLFlBQVksQ0FBQztRQUN0QixDQUFDO1FBRUQsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUM3QixJQUFJLEtBQUssR0FBUSxJQUFJLENBQUMsTUFBTSxDQUFDO1FBRTdCLEtBQUssTUFBTSxHQUFHLElBQUksSUFBSSxFQUFFLENBQUM7WUFDdkIsSUFBSSxLQUFLLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEdBQUcsSUFBSSxLQUFLLEVBQUUsQ0FBQztnQkFDdkQsS0FBSyxHQUFHLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUNyQixDQUFDO2lCQUFNLENBQUM7Z0JBQ04sT0FBTyxZQUFZLENBQUM7WUFDdEIsQ0FBQztRQUNILENBQUM7UUFFRCxPQUFPLEtBQVUsQ0FBQztJQUNwQixDQUFDO0lBRUQ7Ozs7O09BS0c7SUFDSSxLQUFLLENBQUMsYUFBYSxDQUFDLElBQVksRUFBRSxLQUFVO1FBQ2pELElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDakIsTUFBTSxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDMUIsQ0FBQztRQUVELHlEQUF5RDtRQUN6RCxvQkFBb0IsQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLENBQUM7UUFFbkMsc0RBQXNEO1FBQ3RELElBQUksSUFBSSxLQUFLLGNBQWMsRUFBRSxDQUFDO1lBQzVCLE1BQU0sU0FBUyxHQUFHLFlBQVksQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUN0QyxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7Z0JBQ2YsT0FBTztvQkFDTCxPQUFPLEVBQUUsS0FBSztvQkFDZCxPQUFPLEVBQUUsaUJBQWlCLEtBQUssOENBQThDO2lCQUM5RSxDQUFDO1lBQ0osQ0FBQztZQUNELEtBQUssR0FBRyxTQUFTLENBQUM7UUFDcEIsQ0FBQztRQUVELE1BQU0sSUFBSSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDN0IsSUFBSSxPQUFPLEdBQVEsSUFBSSxDQUFDLE1BQU0sQ0FBQztRQUMvQixNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBRTVDLHlEQUF5RDtRQUN6RCxLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztZQUN6QyxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDcEIsc0VBQXNFO1lBQ3RFLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxPQUFPLEVBQUUsR0FBRyxDQUFDLEVBQUUsQ0FBQztnQkFDdEMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLGdCQUFnQixFQUFFLENBQUM7WUFDcEMsQ0FBQztZQUNELE9BQU8sR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDekIsQ0FBQztRQUVELDZDQUE2QztRQUM3QyxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQztRQUN0QyxlQUFlLENBQUMsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLENBQUMsQ0FBQztRQUV6Qyx5QkFBeUI7UUFDekIsTUFBTSxJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7UUFFeEIsTUFBTSxDQUFDLElBQUksQ0FBQywrQkFBK0IsRUFBRTtZQUMzQyxJQUFJO1lBQ0osYUFBYTtZQUNiLFFBQVEsRUFBRSxLQUFLO1NBQ2hCLENBQUMsQ0FBQztRQUVILE9BQU87WUFDTCxPQUFPLEVBQUUsSUFBSTtZQUNiLE9BQU8sRUFBRSxZQUFZLElBQUksd0JBQXdCO1lBQ2pELGFBQWE7WUFDYixRQUFRLEVBQUUsS0FBSztTQUNoQixDQUFDO0lBQ0osQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7T0FZRztJQUNJLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFhO1FBQzFDLElBQUksT0FBTyxRQUFRLEtBQUssUUFBUSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDOUMsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO1FBRUQscUVBQXFFO1FBQ3JFLE1BQU0sZUFBZSxHQUFHLDBCQUEwQixDQUFDO1FBQ25ELE9BQU8sZUFBZSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUN4QyxDQUFDO0lBRUQ7O09BRUc7SUFDSyxLQUFLLENBQUMsVUFBVTtRQUN0QixJQUFJLENBQUMsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ2pCLE1BQU0sSUFBSSxLQUFLLENBQUMsMEJBQTBCLENBQUMsQ0FBQztRQUM5QyxDQUFDO1FBRUQsSUFBSSxDQUFDO1lBQ0gsbUNBQW1DO1lBQ25DLElBQUksTUFBTSxJQUFJLENBQUMsWUFBWSxFQUFFLEVBQUUsQ0FBQztnQkFDOUIsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLE