@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.
290 lines • 41.5 kB
JavaScript
/**
* File-based log sink: writes buffered entries to date-rotated files on disk.
*
* Error reporting in this file intentionally uses `process.stderr.write` rather
* than the application logger. Using the logger would create a circular
* dependency (logger → sink → logger) and risk infinite loops or masking the
* original error. `process.stderr.write` is the correct pattern for any code
* inside the logging subsystem itself.
*/
import os from 'os';
import fs from 'fs/promises';
import path from 'path';
const CATEGORY_PATTERN = /^(application|security|performance|telemetry)-(\d{4}-\d{2}-\d{2})/;
// Captures category, date, optional sequence number, and extension
const ROTATED_FILE_PATTERN = /^(application|security|performance|telemetry)-(\d{4}-\d{2}-\d{2})(?:\.(\d+))?(\.[^.]+)$/;
export class FileLogSink {
logDir;
formatter;
maxFileSize;
retentionDays;
securityRetentionDays;
maxDirSizeBytes;
maxFilesPerCategory;
buffers = new Map();
currentDates = new Map();
sequenceCounters = new Map();
fileSizes = new Map();
initialized = false;
cleanupTimer = null;
constructor(options) {
this.logDir = options.logDir.startsWith('~')
? path.join(os.homedir(), options.logDir.slice(1))
: options.logDir;
this.formatter = options.formatter;
this.maxFileSize = options.maxFileSize;
this.retentionDays = options.retentionDays;
this.securityRetentionDays = options.securityRetentionDays;
this.maxDirSizeBytes = options.maxDirSizeBytes;
this.maxFilesPerCategory = options.maxFilesPerCategory;
}
write(entry) {
const formatted = this.formatter.format(entry);
let buffer = this.buffers.get(entry.category);
if (!buffer) {
buffer = [];
this.buffers.set(entry.category, buffer);
}
buffer.push(formatted);
}
async flush() {
const categoriesToFlush = [];
for (const [category, buffer] of this.buffers) {
if (buffer.length === 0)
continue;
const content = buffer.splice(0).join('');
categoriesToFlush.push([category, content]);
}
if (categoriesToFlush.length === 0)
return;
try {
await this.ensureLogDir();
for (const [category, content] of categoriesToFlush) {
const filePath = await this.resolveFilePath(category, content.length);
await fs.appendFile(filePath, content, { mode: 0o600 });
const currentSize = this.fileSizes.get(category) ?? 0;
this.fileSizes.set(category, currentSize + content.length);
}
}
catch (err) {
// Best-effort: log to stderr, don't throw
process.stderr.write(`[FileLogSink] flush error: ${err}\n`);
}
}
async close() {
await this.flush();
if (this.cleanupTimer !== null) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
}
async cleanupExpiredFiles() {
try {
const files = await fs.readdir(this.logDir);
const now = Date.now();
for (const file of files) {
const match = CATEGORY_PATTERN.exec(file);
if (!match)
continue;
const category = match[1];
const dateStr = match[2];
const fileDate = new Date(dateStr + 'T00:00:00Z');
if (isNaN(fileDate.getTime()))
continue;
const ageMs = now - fileDate.getTime();
const ageDays = ageMs / (1000 * 60 * 60 * 24);
const retentionLimit = category === 'security'
? this.securityRetentionDays
: this.retentionDays;
if (ageDays > retentionLimit) {
try {
await fs.unlink(path.join(this.logDir, file));
}
catch {
// best-effort: skip files that can't be deleted
}
}
}
}
catch (err) {
process.stderr.write(`[FileLogSink] cleanup error: ${err}\n`);
}
await this.cleanupByFileCount();
await this.cleanupByDirSize();
}
startCleanupTimer() {
if (this.cleanupTimer !== null)
return;
// Run initial cleanup
void this.cleanupExpiredFiles();
// Schedule every 24 hours
this.cleanupTimer = setInterval(() => {
void this.cleanupExpiredFiles();
}, 24 * 60 * 60 * 1000);
if (this.cleanupTimer && typeof this.cleanupTimer === 'object' && 'unref' in this.cleanupTimer) {
this.cleanupTimer.unref();
}
}
async ensureLogDir() {
if (this.initialized)
return;
await fs.mkdir(this.logDir, { recursive: true, mode: 0o700 });
this.initialized = true;
}
/**
* Scan the log directory to find the highest existing sequence number for a
* given category+date. Used on startup/day-transition to resume from the
* correct file instead of always resetting to sequence 0.
*
* Performance: O(n) over files in the directory, but called at most once per
* category per calendar day (on process start or UTC midnight rollover). For
* deployments with very large log directories, keep DOLLHOUSE_LOG_MAX_FILES_PER_CATEGORY
* set (default 100) so this scan stays fast.
*
* NOTE: no inter-process locking — if two instances share a log dir they may
* both scan and pick the same max seq, then both write to the same file. Each
* instance should use its own dedicated log directory. See the troubleshooting
* guide ("Multiple server instances sharing a log directory") for details.
*/
async scanMaxSequence(category, today) {
try {
const files = await fs.readdir(this.logDir);
let maxSeq = 0;
for (const file of files) {
const m = ROTATED_FILE_PATTERN.exec(file);
if (!m || m[1] !== category || m[2] !== today)
continue;
const seq = m[3] ? parseInt(m[3], 10) : 0;
if (seq > maxSeq)
maxSeq = seq;
}
return maxSeq;
}
catch {
return 0; // log dir doesn't exist yet
}
}
async resolveFilePath(category, contentSize) {
const today = new Date().toISOString().slice(0, 10);
const prevDate = this.currentDates.get(category);
if (prevDate !== today) {
this.currentDates.set(category, today);
// Scan for max existing sequence to handle restarts correctly
const existingMaxSeq = await this.scanMaxSequence(category, today);
this.sequenceCounters.set(category, existingMaxSeq);
this.fileSizes.set(category, 0);
// Stat the highest-sequence file (not just base) to pick up its current size
const suffix = existingMaxSeq > 0 ? `.${existingMaxSeq}` : '';
const targetPath = path.join(this.logDir, `${category}-${today}${suffix}${this.formatter.fileExtension}`);
try {
const stat = await fs.stat(targetPath);
this.fileSizes.set(category, stat.size);
}
catch {
// File doesn't exist yet
}
}
const currentSize = this.fileSizes.get(category) ?? 0;
let seq = this.sequenceCounters.get(category) ?? 0;
// Check if we need to rotate
if (currentSize > 0 && currentSize + contentSize > this.maxFileSize) {
seq++;
this.sequenceCounters.set(category, seq);
this.fileSizes.set(category, 0);
// Stat the new rotated file to get its size
const rotatedPath = path.join(this.logDir, `${category}-${today}.${seq}${this.formatter.fileExtension}`);
try {
const stat = await fs.stat(rotatedPath);
this.fileSizes.set(category, stat.size);
}
catch {
// File doesn't exist yet
}
}
const suffix = seq > 0 ? `.${seq}` : '';
return path.join(this.logDir, `${category}-${today}${suffix}${this.formatter.fileExtension}`);
}
/**
* Delete oldest files per category when the file count exceeds
* `maxFilesPerCategory`. Sorting is date ASC, then seq ASC within
* the same date, so the oldest files are removed first.
*/
async cleanupByFileCount() {
if (this.maxFilesPerCategory === 0)
return;
try {
const files = await fs.readdir(this.logDir);
const byCategory = new Map();
for (const file of files) {
const m = ROTATED_FILE_PATTERN.exec(file);
if (!m)
continue;
const cat = m[1];
if (!byCategory.has(cat))
byCategory.set(cat, []);
byCategory.get(cat).push({ file, date: m[2], seq: m[3] ? parseInt(m[3], 10) : 0 });
}
for (const [, entries] of byCategory) {
if (entries.length <= this.maxFilesPerCategory)
continue;
// Sort oldest first: date ASC, then seq ASC within same date
entries.sort((a, b) => a.date.localeCompare(b.date) || a.seq - b.seq);
const toDelete = entries.slice(0, entries.length - this.maxFilesPerCategory);
for (const entry of toDelete) {
try {
await fs.unlink(path.join(this.logDir, entry.file));
}
catch { /* best-effort */ }
}
}
}
catch (err) {
process.stderr.write(`[FileLogSink] cleanupByFileCount error: ${err}\n`);
}
}
/**
* Delete oldest log files (by mtime) when the total directory size exceeds
* `maxDirSizeBytes`. Emits a stderr warning when a security log is deleted
* so operators can investigate or increase the cap.
*/
async cleanupByDirSize() {
if (this.maxDirSizeBytes === 0)
return;
try {
const files = await fs.readdir(this.logDir);
const entries = [];
for (const file of files) {
if (!ROTATED_FILE_PATTERN.exec(file))
continue;
try {
const stat = await fs.stat(path.join(this.logDir, file));
entries.push({ file, mtime: stat.mtimeMs, size: stat.size, isSecurity: file.startsWith('security-') });
}
catch { /* best-effort */ }
}
const totalSize = entries.reduce((sum, e) => sum + e.size, 0);
if (totalSize <= this.maxDirSizeBytes)
return;
// Sort oldest first by mtime
entries.sort((a, b) => a.mtime - b.mtime);
let remaining = totalSize;
for (const entry of entries) {
if (remaining <= this.maxDirSizeBytes)
break;
if (entry.isSecurity) {
process.stderr.write(`[FileLogSink] dir-size cap: deleting security log ${entry.file} ` +
`(dir=${remaining} > cap=${this.maxDirSizeBytes})\n`);
}
try {
await fs.unlink(path.join(this.logDir, entry.file));
remaining -= entry.size;
}
catch { /* best-effort */ }
}
}
catch (err) {
process.stderr.write(`[FileLogSink] cleanupByDirSize error: ${err}\n`);
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiRmlsZUxvZ1NpbmsuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvbG9nZ2luZy9zaW5rcy9GaWxlTG9nU2luay50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7R0FRRztBQUVILE9BQU8sRUFBRSxNQUFNLElBQUksQ0FBQztBQUNwQixPQUFPLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDN0IsT0FBTyxJQUFJLE1BQU0sTUFBTSxDQUFDO0FBYXhCLE1BQU0sZ0JBQWdCLEdBQUcsbUVBQW1FLENBQUM7QUFFN0YsbUVBQW1FO0FBQ25FLE1BQU0sb0JBQW9CLEdBQUcseUZBQXlGLENBQUM7QUFFdkgsTUFBTSxPQUFPLFdBQVc7SUFDTCxNQUFNLENBQVM7SUFDZixTQUFTLENBQWdCO0lBQ3pCLFdBQVcsQ0FBUztJQUNwQixhQUFhLENBQVM7SUFDdEIscUJBQXFCLENBQVM7SUFDOUIsZUFBZSxDQUFTO0lBQ3hCLG1CQUFtQixDQUFTO0lBRTVCLE9BQU8sR0FBRyxJQUFJLEdBQUcsRUFBeUIsQ0FBQztJQUMzQyxZQUFZLEdBQUcsSUFBSSxHQUFHLEVBQXVCLENBQUM7SUFDOUMsZ0JBQWdCLEdBQUcsSUFBSSxHQUFHLEVBQXVCLENBQUM7SUFDbEQsU0FBUyxHQUFHLElBQUksR0FBRyxFQUF1QixDQUFDO0lBQ3BELFdBQVcsR0FBRyxLQUFLLENBQUM7SUFDcEIsWUFBWSxHQUEwQyxJQUFJLENBQUM7SUFFbkUsWUFBWSxPQUEyQjtRQUNyQyxJQUFJLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQztZQUMxQyxDQUFDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLEVBQUUsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFDbEQsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7UUFDbkIsSUFBSSxD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SSxLQUFLLENBQUMsSUFBSSxDQUFDO2dCQUMxQixDQUFDO2dCQUFDLE1BQU0sQ0FBQyxDQUFDLGlCQUFpQixDQUFDLENBQUM7WUFDL0IsQ0FBQztRQUNILENBQUM7UUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO1lBQ2IsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMseUNBQXlDLEdBQUcsSUFBSSxDQUFDLENBQUM7UUFDekUsQ0FBQztJQUNILENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogRmlsZS1iYXNlZCBsb2cgc2luazogd3JpdGVzIGJ1ZmZlcmVkIGVudHJpZXMgdG8gZGF0ZS1yb3RhdGVkIGZpbGVzIG9uIGRpc2suXG4gKlxuICogRXJyb3IgcmVwb3J0aW5nIGluIHRoaXMgZmlsZSBpbnRlbnRpb25hbGx5IHVzZXMgYHByb2Nlc3Muc3RkZXJyLndyaXRlYCByYXRoZXJcbiAqIHRoYW4gdGhlIGFwcGxpY2F0aW9uIGxvZ2dlci4gVXNpbmcgdGhlIGxvZ2dlciB3b3VsZCBjcmVhdGUgYSBjaXJjdWxhclxuICogZGVwZW5kZW5jeSAobG9nZ2VyIOKGkiBzaW5rIOKGkiBsb2dnZXIpIGFuZCByaXNrIGluZmluaXRlIGxvb3BzIG9yIG1hc2tpbmcgdGhlXG4gKiBvcmlnaW5hbCBlcnJvci4gYHByb2Nlc3Muc3RkZXJyLndyaXRlYCBpcyB0aGUgY29ycmVjdCBwYXR0ZXJuIGZvciBhbnkgY29kZVxuICogaW5zaWRlIHRoZSBsb2dnaW5nIHN1YnN5c3RlbSBpdHNlbGYuXG4gKi9cblxuaW1wb3J0IG9zIGZyb20gJ29zJztcbmltcG9ydCBmcyBmcm9tICdmcy9wcm9taXNlcyc7XG5pbXBvcnQgcGF0aCBmcm9tICdwYXRoJztcbmltcG9ydCB0eXBlIHsgSUxvZ1NpbmssIElMb2dGb3JtYXR0ZXIsIFVuaWZpZWRMb2dFbnRyeSwgTG9nQ2F0ZWdvcnkgfSBmcm9tICcuLi90eXBlcy5qcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgRmlsZUxvZ1NpbmtPcHRpb25zIHtcbiAgbG9nRGlyOiBzdHJpbmc7XG4gIGZvcm1hdHRlcjogSUxvZ0Zvcm1hdHRlcjtcbiAgbWF4RmlsZVNpemU6IG51bWJlcjtcbiAgcmV0ZW50aW9uRGF5czogbnVtYmVyO1xuICBzZWN1cml0eVJldGVudGlvbkRheXM6IG51bWJlcjtcbiAgbWF4RGlyU2l6ZUJ5dGVzOiBudW1iZXI7XG4gIG1heEZpbGVzUGVyQ2F0ZWdvcnk6IG51bWJlcjtcbn1cblxuY29uc3QgQ0FURUdPUllfUEFUVEVSTiA9IC9eKGFwcGxpY2F0aW9ufHNlY3VyaXR5fHBlcmZvcm1hbmNlfHRlbGVtZXRyeSktKFxcZHs0fS1cXGR7Mn0tXFxkezJ9KS87XG5cbi8vIENhcHR1cmVzIGNhdGVnb3J5LCBkYXRlLCBvcHRpb25hbCBzZXF1ZW5jZSBudW1iZXIsIGFuZCBleHRlbnNpb25cbmNvbnN0IFJPVEFURURfRklMRV9QQVRURVJOID0gL14oYXBwbGljYXRpb258c2VjdXJpdHl8cGVyZm9ybWFuY2V8dGVsZW1ldHJ5KS0oXFxkezR9LVxcZHsyfS1cXGR7Mn0pKD86XFwuKFxcZCspKT8oXFwuW14uXSspJC87XG5cbmV4cG9ydCBjbGFzcyBGaWxlTG9nU2luayBpbXBsZW1lbnRzIElMb2dTaW5rIHtcbiAgcHJpdmF0ZSByZWFkb25seSBsb2dEaXI6IHN0cmluZztcbiAgcHJpdmF0ZSByZWFkb25seSBmb3JtYXR0ZXI6IElMb2dGb3JtYXR0ZXI7XG4gIHByaXZhdGUgcmVhZG9ubHkgbWF4RmlsZVNpemU6IG51bWJlcjtcbiAgcHJpdmF0ZSByZWFkb25seSByZXRlbnRpb25EYXlzOiBudW1iZXI7XG4gIHByaXZhdGUgcmVhZG9ubHkgc2VjdXJpdHlSZXRlbnRpb25EYXlzOiBudW1iZXI7XG4gIHByaXZhdGUgcmVhZG9ubHkgbWF4RGlyU2l6ZUJ5dGVzOiBudW1iZXI7XG4gIHByaXZhdGUgcmVhZG9ubHkgbWF4RmlsZXNQZXJDYXRlZ29yeTogbnVtYmVyO1xuXG4gIHByaXZhdGUgcmVhZG9ubHkgYnVmZmVycyA9IG5ldyBNYXA8TG9nQ2F0ZWdvcnksIHN0cmluZ1tdPigpO1xuICBwcml2YXRlIHJlYWRvbmx5IGN1cnJlbnREYXRlcyA9IG5ldyBNYXA8TG9nQ2F0ZWdvcnksIHN0cmluZz4oKTtcbiAgcHJpdmF0ZSByZWFkb25seSBzZXF1ZW5jZUNvdW50ZXJzID0gbmV3IE1hcDxMb2dDYXRlZ29yeSwgbnVtYmVyPigpO1xuICBwcml2YXRlIHJlYWRvbmx5IGZpbGVTaXplcyA9IG5ldyBNYXA8TG9nQ2F0ZWdvcnksIG51bWJlcj4oKTtcbiAgcHJpdmF0ZSBpbml0aWFsaXplZCA9IGZhbHNlO1xuICBwcml2YXRlIGNsZWFudXBUaW1lcjogUmV0dXJuVHlwZTx0eXBlb2Ygc2V0SW50ZXJ2YWw+IHwgbnVsbCA9IG51bGw7XG5cbiAgY29uc3RydWN0b3Iob3B0aW9uczogRmlsZUxvZ1NpbmtPcHRpb25zKSB7XG4gICAgdGhpcy5sb2dEaXIgPSBvcHRpb25zLmxvZ0Rpci5zdGFydHNXaXRoKCd+JylcbiAgICAgID8gcGF0aC5qb2luKG9zLmhvbWVkaXIoKSwgb3B0aW9ucy5sb2dEaXIuc2xpY2UoMSkpXG4gICAgICA6IG9wdGlvbnMubG9nRGlyO1xuICAgIHRoaXMuZm9ybWF0dGVyID0gb3B0aW9ucy5mb3JtYXR0ZXI7XG4gICAgdGhpcy5tYXhGaWxlU2l6ZSA9IG9wdGlvbnMubWF4RmlsZVNpemU7XG4gICAgdGhpcy5yZXRlbnRpb25EYXlzID0gb3B0aW9ucy5yZXRlbnRpb25EYXlzO1xuICAgIHRoaXMuc2VjdXJpdHlSZXRlbnRpb25EYXlzID0gb3B0aW9ucy5zZWN1cml0eVJldGVudGlvbkRheXM7XG4gICAgdGhpcy5tYXhEaXJTaXplQnl0ZXMgPSBvcHRpb25zLm1heERpclNpemVCeXRlcztcbiAgICB0aGlzLm1heEZpbGVzUGVyQ2F0ZWdvcnkgPSBvcHRpb25zLm1heEZpbGVzUGVyQ2F0ZWdvcnk7XG4gIH1cblxuICB3cml0ZShlbnRyeTogVW5pZmllZExvZ0VudHJ5KTogdm9pZCB7XG4gICAgY29uc3QgZm9ybWF0dGVkID0gdGhpcy5mb3JtYXR0ZXIuZm9ybWF0KGVudHJ5KTtcbiAgICBsZXQgYnVmZmVyID0gdGhpcy5idWZmZXJzLmdldChlbnRyeS5jYXRlZ29yeSk7XG4gICAgaWYgKCFidWZmZXIpIHtcbiAgICAgIGJ1ZmZlciA9IFtdO1xuICAgICAgdGhpcy5idWZmZXJzLnNldChlbnRyeS5jYXRlZ29yeSwgYnVmZmVyKTtcbiAgICB9XG4gICAgYnVmZmVyLnB1c2goZm9ybWF0dGVkKTtcbiAgfVxuXG4gIGFzeW5jIGZsdXNoKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGNvbnN0IGNhdGVnb3JpZXNUb0ZsdXNoOiBBcnJheTxbTG9nQ2F0ZWdvcnksIHN0cmluZ10+ID0gW107XG5cbiAgICBmb3IgKGNvbnN0IFtjYXRlZ29yeSwgYnVmZmVyXSBvZiB0aGlzLmJ1ZmZlcnMpIHtcbiAgICAgIGlmIChidWZmZXIubGVuZ3RoID09PSAwKSBjb250aW51ZTtcbiAgICAgIGNvbnN0IGNvbnRlbnQgPSBidWZmZXIuc3BsaWNlKDApLmpvaW4oJycpO1xuICAgICAgY2F0ZWdvcmllc1RvRmx1c2gucHVzaChbY2F0ZWdvcnksIGNvbnRlbnRdKTtcbiAgICB9XG5cbiAgICBpZiAoY2F0ZWdvcmllc1RvRmx1c2gubGVuZ3RoID09PSAwKSByZXR1cm47XG5cbiAgICB0cnkge1xuICAgICAgYXdhaXQgdGhpcy5lbnN1cmVMb2dEaXIoKTtcblxuICAgICAgZm9yIChjb25zdCBbY2F0ZWdvcnksIGNvbnRlbnRdIG9mIGNhdGVnb3JpZXNUb0ZsdXNoKSB7XG4gICAgICAgIGNvbnN0IGZpbGVQYXRoID0gYXdhaXQgdGhpcy5yZXNvbHZlRmlsZVBhdGgoY2F0ZWdvcnksIGNvbnRlbnQubGVuZ3RoKTtcbiAgICAgICAgYXdhaXQgZnMuYXBwZW5kRmlsZShmaWxlUGF0aCwgY29udGVudCwgeyBtb2RlOiAwbzYwMCB9KTtcblxuICAgICAgICBjb25zdCBjdXJyZW50U2l6ZSA9IHRoaXMuZmlsZVNpemVzLmdldChjYXRlZ29yeSkgPz8gMDtcbiAgICAgICAgdGhpcy5maWxlU2l6ZXMuc2V0KGNhdGVnb3J5LCBjdXJyZW50U2l6ZSArIGNvbnRlbnQubGVuZ3RoKTtcbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIC8vIEJlc3QtZWZmb3J0OiBsb2cgdG8gc3RkZXJyLCBkb24ndCB0aHJvd1xuICAgICAgcHJvY2Vzcy5zdGRlcnIud3JpdGUoYFtGaWxlTG9nU2lua10gZmx1c2ggZXJyb3I6ICR7ZXJyfVxcbmApO1xuICAgIH1cbiAgfVxuXG4gIGFzeW5jIGNsb3NlKCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGF3YWl0IHRoaXMuZmx1c2goKTtcbiAgICBpZiAodGhpcy5jbGVhbnVwVGltZXIgIT09IG51bGwpIHtcbiAgICAgIGNsZWFySW50ZXJ2YWwodGhpcy5jbGVhbnVwVGltZXIpO1xuICAgICAgdGhpcy5jbGVhbnVwVGltZXIgPSBudWxsO1xuICAgIH1cbiAgfVxuXG4gIGFzeW5jIGNsZWFudXBFeHBpcmVkRmlsZXMoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGZpbGVzID0gYXdhaXQgZnMucmVhZGRpcih0aGlzLmxvZ0Rpcik7XG4gICAgICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuXG4gICAgICBmb3IgKGNvbnN0IGZpbGUgb2YgZmlsZXMpIHtcbiAgICAgICAgY29uc3QgbWF0Y2ggPSBDQVRFR09SWV9QQVRURVJOLmV4ZWMoZmlsZSk7XG4gICAgICAgIGlmICghbWF0Y2gpIGNvbnRpbnVlO1xuXG4gICAgICAgIGNvbnN0IGNhdGVnb3J5ID0gbWF0Y2hbMV0gYXMgc3RyaW5nO1xuICAgICAgICBjb25zdCBkYXRlU3RyID0gbWF0Y2hbMl07XG4gICAgICAgIGNvbnN0IGZpbGVEYXRlID0gbmV3IERhdGUoZGF0ZVN0ciArICdUMDA6MDA6MDBaJyk7XG4gICAgICAgIGlmIChpc05hTihmaWxlRGF0ZS5nZXRUaW1lKCkpKSBjb250aW51ZTtcblxuICAgICAgICBjb25zdCBhZ2VNcyA9IG5vdyAtIGZpbGVEYXRlLmdldFRpbWUoKTtcbiAgICAgICAgY29uc3QgYWdlRGF5cyA9IGFnZU1zIC8gKDEwMDAgKiA2MCAqIDYwICogMjQpO1xuICAgICAgICBjb25zdCByZXRlbnRpb25MaW1pdCA9IGNhdGVnb3J5ID09PSAnc2VjdXJpdHknXG4gICAgICAgICAgPyB0aGlzLnNlY3VyaXR5UmV0ZW50aW9uRGF5c1xuICAgICAgICAgIDogdGhpcy5yZXRlbnRpb25EYXlzO1xuXG4gICAgICAgIGlmIChhZ2VEYXlzID4gcmV0ZW50aW9uTGltaXQpIHtcbiAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgYXdhaXQgZnMudW5saW5rKHBhdGguam9pbih0aGlzLmxvZ0RpciwgZmlsZSkpO1xuICAgICAgICAgIH0gY2F0Y2gge1xuICAgICAgICAgICAgLy8gYmVzdC1lZmZvcnQ6IHNraXAgZmlsZXMgdGhhdCBjYW4ndCBiZSBkZWxldGVkXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBwcm9jZXNzLnN0ZGVyci53cml0ZShgW0ZpbGVMb2dTaW5rXSBjbGVhbnVwIGVycm9yOiAke2Vycn1cXG5gKTtcbiAgICB9XG5cbiAgICBhd2FpdCB0aGlzLmNsZWFudXBCeUZpbGVDb3VudCgpO1xuICAgIGF3YWl0IHRoaXMuY2xlYW51cEJ5RGlyU2l6ZSgpO1xuICB9XG5cbiAgc3RhcnRDbGVhbnVwVGltZXIoKTogdm9pZCB7XG4gICAgaWYgKHRoaXMuY2xlYW51cFRpbWVyICE9PSBudWxsKSByZXR1cm47XG5cbiAgICAvLyBSdW4gaW5pdGlhbCBjbGVhbnVwXG4gICAgdm9pZCB0aGlzLmNsZWFudXBFeHBpcmVkRmlsZXMoKTtcblxuICAgIC8vIFNjaGVkdWxlIGV2ZXJ5IDI0IGhvdXJzXG4gICAgdGhpcy5jbGVhbnVwVGltZXIgPSBzZXRJbnRlcnZhbCgoKSA9PiB7XG4gICAgICB2b2lkIHRoaXMuY2xlYW51cEV4cGlyZWRGaWxlcygpO1xuICAgIH0sIDI0ICogNjAgKiA2MCAqIDEwMDApO1xuXG4gICAgaWYgKHRoaXMuY2xlYW51cFRpbWVyICYmIHR5cGVvZiB0aGlzLmNsZWFudXBUaW1lciA9PT0gJ29iamVjdCcgJiYgJ3VucmVmJyBpbiB0aGlzLmNsZWFudXBUaW1lcikge1xuICAgICAgdGhpcy5jbGVhbnVwVGltZXIudW5yZWYoKTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGFzeW5jIGVuc3VyZUxvZ0RpcigpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBpZiAodGhpcy5pbml0aWFsaXplZCkgcmV0dXJuO1xuICAgIGF3YWl0IGZzLm1rZGlyKHRoaXMubG9nRGlyLCB7IHJlY3Vyc2l2ZTogdHJ1ZSwgbW9kZTogMG83MDAgfSk7XG4gICAgdGhpcy5pbml0aWFsaXplZCA9IHRydWU7XG4gIH1cblxuICAvKipcbiAgICogU2NhbiB0aGUgbG9nIGRpcmVjdG9yeSB0byBmaW5kIHRoZSBoaWdoZXN0IGV4aXN0aW5nIHNlcXVlbmNlIG51bWJlciBmb3IgYVxuICAgKiBnaXZlbiBjYXRlZ29yeStkYXRlLiBVc2VkIG9uIHN0YXJ0dXAvZGF5LXRyYW5zaXRpb24gdG8gcmVzdW1lIGZyb20gdGhlXG4gICAqIGNvcnJlY3QgZmlsZSBpbnN0ZWFkIG9mIGFsd2F5cyByZXNldHRpbmcgdG8gc2VxdWVuY2UgMC5cbiAgICpcbiAgICogUGVyZm9ybWFuY2U6IE8obikgb3ZlciBmaWxlcyBpbiB0aGUgZGlyZWN0b3J5LCBidXQgY2FsbGVkIGF0IG1vc3Qgb25jZSBwZXJcbiAgICogY2F0ZWdvcnkgcGVyIGNhbGVuZGFyIGRheSAob24gcHJvY2VzcyBzdGFydCBvciBVVEMgbWlkbmlnaHQgcm9sbG92ZXIpLiBGb3JcbiAgICogZGVwbG95bWVudHMgd2l0aCB2ZXJ5IGxhcmdlIGxvZyBkaXJlY3Rvcmllcywga2VlcCBET0xMSE9VU0VfTE9HX01BWF9GSUxFU19QRVJfQ0FURUdPUllcbiAgICogc2V0IChkZWZhdWx0IDEwMCkgc28gdGhpcyBzY2FuIHN0YXlzIGZhc3QuXG4gICAqXG4gICAqIE5PVEU6IG5vIGludGVyLXByb2Nlc3MgbG9ja2luZyDigJQgaWYgdHdvIGluc3RhbmNlcyBzaGFyZSBhIGxvZyBkaXIgdGhleSBtYXlcbiAgICogYm90aCBzY2FuIGFuZCBwaWNrIHRoZSBzYW1lIG1heCBzZXEsIHRoZW4gYm90aCB3cml0ZSB0byB0aGUgc2FtZSBmaWxlLiBFYWNoXG4gICAqIGluc3RhbmNlIHNob3VsZCB1c2UgaXRzIG93biBkZWRpY2F0ZWQgbG9nIGRpcmVjdG9yeS4gU2VlIHRoZSB0cm91Ymxlc2hvb3RpbmdcbiAgICogZ3VpZGUgKFwiTXVsdGlwbGUgc2VydmVyIGluc3RhbmNlcyBzaGFyaW5nIGEgbG9nIGRpcmVjdG9yeVwiKSBmb3IgZGV0YWlscy5cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgc2Nhbk1heFNlcXVlbmNlKGNhdGVnb3J5OiBMb2dDYXRlZ29yeSwgdG9kYXk6IHN0cmluZyk6IFByb21pc2U8bnVtYmVyPiB7XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGZpbGVzID0gYXdhaXQgZnMucmVhZGRpcih0aGlzLmxvZ0Rpcik7XG4gICAgICBsZXQgbWF4U2VxID0gMDtcbiAgICAgIGZvciAoY29uc3QgZmlsZSBvZiBmaWxlcykge1xuICAgICAgICBjb25zdCBtID0gUk9UQVRFRF9GSUxFX1BBVFRFUk4uZXhlYyhmaWxlKTtcbiAgICAgICAgaWYgKCFtIHx8IG1bMV0gIT09IGNhdGVnb3J5IHx8IG1bMl0gIT09IHRvZGF5KSBjb250aW51ZTtcbiAgICAgICAgY29uc3Qgc2VxID0gbVszXSA/IHBhcnNlSW50KG1bM10sIDEwKSA6IDA7XG4gICAgICAgIGlmIChzZXEgPiBtYXhTZXEpIG1heFNlcSA9IHNlcTtcbiAgICAgIH1cbiAgICAgIHJldHVybiBtYXhTZXE7XG4gICAgfSBjYXRjaCB7XG4gICAgICByZXR1cm4gMDsgLy8gbG9nIGRpciBkb2Vzbid0IGV4aXN0IHlldFxuICAgIH1cbiAgfVxuXG4gIHByaXZhdGUgYXN5bmMgcmVzb2x2ZUZpbGVQYXRoKGNhdGVnb3J5OiBMb2dDYXRlZ29yeSwgY29udGVudFNpemU6IG51bWJlcik6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3QgdG9kYXkgPSBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCkuc2xpY2UoMCwgMTApO1xuICAgIGNvbnN0IHByZXZEYXRlID0gdGhpcy5jdXJyZW50RGF0ZXMuZ2V0KGNhdGVnb3J5KTtcblxuICAgIGlmIChwcmV2RGF0ZSAhPT0gdG9kYXkpIHtcbiAgICAgIHRoaXMuY3VycmVudERhdGVzLnNldChjYXRlZ29yeSwgdG9kYXkpO1xuICAgICAgLy8gU2NhbiBmb3IgbWF4IGV4aXN0aW5nIHNlcXVlbmNlIHRvIGhhbmRsZSByZXN0YXJ0cyBjb3JyZWN0bHlcbiAgICAgIGNvbnN0IGV4aXN0aW5nTWF4U2VxID0gYXdhaXQgdGhpcy5zY2FuTWF4U2VxdWVuY2UoY2F0ZWdvcnksIHRvZGF5KTtcbiAgICAgIHRoaXMuc2VxdWVuY2VDb3VudGVycy5zZXQoY2F0ZWdvcnksIGV4aXN0aW5nTWF4U2VxKTtcbiAgICAgIHRoaXMuZmlsZVNpemVzLnNldChjYXRlZ29yeSwgMCk7XG5cbiAgICAgIC8vIFN0YXQgdGhlIGhpZ2hlc3Qtc2VxdWVuY2UgZmlsZSAobm90IGp1c3QgYmFzZSkgdG8gcGljayB1cCBpdHMgY3VycmVudCBzaXplXG4gICAgICBjb25zdCBzdWZmaXggPSBleGlzdGluZ01heFNlcSA+IDAgPyBgLiR7ZXhpc3RpbmdNYXhTZXF9YCA6ICcnO1xuICAgICAgY29uc3QgdGFyZ2V0UGF0aCA9IHBhdGguam9pbihcbiAgICAgICAgdGhpcy5sb2dEaXIsXG4gICAgICAgIGAke2NhdGVnb3J5fS0ke3RvZGF5fSR7c3VmZml4fSR7dGhpcy5mb3JtYXR0ZXIuZmlsZUV4dGVuc2lvbn1gLFxuICAgICAgKTtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IHN0YXQgPSBhd2FpdCBmcy5zdGF0KHRhcmdldFBhdGgpO1xuICAgICAgICB0aGlzLmZpbGVTaXplcy5zZXQoY2F0ZWdvcnksIHN0YXQuc2l6ZSk7XG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgLy8gRmlsZSBkb2Vzbid0IGV4aXN0IHlldFxuICAgICAgfVxuICAgIH1cblxuICAgIGNvbnN0IGN1cnJlbnRTaXplID0gdGhpcy5maWxlU2l6ZXMuZ2V0KGNhdGVnb3J5KSA/PyAwO1xuICAgIGxldCBzZXEgPSB0aGlzLnNlcXVlbmNlQ291bnRlcnMuZ2V0KGNhdGVnb3J5KSA/PyAwO1xuXG4gICAgLy8gQ2hlY2sgaWYgd2UgbmVlZCB0byByb3RhdGVcbiAgICBpZiAoY3VycmVudFNpemUgPiAwICYmIGN1cnJlbnRTaXplICsgY29udGVudFNpemUgPiB0aGlzLm1heEZpbGVTaXplKSB7XG4gICAgICBzZXErKztcbiAgICAgIHRoaXMuc2VxdWVuY2VDb3VudGVycy5zZXQoY2F0ZWdvcnksIHNlcSk7XG4gICAgICB0aGlzLmZpbGVTaXplcy5zZXQoY2F0ZWdvcnksIDApO1xuXG4gICAgICAvLyBTdGF0IHRoZSBuZXcgcm90YXRlZCBmaWxlIHRvIGdldCBpdHMgc2l6ZVxuICAgICAgY29uc3Qgcm90YXRlZFBhdGggPSBwYXRoLmpvaW4oXG4gICAgICAgIHRoaXMubG9nRGlyLFxuICAgICAgICBgJHtjYXRlZ29yeX0tJHt0b2RheX0uJHtzZXF9JHt0aGlzLmZvcm1hdHRlci5maWxlRXh0ZW5zaW9ufWAsXG4gICAgICApO1xuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3Qgc3RhdCA9IGF3YWl0IGZzLnN0YXQocm90YXRlZFBhdGgpO1xuICAgICAgICB0aGlzLmZpbGVTaXplcy5zZXQoY2F0ZWdvcnksIHN0YXQuc2l6ZSk7XG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgLy8gRmlsZSBkb2Vzbid0IGV4aXN0IHlldFxuICAgICAgfVxuICAgIH1cblxuICAgIGNvbnN0IHN1ZmZpeCA9IHNlcSA+IDAgPyBgLiR7c2VxfWAgOiAnJztcbiAgICByZXR1cm4gcGF0aC5qb2luKFxuICAgICAgdGhpcy5sb2dEaXIsXG4gICAgICBgJHtjYXRlZ29yeX0tJHt0b2RheX0ke3N1ZmZpeH0ke3RoaXMuZm9ybWF0dGVyLmZpbGVFeHRlbnNpb259YCxcbiAgICApO1xuICB9XG5cbiAgLyoqXG4gICAqIERlbGV0ZSBvbGRlc3QgZmlsZXMgcGVyIGNhdGVnb3J5IHdoZW4gdGhlIGZpbGUgY291bnQgZXhjZWVkc1xuICAgKiBgbWF4RmlsZXNQZXJDYXRlZ29yeWAuIFNvcnRpbmcgaXMgZGF0ZSBBU0MsIHRoZW4gc2VxIEFTQyB3aXRoaW5cbiAgICogdGhlIHNhbWUgZGF0ZSwgc28gdGhlIG9sZGVzdCBmaWxlcyBhcmUgcmVtb3ZlZCBmaXJzdC5cbiAgICovXG4gIHByaXZhdGUgYXN5bmMgY2xlYW51cEJ5RmlsZUNvdW50KCk6IFByb21pc2U8dm9pZD4ge1xuICAgIGlmICh0aGlzLm1heEZpbGVzUGVyQ2F0ZWdvcnkgPT09IDApIHJldHVybjtcbiAgICB0cnkge1xuICAgICAgY29uc3QgZmlsZXMgPSBhd2FpdCBmcy5yZWFkZGlyKHRoaXMubG9nRGlyKTtcbiAgICAgIGNvbnN0IGJ5Q2F0ZWdvcnkgPSBuZXcgTWFwPHN0cmluZywgQXJyYXk8eyBmaWxlOiBzdHJpbmc7IGRhdGU6IHN0cmluZzsgc2VxOiBudW1iZXIgfT4+KCk7XG4gICAgICBmb3IgKGNvbnN0IGZpbGUgb2YgZmlsZXMpIHtcbiAgICAgICAgY29uc3QgbSA9IFJPVEFURURfRklMRV9QQVRURVJOLmV4ZWMoZmlsZSk7XG4gICAgICAgIGlmICghbSkgY29udGludWU7XG4gICAgICAgIGNvbnN0IGNhdCA9IG1bMV07XG4gICAgICAgIGlmICghYnlDYXRlZ29yeS5oYXMoY2F0KSkgYnlDYXRlZ29yeS5zZXQoY2F0LCBbXSk7XG4gICAgICAgIGJ5Q2F0ZWdvcnkuZ2V0KGNhdCkhLnB1c2goeyBmaWxlLCBkYXRlOiBtWzJdLCBzZXE6IG1bM10gPyBwYXJzZUludChtWzNdLCAxMCkgOiAwIH0pO1xuICAgICAgfVxuICAgICAgZm9yIChjb25zdCBbLCBlbnRyaWVzXSBvZiBieUNhdGVnb3J5KSB7XG4gICAgICAgIGlmIChlbnRyaWVzLmxlbmd0aCA8PSB0aGlzLm1heEZpbGVzUGVyQ2F0ZWdvcnkpIGNvbnRpbnVlO1xuICAgICAgICAvLyBTb3J0IG9sZGVzdCBmaXJzdDogZGF0ZSBBU0MsIHRoZW4gc2VxIEFTQyB3aXRoaW4gc2FtZSBkYXRlXG4gICAgICAgIGVudHJpZXMuc29ydCgoYSwgYikgPT4gYS5kYXRlLmxvY2FsZUNvbXBhcmUoYi5kYXRlKSB8fCBhLnNlcSAtIGIuc2VxKTtcbiAgICAgICAgY29uc3QgdG9EZWxldGUgPSBlbnRyaWVzLnNsaWNlKDAsIGVudHJpZXMubGVuZ3RoIC0gdGhpcy5tYXhGaWxlc1BlckNhdGVnb3J5KTtcbiAgICAgICAgZm9yIChjb25zdCBlbnRyeSBvZiB0b0RlbGV0ZSkge1xuICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICBhd2FpdCBmcy51bmxpbmsocGF0aC5qb2luKHRoaXMubG9nRGlyLCBlbnRyeS5maWxlKSk7XG4gICAgICAgICAgfSBjYXRjaCB7IC8qIGJlc3QtZWZmb3J0ICovIH1cbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgcHJvY2Vzcy5zdGRlcnIud3JpdGUoYFtGaWxlTG9nU2lua10gY2xlYW51cEJ5RmlsZUNvdW50IGVycm9yOiAke2Vycn1cXG5gKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogRGVsZXRlIG9sZGVzdCBsb2cgZmlsZXMgKGJ5IG10aW1lKSB3aGVuIHRoZSB0b3RhbCBkaXJlY3Rvcnkgc2l6ZSBleGNlZWRzXG4gICAqIGBtYXhEaXJTaXplQnl0ZXNgLiBFbWl0cyBhIHN0ZGVyciB3YXJuaW5nIHdoZW4gYSBzZWN1cml0eSBsb2cgaXMgZGVsZXRlZFxuICAgKiBzbyBvcGVyYXRvcnMgY2FuIGludmVzdGlnYXRlIG9yIGluY3JlYXNlIHRoZSBjYXAuXG4gICAqL1xuICBwcml2YXRlIGFzeW5jIGNsZWFudXBCeURpclNpemUoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgaWYgKHRoaXMubWF4RGlyU2l6ZUJ5dGVzID09PSAwKSByZXR1cm47XG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGZpbGVzID0gYXdhaXQgZnMucmVhZGRpcih0aGlzLmxvZ0Rpcik7XG4gICAgICBjb25zdCBlbnRyaWVzOiBBcnJheTx7IGZpbGU6IHN0cmluZzsgbXRpbWU6IG51bWJlcjsgc2l6ZTogbnVtYmVyOyBpc1NlY3VyaXR5OiBib29sZWFuIH0+ID0gW107XG4gICAgICBmb3IgKGNvbnN0IGZpbGUgb2YgZmlsZXMpIHtcbiAgICAgICAgaWYgKCFST1RBVEVEX0ZJTEVfUEFUVEVSTi5leGVjKGZpbGUpKSBjb250aW51ZTtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBjb25zdCBzdGF0ID0gYXdhaXQgZnMuc3RhdChwYXRoLmpvaW4odGhpcy5sb2dEaXIsIGZpbGUpKTtcbiAgICAgICAgICBlbnRyaWVzLnB1c2goeyBmaWxlLCBtdGltZTogc3RhdC5tdGltZU1zLCBzaXplOiBzdGF0LnNpemUsIGlzU2VjdXJpdHk6IGZpbGUuc3RhcnRzV2l0aCgnc2VjdXJpdHktJykgfSk7XG4gICAgICAgIH0gY2F0Y2ggeyAvKiBiZXN0LWVmZm9ydCAqLyB9XG4gICAgICB9XG4gICAgICBjb25zdCB0b3RhbFNpemUgPSBlbnRyaWVzLnJlZHVjZSgoc3VtLCBlKSA9PiBzdW0gKyBlLnNpemUsIDApO1xuICAgICAgaWYgKHRvdGFsU2l6ZSA8PSB0aGlzLm1heERpclNpemVCeXRlcykgcmV0dXJuO1xuICAgICAgLy8gU29ydCBvbGRlc3QgZmlyc3QgYnkgbXRpbWVcbiAgICAgIGVudHJpZXMuc29ydCgoYSwgYikgPT4gYS5tdGltZSAtIGIubXRpbWUpO1xuICAgICAgbGV0IHJlbWFpbmluZyA9IHRvdGFsU2l6ZTtcbiAgICAgIGZvciAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xuICAgICAgICBpZiAocmVtYWluaW5nIDw9IHRoaXMubWF4RGlyU2l6ZUJ5dGVzKSBicmVhaztcbiAgICAgICAgaWYgKGVudHJ5LmlzU2VjdXJpdHkpIHtcbiAgICAgICAgICBwcm9jZXNzLnN0ZGVyci53cml0ZShcbiAgICAgICAgICAgIGBbRmlsZUxvZ1NpbmtdIGRpci1zaXplIGNhcDogZGVsZXRpbmcgc2VjdXJpdHkgbG9nICR7ZW50cnkuZmlsZX0gYCArXG4gICAgICAgICAgICBgKGRpcj0ke3JlbWFpbmluZ30gPiBjYXA9JHt0aGlzLm1heERpclNpemVCeXRlc30pXFxuYCxcbiAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgYXdhaXQgZnMudW5saW5rKHBhdGguam9pbih0aGlzLmxvZ0RpciwgZW50cnkuZmlsZSkpO1xuICAgICAgICAgIHJlbWFpbmluZyAtPSBlbnRyeS5zaXplO1xuICAgICAgICB9IGNhdGNoIHsgLyogYmVzdC1lZmZvcnQgKi8gfVxuICAgICAgfVxuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgcHJvY2Vzcy5zdGRlcnIud3JpdGUoYFtGaWxlTG9nU2lua10gY2xlYW51cEJ5RGlyU2l6ZSBlcnJvcjogJHtlcnJ9XFxuYCk7XG4gICAgfVxuICB9XG59XG4iXX0=