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.

193 lines 27.1 kB
/** * Submit personas to the collection * Handles both authenticated and anonymous submission workflows * * Security Features: * - Rate limiting to prevent spam (5 submissions per hour per session) * - URL length validation for GitHub limits * - No email submission pathway (GitHub account required) */ import { RateLimiter } from '../utils/RateLimiter.js'; import { SecurityMonitor } from '../security/securityMonitor.js'; // Configuration constants const GITHUB_URL_LIMIT = 8192; // GitHub's URL length limit (~8KB) const COLLECTION_REPO_OWNER = 'DollhouseMCP'; const COLLECTION_REPO_NAME = 'collection'; // Common response components const RESPONSE_COMPONENTS = { SUBMISSION_ICON: '📤', PERSONA_ICON: '🎭', TIP_ICON: '⭐', PRO_TIP_ICON: '💡' }; export class PersonaSubmitter { rateLimiter; constructor() { // Initialize rate limiter: 5 submissions per hour this.rateLimiter = new RateLimiter({ maxRequests: 5, windowMs: 60 * 60 * 1000, // 1 hour minDelayMs: 10000 // Minimum 10 seconds between submissions }); } /** * Generate GitHub issue for persona submission * Includes URL length validation to comply with GitHub's ~8KB limit */ generateSubmissionIssue(persona) { // Check rate limit const rateLimitStatus = this.rateLimiter.checkLimit(); if (!rateLimitStatus.allowed) { // Log potential abuse attempt SecurityMonitor.logSecurityEvent({ type: 'RATE_LIMIT_EXCEEDED', severity: 'MEDIUM', source: 'PersonaSubmitter.generateSubmissionIssue', details: `Submission rate limit exceeded. Retry after ${rateLimitStatus.retryAfterMs}ms` }); throw new Error(`Submission rate limit exceeded. Please wait ${Math.ceil(rateLimitStatus.retryAfterMs / 1000)} seconds before submitting again. ` + `This limit helps prevent spam and ensures quality submissions.`); } const issueTitle = `New Persona Submission: ${persona.metadata.name}`; let issueBody = this.buildIssueBody(persona); // Check URL length and truncate if necessary let githubIssueUrl = this.buildGitHubIssueUrl(issueTitle, issueBody); // If URL exceeds GitHub's limit, truncate the content if (githubIssueUrl.length >= GITHUB_URL_LIMIT) { issueBody = this.buildTruncatedIssueBody(persona); githubIssueUrl = this.buildGitHubIssueUrl(issueTitle, issueBody); } return { issueTitle, issueBody, githubIssueUrl, rateLimitStatus }; } /** * Format submission response for authenticated users */ formatSubmissionResponse(persona, githubIssueUrl, personaIndicator = '') { const header = this.buildResponseHeader('Persona Submission Prepared', persona.metadata.name, 'is ready for collection submission!', personaIndicator); const steps = this.buildStandardSubmissionSteps(githubIssueUrl); const tip = `${RESPONSE_COMPONENTS.TIP_ICON} **Tip:** You can also submit via pull request if you're familiar with Git!`; return `${header}\n\n${steps}\n\n${tip}`; } /** * Format anonymous submission response for unauthenticated users */ formatAnonymousSubmissionResponse(persona, githubIssueUrl, personaIndicator = '') { const header = this.buildResponseHeader('Anonymous Submission Path Available', persona.metadata.name, 'can be submitted without GitHub authentication!', personaIndicator); const process = this.buildAnonymousSubmissionProcess(githubIssueUrl); const nextSteps = this.buildAnonymousNextSteps(); const proTip = `${RESPONSE_COMPONENTS.PRO_TIP_ICON} **Pro tip:** Creating a free GitHub account unlocks additional features, but it's completely optional for submissions!`; return `${header}\n\n${process}\n\n${nextSteps}\n\n${proTip}`; } // Private helper methods for building response components /** * Build the full issue body with all persona details */ buildIssueBody(persona) { return `## Persona Submission\n\n` + `**Name:** ${persona.metadata.name}\n` + `**Author:** ${persona.metadata.author || 'Unknown'}\n` + `**Category:** ${persona.metadata.category || 'General'}\n` + `**Description:** ${persona.metadata.description}\n\n` + `### Persona Content:\n` + `\`\`\`markdown\n` + `---\n` + `${this.serializeMetadata(persona.metadata)}\n` + `---\n\n` + `${persona.content}\n` + `\`\`\`\n\n` + `### Submission Details:\n` + `- Submitted via DollhouseMCP client\n` + `- Filename: ${persona.filename}\n` + `- Unique ID: ${persona.unique_id}\n\n` + `---\n` + `*Please review this persona for inclusion in the collection.*`; } /** * Build a truncated issue body to fit within URL limits */ buildTruncatedIssueBody(persona) { const truncatedContent = persona.content.length > 500 ? `${persona.content.substring(0, 500)}...\n\n[Content truncated due to length]` : persona.content; return `## Persona Submission\n\n` + `**Name:** ${persona.metadata.name}\n` + `**Author:** ${persona.metadata.author || 'Unknown'}\n` + `**Category:** ${persona.metadata.category || 'General'}\n` + `**Description:** ${persona.metadata.description}\n\n` + `### Persona Content (Truncated):\n` + `\`\`\`markdown\n` + `---\n` + `${this.serializeMetadata(persona.metadata)}\n` + `---\n\n` + `${truncatedContent}\n` + `\`\`\`\n\n` + `### Submission Details:\n` + `- Submitted via DollhouseMCP client\n` + `- Filename: ${persona.filename}\n` + `- Unique ID: ${persona.unique_id}\n\n` + `---\n` + `*Please review this persona for inclusion in the collection.*`; } /** * Serialize persona metadata to YAML format */ serializeMetadata(metadata) { return Object.entries(metadata) .map(([key, value]) => `${key}: ${JSON.stringify(value)}`) .join('\n'); } /** * Build the GitHub issue URL */ buildGitHubIssueUrl(title, body) { return `https://github.com/${COLLECTION_REPO_OWNER}/${COLLECTION_REPO_NAME}/issues/new?title=${encodeURIComponent(title)}&body=${encodeURIComponent(body)}`; } /** * Build common response header used by both authenticated and anonymous responses */ buildResponseHeader(title, personaName, subtitle, personaIndicator) { return `${personaIndicator}${RESPONSE_COMPONENTS.SUBMISSION_ICON} **${title}**\n\n` + `${RESPONSE_COMPONENTS.PERSONA_ICON} **${personaName}** ${subtitle}`; } /** * Build standard submission steps for authenticated users */ buildStandardSubmissionSteps(githubIssueUrl) { return `**Next Steps:**\n` + `1. Click this link to create a GitHub issue: \n` + ` ${githubIssueUrl}\n\n` + `2. Review the pre-filled content\n` + `3. Click "Submit new issue"\n` + `4. The maintainers will review your submission`; } /** * Build anonymous submission process instructions */ buildAnonymousSubmissionProcess(githubIssueUrl) { return `**Anonymous Submission Process:**\n` + `1. Click this link to create a GitHub issue:\n` + ` ${githubIssueUrl}\n\n` + `2. **To submit your persona:**\n` + ` • You'll need a GitHub account (free to create)\n` + ` • Click "Submit new issue" to submit directly\n` + ` • The form is pre-filled with all your persona details\n\n` + `**Note:** GitHub account is required for submission to prevent spam and maintain quality.\n` + `Creating an account is free and takes less than a minute: https://github.com/signup`; } /** * Build anonymous submission next steps and expectations */ buildAnonymousNextSteps() { return `**What happens next:**\n` + `• Community maintainers review all submissions\n` + `• Anonymous submissions get the same consideration as authenticated ones\n` + `• If accepted, your persona joins the collection with attribution to "Community Contributor"\n` + `• The review typically takes 2-3 business days`; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiUGVyc29uYVN1Ym1pdHRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jb2xsZWN0aW9uL1BlcnNvbmFTdWJtaXR0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7O0dBUUc7QUFHSCxPQUFPLEVBQUUsV0FBVyxFQUFtQixNQUFNLHlCQUF5QixDQUFDO0FBQ3ZFLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUVqRSwwQkFBMEI7QUFDMUIsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLENBQUMsQ0FBQyxtQ0FBbUM7QUFDbEUsTUFBTSxxQkFBcUIsR0FBRyxjQUFjLENBQUM7QUFDN0MsTUFBTSxvQkFBb0IsR0FBRyxZQUFZLENBQUM7QUFFMUMsNkJBQTZCO0FBQzdCLE1BQU0sbUJBQW1CLEdBQUc7SUFDMUIsZUFBZSxFQUFFLElBQUk7SUFDckIsWUFBWSxFQUFFLElBQUk7SUFDbEIsUUFBUSxFQUFFLEdBQUc7SUFDYixZQUFZLEVBQUUsSUFBSTtDQUNWLENBQUM7QUFFWCxNQUFNLE9BQU8sZ0JBQWdCO0lBQ25CLFdBQVcsQ0FBYztJQUVqQztRQUNFLGtEQUFrRDtRQUNsRCxJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksV0FBVyxDQUFDO1lBQ2pDLFdBQVcsRUFBRSxDQUFDO1lBQ2QsUUFBUSxFQUFFLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxFQUFFLFNBQVM7WUFDbkMsVUFBVSxFQUFFLEtBQUssQ0FBQyx5Q0FBeUM7U0FDNUQsQ0FBQyxDQUFDO0lBQ0wsQ0FBQztJQUNEOzs7T0FHRztJQUNILHVCQUF1QixDQUFDLE9BQWdCO1FBTXRDLG1CQUFtQjtRQUNuQixNQUFNLGVBQWUsR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBRXRELElBQUksQ0FBQyxlQUFlLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDN0IsOEJBQThCO1lBQzlCLGVBQWUsQ0FBQyxnQkFBZ0Is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T0FFRztJQUNLLHVCQUF1QjtRQUM3QixPQUFPLDBCQUEwQjtZQUMvQixrREFBa0Q7WUFDbEQsNEVBQTRFO1lBQzVFLGdHQUFnRztZQUNoRyxnREFBZ0QsQ0FBQztJQUNyRCxDQUFDO0NBQ0YiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFN1Ym1pdCBwZXJzb25hcyB0byB0aGUgY29sbGVjdGlvblxuICogSGFuZGxlcyBib3RoIGF1dGhlbnRpY2F0ZWQgYW5kIGFub255bW91cyBzdWJtaXNzaW9uIHdvcmtmbG93c1xuICogXG4gKiBTZWN1cml0eSBGZWF0dXJlczpcbiAqIC0gUmF0ZSBsaW1pdGluZyB0byBwcmV2ZW50IHNwYW0gKDUgc3VibWlzc2lvbnMgcGVyIGhvdXIgcGVyIHNlc3Npb24pXG4gKiAtIFVSTCBsZW5ndGggdmFsaWRhdGlvbiBmb3IgR2l0SHViIGxpbWl0c1xuICogLSBObyBlbWFpbCBzdWJtaXNzaW9uIHBhdGh3YXkgKEdpdEh1YiBhY2NvdW50IHJlcXVpcmVkKVxuICovXG5cbmltcG9ydCB7IFBlcnNvbmEgfSBmcm9tICcuLi90eXBlcy9wZXJzb25hLmpzJztcbmltcG9ydCB7IFJhdGVMaW1pdGVyLCBSYXRlTGltaXRTdGF0dXMgfSBmcm9tICcuLi91dGlscy9SYXRlTGltaXRlci5qcyc7XG5pbXBvcnQgeyBTZWN1cml0eU1vbml0b3IgfSBmcm9tICcuLi9zZWN1cml0eS9zZWN1cml0eU1vbml0b3IuanMnO1xuXG4vLyBDb25maWd1cmF0aW9uIGNvbnN0YW50c1xuY29uc3QgR0lUSFVCX1VSTF9MSU1JVCA9IDgxOTI7IC8vIEdpdEh1YidzIFVSTCBsZW5ndGggbGltaXQgKH44S0IpXG5jb25zdCBDT0xMRUNUSU9OX1JFUE9fT1dORVIgPSAnRG9sbGhvdXNlTUNQJztcbmNvbnN0IENPTExFQ1RJT05fUkVQT19OQU1FID0gJ2NvbGxlY3Rpb24nO1xuXG4vLyBDb21tb24gcmVzcG9uc2UgY29tcG9uZW50c1xuY29uc3QgUkVTUE9OU0VfQ09NUE9ORU5UUyA9IHtcbiAgU1VCTUlTU0lPTl9JQ09OOiAn8J+TpCcsXG4gIFBFUlNPTkFfSUNPTjogJ/Cfjq0nLFxuICBUSVBfSUNPTjogJ+KtkCcsXG4gIFBST19USVBfSUNPTjogJ/CfkqEnXG59IGFzIGNvbnN0O1xuXG5leHBvcnQgY2xhc3MgUGVyc29uYVN1Ym1pdHRlciB7XG4gIHByaXZhdGUgcmF0ZUxpbWl0ZXI6IFJhdGVMaW1pdGVyO1xuICBcbiAgY29uc3RydWN0b3IoKSB7XG4gICAgLy8gSW5pdGlhbGl6ZSByYXRlIGxpbWl0ZXI6IDUgc3VibWlzc2lvbnMgcGVyIGhvdXJcbiAgICB0aGlzLnJhdGVMaW1pdGVyID0gbmV3IFJhdGVMaW1pdGVyKHtcbiAgICAgIG1heFJlcXVlc3RzOiA1LFxuICAgICAgd2luZG93TXM6IDYwICogNjAgKiAxMDAwLCAvLyAxIGhvdXJcbiAgICAgIG1pbkRlbGF5TXM6IDEwMDAwIC8vIE1pbmltdW0gMTAgc2Vjb25kcyBiZXR3ZWVuIHN1Ym1pc3Npb25zXG4gICAgfSk7XG4gIH1cbiAgLyoqXG4gICAqIEdlbmVyYXRlIEdpdEh1YiBpc3N1ZSBmb3IgcGVyc29uYSBzdWJtaXNzaW9uXG4gICAqIEluY2x1ZGVzIFVSTCBsZW5ndGggdmFsaWRhdGlvbiB0byBjb21wbHkgd2l0aCBHaXRIdWIncyB+OEtCIGxpbWl0XG4gICAqL1xuICBnZW5lcmF0ZVN1Ym1pc3Npb25Jc3N1ZShwZXJzb25hOiBQZXJzb25hKTogeyBcbiAgICBpc3N1ZVRpdGxlOiBzdHJpbmc7IFxuICAgIGlzc3VlQm9keTogc3RyaW5nOyBcbiAgICBnaXRodWJJc3N1ZVVybDogc3RyaW5nO1xuICAgIHJhdGVMaW1pdFN0YXR1cz86IFJhdGVMaW1pdFN0YXR1cztcbiAgfSB7XG4gICAgLy8gQ2hlY2sgcmF0ZSBsaW1pdFxuICAgIGNvbnN0IHJhdGVMaW1pdFN0YXR1cyA9IHRoaXMucmF0ZUxpbWl0ZXIuY2hlY2tMaW1pdCgpO1xuICAgIFxuICAgIGlmICghcmF0ZUxpbWl0U3RhdHVzLmFsbG93ZWQpIHtcbiAgICAgIC8vIExvZyBwb3RlbnRpYWwgYWJ1c2UgYXR0ZW1wdFxuICAgICAgU2VjdXJpdHlNb25pdG9yLmxvZ1NlY3VyaXR5RXZlbnQoe1xuICAgICAgICB0eXBlOiAnUkFURV9MSU1JVF9FWENFRURFRCcsXG4gICAgICAgIHNldmVyaXR5OiAnTUVESVVNJyxcbiAgICAgICAgc291cmNlOiAnUGVyc29uYVN1Ym1pdHRlci5nZW5lcmF0ZVN1Ym1pc3Npb25Jc3N1ZScsXG4gICAgICAgIGRldGFpbHM6IGBTdWJtaXNzaW9uIHJhdGUgbGltaXQgZXhjZWVkZWQuIFJldHJ5IGFmdGVyICR7cmF0ZUxpbWl0U3RhdHVzLnJldHJ5QWZ0ZXJNc31tc2BcbiAgICAgIH0pO1xuICAgICAgXG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgIGBTdWJtaXNzaW9uIHJhdGUgbGltaXQgZXhjZWVkZWQuIFBsZWFzZSB3YWl0ICR7TWF0aC5jZWlsKHJhdGVMaW1pdFN0YXR1cy5yZXRyeUFmdGVyTXMhIC8gMTAwMCl9IHNlY29uZHMgYmVmb3JlIHN1Ym1pdHRpbmcgYWdhaW4uIGAgK1xuICAgICAgICBgVGhpcyBsaW1pdCBoZWxwcyBwcmV2ZW50IHNwYW0gYW5kIGVuc3VyZXMgcXVhbGl0eSBzdWJtaXNzaW9ucy5gXG4gICAgICApO1xuICAgIH1cbiAgICBjb25zdCBpc3N1ZVRpdGxlID0gYE5ldyBQZXJzb25hIFN1Ym1pc3Npb246ICR7cGVyc29uYS5tZXRhZGF0YS5uYW1lfWA7XG4gICAgbGV0IGlzc3VlQm9keSA9IHRoaXMuYnVpbGRJc3N1ZUJvZHkocGVyc29uYSk7XG4gICAgXG4gICAgLy8gQ2hlY2sgVVJMIGxlbmd0aCBhbmQgdHJ1bmNhdGUgaWYgbmVjZXNzYXJ5XG4gICAgbGV0IGdpdGh1Yklzc3VlVXJsID0gdGhpcy5idWlsZEdpdEh1Yklzc3VlVXJsKGlzc3VlVGl0bGUsIGlzc3VlQm9keSk7XG4gICAgXG4gICAgLy8gSWYgVVJMIGV4Y2VlZHMgR2l0SHViJ3MgbGltaXQsIHRydW5jYXRlIHRoZSBjb250ZW50XG4gICAgaWYgKGdpdGh1Yklzc3VlVXJsLmxlbmd0aCA+PSBHSVRIVUJfVVJMX0xJTUlUKSB7XG4gICAgICBpc3N1ZUJvZHkgPSB0aGlzLmJ1aWxkVHJ1bmNhdGVkSXNzdWVCb2R5KHBlcnNvbmEpO1xuICAgICAgZ2l0aHViSXNzdWVVcmwgPSB0aGlzLmJ1aWxkR2l0SHViSXNzdWVVcmwoaXNzdWVUaXRsZSwgaXNzdWVCb2R5KTtcbiAgICB9XG4gICAgXG4gICAgcmV0dXJuIHtcbiAgICAgIGlzc3VlVGl0bGUsXG4gICAgICBpc3N1ZUJvZHksXG4gICAgICBnaXRodWJJc3N1ZVVybCxcbiAgICAgIHJhdGVMaW1pdFN0YXR1c1xuICAgIH07XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBGb3JtYXQgc3VibWlzc2lvbiByZXNwb25zZSBmb3IgYXV0aGVudGljYXRlZCB1c2Vyc1xuICAgKi9cbiAgZm9ybWF0U3VibWlzc2lvblJlc3BvbnNlKHBlcnNvbmE6IFBlcnNvbmEsIGdpdGh1Yklzc3VlVXJsOiBzdHJpbmcsIHBlcnNvbmFJbmRpY2F0b3I6IHN0cmluZyA9ICcnKTogc3RyaW5nIHtcbiAgICBjb25zdCBoZWFkZXIgPSB0aGlzLmJ1aWxkUmVzcG9uc2VIZWFkZXIoXG4gICAgICAnUGVyc29uYSBTdWJtaXNzaW9uIFByZXBhcmVkJyxcbiAgICAgIHBlcnNvbmEubWV0YWRhdGEubmFtZSxcbiAgICAgICdpcyByZWFkeSBmb3IgY29sbGVjdGlvbiBzdWJtaXNzaW9uIScsXG4gICAgICBwZXJzb25hSW5kaWNhdG9yXG4gICAgKTtcbiAgICBcbiAgICBjb25zdCBzdGVwcyA9IHRoaXMuYnVpbGRTdGFuZGFyZFN1Ym1pc3Npb25TdGVwcyhnaXRodWJJc3N1ZVVybCk7XG4gICAgY29uc3QgdGlwID0gYCR7UkVTUE9OU0VfQ09NUE9ORU5UUy5USVBfSUNPTn0gKipUaXA6KiogWW91IGNhbiBhbHNvIHN1Ym1pdCB2aWEgcHVsbCByZXF1ZXN0IGlmIHlvdSdyZSBmYW1pbGlhciB3aXRoIEdpdCFgO1xuICAgIFxuICAgIHJldHVybiBgJHtoZWFkZXJ9XFxuXFxuJHtzdGVwc31cXG5cXG4ke3RpcH1gO1xuICB9XG4gIFxuICAvKipcbiAgICogRm9ybWF0IGFub255bW91cyBzdWJtaXNzaW9uIHJlc3BvbnNlIGZvciB1bmF1dGhlbnRpY2F0ZWQgdXNlcnNcbiAgICovXG4gIGZvcm1hdEFub255bW91c1N1Ym1pc3Npb25SZXNwb25zZShwZXJzb25hOiBQZXJzb25hLCBnaXRodWJJc3N1ZVVybDogc3RyaW5nLCBwZXJzb25hSW5kaWNhdG9yOiBzdHJpbmcgPSAnJyk6IHN0cmluZyB7XG4gICAgY29uc3QgaGVhZGVyID0gdGhpcy5idWlsZFJlc3BvbnNlSGVhZGVyKFxuICAgICAgJ0Fub255bW91cyBTdWJtaXNzaW9uIFBhdGggQXZhaWxhYmxlJyxcbiAgICAgIHBlcnNvbmEubWV0YWRhdGEubmFtZSxcbiAgICAgICdjYW4gYmUgc3VibWl0dGVkIHdpdGhvdXQgR2l0SHViIGF1dGhlbnRpY2F0aW9uIScsXG4gICAgICBwZXJzb25hSW5kaWNhdG9yXG4gICAgKTtcbiAgICBcbiAgICBjb25zdCBwcm9jZXNzID0gdGhpcy5idWlsZEFub255bW91c1N1Ym1pc3Npb25Qcm9jZXNzKGdpdGh1Yklzc3VlVXJsKTtcbiAgICBjb25zdCBuZXh0U3RlcHMgPSB0aGlzLmJ1aWxkQW5vbnltb3VzTmV4dFN0ZXBzKCk7XG4gICAgY29uc3QgcHJvVGlwID0gYCR7UkVTUE9OU0VfQ09NUE9ORU5UUy5QUk9fVElQX0lDT059ICoqUHJvIHRpcDoqKiBDcmVhdGluZyBhIGZyZWUgR2l0SHViIGFjY291bnQgdW5sb2NrcyBhZGRpdGlvbmFsIGZlYXR1cmVzLCBidXQgaXQncyBjb21wbGV0ZWx5IG9wdGlvbmFsIGZvciBzdWJtaXNzaW9ucyFgO1xuICAgIFxuICAgIHJldHVybiBgJHtoZWFkZXJ9XFxuXFxuJHtwcm9jZXNzfVxcblxcbiR7bmV4dFN0ZXBzfVxcblxcbiR7cHJvVGlwfWA7XG4gIH1cblxuICAvLyBQcml2YXRlIGhlbHBlciBtZXRob2RzIGZvciBidWlsZGluZyByZXNwb25zZSBjb21wb25lbnRzXG5cbiAgLyoqXG4gICAqIEJ1aWxkIHRoZSBmdWxsIGlzc3VlIGJvZHkgd2l0aCBhbGwgcGVyc29uYSBkZXRhaWxzXG4gICAqL1xuICBwcml2YXRlIGJ1aWxkSXNzdWVCb2R5KHBlcnNvbmE6IFBlcnNvbmEpOiBzdHJpbmcge1xuICAgIHJldHVybiBgIyMgUGVyc29uYSBTdWJtaXNzaW9uXFxuXFxuYCArXG4gICAgICBgKipOYW1lOioqICR7cGVyc29uYS5tZXRhZGF0YS5uYW1lfVxcbmAgK1xuICAgICAgYCoqQXV0aG9yOioqICR7cGVyc29uYS5tZXRhZGF0YS5hdXRob3IgfHwgJ1Vua25vd24nfVxcbmAgK1xuICAgICAgYCoqQ2F0ZWdvcnk6KiogJHtwZXJzb25hLm1ldGFkYXRhLmNhdGVnb3J5IHx8ICdHZW5lcmFsJ31cXG5gICtcbiAgICAgIGAqKkRlc2NyaXB0aW9uOioqICR7cGVyc29uYS5tZXRhZGF0YS5kZXNjcmlwdGlvbn1cXG5cXG5gICtcbiAgICAgIGAjIyMgUGVyc29uYSBDb250ZW50OlxcbmAgK1xuICAgICAgYFxcYFxcYFxcYG1hcmtkb3duXFxuYCArXG4gICAgICBgLS0tXFxuYCArXG4gICAgICBgJHt0aGlzLnNlcmlhbGl6ZU1ldGFkYXRhKHBlcnNvbmEubWV0YWRhdGEpfVxcbmAgK1xuICAgICAgYC0tLVxcblxcbmAgK1xuICAgICAgYCR7cGVyc29uYS5jb250ZW50fVxcbmAgK1xuICAgICAgYFxcYFxcYFxcYFxcblxcbmAgK1xuICAgICAgYCMjIyBTdWJtaXNzaW9uIERldGFpbHM6XFxuYCArXG4gICAgICBgLSBTdWJtaXR0ZWQgdmlhIERvbGxob3VzZU1DUCBjbGllbnRcXG5gICtcbiAgICAgIGAtIEZpbGVuYW1lOiAke3BlcnNvbmEuZmlsZW5hbWV9XFxuYCArXG4gICAgICBgLSBVbmlxdWUgSUQ6ICR7cGVyc29uYS51bmlxdWVfaWR9XFxuXFxuYCArXG4gICAgICBgLS0tXFxuYCArXG4gICAgICBgKlBsZWFzZSByZXZpZXcgdGhpcyBwZXJzb25hIGZvciBpbmNsdXNpb24gaW4gdGhlIGNvbGxlY3Rpb24uKmA7XG4gIH1cblxuICAvKipcbiAgICogQnVpbGQgYSB0cnVuY2F0ZWQgaXNzdWUgYm9keSB0byBmaXQgd2l0aGluIFVSTCBsaW1pdHNcbiAgICovXG4gIHByaXZhdGUgYnVpbGRUcnVuY2F0ZWRJc3N1ZUJvZHkocGVyc29uYTogUGVyc29uYSk6IHN0cmluZyB7XG4gICAgY29uc3QgdHJ1bmNhdGVkQ29udGVudCA9IHBlcnNvbmEuY29udGVudC5sZW5ndGggPiA1MDAgXG4gICAgICA/IGAke3BlcnNvbmEuY29udGVudC5zdWJzdHJpbmcoMCwgNTAwKX0uLi5cXG5cXG5bQ29udGVudCB0cnVuY2F0ZWQgZHVlIHRvIGxlbmd0aF1gXG4gICAgICA6IHBlcnNvbmEuY29udGVudDtcbiAgICBcbiAgICByZXR1cm4gYCMjIFBlcnNvbmEgU3VibWlzc2lvblxcblxcbmAgK1xuICAgICAgYCoqTmFtZToqKiAke3BlcnNvbmEubWV0YWRhdGEubmFtZX1cXG5gICtcbiAgICAgIGAqKkF1dGhvcjoqKiAke3BlcnNvbmEubWV0YWRhdGEuYXV0aG9yIHx8ICdVbmtub3duJ31cXG5gICtcbiAgICAgIGAqKkNhdGVnb3J5OioqICR7cGVyc29uYS5tZXRhZGF0YS5jYXRlZ29yeSB8fCAnR2VuZXJhbCd9XFxuYCArXG4gICAgICBgKipEZXNjcmlwdGlvbjoqKiAke3BlcnNvbmEubWV0YWRhdGEuZGVzY3JpcHRpb259XFxuXFxuYCArXG4gICAgICBgIyMjIFBlcnNvbmEgQ29udGVudCAoVHJ1bmNhdGVkKTpcXG5gICtcbiAgICAgIGBcXGBcXGBcXGBtYXJrZG93blxcbmAgK1xuICAgICAgYC0tLVxcbmAgK1xuICAgICAgYCR7dGhpcy5zZXJpYWxpemVNZXRhZGF0YShwZXJzb25hLm1ldGFkYXRhKX1cXG5gICtcbiAgICAgIGAtLS1cXG5cXG5gICtcbiAgICAgIGAke3RydW5jYXRlZENvbnRlbnR9XFxuYCArXG4gICAgICBgXFxgXFxgXFxgXFxuXFxuYCArXG4gICAgICBgIyMjIFN1Ym1pc3Npb24gRGV0YWlsczpcXG5gICtcbiAgICAgIGAtIFN1Ym1pdHRlZCB2aWEgRG9sbGhvdXNlTUNQIGNsaWVudFxcbmAgK1xuICAgICAgYC0gRmlsZW5hbWU6ICR7cGVyc29uYS5maWxlbmFtZX1cXG5gICtcbiAgICAgIGAtIFVuaXF1ZSBJRDogJHtwZXJzb25hLnVuaXF1ZV9pZH1cXG5cXG5gICtcbiAgICAgIGAtLS1cXG5gICtcbiAgICAgIGAqUGxlYXNlIHJldmlldyB0aGlzIHBlcnNvbmEgZm9yIGluY2x1c2lvbiBpbiB0aGUgY29sbGVjdGlvbi4qYDtcbiAgfVxuXG4gIC8qKlxuICAgKiBTZXJpYWxpemUgcGVyc29uYSBtZXRhZGF0YSB0byBZQU1MIGZvcm1hdFxuICAgKi9cbiAgcHJpdmF0ZSBzZXJpYWxpemVNZXRhZGF0YShtZXRhZGF0YTogYW55KTogc3RyaW5nIHtcbiAgICByZXR1cm4gT2JqZWN0LmVudHJpZXMobWV0YWRhdGEpXG4gICAgICAubWFwKChba2V5LCB2YWx1ZV0pID0+IGAke2tleX06ICR7SlNPTi5zdHJpbmdpZnkodmFsdWUpfWApXG4gICAgICAuam9pbignXFxuJyk7XG4gIH1cblxuICAvKipcbiAgICogQnVpbGQgdGhlIEdpdEh1YiBpc3N1ZSBVUkxcbiAgICovXG4gIHByaXZhdGUgYnVpbGRHaXRIdWJJc3N1ZVVybCh0aXRsZTogc3RyaW5nLCBib2R5OiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIHJldHVybiBgaHR0cHM6Ly9naXRodWIuY29tLyR7Q09MTEVDVElPTl9SRVBPX09XTkVSfS8ke0NPTExFQ1RJT05fUkVQT19OQU1FfS9pc3N1ZXMvbmV3P3RpdGxlPSR7ZW5jb2RlVVJJQ29tcG9uZW50KHRpdGxlKX0mYm9keT0ke2VuY29kZVVSSUNvbXBvbmVudChib2R5KX1gO1xuICB9XG5cbiAgLyoqXG4gICAqIEJ1aWxkIGNvbW1vbiByZXNwb25zZSBoZWFkZXIgdXNlZCBieSBib3RoIGF1dGhlbnRpY2F0ZWQgYW5kIGFub255bW91cyByZXNwb25zZXNcbiAgICovXG4gIHByaXZhdGUgYnVpbGRSZXNwb25zZUhlYWRlcih0aXRsZTogc3RyaW5nLCBwZXJzb25hTmFtZTogc3RyaW5nLCBzdWJ0aXRsZTogc3RyaW5nLCBwZXJzb25hSW5kaWNhdG9yOiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIHJldHVybiBgJHtwZXJzb25hSW5kaWNhdG9yfSR7UkVTUE9OU0VfQ09NUE9ORU5UUy5TVUJNSVNTSU9OX0lDT059ICoqJHt0aXRsZX0qKlxcblxcbmAgK1xuICAgICAgYCR7UkVTUE9OU0VfQ09NUE9ORU5UUy5QRVJTT05BX0lDT059ICoqJHtwZXJzb25hTmFtZX0qKiAke3N1YnRpdGxlfWA7XG4gIH1cblxuICAvKipcbiAgICogQnVpbGQgc3RhbmRhcmQgc3VibWlzc2lvbiBzdGVwcyBmb3IgYXV0aGVudGljYXRlZCB1c2Vyc1xuICAgKi9cbiAgcHJpdmF0ZSBidWlsZFN0YW5kYXJkU3VibWlzc2lvblN0ZXBzKGdpdGh1Yklzc3VlVXJsOiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIHJldHVybiBgKipOZXh0IFN0ZXBzOioqXFxuYCArXG4gICAgICBgMS4gQ2xpY2sgdGhpcyBsaW5rIHRvIGNyZWF0ZSBhIEdpdEh1YiBpc3N1ZTogXFxuYCArXG4gICAgICBgICAgJHtnaXRodWJJc3N1ZVVybH1cXG5cXG5gICtcbiAgICAgIGAyLiBSZXZpZXcgdGhlIHByZS1maWxsZWQgY29udGVudFxcbmAgK1xuICAgICAgYDMuIENsaWNrIFwiU3VibWl0IG5ldyBpc3N1ZVwiXFxuYCArXG4gICAgICBgNC4gVGhlIG1haW50YWluZXJzIHdpbGwgcmV2aWV3IHlvdXIgc3VibWlzc2lvbmA7XG4gIH1cblxuICAvKipcbiAgICogQnVpbGQgYW5vbnltb3VzIHN1Ym1pc3Npb24gcHJvY2VzcyBpbnN0cnVjdGlvbnNcbiAgICovXG4gIHByaXZhdGUgYnVpbGRBbm9ueW1vdXNTdWJtaXNzaW9uUHJvY2VzcyhnaXRodWJJc3N1ZVVybDogc3RyaW5nKTogc3RyaW5nIHtcbiAgICByZXR1cm4gYCoqQW5vbnltb3VzIFN1Ym1pc3Npb24gUHJvY2VzczoqKlxcbmAgK1xuICAgICAgYDEuIENsaWNrIHRoaXMgbGluayB0byBjcmVhdGUgYSBHaXRIdWIgaXNzdWU6XFxuYCArXG4gICAgICBgICAgJHtnaXRodWJJc3N1ZVVybH1cXG5cXG5gICtcbiAgICAgIGAyLiAqKlRvIHN1Ym1pdCB5b3VyIHBlcnNvbmE6KipcXG5gICtcbiAgICAgIGAgICDigKIgWW91J2xsIG5lZWQgYSBHaXRIdWIgYWNjb3VudCAoZnJlZSB0byBjcmVhdGUpXFxuYCArXG4gICAgICBgICAg4oCiIENsaWNrIFwiU3VibWl0IG5ldyBpc3N1ZVwiIHRvIHN1Ym1pdCBkaXJlY3RseVxcbmAgK1xuICAgICAgYCAgIOKAoiBUaGUgZm9ybSBpcyBwcmUtZmlsbGVkIHdpdGggYWxsIHlvdXIgcGVyc29uYSBkZXRhaWxzXFxuXFxuYCArXG4gICAgICBgKipOb3RlOioqIEdpdEh1YiBhY2NvdW50IGlzIHJlcXVpcmVkIGZvciBzdWJtaXNzaW9uIHRvIHByZXZlbnQgc3BhbSBhbmQgbWFpbnRhaW4gcXVhbGl0eS5cXG5gICtcbiAgICAgIGBDcmVhdGluZyBhbiBhY2NvdW50IGlzIGZyZWUgYW5kIHRha2VzIGxlc3MgdGhhbiBhIG1pbnV0ZTogaHR0cHM6Ly9naXRodWIuY29tL3NpZ251cGA7XG4gIH1cblxuICAvKipcbiAgICogQnVpbGQgYW5vbnltb3VzIHN1Ym1pc3Npb24gbmV4dCBzdGVwcyBhbmQgZXhwZWN0YXRpb25zXG4gICAqL1xuICBwcml2YXRlIGJ1aWxkQW5vbnltb3VzTmV4dFN0ZXBzKCk6IHN0cmluZyB7XG4gICAgcmV0dXJuIGAqKldoYXQgaGFwcGVucyBuZXh0OioqXFxuYCArXG4gICAgICBg4oCiIENvbW11bml0eSBtYWludGFpbmVycyByZXZpZXcgYWxsIHN1Ym1pc3Npb25zXFxuYCArXG4gICAgICBg4oCiIEFub255bW91cyBzdWJtaXNzaW9ucyBnZXQgdGhlIHNhbWUgY29uc2lkZXJhdGlvbiBhcyBhdXRoZW50aWNhdGVkIG9uZXNcXG5gICtcbiAgICAgIGDigKIgSWYgYWNjZXB0ZWQsIHlvdXIgcGVyc29uYSBqb2lucyB0aGUgY29sbGVjdGlvbiB3aXRoIGF0dHJpYnV0aW9uIHRvIFwiQ29tbXVuaXR5IENvbnRyaWJ1dG9yXCJcXG5gICtcbiAgICAgIGDigKIgVGhlIHJldmlldyB0eXBpY2FsbHkgdGFrZXMgMi0zIGJ1c2luZXNzIGRheXNgO1xuICB9XG59Il19