b39a744 ←a7ded40antonApr 13, 2026 api

trashed main.js

? /sublimated/undefined +0 -700
1var __defProp = Object.defineProperty;
2var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3var __getOwnPropNames = Object.getOwnPropertyNames;
4var __hasOwnProp = Object.prototype.hasOwnProperty;
5var __export = (target, all) => {
6 for (var name in all)
7 __defProp(target, name, { get: all[name], enumerable: true });
8};
9var __copyProps = (to, from, except, desc) => {
10 if (from && typeof from === "object" || typeof from === "function") {
11 for (let key of __getOwnPropNames(from))
12 if (!__hasOwnProp.call(to, key) && key !== except)
13 __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14 }
15 return to;
16};
17var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
19// src/main.ts
20var main_exports = {};
21__export(main_exports, {
22 default: () => SublimatedPlugin
23});
24module.exports = __toCommonJS(main_exports);
25var import_obsidian6 = require("obsidian");
26
27// src/settings.ts
28var import_obsidian2 = require("obsidian");
29
30// src/api.ts
31var import_obsidian = require("obsidian");
32var API_BASE = "https://api.sublimated.com";
33var AuthRequiredError = class extends Error {
34 constructor(status) {
35 super(`auth required (${status})`);
36 this.status = status;
37 this.name = "AuthRequiredError";
38 }
39};
40var SublimatedApi = class {
41 constructor(token) {
42 this.token = token;
43 }
44 async request(path) {
45 const headers = {
46 Accept: "application/json"
47 };
48 if (this.token) {
49 headers.Authorization = `Bearer ${this.token}`;
50 }
51 const response = await (0, import_obsidian.requestUrl)({
52 url: `${API_BASE}${path}`,
53 headers,
54 throw: false
55 });
56 if (response.status === 401 || response.status === 403) {
57 throw new AuthRequiredError(response.status);
58 }
59 if (response.status >= 400) {
60 throw new Error(
61 `API error ${response.status}: ${response.text}`
62 );
63 }
64 const body = response.json;
65 if (!body.ok || body.data === void 0) {
66 const msg = body.error?.message || body.error?.type || "unknown error";
67 throw new Error(`API error: ${msg}`);
68 }
69 return body.data;
70 }
71 async getVersions(spaceSlug, itemId) {
72 const raw = await this.request(
73 `/v1/spaces/${encodeURIComponent(spaceSlug)}/items/${encodeURIComponent(itemId)}/versions`
74 );
75 return raw.map(mapVersion).reverse();
76 }
77 async testConnection() {
78 try {
79 const data = await this.request(
80 "/v1/me"
81 );
82 return { ok: true, username: data.username };
83 } catch (e) {
84 return {
85 ok: false,
86 error: e instanceof Error ? e.message : "Unknown error"
87 };
88 }
89 }
90};
91function mapVersion(r) {
92 const author = r.actors.find((a) => a.role === "author") ?? r.actors[0] ?? null;
93 return {
94 id: r.id,
95 number: r.version,
96 authorName: author?.name ?? author?.username ?? "unknown",
97 authorType: author?.actor_type ?? "person",
98 message: r.message,
99 createdAt: r.created_at
100 };
101}
102
103// src/settings.ts
104var DEFAULT_SETTINGS = {
105 apiToken: "",
106 hasSeenWelcome: false
107};
108var SublimatedSettingTab = class extends import_obsidian2.PluginSettingTab {
109 plugin;
110 constructor(app, plugin) {
111 super(app, plugin);
112 this.plugin = plugin;
113 }
114 display() {
115 const { containerEl } = this;
116 containerEl.empty();
117 const header = containerEl.createDiv({ cls: "sbl-settings-header" });
118 header.createEl("h2", { text: "sublimated" });
119 header.createEl("p", {
120 text: "Sync status, version history, and deep links.",
121 cls: "setting-item-description"
122 });
123 const workspaceInfo = this.plugin.detectWorkspace();
124 if (workspaceInfo) {
125 new import_obsidian2.Setting(containerEl).setName("Detected workspace").setDesc(
126 `Space: ${workspaceInfo.space || "unknown"}`
127 );
128 } else {
129 new import_obsidian2.Setting(containerEl).setName("No sublimated file open").setDesc(
130 "Open any synced file from a sublimated folder to see workspace info."
131 );
132 }
133 new import_obsidian2.Setting(containerEl).setName("API token").setDesc(
134 "Required for version history. Generate at sublimated.com/settings/tokens"
135 ).addText(
136 (text) => text.setPlaceholder("sk_...").setValue(this.plugin.settings.apiToken).onChange(async (value) => {
137 this.plugin.settings.apiToken = value;
138 await this.plugin.saveSettings();
139 })
140 );
141 new import_obsidian2.Setting(containerEl).setName("Test connection").setDesc("Verify your API token works").addButton(
142 (btn) => btn.setButtonText("Test").onClick(async () => {
143 if (!this.plugin.settings.apiToken) {
144 new import_obsidian2.Notice("No API token configured");
145 return;
146 }
147 btn.setButtonText("Testing...");
148 btn.setDisabled(true);
149 const api = new SublimatedApi(
150 this.plugin.settings.apiToken
151 );
152 const result = await api.testConnection();
153 if (result.ok) {
154 new import_obsidian2.Notice(
155 `Connected as ${result.username}`
156 );
157 } else {
158 new import_obsidian2.Notice(
159 `Connection failed: ${result.error}`
160 );
161 }
162 btn.setButtonText("Test");
163 btn.setDisabled(false);
164 })
165 );
166 containerEl.createEl("div", { cls: "sbl-settings-links" }, (el) => {
167 el.createEl("a", {
168 text: "sublimated.com",
169 href: "https://sublimated.com"
170 });
171 el.createEl("span", { text: " \xB7 " });
172 el.createEl("a", {
173 text: "Documentation",
174 href: "https://sublimated.com/docs/obsidian"
175 });
176 el.createEl("span", { text: " \xB7 " });
177 el.createEl("a", {
178 text: "Source code",
179 href: "https://sublimated.com/sublimated/code/obsidian"
180 });
181 });
182 }
183};
184
185// src/xattr.ts
186var import_child_process = require("child_process");
187var import_obsidian3 = require("obsidian");
188var XATTR_PREFIX = "com.sublimated.";
189var XATTR_SUFFIX = "#PS";
190var ATTRS = [
191 "version",
192 "commit",
193 "url",
194 "contentHash",
195 "id",
196 "createdBy",
197 "modifiedBy",
198 "modifiedByType",
199 "syncStatus",
200 "space",
201 "branch",
202 "modifiedAt",
203 "visibility"
204];
205function attrToXattrName(attr) {
206 const kebab = attr.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
207 return `${XATTR_PREFIX}${kebab}${XATTR_SUFFIX}`;
208}
209function readXattr(path, xattrName) {
210 return new Promise((resolve) => {
211 (0, import_child_process.exec)(
212 `xattr -p ${JSON.stringify(xattrName)} ${JSON.stringify(path)}`,
213 { timeout: 2e3 },
214 (err, stdout) => {
215 if (err) {
216 resolve(null);
217 return;
218 }
219 resolve(stdout.trim() || null);
220 }
221 );
222 });
223}
224async function readFileMetadata(path) {
225 if (!import_obsidian3.Platform.isMacOS) return null;
226 const results = await Promise.all(
227 ATTRS.map((attr) => readXattr(path, attrToXattrName(attr)))
228 );
229 if (results.every((r) => r === null)) return null;
230 const meta = {
231 version: null,
232 commit: null,
233 url: null,
234 contentHash: null,
235 id: null,
236 createdBy: null,
237 modifiedBy: null,
238 modifiedByType: null,
239 syncStatus: null,
240 space: null,
241 branch: null,
242 modifiedAt: null,
243 visibility: null
244 };
245 ATTRS.forEach((attr, i) => {
246 meta[attr] = results[i];
247 });
248 return meta;
249}
250function isMacOS() {
251 return import_obsidian3.Platform.isMacOS;
252}
253
254// src/version-view.ts
255var import_obsidian4 = require("obsidian");
256var VERSION_VIEW_TYPE = "sublimated-versions";
257var VersionView = class extends import_obsidian4.ItemView {
258 plugin;
259 constructor(leaf, plugin) {
260 super(leaf);
261 this.plugin = plugin;
262 }
263 getViewType() {
264 return VERSION_VIEW_TYPE;
265 }
266 getDisplayText() {
267 return "sublimated versions";
268 }
269 getIcon() {
270 return "history";
271 }
272 async onOpen() {
273 await this.refresh();
274 this.registerEvent(
275 this.app.workspace.on("active-leaf-change", () => {
276 this.refresh();
277 })
278 );
279 }
280 async refresh() {
281 const { contentEl } = this;
282 contentEl.empty();
283 contentEl.addClass("sbl-version-view");
284 const activeFile = this.app.workspace.getActiveFile();
285 if (!activeFile) {
286 contentEl.createEl("p", {
287 text: "No file open.",
288 cls: "sbl-version-empty"
289 });
290 return;
291 }
292 const meta = this.plugin.lastMetadata;
293 if (!meta?.space || !meta?.id) {
294 contentEl.createEl("p", {
295 text: "Not a sublimated file.",
296 cls: "sbl-version-empty"
297 });
298 return;
299 }
300 contentEl.createEl("h4", {
301 text: activeFile.name,
302 cls: "sbl-version-filename"
303 });
304 const loading = contentEl.createEl("p", {
305 text: "Loading versions...",
306 cls: "sbl-version-loading"
307 });
308 try {
309 const api = new SublimatedApi(this.plugin.settings.apiToken);
310 const versions = await api.getVersions(meta.space, meta.id);
311 loading.remove();
312 if (versions.length === 0) {
313 contentEl.createEl("p", {
314 text: "No versions found.",
315 cls: "sbl-version-empty"
316 });
317 return;
318 }
319 const list = contentEl.createEl("div", {
320 cls: "sbl-version-list"
321 });
322 const groups = groupByDay(versions);
323 for (const group of groups) {
324 list.createEl("div", {
325 text: group.label,
326 cls: "sbl-version-day"
327 });
328 for (const v of group.versions) {
329 this.renderVersion(list, v, meta.url);
330 }
331 }
332 } catch (e) {
333 loading.remove();
334 if (e instanceof AuthRequiredError) {
335 contentEl.createEl("p", {
336 text: "This file is private \u2014 add an API token to view version history.",
337 cls: "sbl-version-empty"
338 });
339 contentEl.createEl("p", {
340 text: "Configure in Settings \u2192 sublimated",
341 cls: "sbl-version-hint"
342 });
343 return;
344 }
345 contentEl.createEl("p", {
346 text: `Error: ${e instanceof Error ? e.message : "Unknown"}`,
347 cls: "sbl-version-error"
348 });
349 }
350 }
351 renderVersion(container, v, fileUrl) {
352 const item = container.createDiv({ cls: "sbl-version-item" });
353 const header = item.createDiv({ cls: "sbl-version-header" });
354 header.createEl("span", {
355 text: `v${v.number}`,
356 cls: "sbl-version-number"
357 });
358 header.createEl("span", {
359 text: v.authorName,
360 cls: "sbl-version-author"
361 });
362 const time = item.createDiv({ cls: "sbl-version-time" });
363 time.createEl("span", {
364 text: formatRelativeTime(v.createdAt),
365 attr: { title: new Date(v.createdAt).toLocaleString() }
366 });
367 if (v.message) {
368 item.createDiv({
369 text: v.message,
370 cls: "sbl-version-message"
371 });
372 }
373 if (fileUrl) {
374 const webUrl = `https://${fileUrl}/~versions/v${v.number}`;
375 const link = item.createEl("a", {
376 text: "view on web",
377 cls: "sbl-version-web-link",
378 href: webUrl
379 });
380 link.addEventListener("click", (e) => {
381 e.preventDefault();
382 window.open(webUrl);
383 });
384 }
385 }
386 async onClose() {
387 }
388};
389function groupByDay(versions) {
390 const groups = /* @__PURE__ */ new Map();
391 const order = [];
392 for (const v of versions) {
393 const label = formatDayLabel(v.createdAt);
394 if (!groups.has(label)) {
395 groups.set(label, []);
396 order.push(label);
397 }
398 groups.get(label).push(v);
399 }
400 return order.map((label) => ({
401 label,
402 versions: groups.get(label)
403 }));
404}
405function formatDayLabel(iso) {
406 const now = /* @__PURE__ */ new Date();
407 const date = new Date(iso);
408 const nowDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
409 const thenDay = new Date(
410 date.getFullYear(),
411 date.getMonth(),
412 date.getDate()
413 );
414 const days = Math.round(
415 (nowDay.getTime() - thenDay.getTime()) / (1e3 * 60 * 60 * 24)
416 );
417 if (days === 0) return "Today";
418 if (days === 1) return "Yesterday";
419 if (days < 7) return `${days} days ago`;
420 return date.toLocaleDateString("en-US", {
421 month: "short",
422 day: "numeric"
423 });
424}
425function formatRelativeTime(iso) {
426 const now = Date.now();
427 const then = new Date(iso).getTime();
428 const diff = now - then;
429 const seconds = Math.floor(diff / 1e3);
430 const minutes = Math.floor(seconds / 60);
431 const hours = Math.floor(minutes / 60);
432 const days = Math.floor(hours / 24);
433 if (days > 30) return new Date(iso).toLocaleDateString();
434 if (days > 0) return `${days}d ago`;
435 if (hours > 0) return `${hours}h ago`;
436 if (minutes > 0) return `${minutes}m ago`;
437 return "just now";
438}
439
440// src/welcome-modal.ts
441var import_obsidian5 = require("obsidian");
442var WelcomeModal = class extends import_obsidian5.Modal {
443 constructor(app) {
444 super(app);
445 }
446 onOpen() {
447 const { contentEl } = this;
448 contentEl.addClass("sbl-welcome-modal");
449 contentEl.createEl("h2", { text: "Welcome to sublimated" });
450 contentEl.createEl("p", {
451 text: "sublimated syncs your files with version history, collaboration, and a web viewer \u2014 all powered by git under the hood."
452 });
453 const steps = contentEl.createEl("div", { cls: "sbl-welcome-steps" });
454 const step1 = steps.createDiv({ cls: "sbl-welcome-step" });
455 step1.createEl("strong", { text: "1. Open a sublimated folder as a vault" });
456 step1.createEl("p", {
457 text: "In Finder, open any sublimated folder (or subfolder) under CloudStorage as an Obsidian vault. The plugin activates automatically on synced files."
458 });
459 const step2 = steps.createDiv({ cls: "sbl-welcome-step" });
460 step2.createEl("strong", { text: "2. Status bar shows sync info" });
461 step2.createEl("p", {
462 text: "The bottom bar shows version number, sync status, and author for the current file \u2014 all read from local file metadata, no account needed."
463 });
464 const step3 = steps.createDiv({ cls: "sbl-welcome-step" });
465 step3.createEl("strong", { text: "3. Add an API token for version history" });
466 step3.createEl("p", {
467 text: "To browse past versions, add your API token in Settings \u2192 sublimated. Generate one at sublimated.com/settings/tokens."
468 });
469 const links = contentEl.createDiv({ cls: "sbl-welcome-links" });
470 links.createEl("a", {
471 text: "Documentation",
472 href: "https://sublimated.com/docs/obsidian"
473 });
474 links.createEl("br");
475 links.createEl("a", {
476 text: "Source code",
477 href: "https://sublimated.com/sublimated/code/obsidian"
478 });
479 const footer = contentEl.createDiv({ cls: "sbl-welcome-footer" });
480 footer.createEl("button", {
481 text: "Get started",
482 cls: "mod-cta"
483 }).addEventListener("click", () => this.close());
484 }
485 onClose() {
486 this.contentEl.empty();
487 }
488};
489
490// src/main.ts
491var SublimatedPlugin = class extends import_obsidian6.Plugin {
492 settings = DEFAULT_SETTINGS;
493 lastMetadata = null;
494 statusBarEl = null;
495 async onload() {
496 await this.loadSettings();
497 this.statusBarEl = this.addStatusBarItem();
498 this.statusBarEl.addClass("sbl-status-bar");
499 this.statusBarEl.addEventListener(
500 "click",
501 () => this.openCurrentFileOnWeb()
502 );
503 this.updateStatusBar(null);
504 this.registerView(
505 VERSION_VIEW_TYPE,
506 (leaf) => new VersionView(leaf, this)
507 );
508 this.addCommand({
509 id: "open-on-web",
510 name: "Open on sublimated.com",
511 callback: () => this.openCurrentFileOnWeb()
512 });
513 this.addCommand({
514 id: "show-versions",
515 name: "Show versions",
516 callback: () => this.activateVersionView()
517 });
518 this.addCommand({
519 id: "copy-web-link",
520 name: "Copy web link",
521 callback: () => this.copyWebLink()
522 });
523 this.addRibbonIcon("history", "sublimated versions", () => {
524 this.activateVersionView();
525 });
526 this.registerObsidianProtocolHandler(
527 "sublimated",
528 (params) => this.handleUri(params)
529 );
530 this.addSettingTab(new SublimatedSettingTab(this.app, this));
531 this.registerEvent(
532 this.app.workspace.on("active-leaf-change", () => {
533 this.refreshMetadata();
534 })
535 );
536 this.refreshMetadata();
537 if (!this.settings.hasSeenWelcome) {
538 new WelcomeModal(this.app).open();
539 this.settings.hasSeenWelcome = true;
540 await this.saveSettings();
541 }
542 }
543 onunload() {
544 this.app.workspace.detachLeavesOfType(VERSION_VIEW_TYPE);
545 }
546 async loadSettings() {
547 this.settings = Object.assign(
548 {},
549 DEFAULT_SETTINGS,
550 await this.loadData()
551 );
552 }
553 async saveSettings() {
554 await this.saveData(this.settings);
555 }
556 // detect workspace from xattrs on the currently active file.
557 // every synced file carries com.sublimated.space#PS, so any file
558 // with sublimated metadata identifies the workspace.
559 detectWorkspace() {
560 if (!this.lastMetadata) return null;
561 if (!this.lastMetadata.space && !this.lastMetadata.id) return null;
562 return {
563 space: this.lastMetadata.space ?? null
564 };
565 }
566 async refreshMetadata() {
567 const file = this.app.workspace.getActiveFile();
568 if (!file) {
569 this.lastMetadata = null;
570 this.updateStatusBar(null);
571 return;
572 }
573 if (!isMacOS()) {
574 this.lastMetadata = null;
575 this.updateStatusBar(null);
576 return;
577 }
578 const adapter = this.app.vault.adapter;
579 const basePath = adapter.getBasePath?.();
580 if (!basePath) {
581 this.lastMetadata = null;
582 this.updateStatusBar(null);
583 return;
584 }
585 const absPath = `${basePath}/${file.path}`;
586 this.lastMetadata = await readFileMetadata(absPath);
587 this.updateStatusBar(this.lastMetadata);
588 }
589 updateStatusBar(meta) {
590 if (!this.statusBarEl) return;
591 if (!meta || !meta.space && !meta.id) {
592 this.statusBarEl.setText("");
593 return;
594 }
595 const parts = [];
596 if (meta.syncStatus) {
597 const icon = syncStatusIcon(meta.syncStatus);
598 parts.push(icon ? `${icon} ${meta.syncStatus}` : meta.syncStatus);
599 }
600 if (meta.version) {
601 parts.push(`v${meta.version}`);
602 }
603 if (meta.modifiedBy) {
604 const prefix = meta.modifiedByType === "ai_agent" ? "by AI:" : "by";
605 parts.push(`${prefix} ${meta.modifiedBy}`);
606 }
607 if (meta.branch && meta.branch !== "main") {
608 parts.push(meta.branch);
609 }
610 if (meta.visibility) {
611 parts.push(visibilityIcon(meta.visibility));
612 }
613 this.statusBarEl.setText(parts.join(" "));
614 if (meta.url) {
615 this.statusBarEl.addClass("sbl-clickable");
616 } else {
617 this.statusBarEl.removeClass("sbl-clickable");
618 }
619 }
620 openCurrentFileOnWeb() {
621 const url = this.lastMetadata?.url;
622 if (!url) {
623 new import_obsidian6.Notice("No sublimated URL for this file");
624 return;
625 }
626 window.open(`https://${url}`);
627 }
628 async copyWebLink() {
629 const url = this.lastMetadata?.url;
630 if (!url) {
631 new import_obsidian6.Notice("No sublimated URL for this file");
632 return;
633 }
634 await navigator.clipboard.writeText(`https://${url}`);
635 new import_obsidian6.Notice("Link copied to clipboard");
636 }
637 async activateVersionView() {
638 const existing = this.app.workspace.getLeavesOfType(VERSION_VIEW_TYPE);
639 if (existing.length > 0) {
640 this.app.workspace.revealLeaf(existing[0]);
641 return;
642 }
643 const leaf = this.app.workspace.getRightLeaf(false);
644 if (!leaf) return;
645 await leaf.setViewState({
646 type: VERSION_VIEW_TYPE,
647 active: true
648 });
649 this.app.workspace.revealLeaf(leaf);
650 }
651 handleUri(params) {
652 const { action, file } = params;
653 if (action === "open" && file) {
654 const normalized = (0, import_obsidian6.normalizePath)(file);
655 const existing = this.app.vault.getAbstractFileByPath(normalized);
656 if (existing) {
657 this.app.workspace.openLinkText(normalized, "", false);
658 } else {
659 new import_obsidian6.Notice(`File not found: ${file}`);
660 }
661 return;
662 }
663 if (action === "auth" && params.token) {
664 this.settings.apiToken = params.token;
665 this.saveSettings();
666 new import_obsidian6.Notice("sublimated: signed in successfully");
667 return;
668 }
669 new import_obsidian6.Notice(`Unknown sublimated action: ${action}`);
670 }
671};
672function syncStatusIcon(status) {
673 switch (status) {
674 case "synced":
675 return "\u2713";
676 case "uploading":
677 return "\u2191";
678 case "downloading":
679 return "\u2193";
680 case "error":
681 return "!";
682 case "conflict":
683 return "\u26A0";
684 default:
685 return "";
686 }
687}
688function visibilityIcon(visibility) {
689 switch (visibility) {
690 case "public":
691 return "\u{1F310}";
692 case "shared":
693 return "\u{1F465}";
694 case "private":
695 return "\u{1F512}";
696 default:
697 return "";
698 }
699}
700//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsic3JjL21haW4udHMiLCAic3JjL3NldHRpbmdzLnRzIiwgInNyYy9hcGkudHMiLCAic3JjL3hhdHRyLnRzIiwgInNyYy92ZXJzaW9uLXZpZXcudHMiLCAic3JjL3dlbGNvbWUtbW9kYWwudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImltcG9ydCB7XG5cdE5vdGljZSxcblx0UGx1Z2luLFxuXHRXb3Jrc3BhY2VMZWFmLFxuXHRub3JtYWxpemVQYXRoLFxufSBmcm9tIFwib2JzaWRpYW5cIjtcbmltcG9ydCB7XG5cdHR5cGUgU3VibGltYXRlZFNldHRpbmdzLFxuXHRERUZBVUxUX1NFVFRJTkdTLFxuXHRTdWJsaW1hdGVkU2V0dGluZ1RhYixcbn0gZnJvbSBcIi4vc2V0dGluZ3NcIjtcbmltcG9ydCB7IHR5cGUgRmlsZU1ldGFkYXRhLCByZWFkRmlsZU1ldGFkYXRhLCBpc01hY09TIH0gZnJvbSBcIi4veGF0dHJcIjtcbmltcG9ydCB7IFZlcnNpb25WaWV3LCBWRVJTSU9OX1ZJRVdfVFlQRSB9IGZyb20gXCIuL3ZlcnNpb24tdmlld1wiO1xuaW1wb3J0IHsgV2VsY29tZU1vZGFsIH0gZnJvbSBcIi4vd2VsY29tZS1tb2RhbFwiO1xuXG5leHBvcnQgZGVmYXVsdCBjbGFzcyBTdWJsaW1hdGVkUGx1Z2luIGV4dGVuZHMgUGx1Z2luIHtcblx0c2V0dGluZ3M6IFN1YmxpbWF0ZWRTZXR0aW5ncyA9IERFRkFVTFRfU0VUVElOR1M7XG5cdGxhc3RNZXRhZGF0YTogRmlsZU1ldGFkYXRhIHwgbnVsbCA9IG51bGw7XG5cblx0cHJpdmF0ZSBzdGF0dXNCYXJFbDogSFRNTEVsZW1lbnQgfCBudWxsID0gbnVsbDtcblxuXHRhc3luYyBvbmxvYWQoKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0YXdhaXQgdGhpcy5sb2FkU2V0dGluZ3MoKTtcblxuXHRcdC8vIHN0YXR1cyBiYXJcblx0XHR0aGlzLnN0YXR1c0JhckVsID0gdGhpcy5hZGRTdGF0dXNCYXJJdGVtKCk7XG5cdFx0dGhpcy5zdGF0dXNCYXJFbC5hZGRDbGFzcyhcInNibC1zdGF0dXMtYmFyXCIpO1xuXHRcdHRoaXMuc3RhdHVzQmFyRWwuYWRkRXZlbnRMaXN0ZW5lcihcImNsaWNrXCIsICgpID0+XG5cdFx0XHR0aGlzLm9wZW5DdXJyZW50RmlsZU9uV2ViKClcblx0XHQpO1xuXHRcdHRoaXMudXBkYXRlU3RhdHVzQmFyKG51bGwpO1xuXG5cdFx0Ly8gcmVnaXN0ZXIgdmVyc2lvbiBoaXN0b3J5IHZpZXdcblx0XHR0aGlzLnJlZ2lzdGVyVmlldyhcblx0XHRcdFZFUlNJT05fVklFV19UWVBFLFxuXHRcdFx0KGxlYWYpID0+IG5ldyBWZXJzaW9uVmlldyhsZWFmLCB0aGlzKVxuXHRcdCk7XG5cblx0XHQvLyBjb21tYW5kc1xuXHRcdHRoaXMuYWRkQ29tbWFuZCh7XG5cdFx0XHRpZDogXCJvcGVuLW9uLXdlYlwiLFxuXHRcdFx0bmFtZTogXCJPcGVuIG9uIHN1YmxpbWF0ZWQuY29tXCIsXG5cdFx0XHRjYWxsYmFjazogKCkgPT4gdGhpcy5vcGVuQ3VycmVudEZpbGVPbldlYigpLFxuXHRcdH0pO1xuXG5cdFx0dGhpcy5hZGRDb21tYW5kKHtcblx0XHRcdGlkOiBcInNob3ctdmVyc2lvbnNcIixcblx0XHRcdG5hbWU6IFwiU2hvdyB2ZXJzaW9uc1wiLFxuXHRcdFx0Y2FsbGJhY2s6ICgpID0+IHRoaXMuYWN0aXZhdGVWZXJzaW9uVmlldygpLFxuXHRcdH0pO1xuXG5cdFx0dGhpcy5hZGRDb21tYW5kKHtcblx0XHRcdGlkOiBcImNvcHktd2ViLWxpbmtcIixcblx0XHRcdG5hbWU6IFwiQ29weSB3ZWIgbGlua1wiLFxuXHRcdFx0Y2FsbGJhY2s6ICgpID0+IHRoaXMuY29weVdlYkxpbmsoKSxcblx0XHR9KTtcblxuXHRcdC8vIHJpYmJvbiBpY29uIGZvciB2ZXJzaW9uIHNpZGViYXJcblx0XHR0aGlzLmFkZFJpYmJvbkljb24oXCJoaXN0b3J5XCIsIFwic3VibGltYXRlZCB2ZXJzaW9uc1wiLCAoKSA9PiB7XG5cdFx0XHR0aGlzLmFjdGl2YXRlVmVyc2lvblZpZXcoKTtcblx0XHR9KTtcblxuXHRcdC8vIFVSSSBoYW5kbGVyOiBvYnNpZGlhbjovL3N1YmxpbWF0ZWQ/YWN0aW9uPW9wZW4mZmlsZT1wYXRoL3RvL25vdGUubWRcblx0XHR0aGlzLnJlZ2lzdGVyT2JzaWRpYW5Qcm90b2NvbEhhbmRsZXIoXG5cdFx0XHRcInN1YmxpbWF0ZWRcIixcblx0XHRcdChwYXJhbXMpID0+IHRoaXMuaGFuZGxlVXJpKHBhcmFtcylcblx0XHQpO1xuXG5cdFx0Ly8gc2V0dGluZ3MgdGFiXG5cdFx0dGhpcy5hZGRTZXR0aW5nVGFiKG5ldyBTdWJsaW1hdGVkU2V0dGluZ1RhYih0aGlzLmFwcCwgdGhpcykpO1xuXG5cdFx0Ly8gdXBkYXRlIHN0YXR1cyBiYXIgd2hlbiBhY3RpdmUgZmlsZSBjaGFuZ2VzXG5cdFx0dGhpcy5yZWdpc3RlckV2ZW50KFxuXHRcdFx0dGhpcy5hcHAud29ya3NwYWNlLm9uKFwiYWN0aXZlLWxlYWYtY2hhbmdlXCIsICgpID0+IHtcblx0XHRcdFx0dGhpcy5yZWZyZXNoTWV0YWRhdGEoKTtcblx0XHRcdH0pXG5cdFx0KTtcblxuXHRcdC8vIGluaXRpYWwgbWV0YWRhdGEgcmVhZFxuXHRcdHRoaXMucmVmcmVzaE1ldGFkYXRhKCk7XG5cblx0XHQvLyB3ZWxjb21lIG1vZGFsIG9uIGZpcnN0IGluc3RhbGxcblx0XHRpZiAoIXRoaXMuc2V0dGluZ3MuaGFzU2VlbldlbGNvbWUpIHtcblx0XHRcdG5ldyBXZWxjb21lTW9kYWwodGhpcy5hcHApLm9wZW4oKTtcblx0XHRcdHRoaXMuc2V0dGluZ3MuaGFzU2VlbldlbGNvbWUgPSB0cnVlO1xuXHRcdFx0YXdhaXQgdGhpcy5zYXZlU2V0dGluZ3MoKTtcblx0XHR9XG5cdH1cblxuXHRvbnVubG9hZCgpOiB2b2lkIHtcblx0XHR0aGlzLmFwcC53b3Jrc3BhY2UuZGV0YWNoTGVhdmVzT2ZUeXBlKFZFUlNJT05fVklFV19UWVBFKTtcblx0fVxuXG5cdGFzeW5jIGxvYWRTZXR0aW5ncygpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHR0aGlzLnNldHRpbmdzID0gT2JqZWN0LmFzc2lnbihcblx0XHRcdHt9LFxuXHRcdFx0REVGQVVMVF9TRVRUSU5HUyxcblx0XHRcdGF3YWl0IHRoaXMubG9hZERhdGEoKVxuXHRcdCk7XG5cdH1cblxuXHRhc3luYyBzYXZlU2V0dGluZ3MoKTogUHJvbWlzZTx2b2lkPiB7XG5cdFx0YXdhaXQgdGhpcy5zYXZlRGF0YSh0aGlzLnNldHRpbmdzKTtcblx0fVxuXG5cdC8vIGRldGVjdCB3b3Jrc3BhY2UgZnJvbSB4YXR0cnMgb24gdGhlIGN1cnJlbnRseSBhY3RpdmUgZmlsZS5cblx0Ly8gZXZlcnkgc3luY2VkIGZpbGUgY2FycmllcyBjb20uc3VibGltYXRlZC5zcGFjZSNQUywgc28gYW55IGZpbGVcblx0Ly8gd2l0aCBzdWJsaW1hdGVkIG1ldGFkYXRhIGlkZW50aWZpZXMgdGhlIHdvcmtzcGFjZS5cblx0ZGV0ZWN0V29ya3NwYWNlKCk6IHsgc3BhY2U6IHN0cmluZyB8IG51bGwgfSB8IG51bGwge1xuXHRcdGlmICghdGhpcy5sYXN0TWV0YWRhdGEpIHJldHVybiBudWxsO1xuXHRcdGlmICghdGhpcy5sYXN0TWV0YWRhdGEuc3BhY2UgJiYgIXRoaXMubGFzdE1ldGFkYXRhLmlkKSByZXR1cm4gbnVsbDtcblx0XHRyZXR1cm4ge1xuXHRcdFx0c3BhY2U6IHRoaXMubGFzdE1ldGFkYXRhLnNwYWNlID8/IG51bGwsXG5cdFx0fTtcblx0fVxuXG5cdHByaXZhdGUgYXN5bmMgcmVmcmVzaE1ldGFkYXRhKCk6IFByb21pc2U8dm9pZD4ge1xuXHRcdGNvbnN0IGZpbGUgPSB0aGlzLmFwcC53b3Jrc3BhY2UuZ2V0QWN0aXZlRmlsZSgpO1xuXHRcdGlmICghZmlsZSkge1xuXHRcdFx0dGhpcy5sYXN0TWV0YWRhdGEgPSBudWxsO1xuXHRcdFx0dGhpcy51cGRhdGVTdGF0dXNCYXIobnVsbCk7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXG5cdFx0aWYgKCFpc01hY09TKCkpIHtcblx0XHRcdHRoaXMubGFzdE1ldGFkYXRhID0gbnVsbDtcblx0XHRcdHRoaXMudXBkYXRlU3RhdHVzQmFyKG51bGwpO1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblxuXHRcdC8vIGdldCBhYnNvbHV0ZSBwYXRoIHZpYSB2YXVsdCBhZGFwdGVyXG5cdFx0Y29uc3QgYWRhcHRlciA9IHRoaXMuYXBwLnZhdWx0LmFkYXB0ZXI7XG5cdFx0Y29uc3QgYmFzZVBhdGggPSAoYWRhcHRlciBhcyB7IGdldEJhc2VQYXRoPzogKCkgPT4gc3RyaW5nIH0pXG5cdFx0XHQuZ2V0QmFzZVBhdGg/LigpO1xuXHRcdGlmICghYmFzZVBhdGgpIHtcblx0XHRcdHRoaXMubGFzdE1ldGFkYXRhID0gbnVsbDtcblx0XHRcdHRoaXMudXBkYXRlU3RhdHVzQmFyKG51bGwpO1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblxuXHRcdGNvbnN0IGFic1BhdGggPSBgJHtiYXNlUGF0aH0vJHtmaWxlLnBhdGh9YDtcblx0XHR0aGlzLmxhc3RNZXRhZGF0YSA9IGF3YWl0IHJlYWRGaWxlTWV0YWRhdGEoYWJzUGF0aCk7XG5cdFx0dGhpcy51cGRhdGVTdGF0dXNCYXIodGhpcy5sYXN0TWV0YWRhdGEpO1xuXHR9XG5cblx0cHJpdmF0ZSB1cGRhdGVTdGF0dXNCYXIobWV0YTogRmlsZU1ldGFkYXRhIHwgbnVsbCk6IHZvaWQge1xuXHRcdGlmICghdGhpcy5zdGF0dXNCYXJFbCkgcmV0dXJuO1xuXG5cdFx0Ly8gbm8gc3VibGltYXRlZCB4YXR0cnMgXHUyMTkyIGZpbGUgaXNuJ3QgdHJhY2tlZCBieSBzdWJsaW1hdGVkLCBoaWRlIHN0YXR1cyBiYXJcblx0XHRpZiAoIW1ldGEgfHwgKCFtZXRhLnNwYWNlICYmICFtZXRhLmlkKSkge1xuXHRcdFx0dGhpcy5zdGF0dXNCYXJFbC5zZXRUZXh0KFwiXCIpO1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblxuXHRcdC8vIGNvbXBhY3QgZm9ybWF0OiBcIlx1MjcxMyBzeW5jZWQgdjIxIGJ5IGFudG9uXCIgKHN5bmMgKyB2ZXJzaW9uICsgYXV0aG9yICsgb3B0aW9uYWwgYnJhbmNoL3Zpc2liaWxpdHkpXG5cdFx0Y29uc3QgcGFydHM6IHN0cmluZ1tdID0gW107XG5cblx0XHRpZiAobWV0YS5zeW5jU3RhdHVzKSB7XG5cdFx0XHRjb25zdCBpY29uID0gc3luY1N0YXR1c0ljb24obWV0YS5zeW5jU3RhdHVzKTtcblx0XHRcdHBhcnRzLnB1c2goaWNvbiA/IGAke2ljb259ICR7bWV0YS5zeW5jU3RhdHVzfWAgOiBtZXRhLnN5bmNTdGF0dXMpO1xuXHRcdH1cblxuXHRcdGlmIChtZXRhLnZlcnNpb24pIHtcblx0XHRcdHBhcnRzLnB1c2goYHYke21ldGEudmVyc2lvbn1gKTtcblx0XHR9XG5cblx0XHRpZiAobWV0YS5tb2RpZmllZEJ5KSB7XG5cdFx0XHRjb25zdCBwcmVmaXggPVxuXHRcdFx0XHRtZXRhLm1vZGlmaWVkQnlUeXBlID09PSBcImFpX2FnZW50XCIgPyBcImJ5IEFJOlwiIDogXCJieVwiO1xuXHRcdFx0cGFydHMucHVzaChgJHtwcmVmaXh9ICR7bWV0YS5tb2RpZmllZEJ5fWApO1xuXHRcdH1cblxuXHRcdGlmIChtZXRhLmJyYW5jaCAmJiBtZXRhLmJyYW5jaCAhPT0gXCJtYWluXCIpIHtcblx0XHRcdHBhcnRzLnB1c2gobWV0YS5icmFuY2gpO1xuXHRcdH1cblxuXHRcdGlmIChtZXRhLnZpc2liaWxpdHkpIHtcblx0XHRcdHBhcnRzLnB1c2godmlzaWJpbGl0eUljb24obWV0YS52aXNpYmlsaXR5KSk7XG5cdFx0fVxuXG5cdFx0dGhpcy5zdGF0dXNCYXJFbC5zZXRUZXh0KHBhcnRzLmpvaW4oXCIgXCIpKTtcblxuXHRcdC8vIHNob3cgcG9pbnRlciBjdXJzb3Igd2hlbiBVUkwgYXZhaWxhYmxlXG5cdFx0aWYgKG1ldGEudXJsKSB7XG5cdFx0XHR0aGlzLnN0YXR1c0JhckVsLmFkZENsYXNzKFwic2JsLWNsaWNrYWJsZVwiKTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0dGhpcy5zdGF0dXNCYXJFbC5yZW1vdmVDbGFzcyhcInNibC1jbGlja2FibGVcIik7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSBvcGVuQ3VycmVudEZpbGVPbldlYigpOiB2b2lkIHtcblx0XHRjb25zdCB1cmwgPSB0aGlzLmxhc3RNZXRhZGF0YT8udXJsO1xuXHRcdGlmICghdXJsKSB7XG5cdFx0XHRuZXcgTm90aWNlKFwiTm8gc3VibGltYXRlZCBVUkwgZm9yIHRoaXMgZmlsZVwiKTtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdFx0d2luZG93Lm9wZW4oYGh0dHBzOi8vJHt1cmx9YCk7XG5cdH1cblxuXHRwcml2YXRlIGFzeW5jIGNvcHlXZWJMaW5rKCk6IFByb21pc2U8dm9pZD4ge1xuXHRcdGNvbnN0IHVybCA9IHRoaXMubGFzdE1ldGFkYXRhPy51cmw7XG5cdFx0aWYgKCF1cmwpIHtcblx0XHRcdG5ldyBOb3RpY2UoXCJObyBzdWJsaW1hdGVkIFVSTCBmb3IgdGhpcyBmaWxlXCIpO1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblx0XHRhd2FpdCBuYXZpZ2F0b3IuY2xpcGJvYXJkLndyaXRlVGV4dChgaHR0cHM6Ly8ke3VybH1gKTtcblx0XHRuZXcgTm90aWNlKFwiTGluayBjb3BpZWQgdG8gY2xpcGJvYXJkXCIpO1xuXHR9XG5cblx0cHJpdmF0ZSBhc3luYyBhY3RpdmF0ZVZlcnNpb25WaWV3KCk6IFByb21pc2U8dm9pZD4ge1xuXHRcdGNvbnN0IGV4aXN0aW5nID1cblx0XHRcdHRoaXMuYXBwLndvcmtzcGFjZS5nZXRMZWF2ZXNPZlR5cGUoVkVSU0lPTl9WSUVXX1RZUEUpO1xuXHRcdGlmIChleGlzdGluZy5sZW5ndGggPiAwKSB7XG5cdFx0XHR0aGlzLmFwcC53b3Jrc3BhY2UucmV2ZWFsTGVhZihleGlzdGluZ1swXSk7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXG5cdFx0Y29uc3QgbGVhZiA9IHRoaXMuYXBwLndvcmtzcGFjZS5nZXRSaWdodExlYWYoZmFsc2UpO1xuXHRcdGlmICghbGVhZikgcmV0dXJuO1xuXHRcdGF3YWl0IGxlYWYuc2V0Vmlld1N0YXRlKHtcblx0XHRcdHR5cGU6IFZFUlNJT05fVklFV19UWVBFLFxuXHRcdFx0YWN0aXZlOiB0cnVlLFxuXHRcdH0pO1xuXHRcdHRoaXMuYXBwLndvcmtzcGFjZS5yZXZlYWxMZWFmKGxlYWYpO1xuXHR9XG5cblx0cHJpdmF0ZSBoYW5kbGVVcmkocGFyYW1zOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+KTogdm9pZCB7XG5cdFx0Y29uc3QgeyBhY3Rpb24sIGZpbGUgfSA9IHBhcmFtcztcblxuXHRcdGlmIChhY3Rpb24gPT09IFwib3BlblwiICYmIGZpbGUpIHtcblx0XHRcdGNvbnN0IG5vcm1hbGl6ZWQgPSBub3JtYWxpemVQYXRoKGZpbGUpO1xuXHRcdFx0Y29uc3QgZXhpc3RpbmcgPSB0aGlzLmFwcC52YXVsdC5nZXRBYnN0cmFjdEZpbGVCeVBhdGgobm9ybWFsaXplZCk7XG5cdFx0XHRpZiAoZXhpc3RpbmcpIHtcblx0XHRcdFx0dGhpcy5hcHAud29ya3NwYWNlLm9wZW5MaW5rVGV4dChub3JtYWxpemVkLCBcIlwiLCBmYWxzZSk7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRuZXcgTm90aWNlKGBGaWxlIG5vdCBmb3VuZDogJHtmaWxlfWApO1xuXHRcdFx0fVxuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblxuXHRcdGlmIChhY3Rpb24gPT09IFwiYXV0aFwiICYmIHBhcmFtcy50b2tlbikge1xuXHRcdFx0Ly8gZnV0dXJlOiBPQXV0aCBmbG93IHN0b3JlcyB0b2tlbiBmcm9tIHJlZGlyZWN0XG5cdFx0XHR0aGlzLnNldHRpbmdzLmFwaVRva2VuID0gcGFyYW1zLnRva2VuO1xuXHRcdFx0dGhpcy5zYXZlU2V0dGluZ3MoKTtcblx0XHRcdG5ldyBOb3RpY2UoXCJzdWJsaW1hdGVkOiBzaWduZWQgaW4gc3VjY2Vzc2Z1bGx5XCIpO1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblxuXHRcdG5ldyBOb3RpY2UoYFVua25vd24gc3VibGltYXRlZCBhY3Rpb246ICR7YWN0aW9ufWApO1xuXHR9XG59XG5cbmZ1bmN0aW9uIHN5bmNTdGF0dXNJY29uKHN0YXR1czogc3RyaW5nKTogc3RyaW5nIHtcblx0c3dpdGNoIChzdGF0dXMpIHtcblx0XHRjYXNlIFwic3luY2VkXCI6XG5cdFx0XHRyZXR1cm4gXCJcdTI3MTNcIjtcblx0XHRjYXNlIFwidXBsb2FkaW5nXCI6XG5cdFx0XHRyZXR1cm4gXCJcdTIxOTFcIjtcblx0XHRjYXNlIFwiZG93bmxvYWRpbmdcIjpcblx0XHRcdHJldHVybiBcIlx1MjE5M1wiO1xuXHRcdGNhc2UgXCJlcnJvclwiOlxuXHRcdFx0cmV0dXJuIFwiIVwiO1xuXHRcdGNhc2UgXCJjb25mbGljdFwiOlxuXHRcdFx0cmV0dXJuIFwiXHUyNkEwXCI7XG5cdFx0ZGVmYXVsdDpcblx0XHRcdHJldHVybiBcIlwiO1xuXHR9XG59XG5cbmZ1bmN0aW9uIHZpc2liaWxpdHlJY29uKHZpc2liaWxpdHk6IHN0cmluZyk6IHN0cmluZyB7XG5cdHN3aXRjaCAodmlzaWJpbGl0eSkge1xuXHRcdGNhc2UgXCJwdWJsaWNcIjpcblx0XHRcdHJldHVybiBcIlx1RDgzQ1x1REYxMFwiO1xuXHRcdGNhc2UgXCJzaGFyZWRcIjpcblx0XHRcdHJldHVybiBcIlx1RDgzRFx1REM2NVwiO1xuXHRcdGNhc2UgXCJwcml2YXRlXCI6XG5cdFx0XHRyZXR1cm4gXCJcdUQ4M0RcdUREMTJcIjtcblx0XHRkZWZhdWx0OlxuXHRcdFx0cmV0dXJuIFwiXCI7XG5cdH1cbn1cbiIsICJpbXBvcnQgeyBBcHAsIE5vdGljZSwgUGx1Z2luU2V0dGluZ1RhYiwgU2V0dGluZyB9IGZyb20gXCJvYnNpZGlhblwiO1xuaW1wb3J0IHR5cGUgU3VibGltYXRlZFBsdWdpbiBmcm9tIFwiLi9tYWluXCI7XG5pbXBvcnQgeyBTdWJsaW1hdGVkQXBpIH0gZnJvbSBcIi4vYXBpXCI7XG5cbmV4cG9ydCBpbnRlcmZhY2UgU3VibGltYXRlZFNldHRpbmdzIHtcblx0YXBpVG9rZW46IHN0cmluZztcblx0aGFzU2VlbldlbGNvbWU6IGJvb2xlYW47XG59XG5cbmV4cG9ydCBjb25zdCBERUZBVUxUX1NFVFRJTkdTOiBTdWJsaW1hdGVkU2V0dGluZ3MgPSB7XG5cdGFwaVRva2VuOiBcIlwiLFxuXHRoYXNTZWVuV2VsY29tZTogZmFsc2UsXG59O1xuXG5leHBvcnQgY2xhc3MgU3VibGltYXRlZFNldHRpbmdUYWIgZXh0ZW5kcyBQbHVnaW5TZXR0aW5nVGFiIHtcblx0cGx1Z2luOiBTdWJsaW1hdGVkUGx1Z2luO1xuXG5cdGNvbnN0cnVjdG9yKGFwcDogQXBwLCBwbHVnaW46IFN1YmxpbWF0ZWRQbHVnaW4pIHtcblx0XHRzdXBlcihhcHAsIHBsdWdpbik7XG5cdFx0dGhpcy5wbHVnaW4gPSBwbHVnaW47XG5cdH1cblxuXHRkaXNwbGF5KCk6IHZvaWQge1xuXHRcdGNvbnN0IHsgY29udGFpbmVyRWwgfSA9IHRoaXM7XG5cdFx0Y29udGFpbmVyRWwuZW1wdHkoKTtcblxuXHRcdC8vIGJyYW5kaW5nXG5cdFx0Y29uc3QgaGVhZGVyID0gY29udGFpbmVyRWwuY3JlYXRlRGl2KHsgY2xzOiBcInNibC1zZXR0aW5ncy1oZWFkZXJcIiB9KTtcblx0XHRoZWFkZXIuY3JlYXRlRWwoXCJoMlwiLCB7IHRleHQ6IFwic3VibGltYXRlZFwiIH0pO1xuXHRcdGhlYWRlci5jcmVhdGVFbChcInBcIiwge1xuXHRcdFx0dGV4dDogXCJTeW5jIHN0YXR1cywgdmVyc2lvbiBoaXN0b3J5LCBhbmQgZGVlcCBsaW5rcy5cIixcblx0XHRcdGNsczogXCJzZXR0aW5nLWl0ZW0tZGVzY3JpcHRpb25cIixcblx0XHR9KTtcblxuXHRcdC8vIHdvcmtzcGFjZSBkZXRlY3Rpb24gKGJhc2VkIG9uIHhhdHRycyBvZiB0aGUgbGFzdCBvcGVuZWQgZmlsZSlcblx0XHRjb25zdCB3b3Jrc3BhY2VJbmZvID0gdGhpcy5wbHVnaW4uZGV0ZWN0V29ya3NwYWNlKCk7XG5cdFx0aWYgKHdvcmtzcGFjZUluZm8pIHtcblx0XHRcdG5ldyBTZXR0aW5nKGNvbnRhaW5lckVsKVxuXHRcdFx0XHQuc2V0TmFtZShcIkRldGVjdGVkIHdvcmtzcGFjZVwiKVxuXHRcdFx0XHQuc2V0RGVzYyhcblx0XHRcdFx0XHRgU3BhY2U6ICR7d29ya3NwYWNlSW5mby5zcGFjZSB8fCBcInVua25vd25cIn1gXG5cdFx0XHRcdCk7XG5cdFx0fSBlbHNlIHtcblx0XHRcdG5ldyBTZXR0aW5nKGNvbnRhaW5lckVsKVxuXHRcdFx0XHQuc2V0TmFtZShcIk5vIHN1YmxpbWF0ZWQgZmlsZSBvcGVuXCIpXG5cdFx0XHRcdC5zZXREZXNjKFxuXHRcdFx0XHRcdFwiT3BlbiBhbnkgc3luY2VkIGZpbGUgZnJvbSBhIHN1YmxpbWF0ZWQgZm9sZGVyIHRvIHNlZSB3b3Jrc3BhY2UgaW5mby5cIlxuXHRcdFx0XHQpO1xuXHRcdH1cblxuXHRcdC8vIEFQSSB0b2tlblxuXHRcdG5ldyBTZXR0aW5nKGNvbnRhaW5lckVsKVxuXHRcdFx0LnNldE5hbWUoXCJBUEkgdG9rZW5cIilcblx0XHRcdC5zZXREZXNjKFxuXHRcdFx0XHRcIlJlcXVpcmVkIGZvciB2ZXJzaW9uIGhpc3RvcnkuIEdlbmVyYXRlIGF0IHN1YmxpbWF0ZWQuY29tL3NldHRpbmdzL3Rva2Vuc1wiXG5cdFx0XHQpXG5cdFx0XHQuYWRkVGV4dCgodGV4dCkgPT5cblx0XHRcdFx0dGV4dFxuXHRcdFx0XHRcdC5zZXRQbGFjZWhvbGRlcihcInNrXy4uLlwiKVxuXHRcdFx0XHRcdC5zZXRWYWx1ZSh0aGlzLnBsdWdpbi5zZXR0aW5ncy5hcGlUb2tlbilcblx0XHRcdFx0XHQub25DaGFuZ2UoYXN5bmMgKHZhbHVlKSA9PiB7XG5cdFx0XHRcdFx0XHR0aGlzLnBsdWdpbi5zZXR0aW5ncy5hcGlUb2tlbiA9IHZhbHVlO1xuXHRcdFx0XHRcdFx0YXdhaXQgdGhpcy5wbHVnaW4uc2F2ZVNldHRpbmdzKCk7XG5cdFx0XHRcdFx0fSlcblx0XHRcdCk7XG5cblx0XHQvLyB0ZXN0IGNvbm5lY3Rpb25cblx0XHRuZXcgU2V0dGluZyhjb250YWluZXJFbClcblx0XHRcdC5zZXROYW1lKFwiVGVzdCBjb25uZWN0aW9uXCIpXG5cdFx0XHQuc2V0RGVzYyhcIlZlcmlmeSB5b3VyIEFQSSB0b2tlbiB3b3Jrc1wiKVxuXHRcdFx0LmFkZEJ1dHRvbigoYnRuKSA9PlxuXHRcdFx0XHRidG4uc2V0QnV0dG9uVGV4dChcIlRlc3RcIikub25DbGljayhhc3luYyAoKSA9PiB7XG5cdFx0XHRcdFx0aWYgKCF0aGlzLnBsdWdpbi5zZXR0aW5ncy5hcGlUb2tlbikge1xuXHRcdFx0XHRcdFx0bmV3IE5vdGljZShcIk5vIEFQSSB0b2tlbiBjb25maWd1cmVkXCIpO1xuXHRcdFx0XHRcdFx0cmV0dXJuO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHRidG4uc2V0QnV0dG9uVGV4dChcIlRlc3RpbmcuLi5cIik7XG5cdFx0XHRcdFx0YnRuLnNldERpc2FibGVkKHRydWUpO1xuXHRcdFx0XHRcdGNvbnN0IGFwaSA9IG5ldyBTdWJsaW1hdGVkQXBpKFxuXHRcdFx0XHRcdFx0dGhpcy5wbHVnaW4uc2V0dGluZ3MuYXBpVG9rZW5cblx0XHRcdFx0XHQpO1xuXHRcdFx0XHRcdGNvbnN0IHJlc3VsdCA9IGF3YWl0IGFwaS50ZXN0Q29ubmVjdGlvbigpO1xuXHRcdFx0XHRcdGlmIChyZXN1bHQub2spIHtcblx0XHRcdFx0XHRcdG5ldyBOb3RpY2UoXG5cdFx0XHRcdFx0XHRcdGBDb25uZWN0ZWQgYXMgJHtyZXN1bHQudXNlcm5hbWV9YFxuXHRcdFx0XHRcdFx0KTtcblx0XHRcdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRcdFx0bmV3IE5vdGljZShcblx0XHRcdFx0XHRcdFx0YENvbm5lY3Rpb24gZmFpbGVkOiAke3Jlc3VsdC5lcnJvcn1gXG5cdFx0XHRcdFx0XHQpO1xuXHRcdFx0XHRcdH1cblx0XHRcdFx0XHRidG4uc2V0QnV0dG9uVGV4dChcIlRlc3RcIik7XG5cdFx0XHRcdFx0YnRuLnNldERpc2FibGVkKGZhbHNlKTtcblx0XHRcdFx0fSlcblx0XHRcdCk7XG5cblx0XHQvLyBsaW5rc1xuXHRcdGNvbnRhaW5lckVsLmNyZWF0ZUVsKFwiZGl2XCIsIHsgY2xzOiBcInNibC1zZXR0aW5ncy1saW5rc1wiIH0sIChlbCkgPT4ge1xuXHRcdFx0ZWwuY3JlYXRlRWwoXCJhXCIsIHtcblx0XHRcdFx0dGV4dDogXCJzdWJsaW1hdGVkLmNvbVwiLFxuXHRcdFx0XHRocmVmOiBcImh0dHBzOi8vc3VibGltYXRlZC5jb21cIixcblx0XHRcdH0pO1xuXHRcdFx0ZWwuY3JlYXRlRWwoXCJzcGFuXCIsIHsgdGV4dDogXCIgXHUwMEI3IFwiIH0pO1xuXHRcdFx0ZWwuY3JlYXRlRWwoXCJhXCIsIHtcblx0XHRcdFx0dGV4dDogXCJEb2N1bWVudGF0aW9uXCIsXG5cdFx0XHRcdGhyZWY6IFwiaHR0cHM6Ly9zdWJsaW1hdGVkLmNvbS9kb2NzL29ic2lkaWFuXCIsXG5cdFx0XHR9KTtcblx0XHRcdGVsLmNyZWF0ZUVsKFwic3BhblwiLCB7IHRleHQ6IFwiIFx1MDBCNyBcIiB9KTtcblx0XHRcdGVsLmNyZWF0ZUVsKFwiYVwiLCB7XG5cdFx0XHRcdHRleHQ6IFwiU291cmNlIGNvZGVcIixcblx0XHRcdFx0aHJlZjogXCJodHRwczovL3N1YmxpbWF0ZWQuY29tL3N1YmxpbWF0ZWQvY29kZS9vYnNpZGlhblwiLFxuXHRcdFx0fSk7XG5cdFx0fSk7XG5cdH1cbn1cbiIsICJpbXBvcnQgeyByZXF1ZXN0VXJsIH0gZnJvbSBcIm9ic2lkaWFuXCI7XG5cbmNvbnN0IEFQSV9CQVNFID0gXCJodHRwczovL2FwaS5zdWJsaW1hdGVkLmNvbVwiO1xuXG5leHBvcnQgaW50ZXJmYWNlIFZlcnNpb24ge1xuXHRpZDogc3RyaW5nO1xuXHRudW1iZXI6IG51bWJlcjtcblx0YXV0aG9yTmFtZTogc3RyaW5nO1xuXHRhdXRob3JUeXBlOiBzdHJpbmc7XG5cdG1lc3NhZ2U6IHN0cmluZyB8IG51bGw7XG5cdGNyZWF0ZWRBdDogc3RyaW5nO1xufVxuXG5leHBvcnQgY2xhc3MgQXV0aFJlcXVpcmVkRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG5cdGNvbnN0cnVjdG9yKHB1YmxpYyBzdGF0dXM6IG51bWJlcikge1xuXHRcdHN1cGVyKGBhdXRoIHJlcXVpcmVkICgke3N0YXR1c30pYCk7XG5cdFx0dGhpcy5uYW1lID0gXCJBdXRoUmVxdWlyZWRFcnJvclwiO1xuXHR9XG59XG5cbi8vIFJhdyB3aXJlIGZvcm1hdCBmcm9tIGFwaS5zdWJsaW1hdGVkLmNvbVxuaW50ZXJmYWNlIFJhd0FjdG9yIHtcblx0dXNlcm5hbWU6IHN0cmluZyB8IG51bGw7XG5cdG5hbWU6IHN0cmluZyB8IG51bGw7XG5cdHJvbGU6IHN0cmluZztcblx0YWN0b3JfdHlwZTogc3RyaW5nO1xufVxuXG5pbnRlcmZhY2UgUmF3VmVyc2lvbiB7XG5cdGlkOiBzdHJpbmc7XG5cdGl0ZW1faWQ6IHN0cmluZztcblx0dmVyc2lvbjogbnVtYmVyO1xuXHRjb250ZW50X2hhc2g6IHN0cmluZztcblx0YmxvYl9zaGExOiBzdHJpbmcgfCBudWxsO1xuXHRzaXplX2J5dGVzOiBudW1iZXI7XG5cdG1lc3NhZ2U6IHN0cmluZyB8IG51bGw7XG5cdG5hbWU6IHN0cmluZyB8IG51bGw7XG5cdGNyZWF0ZWRfYXQ6IHN0cmluZztcblx0YWN0b3JzOiBSYXdBY3RvcltdO1xufVxuXG5pbnRlcmZhY2UgQXBpRW52ZWxvcGU8VD4ge1xuXHRvazogYm9vbGVhbjtcblx0ZGF0YT86IFQ7XG5cdGVycm9yPzogeyB0eXBlOiBzdHJpbmc7IG1lc3NhZ2U/OiBzdHJpbmcgfTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBUZXN0Q29ubmVjdGlvblJlc3VsdCB7XG5cdG9rOiBib29sZWFuO1xuXHR1c2VybmFtZT86IHN0cmluZztcblx0ZXJyb3I/OiBzdHJpbmc7XG59XG5cbmV4cG9ydCBjbGFzcyBTdWJsaW1hdGVkQXBpIHtcblx0Y29uc3RydWN0b3IocHJpdmF0ZSB0b2tlbjogc3RyaW5nKSB7fVxuXG5cdHByaXZhdGUgYXN5bmMgcmVxdWVzdDxUPihwYXRoOiBzdHJpbmcpOiBQcm9taXNlPFQ+IHtcblx0XHRjb25zdCBoZWFkZXJzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge1xuXHRcdFx0QWNjZXB0OiBcImFwcGxpY2F0aW9uL2pzb25cIixcblx0XHR9O1xuXHRcdGlmICh0aGlzLnRva2VuKSB7XG5cdFx0XHRoZWFkZXJzLkF1dGhvcml6YXRpb24gPSBgQmVhcmVyICR7dGhpcy50b2tlbn1gO1xuXHRcdH1cblx0XHRjb25zdCByZXNwb25zZSA9IGF3YWl0IHJlcXVlc3RVcmwoe1xuXHRcdFx0dXJsOiBgJHtBUElfQkFTRX0ke3BhdGh9YCxcblx0XHRcdGhlYWRlcnMsXG5cdFx0XHR0aHJvdzogZmFsc2UsXG5cdFx0fSk7XG5cblx0XHRpZiAocmVzcG9uc2Uuc3RhdHVzID09PSA0MDEgfHwgcmVzcG9uc2Uuc3RhdHVzID09PSA0MDMpIHtcblx0XHRcdHRocm93IG5ldyBBdXRoUmVxdWlyZWRFcnJvcihyZXNwb25zZS5zdGF0dXMpO1xuXHRcdH1cblx0XHRpZiAocmVzcG9uc2Uuc3RhdHVzID49IDQwMCkge1xuXHRcdFx0dGhyb3cgbmV3IEVycm9yKFxuXHRcdFx0XHRgQVBJIGVycm9yICR7cmVzcG9uc2Uuc3RhdHVzfTogJHtyZXNwb25zZS50ZXh0fWBcblx0XHRcdCk7XG5cdFx0fVxuXG5cdFx0Y29uc3QgYm9keSA9IHJlc3BvbnNlLmpzb24gYXMgQXBpRW52ZWxvcGU8VD47XG5cdFx0aWYgKCFib2R5Lm9rIHx8IGJvZHkuZGF0YSA9PT0gdW5kZWZpbmVkKSB7XG5cdFx0XHRjb25zdCBtc2cgPSBib2R5LmVycm9yPy5tZXNzYWdlIHx8IGJvZHkuZXJyb3I/LnR5cGUgfHwgXCJ1bmtub3duIGVycm9yXCI7XG5cdFx0XHR0aHJvdyBuZXcgRXJyb3IoYEFQSSBlcnJvcjogJHttc2d9YCk7XG5cdFx0fVxuXHRcdHJldHVybiBib2R5LmRhdGE7XG5cdH1cblxuXHRhc3luYyBnZXRWZXJzaW9ucyhcblx0XHRzcGFjZVNsdWc6IHN0cmluZyxcblx0XHRpdGVtSWQ6IHN0cmluZ1xuXHQpOiBQcm9taXNlPFZlcnNpb25bXT4ge1xuXHRcdGNvbnN0IHJhdyA9IGF3YWl0IHRoaXMucmVxdWVzdDxSYXdWZXJzaW9uW10+KFxuXHRcdFx0YC92MS9zcGFjZXMvJHtlbmNvZGVVUklDb21wb25lbnQoc3BhY2VTbHVnKX0vaXRlbXMvJHtlbmNvZGVVUklDb21wb25lbnQoaXRlbUlkKX0vdmVyc2lvbnNgXG5cdFx0KTtcblx0XHRyZXR1cm4gcmF3Lm1hcChtYXBWZXJzaW9uKS5yZXZlcnNlKCk7XG5cdH1cblxuXHRhc3luYyB0ZXN0Q29ubmVjdGlvbigpOiBQcm9taXNlPFRlc3RDb25uZWN0aW9uUmVzdWx0PiB7XG5cdFx0dHJ5IHtcblx0XHRcdGNvbnN0IGRhdGEgPSBhd2FpdCB0aGlzLnJlcXVlc3Q8eyB1c2VybmFtZTogc3RyaW5nIH0+KFxuXHRcdFx0XHRcIi92MS9tZVwiXG5cdFx0XHQpO1xuXHRcdFx0cmV0dXJuIHsgb2s6IHRydWUsIHVzZXJuYW1lOiBkYXRhLnVzZXJuYW1lIH07XG5cdFx0fSBjYXRjaCAoZSkge1xuXHRcdFx0cmV0dXJuIHtcblx0XHRcdFx0b2s6IGZhbHNlLFxuXHRcdFx0XHRlcnJvcjogZSBpbnN0YW5jZW9mIEVycm9yID8gZS5tZXNzYWdlIDogXCJVbmtub3duIGVycm9yXCIsXG5cdFx0XHR9O1xuXHRcdH1cblx0fVxufVxuXG5mdW5jdGlvbiBtYXBWZXJzaW9uKHI6IFJhd1ZlcnNpb24pOiBWZXJzaW9uIHtcblx0Ly8gcGljayB0aGUgYXV0aG9yIGFjdG9yOyBmYWxsIGJhY2sgdG8gdGhlIGZpcnN0IGFjdG9yIG9yIGEgcGxhY2Vob2xkZXJcblx0Y29uc3QgYXV0aG9yID1cblx0XHRyLmFjdG9ycy5maW5kKChhKSA9PiBhLnJvbGUgPT09IFwiYXV0aG9yXCIpID8/IHIuYWN0b3JzWzBdID8/IG51bGw7XG5cdHJldHVybiB7XG5cdFx0aWQ6IHIuaWQsXG5cdFx0bnVtYmVyOiByLnZlcnNpb24sXG5cdFx0YXV0aG9yTmFtZTogYXV0aG9yPy5uYW1lID8/IGF1dGhvcj8udXNlcm5hbWUgPz8gXCJ1bmtub3duXCIsXG5cdFx0YXV0aG9yVHlwZTogYXV0aG9yPy5hY3Rvcl90eXBlID8/IFwicGVyc29uXCIsXG5cdFx0bWVzc2FnZTogci5tZXNzYWdlLFxuXHRcdGNyZWF0ZWRBdDogci5jcmVhdGVkX2F0LFxuXHR9O1xufVxuIiwgImltcG9ydCB7IGV4ZWMgfSBmcm9tIFwiY2hpbGRfcHJvY2Vzc1wiO1xuaW1wb3J0IHsgUGxhdGZvcm0gfSBmcm9tIFwib2JzaWRpYW5cIjtcblxuY29uc3QgWEFUVFJfUFJFRklYID0gXCJjb20uc3VibGltYXRlZC5cIjtcbmNvbnN0IFhBVFRSX1NVRkZJWCA9IFwiI1BTXCI7XG5cbmV4cG9ydCBpbnRlcmZhY2UgRmlsZU1ldGFkYXRhIHtcblx0dmVyc2lvbjogc3RyaW5nIHwgbnVsbDtcblx0Y29tbWl0OiBzdHJpbmcgfCBudWxsO1xuXHR1cmw6IHN0cmluZyB8IG51bGw7XG5cdGNvbnRlbnRIYXNoOiBzdHJpbmcgfCBudWxsO1xuXHRpZDogc3RyaW5nIHwgbnVsbDtcblx0Y3JlYXRlZEJ5OiBzdHJpbmcgfCBudWxsO1xuXHRtb2RpZmllZEJ5OiBzdHJpbmcgfCBudWxsO1xuXHRtb2RpZmllZEJ5VHlwZTogc3RyaW5nIHwgbnVsbDtcblx0c3luY1N0YXR1czogc3RyaW5nIHwgbnVsbDtcblx0c3BhY2U6IHN0cmluZyB8IG51bGw7XG5cdGJyYW5jaDogc3RyaW5nIHwgbnVsbDtcblx0bW9kaWZpZWRBdDogc3RyaW5nIHwgbnVsbDtcblx0dmlzaWJpbGl0eTogc3RyaW5nIHwgbnVsbDtcbn1cblxuY29uc3QgQVRUUlM6IChrZXlvZiBGaWxlTWV0YWRhdGEpW10gPSBbXG5cdFwidmVyc2lvblwiLFxuXHRcImNvbW1pdFwiLFxuXHRcInVybFwiLFxuXHRcImNvbnRlbnRIYXNoXCIsXG5cdFwiaWRcIixcblx0XCJjcmVhdGVkQnlcIixcblx0XCJtb2RpZmllZEJ5XCIsXG5cdFwibW9kaWZpZWRCeVR5cGVcIixcblx0XCJzeW5jU3RhdHVzXCIsXG5cdFwic3BhY2VcIixcblx0XCJicmFuY2hcIixcblx0XCJtb2RpZmllZEF0XCIsXG5cdFwidmlzaWJpbGl0eVwiLFxuXTtcblxuZnVuY3Rpb24gYXR0clRvWGF0dHJOYW1lKGF0dHI6IHN0cmluZyk6IHN0cmluZyB7XG5cdC8vIGNhbWVsQ2FzZSBcdTIxOTIga2ViYWItY2FzZTogY29udGVudEhhc2ggXHUyMTkyIGNvbnRlbnQtaGFzaFxuXHRjb25zdCBrZWJhYiA9IGF0dHIucmVwbGFjZSgvW0EtWl0vZywgKGMpID0+IFwiLVwiICsgYy50b0xvd2VyQ2FzZSgpKTtcblx0cmV0dXJuIGAke1hBVFRSX1BSRUZJWH0ke2tlYmFifSR7WEFUVFJfU1VGRklYfWA7XG59XG5cbmZ1bmN0aW9uIHJlYWRYYXR0cihwYXRoOiBzdHJpbmcsIHhhdHRyTmFtZTogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmcgfCBudWxsPiB7XG5cdHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4ge1xuXHRcdGV4ZWMoXG5cdFx0XHRgeGF0dHIgLXAgJHtKU09OLnN0cmluZ2lmeSh4YXR0ck5hbWUpfSAke0pTT04uc3RyaW5naWZ5KHBhdGgpfWAsXG5cdFx0XHR7IHRpbWVvdXQ6IDIwMDAgfSxcblx0XHRcdChlcnIsIHN0ZG91dCkgPT4ge1xuXHRcdFx0XHRpZiAoZXJyKSB7XG5cdFx0XHRcdFx0cmVzb2x2ZShudWxsKTtcblx0XHRcdFx0XHRyZXR1cm47XG5cdFx0XHRcdH1cblx0XHRcdFx0cmVzb2x2ZShzdGRvdXQudHJpbSgpIHx8IG51bGwpO1xuXHRcdFx0fVxuXHRcdCk7XG5cdH0pO1xufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVhZEZpbGVNZXRhZGF0YShcblx0cGF0aDogc3RyaW5nXG4pOiBQcm9taXNlPEZpbGVNZXRhZGF0YSB8IG51bGw+IHtcblx0aWYgKCFQbGF0Zm9ybS5pc01hY09TKSByZXR1cm4gbnVsbDtcblxuXHRjb25zdCByZXN1bHRzID0gYXdhaXQgUHJvbWlzZS5hbGwoXG5cdFx0QVRUUlMubWFwKChhdHRyKSA9PiByZWFkWGF0dHIocGF0aCwgYXR0clRvWGF0dHJOYW1lKGF0dHIpKSlcblx0KTtcblxuXHQvLyBJZiBubyB4YXR0cnMgZm91bmQgYXQgYWxsLCB0aGlzIGlzbid0IGEgc3VibGltYXRlZCBmaWxlXG5cdGlmIChyZXN1bHRzLmV2ZXJ5KChyKSA9PiByID09PSBudWxsKSkgcmV0dXJuIG51bGw7XG5cblx0Y29uc3QgbWV0YTogRmlsZU1ldGFkYXRhID0ge1xuXHRcdHZlcnNpb246IG51bGwsIGNvbW1pdDogbnVsbCwgdXJsOiBudWxsLCBjb250ZW50SGFzaDogbnVsbCxcblx0XHRpZDogbnVsbCwgY3JlYXRlZEJ5OiBudWxsLCBtb2RpZmllZEJ5OiBudWxsLCBtb2RpZmllZEJ5VHlwZTogbnVsbCxcblx0XHRzeW5jU3RhdHVzOiBudWxsLCBzcGFjZTogbnVsbCwgYnJhbmNoOiBudWxsLCBtb2RpZmllZEF0OiBudWxsLCB2aXNpYmlsaXR5OiBudWxsLFxuXHR9O1xuXHRBVFRSUy5mb3JFYWNoKChhdHRyLCBpKSA9PiB7XG5cdFx0bWV0YVthdHRyXSA9IHJlc3VsdHNbaV07XG5cdH0pO1xuXHRyZXR1cm4gbWV0YTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGlzTWFjT1MoKTogYm9vbGVhbiB7XG5cdHJldHVybiBQbGF0Zm9ybS5pc01hY09TO1xufVxuIiwgImltcG9ydCB7IEl0ZW1WaWV3LCBOb3RpY2UsIFdvcmtzcGFjZUxlYWYgfSBmcm9tIFwib2JzaWRpYW5cIjtcbmltcG9ydCB0eXBlIFN1YmxpbWF0ZWRQbHVnaW4gZnJvbSBcIi4vbWFpblwiO1xuaW1wb3J0IHsgU3VibGltYXRlZEFwaSwgQXV0aFJlcXVpcmVkRXJyb3IsIHR5cGUgVmVyc2lvbiB9IGZyb20gXCIuL2FwaVwiO1xuXG5leHBvcnQgY29uc3QgVkVSU0lPTl9WSUVXX1RZUEUgPSBcInN1YmxpbWF0ZWQtdmVyc2lvbnNcIjtcblxuZXhwb3J0IGNsYXNzIFZlcnNpb25WaWV3IGV4dGVuZHMgSXRlbVZpZXcge1xuXHRwbHVnaW46IFN1YmxpbWF0ZWRQbHVnaW47XG5cblx0Y29uc3RydWN0b3IobGVhZjogV29ya3NwYWNlTGVhZiwgcGx1Z2luOiBTdWJsaW1hdGVkUGx1Z2luKSB7XG5cdFx0c3VwZXIobGVhZik7XG5cdFx0dGhpcy5wbHVnaW4gPSBwbHVnaW47XG5cdH1cblxuXHRnZXRWaWV3VHlwZSgpOiBzdHJpbmcge1xuXHRcdHJldHVybiBWRVJTSU9OX1ZJRVdfVFlQRTtcblx0fVxuXG5cdGdldERpc3BsYXlUZXh0KCk6IHN0cmluZyB7XG5cdFx0cmV0dXJuIFwic3VibGltYXRlZCB2ZXJzaW9uc1wiO1xuXHR9XG5cblx0Z2V0SWNvbigpOiBzdHJpbmcge1xuXHRcdHJldHVybiBcImhpc3RvcnlcIjtcblx0fVxuXG5cdGFzeW5jIG9uT3BlbigpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRhd2FpdCB0aGlzLnJlZnJlc2goKTtcblx0XHQvLyByZWZyZXNoIHdoZW4gYWN0aXZlIGZpbGUgY2hhbmdlc1xuXHRcdHRoaXMucmVnaXN0ZXJFdmVudChcblx0XHRcdHRoaXMuYXBwLndvcmtzcGFjZS5vbihcImFjdGl2ZS1sZWFmLWNoYW5nZVwiLCAoKSA9PiB7XG5cdFx0XHRcdHRoaXMucmVmcmVzaCgpO1xuXHRcdFx0fSlcblx0XHQpO1xuXHR9XG5cblx0YXN5bmMgcmVmcmVzaCgpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHRjb25zdCB7IGNvbnRlbnRFbCB9ID0gdGhpcztcblx0XHRjb250ZW50RWwuZW1wdHkoKTtcblx0XHRjb250ZW50RWwuYWRkQ2xhc3MoXCJzYmwtdmVyc2lvbi12aWV3XCIpO1xuXG5cdFx0Y29uc3QgYWN0aXZlRmlsZSA9IHRoaXMuYXBwLndvcmtzcGFjZS5nZXRBY3RpdmVGaWxlKCk7XG5cdFx0aWYgKCFhY3RpdmVGaWxlKSB7XG5cdFx0XHRjb250ZW50RWwuY3JlYXRlRWwoXCJwXCIsIHtcblx0XHRcdFx0dGV4dDogXCJObyBmaWxlIG9wZW4uXCIsXG5cdFx0XHRcdGNsczogXCJzYmwtdmVyc2lvbi1lbXB0eVwiLFxuXHRcdFx0fSk7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXG5cdFx0Ly8gcmVhZCB4YXR0cnMgdG8gZ2V0IHNwYWNlICsgaXRlbSBJRFxuXHRcdGNvbnN0IG1ldGEgPSB0aGlzLnBsdWdpbi5sYXN0TWV0YWRhdGE7XG5cdFx0aWYgKCFtZXRhPy5zcGFjZSB8fCAhbWV0YT8uaWQpIHtcblx0XHRcdGNvbnRlbnRFbC5jcmVhdGVFbChcInBcIiwge1xuXHRcdFx0XHR0ZXh0OiBcIk5vdCBhIHN1YmxpbWF0ZWQgZmlsZS5cIixcblx0XHRcdFx0Y2xzOiBcInNibC12ZXJzaW9uLWVtcHR5XCIsXG5cdFx0XHR9KTtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cblx0XHRjb250ZW50RWwuY3JlYXRlRWwoXCJoNFwiLCB7XG5cdFx0XHR0ZXh0OiBhY3RpdmVGaWxlLm5hbWUsXG5cdFx0XHRjbHM6IFwic2JsLXZlcnNpb24tZmlsZW5hbWVcIixcblx0XHR9KTtcblxuXHRcdGNvbnN0IGxvYWRpbmcgPSBjb250ZW50RWwuY3JlYXRlRWwoXCJwXCIsIHtcblx0XHRcdHRleHQ6IFwiTG9hZGluZyB2ZXJzaW9ucy4uLlwiLFxuXHRcdFx0Y2xzOiBcInNibC12ZXJzaW9uLWxvYWRpbmdcIixcblx0XHR9KTtcblxuXHRcdHRyeSB7XG5cdFx0XHRjb25zdCBhcGkgPSBuZXcgU3VibGltYXRlZEFwaSh0aGlzLnBsdWdpbi5zZXR0aW5ncy5hcGlUb2tlbik7XG5cdFx0XHRjb25zdCB2ZXJzaW9ucyA9IGF3YWl0IGFwaS5nZXRWZXJzaW9ucyhtZXRhLnNwYWNlLCBtZXRhLmlkKTtcblx0XHRcdGxvYWRpbmcucmVtb3ZlKCk7XG5cblx0XHRcdGlmICh2ZXJzaW9ucy5sZW5ndGggPT09IDApIHtcblx0XHRcdFx0Y29udGVudEVsLmNyZWF0ZUVsKFwicFwiLCB7XG5cdFx0XHRcdFx0dGV4dDogXCJObyB2ZXJzaW9ucyBmb3VuZC5cIixcblx0XHRcdFx0XHRjbHM6IFwic2JsLXZlcnNpb24tZW1wdHlcIixcblx0XHRcdFx0fSk7XG5cdFx0XHRcdHJldHVybjtcblx0XHRcdH1cblxuXHRcdFx0Y29uc3QgbGlzdCA9IGNvbnRlbnRFbC5jcmVhdGVFbChcImRpdlwiLCB7XG5cdFx0XHRcdGNsczogXCJzYmwtdmVyc2lvbi1saXN0XCIsXG5cdFx0XHR9KTtcblx0XHRcdGNvbnN0IGdyb3VwcyA9IGdyb3VwQnlEYXkodmVyc2lvbnMpO1xuXHRcdFx0Zm9yIChjb25zdCBncm91cCBvZiBncm91cHMpIHtcblx0XHRcdFx0bGlzdC5jcmVhdGVFbChcImRpdlwiLCB7XG5cdFx0XHRcdFx0dGV4dDogZ3JvdXAubGFiZWwsXG5cdFx0XHRcdFx0Y2xzOiBcInNibC12ZXJzaW9uLWRheVwiLFxuXHRcdFx0XHR9KTtcblx0XHRcdFx0Zm9yIChjb25zdCB2IG9mIGdyb3VwLnZlcnNpb25zKSB7XG5cdFx0XHRcdFx0dGhpcy5yZW5kZXJWZXJzaW9uKGxpc3QsIHYsIG1ldGEudXJsKTtcblx0XHRcdFx0fVxuXHRcdFx0fVxuXHRcdH0gY2F0Y2ggKGUpIHtcblx0XHRcdGxvYWRpbmcucmVtb3ZlKCk7XG5cdFx0XHRpZiAoZSBpbnN0YW5jZW9mIEF1dGhSZXF1aXJlZEVycm9yKSB7XG5cdFx0XHRcdGNvbnRlbnRFbC5jcmVhdGVFbChcInBcIiwge1xuXHRcdFx0XHRcdHRleHQ6IFwiVGhpcyBmaWxlIGlzIHByaXZhdGUgXHUyMDE0IGFkZCBhbiBBUEkgdG9rZW4gdG8gdmlldyB2ZXJzaW9uIGhpc3RvcnkuXCIsXG5cdFx0XHRcdFx0Y2xzOiBcInNibC12ZXJzaW9uLWVtcHR5XCIsXG5cdFx0XHRcdH0pO1xuXHRcdFx0XHRjb250ZW50RWwuY3JlYXRlRWwoXCJwXCIsIHtcblx0XHRcdFx0XHR0ZXh0OiBcIkNvbmZpZ3VyZSBpbiBTZXR0aW5ncyBcdTIxOTIgc3VibGltYXRlZFwiLFxuXHRcdFx0XHRcdGNsczogXCJzYmwtdmVyc2lvbi1oaW50XCIsXG5cdFx0XHRcdH0pO1xuXHRcdFx0XHRyZXR1cm47XG5cdFx0XHR9XG5cdFx0XHRjb250ZW50RWwuY3JlYXRlRWwoXCJwXCIsIHtcblx0XHRcdFx0dGV4dDogYEVycm9yOiAke2UgaW5zdGFuY2VvZiBFcnJvciA/IGUubWVzc2FnZSA6IFwiVW5rbm93blwifWAsXG5cdFx0XHRcdGNsczogXCJzYmwtdmVyc2lvbi1lcnJvclwiLFxuXHRcdFx0fSk7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSByZW5kZXJWZXJzaW9uKFxuXHRcdGNvbnRhaW5lcjogSFRNTEVsZW1lbnQsXG5cdFx0djogVmVyc2lvbixcblx0XHRmaWxlVXJsOiBzdHJpbmcgfCBudWxsXG5cdCk6IHZvaWQge1xuXHRcdGNvbnN0IGl0ZW0gPSBjb250YWluZXIuY3JlYXRlRGl2KHsgY2xzOiBcInNibC12ZXJzaW9uLWl0ZW1cIiB9KTtcblxuXHRcdGNvbnN0IGhlYWRlciA9IGl0ZW0uY3JlYXRlRGl2KHsgY2xzOiBcInNibC12ZXJzaW9uLWhlYWRlclwiIH0pO1xuXHRcdGhlYWRlci5jcmVhdGVFbChcInNwYW5cIiwge1xuXHRcdFx0dGV4dDogYHYke3YubnVtYmVyfWAsXG5cdFx0XHRjbHM6IFwic2JsLXZlcnNpb24tbnVtYmVyXCIsXG5cdFx0fSk7XG5cdFx0aGVhZGVyLmNyZWF0ZUVsKFwic3BhblwiLCB7XG5cdFx0XHR0ZXh0OiB2LmF1dGhvck5hbWUsXG5cdFx0XHRjbHM6IFwic2JsLXZlcnNpb24tYXV0aG9yXCIsXG5cdFx0fSk7XG5cblx0XHRjb25zdCB0aW1lID0gaXRlbS5jcmVhdGVEaXYoeyBjbHM6IFwic2JsLXZlcnNpb24tdGltZVwiIH0pO1xuXHRcdHRpbWUuY3JlYXRlRWwoXCJzcGFuXCIsIHtcblx0XHRcdHRleHQ6IGZvcm1hdFJlbGF0aXZlVGltZSh2LmNyZWF0ZWRBdCksXG5cdFx0XHRhdHRyOiB7IHRpdGxlOiBuZXcgRGF0ZSh2LmNyZWF0ZWRBdCkudG9Mb2NhbGVTdHJpbmcoKSB9LFxuXHRcdH0pO1xuXG5cdFx0aWYgKHYubWVzc2FnZSkge1xuXHRcdFx0aXRlbS5jcmVhdGVEaXYoe1xuXHRcdFx0XHR0ZXh0OiB2Lm1lc3NhZ2UsXG5cdFx0XHRcdGNsczogXCJzYmwtdmVyc2lvbi1tZXNzYWdlXCIsXG5cdFx0XHR9KTtcblx0XHR9XG5cblx0XHRpZiAoZmlsZVVybCkge1xuXHRcdFx0Y29uc3Qgd2ViVXJsID0gYGh0dHBzOi8vJHtmaWxlVXJsfS9+dmVyc2lvbnMvdiR7di5udW1iZXJ9YDtcblx0XHRcdGNvbnN0IGxpbmsgPSBpdGVtLmNyZWF0ZUVsKFwiYVwiLCB7XG5cdFx0XHRcdHRleHQ6IFwidmlldyBvbiB3ZWJcIixcblx0XHRcdFx0Y2xzOiBcInNibC12ZXJzaW9uLXdlYi1saW5rXCIsXG5cdFx0XHRcdGhyZWY6IHdlYlVybCxcblx0XHRcdH0pO1xuXHRcdFx0bGluay5hZGRFdmVudExpc3RlbmVyKFwiY2xpY2tcIiwgKGUpID0+IHtcblx0XHRcdFx0ZS5wcmV2ZW50RGVmYXVsdCgpO1xuXHRcdFx0XHR3aW5kb3cub3Blbih3ZWJVcmwpO1xuXHRcdFx0fSk7XG5cdFx0fVxuXHR9XG5cblx0YXN5bmMgb25DbG9zZSgpOiBQcm9taXNlPHZvaWQ+IHtcblx0XHQvLyBjbGVhbnVwIGhhbmRsZWQgYnkgb2JzaWRpYW5cblx0fVxufVxuXG5pbnRlcmZhY2UgRGF5R3JvdXAge1xuXHRsYWJlbDogc3RyaW5nO1xuXHR2ZXJzaW9uczogVmVyc2lvbltdO1xufVxuXG5mdW5jdGlvbiBncm91cEJ5RGF5KHZlcnNpb25zOiBWZXJzaW9uW10pOiBEYXlHcm91cFtdIHtcblx0Y29uc3QgZ3JvdXBzID0gbmV3IE1hcDxzdHJpbmcsIFZlcnNpb25bXT4oKTtcblx0Y29uc3Qgb3JkZXI6IHN0cmluZ1tdID0gW107XG5cblx0Zm9yIChjb25zdCB2IG9mIHZlcnNpb25zKSB7XG5cdFx0Y29uc3QgbGFiZWwgPSBmb3JtYXREYXlMYWJlbCh2LmNyZWF0ZWRBdCk7XG5cdFx0aWYgKCFncm91cHMuaGFzKGxhYmVsKSkge1xuXHRcdFx0Z3JvdXBzLnNldChsYWJlbCwgW10pO1xuXHRcdFx0b3JkZXIucHVzaChsYWJlbCk7XG5cdFx0fVxuXHRcdGdyb3Vwcy5nZXQobGFiZWwpIS5wdXNoKHYpO1xuXHR9XG5cblx0cmV0dXJuIG9yZGVyLm1hcCgobGFiZWwpID0+ICh7XG5cdFx0bGFiZWwsXG5cdFx0dmVyc2lvbnM6IGdyb3Vwcy5nZXQobGFiZWwpISxcblx0fSkpO1xufVxuXG5mdW5jdGlvbiBmb3JtYXREYXlMYWJlbChpc286IHN0cmluZyk6IHN0cmluZyB7XG5cdGNvbnN0IG5vdyA9IG5ldyBEYXRlKCk7XG5cdGNvbnN0IGRhdGUgPSBuZXcgRGF0ZShpc28pO1xuXG5cdGNvbnN0IG5vd0RheSA9IG5ldyBEYXRlKG5vdy5nZXRGdWxsWWVhcigpLCBub3cuZ2V0TW9udGgoKSwgbm93LmdldERhdGUoKSk7XG5cdGNvbnN0IHRoZW5EYXkgPSBuZXcgRGF0ZShcblx0XHRkYXRlLmdldEZ1bGxZZWFyKCksXG5cdFx0ZGF0ZS5nZXRNb250aCgpLFxuXHRcdGRhdGUuZ2V0RGF0ZSgpXG5cdCk7XG5cdGNvbnN0IGRheXMgPSBNYXRoLnJvdW5kKFxuXHRcdChub3dEYXkuZ2V0VGltZSgpIC0gdGhlbkRheS5nZXRUaW1lKCkpIC8gKDEwMDAgKiA2MCAqIDYwICogMjQpXG5cdCk7XG5cblx0aWYgKGRheXMgPT09IDApIHJldHVybiBcIlRvZGF5XCI7XG5cdGlmIChkYXlzID09PSAxKSByZXR1cm4gXCJZZXN0ZXJkYXlcIjtcblx0aWYgKGRheXMgPCA3KSByZXR1cm4gYCR7ZGF5c30gZGF5cyBhZ29gO1xuXHRyZXR1cm4gZGF0ZS50b0xvY2FsZURhdGVTdHJpbmcoXCJlbi1VU1wiLCB7XG5cdFx0bW9udGg6IFwic2hvcnRcIixcblx0XHRkYXk6IFwibnVtZXJpY1wiLFxuXHR9KTtcbn1cblxuZnVuY3Rpb24gZm9ybWF0UmVsYXRpdmVUaW1lKGlzbzogc3RyaW5nKTogc3RyaW5nIHtcblx0Y29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcblx0Y29uc3QgdGhlbiA9IG5ldyBEYXRlKGlzbykuZ2V0VGltZSgpO1xuXHRjb25zdCBkaWZmID0gbm93IC0gdGhlbjtcblx0Y29uc3Qgc2Vjb25kcyA9IE1hdGguZmxvb3IoZGlmZiAvIDEwMDApO1xuXHRjb25zdCBtaW51dGVzID0gTWF0aC5mbG9vcihzZWNvbmRzIC8gNjApO1xuXHRjb25zdCBob3VycyA9IE1hdGguZmxvb3IobWludXRlcyAvIDYwKTtcblx0Y29uc3QgZGF5cyA9IE1hdGguZmxvb3IoaG91cnMgLyAyNCk7XG5cblx0aWYgKGRheXMgPiAzMCkgcmV0dXJuIG5ldyBEYXRlKGlzbykudG9Mb2NhbGVEYXRlU3RyaW5nKCk7XG5cdGlmIChkYXlzID4gMCkgcmV0dXJuIGAke2RheXN9ZCBhZ29gO1xuXHRpZiAoaG91cnMgPiAwKSByZXR1cm4gYCR7aG91cnN9aCBhZ29gO1xuXHRpZiAobWludXRlcyA+IDApIHJldHVybiBgJHttaW51dGVzfW0gYWdvYDtcblx0cmV0dXJuIFwianVzdCBub3dcIjtcbn1cbiIsICJpbXBvcnQgeyBBcHAsIE1vZGFsIH0gZnJvbSBcIm9ic2lkaWFuXCI7XG5cbmV4cG9ydCBjbGFzcyBXZWxjb21lTW9kYWwgZXh0ZW5kcyBNb2RhbCB7XG5cdGNvbnN0cnVjdG9yKGFwcDogQXBwKSB7XG5cdFx0c3VwZXIoYXBwKTtcblx0fVxuXG5cdG9uT3BlbigpOiB2b2lkIHtcblx0XHRjb25zdCB7IGNvbnRlbnRFbCB9ID0gdGhpcztcblx0XHRjb250ZW50RWwuYWRkQ2xhc3MoXCJzYmwtd2VsY29tZS1tb2RhbFwiKTtcblxuXHRcdGNvbnRlbnRFbC5jcmVhdGVFbChcImgyXCIsIHsgdGV4dDogXCJXZWxjb21lIHRvIHN1YmxpbWF0ZWRcIiB9KTtcblxuXHRcdGNvbnRlbnRFbC5jcmVhdGVFbChcInBcIiwge1xuXHRcdFx0dGV4dDogXCJzdWJsaW1hdGVkIHN5bmNzIHlvdXIgZmlsZXMgd2l0aCB2ZXJzaW9uIGhpc3RvcnksIGNvbGxhYm9yYXRpb24sIGFuZCBhIHdlYiB2aWV3ZXIgXHUyMDE0IGFsbCBwb3dlcmVkIGJ5IGdpdCB1bmRlciB0aGUgaG9vZC5cIixcblx0XHR9KTtcblxuXHRcdGNvbnN0IHN0ZXBzID0gY29udGVudEVsLmNyZWF0ZUVsKFwiZGl2XCIsIHsgY2xzOiBcInNibC13ZWxjb21lLXN0ZXBzXCIgfSk7XG5cblx0XHRjb25zdCBzdGVwMSA9IHN0ZXBzLmNyZWF0ZURpdih7IGNsczogXCJzYmwtd2VsY29tZS1zdGVwXCIgfSk7XG5cdFx0c3RlcDEuY3JlYXRlRWwoXCJzdHJvbmdcIiwgeyB0ZXh0OiBcIjEuIE9wZW4gYSBzdWJsaW1hdGVkIGZvbGRlciBhcyBhIHZhdWx0XCIgfSk7XG5cdFx0c3RlcDEuY3JlYXRlRWwoXCJwXCIsIHtcblx0XHRcdHRleHQ6IFwiSW4gRmluZGVyLCBvcGVuIGFueSBzdWJsaW1hdGVkIGZvbGRlciAob3Igc3ViZm9sZGVyKSB1bmRlciBDbG91ZFN0b3JhZ2UgYXMgYW4gT2JzaWRpYW4gdmF1bHQuIFRoZSBwbHVnaW4gYWN0aXZhdGVzIGF1dG9tYXRpY2FsbHkgb24gc3luY2VkIGZpbGVzLlwiLFxuXHRcdH0pO1xuXG5cdFx0Y29uc3Qgc3RlcDIgPSBzdGVwcy5jcmVhdGVEaXYoeyBjbHM6IFwic2JsLXdlbGNvbWUtc3RlcFwiIH0pO1xuXHRcdHN0ZXAyLmNyZWF0ZUVsKFwic3Ryb25nXCIsIHsgdGV4dDogXCIyLiBTdGF0dXMgYmFyIHNob3dzIHN5bmMgaW5mb1wiIH0pO1xuXHRcdHN0ZXAyLmNyZWF0ZUVsKFwicFwiLCB7XG5cdFx0XHR0ZXh0OiBcIlRoZSBib3R0b20gYmFyIHNob3dzIHZlcnNpb24gbnVtYmVyLCBzeW5jIHN0YXR1cywgYW5kIGF1dGhvciBmb3IgdGhlIGN1cnJlbnQgZmlsZSBcdTIwMTQgYWxsIHJlYWQgZnJvbSBsb2NhbCBmaWxlIG1ldGFkYXRhLCBubyBhY2NvdW50IG5lZWRlZC5cIixcblx0XHR9KTtcblxuXHRcdGNvbnN0IHN0ZXAzID0gc3RlcHMuY3JlYXRlRGl2KHsgY2xzOiBcInNibC13ZWxjb21lLXN0ZXBcIiB9KTtcblx0XHRzdGVwMy5jcmVhdGVFbChcInN0cm9uZ1wiLCB7IHRleHQ6IFwiMy4gQWRkIGFuIEFQSSB0b2tlbiBmb3IgdmVyc2lvbiBoaXN0b3J5XCIgfSk7XG5cdFx0c3RlcDMuY3JlYXRlRWwoXCJwXCIsIHtcblx0XHRcdHRleHQ6IFwiVG8gYnJvd3NlIHBhc3QgdmVyc2lvbnMsIGFkZCB5b3VyIEFQSSB0b2tlbiBpbiBTZXR0aW5ncyBcdTIxOTIgc3VibGltYXRlZC4gR2VuZXJhdGUgb25lIGF0IHN1YmxpbWF0ZWQuY29tL3NldHRpbmdzL3Rva2Vucy5cIixcblx0XHR9KTtcblxuXHRcdGNvbnN0IGxpbmtzID0gY29udGVudEVsLmNyZWF0ZURpdih7IGNsczogXCJzYmwtd2VsY29tZS1saW5rc1wiIH0pO1xuXHRcdGxpbmtzLmNyZWF0ZUVsKFwiYVwiLCB7XG5cdFx0XHR0ZXh0OiBcIkRvY3VtZW50YXRpb25cIixcblx0XHRcdGhyZWY6IFwiaHR0cHM6Ly9zdWJsaW1hdGVkLmNvbS9kb2NzL29ic2lkaWFuXCIsXG5cdFx0fSk7XG5cdFx0bGlua3MuY3JlYXRlRWwoXCJiclwiKTtcblx0XHRsaW5rcy5jcmVhdGVFbChcImFcIiwge1xuXHRcdFx0dGV4dDogXCJTb3VyY2UgY29kZVwiLFxuXHRcdFx0aHJlZjogXCJodHRwczovL3N1YmxpbWF0ZWQuY29tL3N1YmxpbWF0ZWQvY29kZS9vYnNpZGlhblwiLFxuXHRcdH0pO1xuXG5cdFx0Y29uc3QgZm9vdGVyID0gY29udGVudEVsLmNyZWF0ZURpdih7IGNsczogXCJzYmwtd2VsY29tZS1mb290ZXJcIiB9KTtcblx0XHRmb290ZXIuY3JlYXRlRWwoXCJidXR0b25cIiwge1xuXHRcdFx0dGV4dDogXCJHZXQgc3RhcnRlZFwiLFxuXHRcdFx0Y2xzOiBcIm1vZC1jdGFcIixcblx0XHR9KS5hZGRFdmVudExpc3RlbmVyKFwiY2xpY2tcIiwgKCkgPT4gdGhpcy5jbG9zZSgpKTtcblx0fVxuXG5cdG9uQ2xvc2UoKTogdm9pZCB7XG5cdFx0dGhpcy5jb250ZW50RWwuZW1wdHkoKTtcblx0fVxufVxuIl0sCiAgIm1hcHBpbmdzIjogIjs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQSxJQUFBQSxtQkFLTzs7O0FDTFAsSUFBQUMsbUJBQXVEOzs7QUNBdkQsc0JBQTJCO0FBRTNCLElBQU0sV0FBVztBQVdWLElBQU0sb0JBQU4sY0FBZ0MsTUFBTTtBQUFBLEVBQzVDLFlBQW1CLFFBQWdCO0FBQ2xDLFVBQU0sa0JBQWtCLE1BQU0sR0FBRztBQURmO0FBRWxCLFNBQUssT0FBTztBQUFBLEVBQ2I7QUFDRDtBQW1DTyxJQUFNLGdCQUFOLE1BQW9CO0FBQUEsRUFDMUIsWUFBb0IsT0FBZTtBQUFmO0FBQUEsRUFBZ0I7QUFBQSxFQUVwQyxNQUFjLFFBQVcsTUFBMEI7QUFDbEQsVUFBTSxVQUFrQztBQUFBLE1BQ3ZDLFFBQVE7QUFBQSxJQUNUO0FBQ0EsUUFBSSxLQUFLLE9BQU87QUFDZixjQUFRLGdCQUFnQixVQUFVLEtBQUssS0FBSztBQUFBLElBQzdDO0FBQ0EsVUFBTSxXQUFXLFVBQU0sNEJBQVc7QUFBQSxNQUNqQyxLQUFLLEdBQUcsUUFBUSxHQUFHLElBQUk7QUFBQSxNQUN2QjtBQUFBLE1BQ0EsT0FBTztBQUFBLElBQ1IsQ0FBQztBQUVELFFBQUksU0FBUyxXQUFXLE9BQU8sU0FBUyxXQUFXLEtBQUs7QUFDdkQsWUFBTSxJQUFJLGtCQUFrQixTQUFTLE1BQU07QUFBQSxJQUM1QztBQUNBLFFBQUksU0FBUyxVQUFVLEtBQUs7QUFDM0IsWUFBTSxJQUFJO0FBQUEsUUFDVCxhQUFhLFNBQVMsTUFBTSxLQUFLLFNBQVMsSUFBSTtBQUFBLE1BQy9DO0FBQUEsSUFDRDtBQUVBLFVBQU0sT0FBTyxTQUFTO0FBQ3RCLFFBQUksQ0FBQyxLQUFLLE1BQU0sS0FBSyxTQUFTLFFBQVc7QUFDeEMsWUFBTSxNQUFNLEtBQUssT0FBTyxXQUFXLEtBQUssT0FBTyxRQUFRO0FBQ3ZELFlBQU0sSUFBSSxNQUFNLGNBQWMsR0FBRyxFQUFFO0FBQUEsSUFDcEM7QUFDQSxXQUFPLEtBQUs7QUFBQSxFQUNiO0FBQUEsRUFFQSxNQUFNLFlBQ0wsV0FDQSxRQUNxQjtBQUNyQixVQUFNLE1BQU0sTUFBTSxLQUFLO0FBQUEsTUFDdEIsY0FBYyxtQkFBbUIsU0FBUyxDQUFDLFVBQVUsbUJBQW1CLE1BQU0sQ0FBQztBQUFBLElBQ2hGO0FBQ0EsV0FBTyxJQUFJLElBQUksVUFBVSxFQUFFLFFBQVE7QUFBQSxFQUNwQztBQUFBLEVBRUEsTUFBTSxpQkFBZ0Q7QUFDckQsUUFBSTtBQUNILFlBQU0sT0FBTyxNQUFNLEtBQUs7QUFBQSxRQUN2QjtBQUFBLE1BQ0Q7QUFDQSxhQUFPLEVBQUUsSUFBSSxNQUFNLFVBQVUsS0FBSyxTQUFTO0FBQUEsSUFDNUMsU0FBUyxHQUFHO0FBQ1gsYUFBTztBQUFBLFFBQ04sSUFBSTtBQUFBLFFBQ0osT0FBTyxhQUFhLFFBQVEsRUFBRSxVQUFVO0FBQUEsTUFDekM7QUFBQSxJQUNEO0FBQUEsRUFDRDtBQUNEO0FBRUEsU0FBUyxXQUFXLEdBQXdCO0FBRTNDLFFBQU0sU0FDTCxFQUFFLE9BQU8sS0FBSyxDQUFDLE1BQU0sRUFBRSxTQUFTLFFBQVEsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLO0FBQzdELFNBQU87QUFBQSxJQUNOLElBQUksRUFBRTtBQUFBLElBQ04sUUFBUSxFQUFFO0FBQUEsSUFDVixZQUFZLFFBQVEsUUFBUSxRQUFRLFlBQVk7QUFBQSxJQUNoRCxZQUFZLFFBQVEsY0FBYztBQUFBLElBQ2xDLFNBQVMsRUFBRTtBQUFBLElBQ1gsV0FBVyxFQUFFO0FBQUEsRUFDZDtBQUNEOzs7QURsSE8sSUFBTSxtQkFBdUM7QUFBQSxFQUNuRCxVQUFVO0FBQUEsRUFDVixnQkFBZ0I7QUFDakI7QUFFTyxJQUFNLHVCQUFOLGNBQW1DLGtDQUFpQjtBQUFBLEVBQzFEO0FBQUEsRUFFQSxZQUFZLEtBQVUsUUFBMEI7QUFDL0MsVUFBTSxLQUFLLE1BQU07QUFDakIsU0FBSyxTQUFTO0FBQUEsRUFDZjtBQUFBLEVBRUEsVUFBZ0I7QUFDZixVQUFNLEVBQUUsWUFBWSxJQUFJO0FBQ3hCLGdCQUFZLE1BQU07QUFHbEIsVUFBTSxTQUFTLFlBQVksVUFBVSxFQUFFLEtBQUssc0JBQXNCLENBQUM7QUFDbkUsV0FBTyxTQUFTLE1BQU0sRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUM1QyxXQUFPLFNBQVMsS0FBSztBQUFBLE1BQ3BCLE1BQU07QUFBQSxNQUNOLEtBQUs7QUFBQSxJQUNOLENBQUM7QUFHRCxVQUFNLGdCQUFnQixLQUFLLE9BQU8sZ0JBQWdCO0FBQ2xELFFBQUksZUFBZTtBQUNsQixVQUFJLHlCQUFRLFdBQVcsRUFDckIsUUFBUSxvQkFBb0IsRUFDNUI7QUFBQSxRQUNBLFVBQVUsY0FBYyxTQUFTLFNBQVM7QUFBQSxNQUMzQztBQUFBLElBQ0YsT0FBTztBQUNOLFVBQUkseUJBQVEsV0FBVyxFQUNyQixRQUFRLHlCQUF5QixFQUNqQztBQUFBLFFBQ0E7QUFBQSxNQUNEO0FBQUEsSUFDRjtBQUdBLFFBQUkseUJBQVEsV0FBVyxFQUNyQixRQUFRLFdBQVcsRUFDbkI7QUFBQSxNQUNBO0FBQUEsSUFDRCxFQUNDO0FBQUEsTUFBUSxDQUFDLFNBQ1QsS0FDRSxlQUFlLFFBQVEsRUFDdkIsU0FBUyxLQUFLLE9BQU8sU0FBUyxRQUFRLEVBQ3RDLFNBQVMsT0FBTyxVQUFVO0FBQzFCLGFBQUssT0FBTyxTQUFTLFdBQVc7QUFDaEMsY0FBTSxLQUFLLE9BQU8sYUFBYTtBQUFBLE1BQ2hDLENBQUM7QUFBQSxJQUNIO0FBR0QsUUFBSSx5QkFBUSxXQUFXLEVBQ3JCLFFBQVEsaUJBQWlCLEVBQ3pCLFFBQVEsNkJBQTZCLEVBQ3JDO0FBQUEsTUFBVSxDQUFDLFFBQ1gsSUFBSSxjQUFjLE1BQU0sRUFBRSxRQUFRLFlBQVk7QUFDN0MsWUFBSSxDQUFDLEtBQUssT0FBTyxTQUFTLFVBQVU7QUFDbkMsY0FBSSx3QkFBTyx5QkFBeUI7QUFDcEM7QUFBQSxRQUNEO0FBQ0EsWUFBSSxjQUFjLFlBQVk7QUFDOUIsWUFBSSxZQUFZLElBQUk7QUFDcEIsY0FBTSxNQUFNLElBQUk7QUFBQSxVQUNmLEtBQUssT0FBTyxTQUFTO0FBQUEsUUFDdEI7QUFDQSxjQUFNLFNBQVMsTUFBTSxJQUFJLGVBQWU7QUFDeEMsWUFBSSxPQUFPLElBQUk7QUFDZCxjQUFJO0FBQUEsWUFDSCxnQkFBZ0IsT0FBTyxRQUFRO0FBQUEsVUFDaEM7QUFBQSxRQUNELE9BQU87QUFDTixjQUFJO0FBQUEsWUFDSCxzQkFBc0IsT0FBTyxLQUFLO0FBQUEsVUFDbkM7QUFBQSxRQUNEO0FBQ0EsWUFBSSxjQUFjLE1BQU07QUFDeEIsWUFBSSxZQUFZLEtBQUs7QUFBQSxNQUN0QixDQUFDO0FBQUEsSUFDRjtBQUdELGdCQUFZLFNBQVMsT0FBTyxFQUFFLEtBQUsscUJBQXFCLEdBQUcsQ0FBQyxPQUFPO0FBQ2xFLFNBQUcsU0FBUyxLQUFLO0FBQUEsUUFDaEIsTUFBTTtBQUFBLFFBQ04sTUFBTTtBQUFBLE1BQ1AsQ0FBQztBQUNELFNBQUcsU0FBUyxRQUFRLEVBQUUsTUFBTSxTQUFNLENBQUM7QUFDbkMsU0FBRyxTQUFTLEtBQUs7QUFBQSxRQUNoQixNQUFNO0FBQUEsUUFDTixNQUFNO0FBQUEsTUFDUCxDQUFDO0FBQ0QsU0FBRyxTQUFTLFFBQVEsRUFBRSxNQUFNLFNBQU0sQ0FBQztBQUNuQyxTQUFHLFNBQVMsS0FBSztBQUFBLFFBQ2hCLE1BQU07QUFBQSxRQUNOLE1BQU07QUFBQSxNQUNQLENBQUM7QUFBQSxJQUNGLENBQUM7QUFBQSxFQUNGO0FBQ0Q7OztBRWxIQSwyQkFBcUI7QUFDckIsSUFBQUMsbUJBQXlCO0FBRXpCLElBQU0sZUFBZTtBQUNyQixJQUFNLGVBQWU7QUFrQnJCLElBQU0sUUFBZ0M7QUFBQSxFQUNyQztBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsRUFDQTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsRUFDQTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsRUFDQTtBQUFBLEVBQ0E7QUFBQSxFQUNBO0FBQUEsRUFDQTtBQUNEO0FBRUEsU0FBUyxnQkFBZ0IsTUFBc0I7QUFFOUMsUUFBTSxRQUFRLEtBQUssUUFBUSxVQUFVLENBQUMsTUFBTSxNQUFNLEVBQUUsWUFBWSxDQUFDO0FBQ2pFLFNBQU8sR0FBRyxZQUFZLEdBQUcsS0FBSyxHQUFHLFlBQVk7QUFDOUM7QUFFQSxTQUFTLFVBQVUsTUFBYyxXQUEyQztBQUMzRSxTQUFPLElBQUksUUFBUSxDQUFDLFlBQVk7QUFDL0I7QUFBQSxNQUNDLFlBQVksS0FBSyxVQUFVLFNBQVMsQ0FBQyxJQUFJLEtBQUssVUFBVSxJQUFJLENBQUM7QUFBQSxNQUM3RCxFQUFFLFNBQVMsSUFBSztBQUFBLE1BQ2hCLENBQUMsS0FBSyxXQUFXO0FBQ2hCLFlBQUksS0FBSztBQUNSLGtCQUFRLElBQUk7QUFDWjtBQUFBLFFBQ0Q7QUFDQSxnQkFBUSxPQUFPLEtBQUssS0FBSyxJQUFJO0FBQUEsTUFDOUI7QUFBQSxJQUNEO0FBQUEsRUFDRCxDQUFDO0FBQ0Y7QUFFQSxlQUFzQixpQkFDckIsTUFDK0I7QUFDL0IsTUFBSSxDQUFDLDBCQUFTLFFBQVMsUUFBTztBQUU5QixRQUFNLFVBQVUsTUFBTSxRQUFRO0FBQUEsSUFDN0IsTUFBTSxJQUFJLENBQUMsU0FBUyxVQUFVLE1BQU0sZ0JBQWdCLElBQUksQ0FBQyxDQUFDO0FBQUEsRUFDM0Q7QUFHQSxNQUFJLFFBQVEsTUFBTSxDQUFDLE1BQU0sTUFBTSxJQUFJLEVBQUcsUUFBTztBQUU3QyxRQUFNLE9BQXFCO0FBQUEsSUFDMUIsU0FBUztBQUFBLElBQU0sUUFBUTtBQUFBLElBQU0sS0FBSztBQUFBLElBQU0sYUFBYTtBQUFBLElBQ3JELElBQUk7QUFBQSxJQUFNLFdBQVc7QUFBQSxJQUFNLFlBQVk7QUFBQSxJQUFNLGdCQUFnQjtBQUFBLElBQzdELFlBQVk7QUFBQSxJQUFNLE9BQU87QUFBQSxJQUFNLFFBQVE7QUFBQSxJQUFNLFlBQVk7QUFBQSxJQUFNLFlBQVk7QUFBQSxFQUM1RTtBQUNBLFFBQU0sUUFBUSxDQUFDLE1BQU0sTUFBTTtBQUMxQixTQUFLLElBQUksSUFBSSxRQUFRLENBQUM7QUFBQSxFQUN2QixDQUFDO0FBQ0QsU0FBTztBQUNSO0FBRU8sU0FBUyxVQUFtQjtBQUNsQyxTQUFPLDBCQUFTO0FBQ2pCOzs7QUNyRkEsSUFBQUMsbUJBQWdEO0FBSXpDLElBQU0sb0JBQW9CO0FBRTFCLElBQU0sY0FBTixjQUEwQiwwQkFBUztBQUFBLEVBQ3pDO0FBQUEsRUFFQSxZQUFZLE1BQXFCLFFBQTBCO0FBQzFELFVBQU0sSUFBSTtBQUNWLFNBQUssU0FBUztBQUFBLEVBQ2Y7QUFBQSxFQUVBLGNBQXNCO0FBQ3JCLFdBQU87QUFBQSxFQUNSO0FBQUEsRUFFQSxpQkFBeUI7QUFDeEIsV0FBTztBQUFBLEVBQ1I7QUFBQSxFQUVBLFVBQWtCO0FBQ2pCLFdBQU87QUFBQSxFQUNSO0FBQUEsRUFFQSxNQUFNLFNBQXdCO0FBQzdCLFVBQU0sS0FBSyxRQUFRO0FBRW5CLFNBQUs7QUFBQSxNQUNKLEtBQUssSUFBSSxVQUFVLEdBQUcsc0JBQXNCLE1BQU07QUFDakQsYUFBSyxRQUFRO0FBQUEsTUFDZCxDQUFDO0FBQUEsSUFDRjtBQUFBLEVBQ0Q7QUFBQSxFQUVBLE1BQU0sVUFBeUI7QUFDOUIsVUFBTSxFQUFFLFVBQVUsSUFBSTtBQUN0QixjQUFVLE1BQU07QUFDaEIsY0FBVSxTQUFTLGtCQUFrQjtBQUVyQyxVQUFNLGFBQWEsS0FBSyxJQUFJLFVBQVUsY0FBYztBQUNwRCxRQUFJLENBQUMsWUFBWTtBQUNoQixnQkFBVSxTQUFTLEtBQUs7QUFBQSxRQUN2QixNQUFNO0FBQUEsUUFDTixLQUFLO0FBQUEsTUFDTixDQUFDO0FBQ0Q7QUFBQSxJQUNEO0FBR0EsVUFBTSxPQUFPLEtBQUssT0FBTztBQUN6QixRQUFJLENBQUMsTUFBTSxTQUFTLENBQUMsTUFBTSxJQUFJO0FBQzlCLGdCQUFVLFNBQVMsS0FBSztBQUFBLFFBQ3ZCLE1BQU07QUFBQSxRQUNOLEtBQUs7QUFBQSxNQUNOLENBQUM7QUFDRDtBQUFBLElBQ0Q7QUFFQSxjQUFVLFNBQVMsTUFBTTtBQUFBLE1BQ3hCLE1BQU0sV0FBVztBQUFBLE1BQ2pCLEtBQUs7QUFBQSxJQUNOLENBQUM7QUFFRCxVQUFNLFVBQVUsVUFBVSxTQUFTLEtBQUs7QUFBQSxNQUN2QyxNQUFNO0FBQUEsTUFDTixLQUFLO0FBQUEsSUFDTixDQUFDO0FBRUQsUUFBSTtBQUNILFlBQU0sTUFBTSxJQUFJLGNBQWMsS0FBSyxPQUFPLFNBQVMsUUFBUTtBQUMzRCxZQUFNLFdBQVcsTUFBTSxJQUFJLFlBQVksS0FBSyxPQUFPLEtBQUssRUFBRTtBQUMxRCxjQUFRLE9BQU87QUFFZixVQUFJLFNBQVMsV0FBVyxHQUFHO0FBQzFCLGtCQUFVLFNBQVMsS0FBSztBQUFBLFVBQ3ZCLE1BQU07QUFBQSxVQUNOLEtBQUs7QUFBQSxRQUNOLENBQUM7QUFDRDtBQUFBLE1BQ0Q7QUFFQSxZQUFNLE9BQU8sVUFBVSxTQUFTLE9BQU87QUFBQSxRQUN0QyxLQUFLO0FBQUEsTUFDTixDQUFDO0FBQ0QsWUFBTSxTQUFTLFdBQVcsUUFBUTtBQUNsQyxpQkFBVyxTQUFTLFFBQVE7QUFDM0IsYUFBSyxTQUFTLE9BQU87QUFBQSxVQUNwQixNQUFNLE1BQU07QUFBQSxVQUNaLEtBQUs7QUFBQSxRQUNOLENBQUM7QUFDRCxtQkFBVyxLQUFLLE1BQU0sVUFBVTtBQUMvQixlQUFLLGNBQWMsTUFBTSxHQUFHLEtBQUssR0FBRztBQUFBLFFBQ3JDO0FBQUEsTUFDRDtBQUFBLElBQ0QsU0FBUyxHQUFHO0FBQ1gsY0FBUSxPQUFPO0FBQ2YsVUFBSSxhQUFhLG1CQUFtQjtBQUNuQyxrQkFBVSxTQUFTLEtBQUs7QUFBQSxVQUN2QixNQUFNO0FBQUEsVUFDTixLQUFLO0FBQUEsUUFDTixDQUFDO0FBQ0Qsa0JBQVUsU0FBUyxLQUFLO0FBQUEsVUFDdkIsTUFBTTtBQUFBLFVBQ04sS0FBSztBQUFBLFFBQ04sQ0FBQztBQUNEO0FBQUEsTUFDRDtBQUNBLGdCQUFVLFNBQVMsS0FBSztBQUFBLFFBQ3ZCLE1BQU0sVUFBVSxhQUFhLFFBQVEsRUFBRSxVQUFVLFNBQVM7QUFBQSxRQUMxRCxLQUFLO0FBQUEsTUFDTixDQUFDO0FBQUEsSUFDRjtBQUFBLEVBQ0Q7QUFBQSxFQUVRLGNBQ1AsV0FDQSxHQUNBLFNBQ087QUFDUCxVQUFNLE9BQU8sVUFBVSxVQUFVLEVBQUUsS0FBSyxtQkFBbUIsQ0FBQztBQUU1RCxVQUFNLFNBQVMsS0FBSyxVQUFVLEVBQUUsS0FBSyxxQkFBcUIsQ0FBQztBQUMzRCxXQUFPLFNBQVMsUUFBUTtBQUFBLE1BQ3ZCLE1BQU0sSUFBSSxFQUFFLE1BQU07QUFBQSxNQUNsQixLQUFLO0FBQUEsSUFDTixDQUFDO0FBQ0QsV0FBTyxTQUFTLFFBQVE7QUFBQSxNQUN2QixNQUFNLEVBQUU7QUFBQSxNQUNSLEtBQUs7QUFBQSxJQUNOLENBQUM7QUFFRCxVQUFNLE9BQU8sS0FBSyxVQUFVLEVBQUUsS0FBSyxtQkFBbUIsQ0FBQztBQUN2RCxTQUFLLFNBQVMsUUFBUTtBQUFBLE1BQ3JCLE1BQU0sbUJBQW1CLEVBQUUsU0FBUztBQUFBLE1BQ3BDLE1BQU0sRUFBRSxPQUFPLElBQUksS0FBSyxFQUFFLFNBQVMsRUFBRSxlQUFlLEVBQUU7QUFBQSxJQUN2RCxDQUFDO0FBRUQsUUFBSSxFQUFFLFNBQVM7QUFDZCxXQUFLLFVBQVU7QUFBQSxRQUNkLE1BQU0sRUFBRTtBQUFBLFFBQ1IsS0FBSztBQUFBLE1BQ04sQ0FBQztBQUFBLElBQ0Y7QUFFQSxRQUFJLFNBQVM7QUFDWixZQUFNLFNBQVMsV0FBVyxPQUFPLGVBQWUsRUFBRSxNQUFNO0FBQ3hELFlBQU0sT0FBTyxLQUFLLFNBQVMsS0FBSztBQUFBLFFBQy9CLE1BQU07QUFBQSxRQUNOLEtBQUs7QUFBQSxRQUNMLE1BQU07QUFBQSxNQUNQLENBQUM7QUFDRCxXQUFLLGlCQUFpQixTQUFTLENBQUMsTUFBTTtBQUNyQyxVQUFFLGVBQWU7QUFDakIsZUFBTyxLQUFLLE1BQU07QUFBQSxNQUNuQixDQUFDO0FBQUEsSUFDRjtBQUFBLEVBQ0Q7QUFBQSxFQUVBLE1BQU0sVUFBeUI7QUFBQSxFQUUvQjtBQUNEO0FBT0EsU0FBUyxXQUFXLFVBQWlDO0FBQ3BELFFBQU0sU0FBUyxvQkFBSSxJQUF1QjtBQUMxQyxRQUFNLFFBQWtCLENBQUM7QUFFekIsYUFBVyxLQUFLLFVBQVU7QUFDekIsVUFBTSxRQUFRLGVBQWUsRUFBRSxTQUFTO0FBQ3hDLFFBQUksQ0FBQyxPQUFPLElBQUksS0FBSyxHQUFHO0FBQ3ZCLGFBQU8sSUFBSSxPQUFPLENBQUMsQ0FBQztBQUNwQixZQUFNLEtBQUssS0FBSztBQUFBLElBQ2pCO0FBQ0EsV0FBTyxJQUFJLEtBQUssRUFBRyxLQUFLLENBQUM7QUFBQSxFQUMxQjtBQUVBLFNBQU8sTUFBTSxJQUFJLENBQUMsV0FBVztBQUFBLElBQzVCO0FBQUEsSUFDQSxVQUFVLE9BQU8sSUFBSSxLQUFLO0FBQUEsRUFDM0IsRUFBRTtBQUNIO0FBRUEsU0FBUyxlQUFlLEtBQXFCO0FBQzVDLFFBQU0sTUFBTSxvQkFBSSxLQUFLO0FBQ3JCLFFBQU0sT0FBTyxJQUFJLEtBQUssR0FBRztBQUV6QixRQUFNLFNBQVMsSUFBSSxLQUFLLElBQUksWUFBWSxHQUFHLElBQUksU0FBUyxHQUFHLElBQUksUUFBUSxDQUFDO0FBQ3hFLFFBQU0sVUFBVSxJQUFJO0FBQUEsSUFDbkIsS0FBSyxZQUFZO0FBQUEsSUFDakIsS0FBSyxTQUFTO0FBQUEsSUFDZCxLQUFLLFFBQVE7QUFBQSxFQUNkO0FBQ0EsUUFBTSxPQUFPLEtBQUs7QUFBQSxLQUNoQixPQUFPLFFBQVEsSUFBSSxRQUFRLFFBQVEsTUFBTSxNQUFPLEtBQUssS0FBSztBQUFBLEVBQzVEO0FBRUEsTUFBSSxTQUFTLEVBQUcsUUFBTztBQUN2QixNQUFJLFNBQVMsRUFBRyxRQUFPO0FBQ3ZCLE1BQUksT0FBTyxFQUFHLFFBQU8sR0FBRyxJQUFJO0FBQzVCLFNBQU8sS0FBSyxtQkFBbUIsU0FBUztBQUFBLElBQ3ZDLE9BQU87QUFBQSxJQUNQLEtBQUs7QUFBQSxFQUNOLENBQUM7QUFDRjtBQUVBLFNBQVMsbUJBQW1CLEtBQXFCO0FBQ2hELFFBQU0sTUFBTSxLQUFLLElBQUk7QUFDckIsUUFBTSxPQUFPLElBQUksS0FBSyxHQUFHLEVBQUUsUUFBUTtBQUNuQyxRQUFNLE9BQU8sTUFBTTtBQUNuQixRQUFNLFVBQVUsS0FBSyxNQUFNLE9BQU8sR0FBSTtBQUN0QyxRQUFNLFVBQVUsS0FBSyxNQUFNLFVBQVUsRUFBRTtBQUN2QyxRQUFNLFFBQVEsS0FBSyxNQUFNLFVBQVUsRUFBRTtBQUNyQyxRQUFNLE9BQU8sS0FBSyxNQUFNLFFBQVEsRUFBRTtBQUVsQyxNQUFJLE9BQU8sR0FBSSxRQUFPLElBQUksS0FBSyxHQUFHLEVBQUUsbUJBQW1CO0FBQ3ZELE1BQUksT0FBTyxFQUFHLFFBQU8sR0FBRyxJQUFJO0FBQzVCLE1BQUksUUFBUSxFQUFHLFFBQU8sR0FBRyxLQUFLO0FBQzlCLE1BQUksVUFBVSxFQUFHLFFBQU8sR0FBRyxPQUFPO0FBQ2xDLFNBQU87QUFDUjs7O0FDbE9BLElBQUFDLG1CQUEyQjtBQUVwQixJQUFNLGVBQU4sY0FBMkIsdUJBQU07QUFBQSxFQUN2QyxZQUFZLEtBQVU7QUFDckIsVUFBTSxHQUFHO0FBQUEsRUFDVjtBQUFBLEVBRUEsU0FBZTtBQUNkLFVBQU0sRUFBRSxVQUFVLElBQUk7QUFDdEIsY0FBVSxTQUFTLG1CQUFtQjtBQUV0QyxjQUFVLFNBQVMsTUFBTSxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFFMUQsY0FBVSxTQUFTLEtBQUs7QUFBQSxNQUN2QixNQUFNO0FBQUEsSUFDUCxDQUFDO0FBRUQsVUFBTSxRQUFRLFVBQVUsU0FBUyxPQUFPLEVBQUUsS0FBSyxvQkFBb0IsQ0FBQztBQUVwRSxVQUFNLFFBQVEsTUFBTSxVQUFVLEVBQUUsS0FBSyxtQkFBbUIsQ0FBQztBQUN6RCxVQUFNLFNBQVMsVUFBVSxFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDM0UsVUFBTSxTQUFTLEtBQUs7QUFBQSxNQUNuQixNQUFNO0FBQUEsSUFDUCxDQUFDO0FBRUQsVUFBTSxRQUFRLE1BQU0sVUFBVSxFQUFFLEtBQUssbUJBQW1CLENBQUM7QUFDekQsVUFBTSxTQUFTLFVBQVUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ2xFLFVBQU0sU0FBUyxLQUFLO0FBQUEsTUFDbkIsTUFBTTtBQUFBLElBQ1AsQ0FBQztBQUVELFVBQU0sUUFBUSxNQUFNLFVBQVUsRUFBRSxLQUFLLG1CQUFtQixDQUFDO0FBQ3pELFVBQU0sU0FBUyxVQUFVLEVBQUUsTUFBTSwwQ0FBMEMsQ0FBQztBQUM1RSxVQUFNLFNBQVMsS0FBSztBQUFBLE1BQ25CLE1BQU07QUFBQSxJQUNQLENBQUM7QUFFRCxVQUFNLFFBQVEsVUFBVSxVQUFVLEVBQUUsS0FBSyxvQkFBb0IsQ0FBQztBQUM5RCxVQUFNLFNBQVMsS0FBSztBQUFBLE1BQ25CLE1BQU07QUFBQSxNQUNOLE1BQU07QUFBQSxJQUNQLENBQUM7QUFDRCxVQUFNLFNBQVMsSUFBSTtBQUNuQixVQUFNLFNBQVMsS0FBSztBQUFBLE1BQ25CLE1BQU07QUFBQSxNQUNOLE1BQU07QUFBQSxJQUNQLENBQUM7QUFFRCxVQUFNLFNBQVMsVUFBVSxVQUFVLEVBQUUsS0FBSyxxQkFBcUIsQ0FBQztBQUNoRSxXQUFPLFNBQVMsVUFBVTtBQUFBLE1BQ3pCLE1BQU07QUFBQSxNQUNOLEtBQUs7QUFBQSxJQUNOLENBQUMsRUFBRSxpQkFBaUIsU0FBUyxNQUFNLEtBQUssTUFBTSxDQUFDO0FBQUEsRUFDaEQ7QUFBQSxFQUVBLFVBQWdCO0FBQ2YsU0FBSyxVQUFVLE1BQU07QUFBQSxFQUN0QjtBQUNEOzs7QUwzQ0EsSUFBcUIsbUJBQXJCLGNBQThDLHdCQUFPO0FBQUEsRUFDcEQsV0FBK0I7QUFBQSxFQUMvQixlQUFvQztBQUFBLEVBRTVCLGNBQWtDO0FBQUEsRUFFMUMsTUFBTSxTQUF3QjtBQUM3QixVQUFNLEtBQUssYUFBYTtBQUd4QixTQUFLLGNBQWMsS0FBSyxpQkFBaUI7QUFDekMsU0FBSyxZQUFZLFNBQVMsZ0JBQWdCO0FBQzFDLFNBQUssWUFBWTtBQUFBLE1BQWlCO0FBQUEsTUFBUyxNQUMxQyxLQUFLLHFCQUFxQjtBQUFBLElBQzNCO0FBQ0EsU0FBSyxnQkFBZ0IsSUFBSTtBQUd6QixTQUFLO0FBQUEsTUFDSjtBQUFBLE1BQ0EsQ0FBQyxTQUFTLElBQUksWUFBWSxNQUFNLElBQUk7QUFBQSxJQUNyQztBQUdBLFNBQUssV0FBVztBQUFBLE1BQ2YsSUFBSTtBQUFBLE1BQ0osTUFBTTtBQUFBLE1BQ04sVUFBVSxNQUFNLEtBQUsscUJBQXFCO0FBQUEsSUFDM0MsQ0FBQztBQUVELFNBQUssV0FBVztBQUFBLE1BQ2YsSUFBSTtBQUFBLE1BQ0osTUFBTTtBQUFBLE1BQ04sVUFBVSxNQUFNLEtBQUssb0JBQW9CO0FBQUEsSUFDMUMsQ0FBQztBQUVELFNBQUssV0FBVztBQUFBLE1BQ2YsSUFBSTtBQUFBLE1BQ0osTUFBTTtBQUFBLE1BQ04sVUFBVSxNQUFNLEtBQUssWUFBWTtBQUFBLElBQ2xDLENBQUM7QUFHRCxTQUFLLGNBQWMsV0FBVyx1QkFBdUIsTUFBTTtBQUMxRCxXQUFLLG9CQUFvQjtBQUFBLElBQzFCLENBQUM7QUFHRCxTQUFLO0FBQUEsTUFDSjtBQUFBLE1BQ0EsQ0FBQyxXQUFXLEtBQUssVUFBVSxNQUFNO0FBQUEsSUFDbEM7QUFHQSxTQUFLLGNBQWMsSUFBSSxxQkFBcUIsS0FBSyxLQUFLLElBQUksQ0FBQztBQUczRCxTQUFLO0FBQUEsTUFDSixLQUFLLElBQUksVUFBVSxHQUFHLHNCQUFzQixNQUFNO0FBQ2pELGFBQUssZ0JBQWdCO0FBQUEsTUFDdEIsQ0FBQztBQUFBLElBQ0Y7QUFHQSxTQUFLLGdCQUFnQjtBQUdyQixRQUFJLENBQUMsS0FBSyxTQUFTLGdCQUFnQjtBQUNsQyxVQUFJLGFBQWEsS0FBSyxHQUFHLEVBQUUsS0FBSztBQUNoQyxXQUFLLFNBQVMsaUJBQWlCO0FBQy9CLFlBQU0sS0FBSyxhQUFhO0FBQUEsSUFDekI7QUFBQSxFQUNEO0FBQUEsRUFFQSxXQUFpQjtBQUNoQixTQUFLLElBQUksVUFBVSxtQkFBbUIsaUJBQWlCO0FBQUEsRUFDeEQ7QUFBQSxFQUVBLE1BQU0sZUFBOEI7QUFDbkMsU0FBSyxXQUFXLE9BQU87QUFBQSxNQUN0QixDQUFDO0FBQUEsTUFDRDtBQUFBLE1BQ0EsTUFBTSxLQUFLLFNBQVM7QUFBQSxJQUNyQjtBQUFBLEVBQ0Q7QUFBQSxFQUVBLE1BQU0sZUFBOEI7QUFDbkMsVUFBTSxLQUFLLFNBQVMsS0FBSyxRQUFRO0FBQUEsRUFDbEM7QUFBQTtBQUFBO0FBQUE7QUFBQSxFQUtBLGtCQUFtRDtBQUNsRCxRQUFJLENBQUMsS0FBSyxhQUFjLFFBQU87QUFDL0IsUUFBSSxDQUFDLEtBQUssYUFBYSxTQUFTLENBQUMsS0FBSyxhQUFhLEdBQUksUUFBTztBQUM5RCxXQUFPO0FBQUEsTUFDTixPQUFPLEtBQUssYUFBYSxTQUFTO0FBQUEsSUFDbkM7QUFBQSxFQUNEO0FBQUEsRUFFQSxNQUFjLGtCQUFpQztBQUM5QyxVQUFNLE9BQU8sS0FBSyxJQUFJLFVBQVUsY0FBYztBQUM5QyxRQUFJLENBQUMsTUFBTTtBQUNWLFdBQUssZUFBZTtBQUNwQixXQUFLLGdCQUFnQixJQUFJO0FBQ3pCO0FBQUEsSUFDRDtBQUVBLFFBQUksQ0FBQyxRQUFRLEdBQUc7QUFDZixXQUFLLGVBQWU7QUFDcEIsV0FBSyxnQkFBZ0IsSUFBSTtBQUN6QjtBQUFBLElBQ0Q7QUFHQSxVQUFNLFVBQVUsS0FBSyxJQUFJLE1BQU07QUFDL0IsVUFBTSxXQUFZLFFBQ2hCLGNBQWM7QUFDaEIsUUFBSSxDQUFDLFVBQVU7QUFDZCxXQUFLLGVBQWU7QUFDcEIsV0FBSyxnQkFBZ0IsSUFBSTtBQUN6QjtBQUFBLElBQ0Q7QUFFQSxVQUFNLFVBQVUsR0FBRyxRQUFRLElBQUksS0FBSyxJQUFJO0FBQ3hDLFNBQUssZUFBZSxNQUFNLGlCQUFpQixPQUFPO0FBQ2xELFNBQUssZ0JBQWdCLEtBQUssWUFBWTtBQUFBLEVBQ3ZDO0FBQUEsRUFFUSxnQkFBZ0IsTUFBaUM7QUFDeEQsUUFBSSxDQUFDLEtBQUssWUFBYTtBQUd2QixRQUFJLENBQUMsUUFBUyxDQUFDLEtBQUssU0FBUyxDQUFDLEtBQUssSUFBSztBQUN2QyxXQUFLLFlBQVksUUFBUSxFQUFFO0FBQzNCO0FBQUEsSUFDRDtBQUdBLFVBQU0sUUFBa0IsQ0FBQztBQUV6QixRQUFJLEtBQUssWUFBWTtBQUNwQixZQUFNLE9BQU8sZUFBZSxLQUFLLFVBQVU7QUFDM0MsWUFBTSxLQUFLLE9BQU8sR0FBRyxJQUFJLElBQUksS0FBSyxVQUFVLEtBQUssS0FBSyxVQUFVO0FBQUEsSUFDakU7QUFFQSxRQUFJLEtBQUssU0FBUztBQUNqQixZQUFNLEtBQUssSUFBSSxLQUFLLE9BQU8sRUFBRTtBQUFBLElBQzlCO0FBRUEsUUFBSSxLQUFLLFlBQVk7QUFDcEIsWUFBTSxTQUNMLEtBQUssbUJBQW1CLGFBQWEsV0FBVztBQUNqRCxZQUFNLEtBQUssR0FBRyxNQUFNLElBQUksS0FBSyxVQUFVLEVBQUU7QUFBQSxJQUMxQztBQUVBLFFBQUksS0FBSyxVQUFVLEtBQUssV0FBVyxRQUFRO0FBQzFDLFlBQU0sS0FBSyxLQUFLLE1BQU07QUFBQSxJQUN2QjtBQUVBLFFBQUksS0FBSyxZQUFZO0FBQ3BCLFlBQU0sS0FBSyxlQUFlLEtBQUssVUFBVSxDQUFDO0FBQUEsSUFDM0M7QUFFQSxTQUFLLFlBQVksUUFBUSxNQUFNLEtBQUssR0FBRyxDQUFDO0FBR3hDLFFBQUksS0FBSyxLQUFLO0FBQ2IsV0FBSyxZQUFZLFNBQVMsZUFBZTtBQUFBLElBQzFDLE9BQU87QUFDTixXQUFLLFlBQVksWUFBWSxlQUFlO0FBQUEsSUFDN0M7QUFBQSxFQUNEO0FBQUEsRUFFUSx1QkFBNkI7QUFDcEMsVUFBTSxNQUFNLEtBQUssY0FBYztBQUMvQixRQUFJLENBQUMsS0FBSztBQUNULFVBQUksd0JBQU8saUNBQWlDO0FBQzVDO0FBQUEsSUFDRDtBQUNBLFdBQU8sS0FBSyxXQUFXLEdBQUcsRUFBRTtBQUFBLEVBQzdCO0FBQUEsRUFFQSxNQUFjLGNBQTZCO0FBQzFDLFVBQU0sTUFBTSxLQUFLLGNBQWM7QUFDL0IsUUFBSSxDQUFDLEtBQUs7QUFDVCxVQUFJLHdCQUFPLGlDQUFpQztBQUM1QztBQUFBLElBQ0Q7QUFDQSxVQUFNLFVBQVUsVUFBVSxVQUFVLFdBQVcsR0FBRyxFQUFFO0FBQ3BELFFBQUksd0JBQU8sMEJBQTBCO0FBQUEsRUFDdEM7QUFBQSxFQUVBLE1BQWMsc0JBQXFDO0FBQ2xELFVBQU0sV0FDTCxLQUFLLElBQUksVUFBVSxnQkFBZ0IsaUJBQWlCO0FBQ3JELFFBQUksU0FBUyxTQUFTLEdBQUc7QUFDeEIsV0FBSyxJQUFJLFVBQVUsV0FBVyxTQUFTLENBQUMsQ0FBQztBQUN6QztBQUFBLElBQ0Q7QUFFQSxVQUFNLE9BQU8sS0FBSyxJQUFJLFVBQVUsYUFBYSxLQUFLO0FBQ2xELFFBQUksQ0FBQyxLQUFNO0FBQ1gsVUFBTSxLQUFLLGFBQWE7QUFBQSxNQUN2QixNQUFNO0FBQUEsTUFDTixRQUFRO0FBQUEsSUFDVCxDQUFDO0FBQ0QsU0FBSyxJQUFJLFVBQVUsV0FBVyxJQUFJO0FBQUEsRUFDbkM7QUFBQSxFQUVRLFVBQVUsUUFBc0M7QUFDdkQsVUFBTSxFQUFFLFFBQVEsS0FBSyxJQUFJO0FBRXpCLFFBQUksV0FBVyxVQUFVLE1BQU07QUFDOUIsWUFBTSxpQkFBYSxnQ0FBYyxJQUFJO0FBQ3JDLFlBQU0sV0FBVyxLQUFLLElBQUksTUFBTSxzQkFBc0IsVUFBVTtBQUNoRSxVQUFJLFVBQVU7QUFDYixhQUFLLElBQUksVUFBVSxhQUFhLFlBQVksSUFBSSxLQUFLO0FBQUEsTUFDdEQsT0FBTztBQUNOLFlBQUksd0JBQU8sbUJBQW1CLElBQUksRUFBRTtBQUFBLE1BQ3JDO0FBQ0E7QUFBQSxJQUNEO0FBRUEsUUFBSSxXQUFXLFVBQVUsT0FBTyxPQUFPO0FBRXRDLFdBQUssU0FBUyxXQUFXLE9BQU87QUFDaEMsV0FBSyxhQUFhO0FBQ2xCLFVBQUksd0JBQU8sb0NBQW9DO0FBQy9DO0FBQUEsSUFDRDtBQUVBLFFBQUksd0JBQU8sOEJBQThCLE1BQU0sRUFBRTtBQUFBLEVBQ2xEO0FBQ0Q7QUFFQSxTQUFTLGVBQWUsUUFBd0I7QUFDL0MsVUFBUSxRQUFRO0FBQUEsSUFDZixLQUFLO0FBQ0osYUFBTztBQUFBLElBQ1IsS0FBSztBQUNKLGFBQU87QUFBQSxJQUNSLEtBQUs7QUFDSixhQUFPO0FBQUEsSUFDUixLQUFLO0FBQ0osYUFBTztBQUFBLElBQ1IsS0FBSztBQUNKLGFBQU87QUFBQSxJQUNSO0FBQ0MsYUFBTztBQUFBLEVBQ1Q7QUFDRDtBQUVBLFNBQVMsZUFBZSxZQUE0QjtBQUNuRCxVQUFRLFlBQVk7QUFBQSxJQUNuQixLQUFLO0FBQ0osYUFBTztBQUFBLElBQ1IsS0FBSztBQUNKLGFBQU87QUFBQSxJQUNSLEtBQUs7QUFDSixhQUFPO0FBQUEsSUFDUjtBQUNDLGFBQU87QUFBQSxFQUNUO0FBQ0Q7IiwKICAibmFtZXMiOiBbImltcG9ydF9vYnNpZGlhbiIsICJpbXBvcnRfb2JzaWRpYW4iLCAiaW1wb3J0X29ic2lkaWFuIiwgImltcG9ydF9vYnNpZGlhbiIsICJpbXBvcnRfb2JzaWRpYW4iXQp9Cg==
A /sublimated/undefined +143 -0
1# obsidian plugin
2
3Obsidian community plugin for sublimated. reads file metadata directly from xattrs set by FileProvider. no signup required for core features.
4
5consistency rule: VS Code plugin (src/vscode-sublimated/) is the reference implementation. both plugins must show the same information in the same format, adapted to each platform's UI.
6
7macOS only for now. iOS later. no Windows/Linux — sublimated itself is macOS/iOS only.
8
9
10## xattrs
11
12FileProvider writes these on every synced file (all implemented and working):
13- `com.sublimated.version#PS` — version number (integer)
14- `com.sublimated.commit#PS` — last commit hash
15- `com.sublimated.url#PS` — web link (sublimated.com/anton/blog/readme_md)
16- `com.sublimated.content-hash#PS` — BLAKE3 hash
17- `com.sublimated.id#PS` — item ID (stable across renames)
18- `com.sublimated.created-by#PS` — original author name
19- `com.sublimated.modified-by#PS` — last author name
20- `com.sublimated.modified-by-type#PS` — person/ai_agent/bot
21- `com.sublimated.sync-status#PS` — synced/uploading/downloading/error
22- `com.sublimated.space#PS` — space slug
23- `com.sublimated.branch#PS` — current branch name
24- `com.sublimated.modified-at#PS` — server-side timestamp of last change
25
26plugin reads these directly from filesystem. no API calls, no token, no signup for core features.
27
28## official Obsidian APIs only
29
30this plugin only uses official, stable Obsidian APIs:
31- `addStatusBarItem()` — status bar text at the bottom
32- `addRibbonIcon()` — ribbon icon in the left sidebar
33- `registerView()` — custom sidebar panel (version history)
34- `addCommand()` — command palette entries
35- `registerObsidianProtocolHandler()` — URI handler
36- `addSettingTab()` — settings page
37
38Obsidian has no official API for file explorer badges, editor hover tooltips, or inline decorations. VS Code has `FileDecorationProvider`, `HoverProvider`, `CodeLens` etc — Obsidian doesn't. plugins that add file tree badges do it via DOM manipulation (finding internal `fileItems`, appending `<span>` elements, watching with MutationObserver). this is fragile — Obsidian can change its internal DOM structure in any update and badges break silently. not worth the maintenance cost.
39
40we deliberately skip these VS Code features that have no stable Obsidian equivalent:
41- file explorer sync badges (requires DOM hacking)
42- hover tooltips on wikilinks (no HoverProvider API)
43- CodeLens-style section attribution above headings (no CodeLens API)
44- inline editor decorations (no stable decoration API)
45
46## done
47
48- status bar: compact format `✓ synced v21 by anton` (sync icon + version + author + optional branch/visibility). click to open on web. hidden for non-sublimated files. updates on file change
49- version history sidebar: newest-first, grouped by day (Today / Yesterday / N days ago), with author, timestamp, commit message, per-version "view on web" link (`/~versions/v{number}`)
50- anonymous version history: public files fetch history without an API token. private files show a friendly "add token in settings" message on 401/403
51- ribbon icon: history button opens version sidebar
52- view on web: command palette + status bar click opens sublimated.com URL from xattr
53- copy web link: command to copy sublimated.com URL to clipboard
54- open in Obsidian: URI handler (`obsidian://sublimated?action=open&file=path`), OAuth token flow stub (`action=auth`)
55- welcome modal: first-run with setup steps, docs + source links, hasSeenWelcome flag
56- settings page: API token input, test connection, xattr-based workspace detection, links
57- xattr-based workspace detection: per-file, works for subfolder vaults and mixed vaults, no `.sbl/` folder required
58- all 13 xattr fields read: version, commit, url, contentHash, id, createdBy, modifiedBy, modifiedByType, syncStatus, space, branch, modifiedAt, visibility
59
60## todo
61
62### cache layer
63
64Obsidian re-reads all xattrs on every file switch (shells out `xattr` 13 times per tab switch).
65- add TTL-based in-memory cache for xattr reads
66- cache API responses (folder context, file context)
67- invalidate on file save events
68
69### workspace detection
70
71done: per-file xattr-based detection. any file with `com.sublimated.space#PS` identifies the workspace. works for subfolder vaults, works without `.sbl/`, works for mixed vaults.
72
73possible future additions:
74- CloudStorage mount path detection as a fallback for empty vaults: `~/Library/CloudStorage/Sublimated-{space}/` (prod) and `~/Library/CloudStorage/sublimated-dev-{space}/` (dev)
75- cache detected space slug across sessions so settings page shows it before any file is opened
76
77### version history improvements
78
79phase 1 (done):
80- [x] group versions by day: "Today", "Yesterday", "3 days ago", "Mon DD"
81- [x] commit messages render with improved visual layout
82- [x] "view on web" link per version using `/:path/~versions/v{number}` URL pattern
83- [x] newest version first (reverse server order)
84- [x] anonymous fetch for public files (no token needed)
85
86phase 2 (diff viewer, needs API):
87- click version to see content at that point (read-only tab)
88- API: `GET /v1/spaces/:slug/items/:id/diff?v1=X&v2=Y` already exists
89- API: `GET /v1/spaces/:slug/content/:hash` for fetching old version content
90- Obsidian Sync does NOT show diffs natively. community plugin (obsidian-version-history-diff) does but uses private APIs
91- our approach: open old version content in a read-only leaf, let user compare visually. no private APIs
92
93phase 3 (restore):
94- "restore this version" button per version
95- API: `POST /v1/spaces/:slug/items/:id/restore` with `{ from_version: number }` already exists
96
97### OAuth sign-in
98
99add one-click sign-in instead of pasting API tokens:
1001. user clicks "Sign in with sublimated" in settings
1012. browser opens `id.sublimated.com/auth/obsidian`
1023. user signs in, server redirects to `obsidian://sublimated?action=auth&token={token}`
1034. plugin stores token, API calls use `Authorization: Bearer {token}`
104
105plugin-side: `main.ts` already handles the URI. settings.ts needs a "Sign in" button.
106server-side: add GET `/auth/obsidian` route to id.sublimated.com. bearer plugin already set up.
107keep manual API token input as fallback. token should be long-lived.
108
109### comments sidebar
110
111VS Code has full CommentController (513 lines) with gutter "+", threading, resolve, reactions. Obsidian has no comment API — use a sidebar view (official `registerView()` API).
112
113- sidebar view: list comments anchored to specific text
114- author, timestamp, quoted text, resolved/unresolved status
115- threaded replies
116- clicking a comment scrolls to anchored text
117- "resolve" button (API call)
118- new comment creation on sublimated.com (link to web UI)
119- needs new API endpoint — comments are not currently exposed over HTTP
120
121## API endpoints
122
123plugin currently uses:
124- GET /v1/spaces/:slug/items/:id/versions — list version history (anonymous for public items, token required for private)
125
126public items work without an API token. the middleware in `src/shared/auth/authorize.ts` grants anonymous callers `{ read: true }` when folder visibility is public.
127
128## submission
129
130- source of truth: https://sublimated.com/sublimated/code/obsidian
131- GitHub live mirror: https://github.com/sublimates/obsidian — live-synced from sublimated.com
132- todo: PR to obsidianmd/obsidian-releases adding entry to community-plugins.json
133- todo: GitHub release workflow for plugin submission (main.js, manifest.json, styles.css as release assets)
134- todo: README with screenshots, description, setup instructions
135- automated validation + manual review by Obsidian team
136
137## promotion
138
139- r/ObsidianMD
140- Obsidian Forum "Share & showcase"
141- Obsidian Discord #updates
142- twitter #ObsidianMD
143- blog post: "how to use Obsidian with sublimated"