@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.
140 lines • 20.5 kB
JavaScript
/**
* Install AI customization elements from collection
* Supports all element types: personas, skills, templates, agents, memories, ensembles
*/
import * as fs from 'fs/promises';
import * as path from 'path';
import { validatePath, validateFilename, validateContentSize } from '../security/InputValidator.js';
import { SECURITY_LIMITS } from '../security/constants.js';
import { ContentValidator } from '../security/contentValidator.js';
import { SecureYamlParser } from '../security/secureYamlParser.js';
import { SecurityError } from '../errors/SecurityError.js';
import { PortfolioManager, ElementType } from '../portfolio/PortfolioManager.js';
export class ElementInstaller {
githubClient;
portfolioManager;
baseUrl = 'https://api.github.com/repos/DollhouseMCP/collection/contents';
constructor(githubClient) {
this.githubClient = githubClient;
this.portfolioManager = PortfolioManager.getInstance();
}
/**
* Install AI customization element from the collection
* Automatically detects element type from path structure
*/
async installContent(inputPath) {
// Validate and sanitize the input path
const sanitizedPath = validatePath(inputPath);
// Detect element type from path structure
// Expected format: library/[element-type]/[category]/[element].md
const pathParts = sanitizedPath.split('/');
if (pathParts.length < 3 || pathParts[0] !== 'library') {
throw new Error('Invalid collection path format. Expected: library/[element-type]/[category]/[element].md');
}
const elementTypeStr = pathParts[1];
const elementType = this.getElementTypeFromString(elementTypeStr);
// Ensure the path ends with .md
if (!sanitizedPath.endsWith('.md')) {
throw new Error('Invalid file type. Only .md files are allowed.');
}
const url = `${this.baseUrl}/${sanitizedPath}`;
const data = await this.githubClient.fetchFromGitHub(url);
if (data.type !== 'file') {
throw new Error('Path does not point to a file');
}
// Check file size before downloading
if (data.size > SECURITY_LIMITS.MAX_PERSONA_SIZE_BYTES) {
throw new Error(`File too large (${data.size} bytes, max ${SECURITY_LIMITS.MAX_PERSONA_SIZE_BYTES} bytes)`);
}
// Decode Base64 content
const content = Buffer.from(data.content, 'base64').toString('utf-8');
// Validate content size after decoding
validateContentSize(content, SECURITY_LIMITS.MAX_PERSONA_SIZE_BYTES);
// Sanitize content for security threats
const sanitizedContent = ContentValidator.sanitizePersonaContent(content);
// Use secure YAML parser
let parsed;
try {
parsed = SecureYamlParser.safeMatter(sanitizedContent);
}
catch (error) {
if (error instanceof SecurityError) {
throw new Error(`Security threat in content: ${error.message}`);
}
throw error;
}
const metadata = parsed.data;
// Additional metadata validation for injection attacks
const metadataValidation = ContentValidator.validateMetadata(metadata);
if (!metadataValidation.isValid) {
throw new Error(`Security validation failed: ${metadataValidation.detectedPatterns?.join(', ')}`);
}
// Validate metadata
if (!metadata.name || !metadata.description) {
throw new Error('Invalid content: missing required name or description');
}
// Generate and validate local filename
const originalFilename = sanitizedPath.split('/').pop() || 'downloaded-element.md';
const filename = validateFilename(originalFilename);
// Get appropriate directory for element type
const elementDir = this.portfolioManager.getElementDir(elementType);
const localPath = path.join(elementDir, filename);
// Check if file already exists
try {
await fs.access(localPath);
return {
success: false,
message: `AI customization element already exists: ${filename}\n\nThe element has already been installed.`
};
}
catch {
// File doesn't exist, proceed with installation
}
// Write the sanitized file
await fs.writeFile(localPath, sanitizedContent, 'utf-8');
return {
success: true,
message: `AI customization element installed successfully!`,
metadata,
filename,
elementType
};
}
/**
* Get ElementType from string
*/
getElementTypeFromString(typeStr) {
const typeMap = {
'personas': ElementType.PERSONA,
'skills': ElementType.SKILL,
'templates': ElementType.TEMPLATE,
'agents': ElementType.AGENT
};
const elementType = typeMap[typeStr];
if (!elementType) {
throw new Error(`Unknown element type: ${typeStr}. Valid types: ${Object.keys(typeMap).join(', ')}`);
}
return elementType;
}
/**
* Format installation success message
*/
formatInstallSuccess(metadata, filename, elementType) {
const typeEmojis = {
[ElementType.PERSONA]: '🎭',
[ElementType.SKILL]: '🎯',
[ElementType.TEMPLATE]: '📄',
[ElementType.AGENT]: '🤖',
[ElementType.MEMORY]: '🧠',
[ElementType.ENSEMBLE]: '🎼'
};
const emoji = typeEmojis[elementType] || '📦';
const typeName = elementType.charAt(0).toUpperCase() + elementType.slice(1);
return `✅ **AI Customization Element Installed Successfully!**\n\n` +
`${emoji} **${metadata.name}** ${metadata.author ? `by ${metadata.author}` : ''}\n` +
`📁 Type: ${typeName}\n` +
`📄 Saved as: ${filename}\n\n` +
`🚀 **Ready to use!**`;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRWxlbWVudEluc3RhbGxlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb2xsZWN0aW9uL0VsZW1lbnRJbnN0YWxsZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztHQUdHO0FBRUgsT0FBTyxLQUFLLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDbEMsT0FBTyxLQUFLLElBQUksTUFBTSxNQUFNLENBQUM7QUFHN0IsT0FBTyxFQUFFLFlBQVksRUFBRSxnQkFBZ0IsRUFBRSxtQkFBbUIsRUFBRSxNQUFNLCtCQUErQixDQUFDO0FBQ3BHLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUMzRCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNuRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNuRSxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDM0QsT0FBTyxFQUFFLGdCQUFnQixFQUFFLFdBQVcsRUFBRSxNQUFNLGtDQUFrQyxDQUFDO0FBRWpGLE1BQU0sT0FBTyxnQkFBZ0I7SUFDbkIsWUFBWSxDQUFlO0lBQzNCLGdCQUFnQixDQUFtQjtJQUNuQyxPQUFPLEdBQUcsK0RBQStELENBQUM7SUFFbEYsWUFBWSxZQUEwQjtRQUNwQyxJQUFJLENBQUMsWUFBWSxHQUFHLFlBQVksQ0FBQztRQUNqQyxJQUFJLENBQUMsZ0JBQWdCLEdBQUcsZ0JBQWdCLENBQUMsV0FBVyxFQUFFLENBQUM7SUFDekQsQ0FBQztJQUVEOzs7T0FHRztJQUNILEtBQUssQ0FBQyxjQUFjLENBQUMsU0FBaUI7UUFPcEMsdUNBQXVDO1FBQ3ZDLE1BQU0sYUFBYSxHQUFHLFlBQVksQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUU5QywwQ0FBMEM7UUFDMUMsa0VBQWtFO1FBQ2xFLE1BQU0sU0FBUyxHQUFHLGFBQWEsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFD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QyxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ3ZHLENBQUM7UUFFRCxPQUFPLFdBQVcsQ0FBQztJQUNyQixDQUFDO0lBRUQ7O09BRUc7SUFDSCxvQkFBb0IsQ0FBQyxRQUEwQixFQUFFLFFBQWdCLEVBQUUsV0FBd0I7UUFDekYsTUFBTSxVQUFVLEdBQWdDO1lBQzlDLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQyxFQUFFLElBQUk7WUFDM0IsQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLEVBQUUsSUFBSTtZQUN6QixDQUFDLFdBQVcsQ0FBQyxRQUFRLENBQUMsRUFBRSxJQUFJO1lBQzVCLENBQUMsV0FBVyxDQUFDLEtBQUssQ0FBQyxFQUFFLElBQUk7WUFDekIsQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDLEVBQUUsSUFBSTtZQUMxQixDQUFDLFdBQVcsQ0FBQyxRQUFRLENBQUMsRUFBRSxJQUFJO1NBQzdCLENBQUM7UUFFRixNQUFNLEtBQUssR0FBRyxVQUFVLENBQUMsV0FBVyxDQUFDLElBQUksSUFBSSxDQUFDO1FBQzlDLE1BQU0sUUFBUSxHQUFHLFdBQVcsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUMsV0FBVyxFQUFFLEdBQUcsV0FBVyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUU1RSxPQUFPLDREQUE0RDtZQUNqRSxHQUFHLEtBQUssTUFBTSxRQUFRLENBQUMsSUFBSSxNQUFNLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDLE1BQU0sUUFBUSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLElBQUk7WUFDbkYsWUFBWSxRQUFRLElBQUk7WUFDeEIsZ0JBQWdCLFFBQVEsTUFBTTtZQUM5QixzQkFBc0IsQ0FBQztJQUMzQixDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEluc3RhbGwgQUkgY3VzdG9taXphdGlvbiBlbGVtZW50cyBmcm9tIGNvbGxlY3Rpb25cbiAqIFN1cHBvcnRzIGFsbCBlbGVtZW50IHR5cGVzOiBwZXJzb25hcywgc2tpbGxzLCB0ZW1wbGF0ZXMsIGFnZW50cywgbWVtb3JpZXMsIGVuc2VtYmxlc1xuICovXG5cbmltcG9ydCAqIGFzIGZzIGZyb20gJ2ZzL3Byb21pc2VzJztcbmltcG9ydCAqIGFzIHBhdGggZnJvbSAncGF0aCc7XG5pbXBvcnQgeyBHaXRIdWJDbGllbnQgfSBmcm9tICcuL0dpdEh1YkNsaWVudC5qcyc7XG5pbXBvcnQgeyBJRWxlbWVudE1ldGFkYXRhIH0gZnJvbSAnLi4vdHlwZXMvZWxlbWVudHMvSUVsZW1lbnQuanMnO1xuaW1wb3J0IHsgdmFsaWRhdGVQYXRoLCB2YWxpZGF0ZUZpbGVuYW1lLCB2YWxpZGF0ZUNvbnRlbnRTaXplIH0gZnJvbSAnLi4vc2VjdXJpdHkvSW5wdXRWYWxpZGF0b3IuanMnO1xuaW1wb3J0IHsgU0VDVVJJVFlfTElNSVRTIH0gZnJvbSAnLi4vc2VjdXJpdHkvY29uc3RhbnRzLmpzJztcbmltcG9ydCB7IENvbnRlbnRWYWxpZGF0b3IgfSBmcm9tICcuLi9zZWN1cml0eS9jb250ZW50VmFsaWRhdG9yLmpzJztcbmltcG9ydCB7IFNlY3VyZVlhbWxQYXJzZXIgfSBmcm9tICcuLi9zZWN1cml0eS9zZWN1cmVZYW1sUGFyc2VyLmpzJztcbmltcG9ydCB7IFNlY3VyaXR5RXJyb3IgfSBmcm9tICcuLi9lcnJvcnMvU2VjdXJpdHlFcnJvci5qcyc7XG5pbXBvcnQgeyBQb3J0Zm9saW9NYW5hZ2VyLCBFbGVtZW50VHlwZSB9IGZyb20gJy4uL3BvcnRmb2xpby9Qb3J0Zm9saW9NYW5hZ2VyLmpzJztcblxuZXhwb3J0IGNsYXNzIEVsZW1lbnRJbnN0YWxsZXIge1xuICBwcml2YXRlIGdpdGh1YkNsaWVudDogR2l0SHViQ2xpZW50O1xuICBwcml2YXRlIHBvcnRmb2xpb01hbmFnZXI6IFBvcnRmb2xpb01hbmFnZXI7XG4gIHByaXZhdGUgYmFzZVVybCA9ICdodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL0RvbGxob3VzZU1DUC9jb2xsZWN0aW9uL2NvbnRlbnRzJztcbiAgXG4gIGNvbnN0cnVjdG9yKGdpdGh1YkNsaWVudDogR2l0SHViQ2xpZW50KSB7XG4gICAgdGhpcy5naXRodWJDbGllbnQgPSBnaXRodWJDbGllbnQ7XG4gICAgdGhpcy5wb3J0Zm9saW9NYW5hZ2VyID0gUG9ydGZvbGlvTWFuYWdlci5nZXRJbnN0YW5jZSgpO1xuICB9XG4gIFxuICAvKipcbiAgICogSW5zdGFsbCBBSSBjdXN0b21pemF0aW9uIGVsZW1lbnQgZnJvbSB0aGUgY29sbGVjdGlvblxuICAgKiBBdXRvbWF0aWNhbGx5IGRldGVjdHMgZWxlbWVudCB0eXBlIGZyb20gcGF0aCBzdHJ1Y3R1cmVcbiAgICovXG4gIGFzeW5jIGluc3RhbGxDb250ZW50KGlucHV0UGF0aDogc3RyaW5nKTogUHJvbWlzZTx7IFxuICAgIHN1Y2Nlc3M6IGJvb2xlYW47IFxuICAgIG1lc3NhZ2U6IHN0cmluZztcbiAgICBtZXRhZGF0YT86IElFbGVtZW50TWV0YWRhdGE7XG4gICAgZWxlbWVudFR5cGU/OiBFbGVtZW50VHlwZTtcbiAgICBmaWxlbmFtZT86IHN0cmluZztcbiAgfT4ge1xuICAgIC8vIFZhbGlkYXRlIGFuZCBzYW5pdGl6ZSB0aGUgaW5wdXQgcGF0aFxuICAgIGNvbnN0IHNhbml0aXplZFBhdGggPSB2YWxpZGF0ZVBhdGgoaW5wdXRQYXRoKTtcbiAgICBcbiAgICAvLyBEZXRlY3QgZWxlbWVudCB0eXBlIGZyb20gcGF0aCBzdHJ1Y3R1cmVcbiAgICAvLyBFeHBlY3RlZCBmb3JtYXQ6IGxpYnJhcnkvW2VsZW1lbnQtdHlwZV0vW2NhdGVnb3J5XS9bZWxlbWVudF0ubWRcbiAgICBjb25zdCBwYXRoUGFydHMgPSBzYW5pdGl6ZWRQYXRoLnNwbGl0KCcvJyk7XG4gICAgaWYgKHBhdGhQYXJ0cy5sZW5ndGggPCAzIHx8IHBhdGhQYXJ0c1swXSAhPT0gJ2xpYnJhcnknKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0ludmFsaWQgY29sbGVjdGlvbiBwYXRoIGZvcm1hdC4gRXhwZWN0ZWQ6IGxpYnJhcnkvW2VsZW1lbnQtdHlwZV0vW2NhdGVnb3J5XS9bZWxlbWVudF0ubWQnKTtcbiAgICB9XG4gICAgXG4gICAgY29uc3QgZWxlbWVudFR5cGVTdHIgPSBwYXRoUGFydHNbMV07XG4gICAgY29uc3QgZWxlbWVudFR5cGUgPSB0aGlzLmdldEVsZW1lbnRUeXBlRnJvbVN0cmluZyhlbGVtZW50VHlwZVN0cik7XG4gICAgXG4gICAgLy8gRW5zdXJlIHRoZSBwYXRoIGVuZHMgd2l0aCAubWRcbiAgICBpZiAoIXNhbml0aXplZFBhdGguZW5kc1dpdGgoJy5tZCcpKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0ludmFsaWQgZmlsZSB0eXBlLiBPbmx5IC5tZCBmaWxlcyBhcmUgYWxsb3dlZC4nKTtcbiAgICB9XG4gICAgXG4gICAgY29uc3QgdXJsID0gYCR7dGhpcy5iYXNlVXJsfS8ke3Nhbml0aXplZFBhdGh9YDtcbiAgICBjb25zdCBkYXRhID0gYXdhaXQgdGhpcy5naXRodWJDbGllbnQuZmV0Y2hGcm9tR2l0SHViKHVybCk7XG4gICAgXG4gICAgaWYgKGRhdGEudHlwZSAhPT0gJ2ZpbGUnKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1BhdGggZG9lcyBub3QgcG9pbnQgdG8gYSBmaWxlJyk7XG4gICAgfVxuICAgIFxuICAgIC8vIENoZWNrIGZpbGUgc2l6ZSBiZWZvcmUgZG93bmxvYWRpbmdcbiAgICBpZiAoZGF0YS5zaXplID4gU0VDVVJJVFlfTElNSVRTLk1BWF9QRVJTT05BX1NJWkVfQllURVMpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgRmlsZSB0b28gbGFyZ2UgKCR7ZGF0YS5zaXplfSBieXRlcywgbWF4ICR7U0VDVVJJVFlfTElNSVRTLk1BWF9QRVJTT05BX1NJWkVfQllURVN9IGJ5dGVzKWApO1xuICAgIH1cbiAgICBcbiAgICAvLyBEZWNvZGUgQmFzZTY0IGNvbnRlbnRcbiAgICBjb25zdCBjb250ZW50ID0gQnVmZmVyLmZyb20oZGF0YS5jb250ZW50LCAnYmFzZTY0JykudG9TdHJpbmcoJ3V0Zi04Jyk7XG4gICAgXG4gICAgLy8gVmFsaWRhdGUgY29udGVudCBzaXplIGFmdGVyIGRlY29kaW5nXG4gICAgdmFsaWRhdGVDb250ZW50U2l6ZShjb250ZW50LCBTRUNVUklUWV9MSU1JVFMuTUFYX1BFUlNPTkFfU0laRV9CWVRFUyk7XG4gICAgXG4gICAgLy8gU2FuaXRpemUgY29udGVudCBmb3Igc2VjdXJpdHkgdGhyZWF0c1xuICAgIGNvbnN0IHNhbml0aXplZENvbnRlbnQgPSBDb250ZW50VmFsaWRhdG9yLnNhbml0aXplUGVyc29uYUNvbnRlbnQoY29udGVudCk7XG4gICAgXG4gICAgLy8gVXNlIHNlY3VyZSBZQU1MIHBhcnNlclxuICAgIGxldCBwYXJzZWQ7XG4gICAgdHJ5IHtcbiAgICAgIHBhcnNlZCA9IFNlY3VyZVlhbWxQYXJzZXIuc2FmZU1hdHRlcihzYW5pdGl6ZWRDb250ZW50KTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgaWYgKGVycm9yIGluc3RhbmNlb2YgU2VjdXJpdHlFcnJvcikge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYFNlY3VyaXR5IHRocmVhdCBpbiBjb250ZW50OiAke2Vycm9yLm1lc3NhZ2V9YCk7XG4gICAgICB9XG4gICAgICB0aHJvdyBlcnJvcjtcbiAgICB9XG4gICAgXG4gICAgY29uc3QgbWV0YWRhdGEgPSBwYXJzZWQuZGF0YSBhcyBJRWxlbWVudE1ldGFkYXRhO1xuICAgIFxuICAgIC8vIEFkZGl0aW9uYWwgbWV0YWRhdGEgdmFsaWRhdGlvbiBmb3IgaW5qZWN0aW9uIGF0dGFja3NcbiAgICBjb25zdCBtZXRhZGF0YVZhbGlkYXRpb24gPSBDb250ZW50VmFsaWRhdG9yLnZhbGlkYXRlTWV0YWRhdGEobWV0YWRhdGEpO1xuICAgIGlmICghbWV0YWRhdGFWYWxpZGF0aW9uLmlzVmFsaWQpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgU2VjdXJpdHkgdmFsaWRhdGlvbiBmYWlsZWQ6ICR7bWV0YWRhdGFWYWxpZGF0aW9uLmRldGVjdGVkUGF0dGVybnM/LmpvaW4oJywgJyl9YCk7XG4gICAgfVxuICAgIFxuICAgIC8vIFZhbGlkYXRlIG1ldGFkYXRhXG4gICAgaWYgKCFtZXRhZGF0YS5uYW1lIHx8ICFtZXRhZGF0YS5kZXNjcmlwdGlvbikge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdJbnZhbGlkIGNvbnRlbnQ6IG1pc3NpbmcgcmVxdWlyZWQgbmFtZSBvciBkZXNjcmlwdGlvbicpO1xuICAgIH1cbiAgICBcbiAgICAvLyBHZW5lcmF0ZSBhbmQgdmFsaWRhdGUgbG9jYWwgZmlsZW5hbWVcbiAgICBjb25zdCBvcmlnaW5hbEZpbGVuYW1lID0gc2FuaXRpemVkUGF0aC5zcGxpdCgnLycpLnBvcCgpIHx8ICdkb3dubG9hZGVkLWVsZW1lbnQubWQnO1xuICAgIGNvbnN0IGZpbGVuYW1lID0gdmFsaWRhdGVGaWxlbmFtZShvcmlnaW5hbEZpbGVuYW1lKTtcbiAgICBcbiAgICAvLyBHZXQgYXBwcm9wcmlhdGUgZGlyZWN0b3J5IGZvciBlbGVtZW50IHR5cGVcbiAgICBjb25zdCBlbGVtZW50RGlyID0gdGhpcy5wb3J0Zm9saW9NYW5hZ2VyLmdldEVsZW1lbnREaXIoZWxlbWVudFR5cGUpO1xuICAgIGNvbnN0IGxvY2FsUGF0aCA9IHBhdGguam9pbihlbGVtZW50RGlyLCBmaWxlbmFtZSk7XG4gICAgXG4gICAgLy8gQ2hlY2sgaWYgZmlsZSBhbHJlYWR5IGV4aXN0c1xuICAgIHRyeSB7XG4gICAgICBhd2FpdCBmcy5hY2Nlc3MobG9jYWxQYXRoKTtcbiAgICAgIHJldHVybiB7XG4gICAgICAgIHN1Y2Nlc3M6IGZhbHNlLFxuICAgICAgICBtZXNzYWdlOiBgQUkgY3VzdG9taXphdGlvbiBlbGVtZW50IGFscmVhZHkgZXhpc3RzOiAke2ZpbGVuYW1lfVxcblxcblRoZSBlbGVtZW50IGhhcyBhbHJlYWR5IGJlZW4gaW5zdGFsbGVkLmBcbiAgICAgIH07XG4gICAgfSBjYXRjaCB7XG4gICAgICAvLyBGaWxlIGRvZXNuJ3QgZXhpc3QsIHByb2NlZWQgd2l0aCBpbnN0YWxsYXRpb25cbiAgICB9XG4gICAgXG4gICAgLy8gV3JpdGUgdGhlIHNhbml0aXplZCBmaWxlXG4gICAgYXdhaXQgZnMud3JpdGVGaWxlKGxvY2FsUGF0aCwgc2FuaXRpemVkQ29udGVudCwgJ3V0Zi04Jyk7XG4gICAgXG4gICAgcmV0dXJuIHtcbiAgICAgIHN1Y2Nlc3M6IHRydWUsXG4gICAgICBtZXNzYWdlOiBgQUkgY3VzdG9taXphdGlvbiBlbGVtZW50IGluc3RhbGxlZCBzdWNjZXNzZnVsbHkhYCxcbiAgICAgIG1ldGFkYXRhLFxuICAgICAgZmlsZW5hbWUsXG4gICAgICBlbGVtZW50VHlwZVxuICAgIH07XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBHZXQgRWxlbWVudFR5cGUgZnJvbSBzdHJpbmdcbiAgICovXG4gIHByaXZhdGUgZ2V0RWxlbWVudFR5cGVGcm9tU3RyaW5nKHR5cGVTdHI6IHN0cmluZyk6IEVsZW1lbnRUeXBlIHtcbiAgICBjb25zdCB0eXBlTWFwOiBSZWNvcmQ8c3RyaW5nLCBFbGVtZW50VHlwZT4gPSB7XG4gICAgICAncGVyc29uYXMnOiBFbGVtZW50VHlwZS5QRVJTT05BLFxuICAgICAgJ3NraWxscyc6IEVsZW1lbnRUeXBlLlNLSUxMLFxuICAgICAgJ3RlbXBsYXRlcyc6IEVsZW1lbnRUeXBlLlRFTVBMQVRFLFxuICAgICAgJ2FnZW50cyc6IEVsZW1lbnRUeXBlLkFHRU5UXG4gICAgfTtcbiAgICBcbiAgICBjb25zdCBlbGVtZW50VHlwZSA9IHR5cGVNYXBbdHlwZVN0cl07XG4gICAgaWYgKCFlbGVtZW50VHlwZSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBVbmtub3duIGVsZW1lbnQgdHlwZTogJHt0eXBlU3RyfS4gVmFsaWQgdHlwZXM6ICR7T2JqZWN0LmtleXModHlwZU1hcCkuam9pbignLCAnKX1gKTtcbiAgICB9XG4gICAgXG4gICAgcmV0dXJuIGVsZW1lbnRUeXBlO1xuICB9XG4gIFxuICAvKipcbiAgICogRm9ybWF0IGluc3RhbGxhdGlvbiBzdWNjZXNzIG1lc3NhZ2VcbiAgICovXG4gIGZvcm1hdEluc3RhbGxTdWNjZXNzKG1ldGFkYXRhOiBJRWxlbWVudE1ldGFkYXRhLCBmaWxlbmFtZTogc3RyaW5nLCBlbGVtZW50VHlwZTogRWxlbWVudFR5cGUpOiBzdHJpbmcge1xuICAgIGNvbnN0IHR5cGVFbW9qaXM6IFJlY29yZDxFbGVtZW50VHlwZSwgc3RyaW5nPiA9IHtcbiAgICAgIFtFbGVtZW50VHlwZS5QRVJTT05BXTogJ/Cfjq0nLFxuICAgICAgW0VsZW1lbnRUeXBlLlNLSUxMXTogJ/Cfjq8nLFxuICAgICAgW0VsZW1lbnRUeXBlLlRFTVBMQVRFXTogJ/Cfk4QnLFxuICAgICAgW0VsZW1lbnRUeXBlLkFHRU5UXTogJ/CfpJYnLFxuICAgICAgW0VsZW1lbnRUeXBlLk1FTU9SWV06ICfwn6egJyxcbiAgICAgIFtFbGVtZW50VHlwZS5FTlNFTUJMRV06ICfwn468J1xuICAgIH07XG4gICAgXG4gICAgY29uc3QgZW1vamkgPSB0eXBlRW1vamlzW2VsZW1lbnRUeXBlXSB8fCAn8J+Tpic7XG4gICAgY29uc3QgdHlwZU5hbWUgPSBlbGVtZW50VHlwZS5jaGFyQXQoMCkudG9VcHBlckNhc2UoKSArIGVsZW1lbnRUeXBlLnNsaWNlKDEpO1xuICAgIFxuICAgIHJldHVybiBg4pyFICoqQUkgQ3VzdG9taXphdGlvbiBFbGVtZW50IEluc3RhbGxlZCBTdWNjZXNzZnVsbHkhKipcXG5cXG5gICtcbiAgICAgIGAke2Vtb2ppfSAqKiR7bWV0YWRhdGEubmFtZX0qKiAke21ldGFkYXRhLmF1dGhvciA/IGBieSAke21ldGFkYXRhLmF1dGhvcn1gIDogJyd9XFxuYCArXG4gICAgICBg8J+TgSBUeXBlOiAke3R5cGVOYW1lfVxcbmAgK1xuICAgICAgYPCfk4QgU2F2ZWQgYXM6ICR7ZmlsZW5hbWV9XFxuXFxuYCArXG4gICAgICBg8J+agCAqKlJlYWR5IHRvIHVzZSEqKmA7XG4gIH1cbn0iXX0=