Filemedium importancesource

PluginOptionsFlow.tsx

commands/plugin/PluginOptionsFlow.tsx

135
Lines
18702
Bytes
2
Exports
8
Imports
10
Keywords

What this is

This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.

Beginner explanation

This file lives in the command layer. It likely turns a user action into concrete program behavior.

How it is used

Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.

Expert explanation

Architecturally, this file intersects with commands. It contains 135 lines, 8 detected imports, and 2 detected exports.

Important relationships

Detected exports

  • findPluginOptionsTarget
  • PluginOptionsFlow

Keywords

pluginpluginidcurrentondonereactchannelsavepluginoptionsdialogstepsloadedplugin

Detected imports

  • react
  • ../../types/plugin.js
  • ../../utils/errors.js
  • ../../utils/plugins/mcpbHandler.js
  • ../../utils/plugins/mcpPluginIntegration.js
  • ../../utils/plugins/pluginLoader.js
  • ../../utils/plugins/pluginOptionsStorage.js
  • ./PluginOptionsDialog.js

Source notes

This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.

Open parent directory

Full source

/**
 * Post-install/post-enable config prompt.
 *
 * Given a LoadedPlugin, checks both the top-level manifest.userConfig and the
 * channel-specific userConfig. Walks PluginOptionsDialog through each
 * unconfigured item, saving via the appropriate storage function. Calls
 * onDone('skipped') immediately if nothing needs filling.
 */

import * as React from 'react';
import type { LoadedPlugin } from '../../types/plugin.js';
import { errorMessage } from '../../utils/errors.js';
import { loadMcpServerUserConfig, saveMcpServerUserConfig } from '../../utils/plugins/mcpbHandler.js';
import { getUnconfiguredChannels, type UnconfiguredChannel } from '../../utils/plugins/mcpPluginIntegration.js';
import { loadAllPlugins } from '../../utils/plugins/pluginLoader.js';
import { getUnconfiguredOptions, loadPluginOptions, type PluginOptionSchema, type PluginOptionValues, savePluginOptions } from '../../utils/plugins/pluginOptionsStorage.js';
import { PluginOptionsDialog } from './PluginOptionsDialog.js';

/**
 * Post-install lookup: return the LoadedPlugin for the just-installed
 * pluginId so the caller can divert to PluginOptionsFlow. Returns undefined
 * if the plugin somehow didn't make it into the fresh load — callers treat
 * undefined as "carry on closing."
 *
 * Install should have cleared caches already; loadAllPlugins reads fresh.
 */
export async function findPluginOptionsTarget(pluginId: string): Promise<LoadedPlugin | undefined> {
  const {
    enabled,
    disabled
  } = await loadAllPlugins();
  return [...enabled, ...disabled].find(p => p.repository === pluginId || p.source === pluginId);
}

/**
 * A single dialog step in the walk. Top-level options and channels both
 * collapse to this shape — the only difference is which save function runs.
 */
type ConfigStep = {
  key: string;
  title: string;
  subtitle: string;
  schema: PluginOptionSchema;
  /** Returns any already-saved values so PluginOptionsDialog can pre-fill and
   *  skip unchanged sensitive fields on reconfigure. */
  load: () => PluginOptionValues | undefined;
  save: (values: PluginOptionValues) => void;
};
type Props = {
  plugin: LoadedPlugin;
  /** `name@marketplace` — the savePluginOptions / saveMcpServerUserConfig key. */
  pluginId: string;
  /**
   * `configured` = user filled all fields. `skipped` = nothing needed
   * configuring, or user hit cancel. `error` = save threw.
   */
  onDone: (outcome: 'configured' | 'skipped' | 'error', detail?: string) => void;
};
export function PluginOptionsFlow({
  plugin,
  pluginId,
  onDone
}: Props): React.ReactNode {
  // Build the step list once at mount. Re-calling after a save would drop the
  // item we just configured.
  const [steps] = React.useState<ConfigStep[]>(() => {
    const result: ConfigStep[] = [];

    // Top-level manifest.userConfig
    const unconfigured = getUnconfiguredOptions(plugin);
    if (Object.keys(unconfigured).length > 0) {
      result.push({
        key: 'top-level',
        title: `Configure ${plugin.name}`,
        subtitle: 'Plugin options',
        schema: unconfigured,
        load: () => loadPluginOptions(pluginId),
        save: values => savePluginOptions(pluginId, values, plugin.manifest.userConfig!)
      });
    }

    // Per-channel userConfig (assistant-mode channels)
    const channels: UnconfiguredChannel[] = getUnconfiguredChannels(plugin);
    for (const channel of channels) {
      result.push({
        key: `channel:${channel.server}`,
        title: `Configure ${channel.displayName}`,
        subtitle: `Plugin: ${plugin.name}`,
        schema: channel.configSchema,
        load: () => loadMcpServerUserConfig(pluginId, channel.server) ?? undefined,
        save: values_0 => saveMcpServerUserConfig(pluginId, channel.server, values_0, channel.configSchema)
      });
    }
    return result;
  });
  const [index, setIndex] = React.useState(0);

  // Latest-ref: lets the effect close over the current onDone without
  // re-running when the parent re-renders.
  const onDoneRef = React.useRef(onDone);
  onDoneRef.current = onDone;

  // Nothing to configure → tell the caller and render nothing. Effect,
  // not inline call: calling setState in the parent during our render
  // is a React rules-of-hooks violation.
  React.useEffect(() => {
    if (steps.length === 0) {
      onDoneRef.current('skipped');
    }
  }, [steps.length]);
  if (steps.length === 0) {
    return null;
  }
  const current = steps[index]!;
  function handleSave(values_1: PluginOptionValues): void {
    try {
      current.save(values_1);
    } catch (err) {
      onDone('error', errorMessage(err));
      return;
    }
    const next = index + 1;
    if (next < steps.length) {
      setIndex(next);
    } else {
      onDone('configured');
    }
  }

  // key forces a remount when advancing to the next step — React would
  // otherwise reuse the instance and carry PluginOptionsDialog's
  // internal useState (field index, typed values) over.
  return <PluginOptionsDialog key={current.key} title={current.title} subtitle={current.subtitle} configSchema={current.schema} initialValues={current.load()} onSave={handleSave} onCancel={() => onDone('skipped')} />;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIkxvYWRlZFBsdWdpbiIsImVycm9yTWVzc2FnZSIsImxvYWRNY3BTZXJ2ZXJVc2VyQ29uZmlnIiwic2F2ZU1jcFNlcnZlclVzZXJDb25maWciLCJnZXRVbmNvbmZpZ3VyZWRDaGFubmVscyIsIlVuY29uZmlndXJlZENoYW5uZWwiLCJsb2FkQWxsUGx1Z2lucyIsImdldFVuY29uZmlndXJlZE9wdGlvbnMiLCJsb2FkUGx1Z2luT3B0aW9ucyIsIlBsdWdpbk9wdGlvblNjaGVtYSIsIlBsdWdpbk9wdGlvblZhbHVlcyIsInNhdmVQbHVnaW5PcHRpb25zIiwiUGx1Z2luT3B0aW9uc0RpYWxvZyIsImZpbmRQbHVnaW5PcHRpb25zVGFyZ2V0IiwicGx1Z2luSWQiLCJQcm9taXNlIiwiZW5hYmxlZCIsImRpc2FibGVkIiwiZmluZCIsInAiLCJyZXBvc2l0b3J5Iiwic291cmNlIiwiQ29uZmlnU3RlcCIsImtleSIsInRpdGxlIiwic3VidGl0bGUiLCJzY2hlbWEiLCJsb2FkIiwic2F2ZSIsInZhbHVlcyIsIlByb3BzIiwicGx1Z2luIiwib25Eb25lIiwib3V0Y29tZSIsImRldGFpbCIsIlBsdWdpbk9wdGlvbnNGbG93IiwiUmVhY3ROb2RlIiwic3RlcHMiLCJ1c2VTdGF0ZSIsInJlc3VsdCIsInVuY29uZmlndXJlZCIsIk9iamVjdCIsImtleXMiLCJsZW5ndGgiLCJwdXNoIiwibmFtZSIsIm1hbmlmZXN0IiwidXNlckNvbmZpZyIsImNoYW5uZWxzIiwiY2hhbm5lbCIsInNlcnZlciIsImRpc3BsYXlOYW1lIiwiY29uZmlnU2NoZW1hIiwidW5kZWZpbmVkIiwiaW5kZXgiLCJzZXRJbmRleCIsIm9uRG9uZVJlZiIsInVzZVJlZiIsImN1cnJlbnQiLCJ1c2VFZmZlY3QiLCJoYW5kbGVTYXZlIiwiZXJyIiwibmV4dCJdLCJzb3VyY2VzIjpbIlBsdWdpbk9wdGlvbnNGbG93LnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFBvc3QtaW5zdGFsbC9wb3N0LWVuYWJsZSBjb25maWcgcHJvbXB0LlxuICpcbiAqIEdpdmVuIGEgTG9hZGVkUGx1Z2luLCBjaGVja3MgYm90aCB0aGUgdG9wLWxldmVsIG1hbmlmZXN0LnVzZXJDb25maWcgYW5kIHRoZVxuICogY2hhbm5lbC1zcGVjaWZpYyB1c2VyQ29uZmlnLiBXYWxrcyBQbHVnaW5PcHRpb25zRGlhbG9nIHRocm91Z2ggZWFjaFxuICogdW5jb25maWd1cmVkIGl0ZW0sIHNhdmluZyB2aWEgdGhlIGFwcHJvcHJpYXRlIHN0b3JhZ2UgZnVuY3Rpb24uIENhbGxzXG4gKiBvbkRvbmUoJ3NraXBwZWQnKSBpbW1lZGlhdGVseSBpZiBub3RoaW5nIG5lZWRzIGZpbGxpbmcuXG4gKi9cblxuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgdHlwZSB7IExvYWRlZFBsdWdpbiB9IGZyb20gJy4uLy4uL3R5cGVzL3BsdWdpbi5qcydcbmltcG9ydCB7IGVycm9yTWVzc2FnZSB9IGZyb20gJy4uLy4uL3V0aWxzL2Vycm9ycy5qcydcbmltcG9ydCB7XG4gIGxvYWRNY3BTZXJ2ZXJVc2VyQ29uZmlnLFxuICBzYXZlTWNwU2VydmVyVXNlckNvbmZpZyxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9tY3BiSGFuZGxlci5qcydcbmltcG9ydCB7XG4gIGdldFVuY29uZmlndXJlZENoYW5uZWxzLFxuICB0eXBlIFVuY29uZmlndXJlZENoYW5uZWwsXG59IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvbWNwUGx1Z2luSW50ZWdyYXRpb24uanMnXG5pbXBvcnQgeyBsb2FkQWxsUGx1Z2lucyB9IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvcGx1Z2luTG9hZGVyLmpzJ1xuaW1wb3J0IHtcbiAgZ2V0VW5jb25maWd1cmVkT3B0aW9ucyxcbiAgbG9hZFBsdWdpbk9wdGlvbnMsXG4gIHR5cGUgUGx1Z2luT3B0aW9uU2NoZW1hLFxuICB0eXBlIFBsdWdpbk9wdGlvblZhbHVlcyxcbiAgc2F2ZVBsdWdpbk9wdGlvbnMsXG59IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvcGx1Z2luT3B0aW9uc1N0b3JhZ2UuanMnXG5pbXBvcnQgeyBQbHVnaW5PcHRpb25zRGlhbG9nIH0gZnJvbSAnLi9QbHVnaW5PcHRpb25zRGlhbG9nLmpzJ1xuXG4vKipcbiAqIFBvc3QtaW5zdGFsbCBsb29rdXA6IHJldHVybiB0aGUgTG9hZGVkUGx1Z2luIGZvciB0aGUganVzdC1pbnN0YWxsZWRcbiAqIHBsdWdpbklkIHNvIHRoZSBjYWxsZXIgY2FuIGRpdmVydCB0byBQbHVnaW5PcHRpb25zRmxvdy4gUmV0dXJucyB1bmRlZmluZWRcbiAqIGlmIHRoZSBwbHVnaW4gc29tZWhvdyBkaWRuJ3QgbWFrZSBpdCBpbnRvIHRoZSBmcmVzaCBsb2FkIOKAlCBjYWxsZXJzIHRyZWF0XG4gKiB1bmRlZmluZWQgYXMgXCJjYXJyeSBvbiBjbG9zaW5nLlwiXG4gKlxuICogSW5zdGFsbCBzaG91bGQgaGF2ZSBjbGVhcmVkIGNhY2hlcyBhbHJlYWR5OyBsb2FkQWxsUGx1Z2lucyByZWFkcyBmcmVzaC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZpbmRQbHVnaW5PcHRpb25zVGFyZ2V0KFxuICBwbHVnaW5JZDogc3RyaW5nLFxuKTogUHJvbWlzZTxMb2FkZWRQbHVnaW4gfCB1bmRlZmluZWQ+IHtcbiAgY29uc3QgeyBlbmFibGVkLCBkaXNhYmxlZCB9ID0gYXdhaXQgbG9hZEFsbFBsdWdpbnMoKVxuICByZXR1cm4gWy4uLmVuYWJsZWQsIC4uLmRpc2FibGVkXS5maW5kKFxuICAgIHAgPT4gcC5yZXBvc2l0b3J5ID09PSBwbHVnaW5JZCB8fCBwLnNvdXJjZSA9PT0gcGx1Z2luSWQsXG4gIClcbn1cblxuLyoqXG4gKiBBIHNpbmdsZSBkaWFsb2cgc3RlcCBpbiB0aGUgd2Fsay4gVG9wLWxldmVsIG9wdGlvbnMgYW5kIGNoYW5uZWxzIGJvdGhcbiAqIGNvbGxhcHNlIHRvIHRoaXMgc2hhcGUg4oCUIHRoZSBvbmx5IGRpZmZlcmVuY2UgaXMgd2hpY2ggc2F2ZSBmdW5jdGlvbiBydW5zLlxuICovXG50eXBlIENvbmZpZ1N0ZXAgPSB7XG4gIGtleTogc3RyaW5nXG4gIHRpdGxlOiBzdHJpbmdcbiAgc3VidGl0bGU6IHN0cmluZ1xuICBzY2hlbWE6IFBsdWdpbk9wdGlvblNjaGVtYVxuICAvKiogUmV0dXJucyBhbnkgYWxyZWFkeS1zYXZlZCB2YWx1ZXMgc28gUGx1Z2luT3B0aW9uc0RpYWxvZyBjYW4gcHJlLWZpbGwgYW5kXG4gICAqICBza2lwIHVuY2hhbmdlZCBzZW5zaXRpdmUgZmllbGRzIG9uIHJlY29uZmlndXJlLiAqL1xuICBsb2FkOiAoKSA9PiBQbHVnaW5PcHRpb25WYWx1ZXMgfCB1bmRlZmluZWRcbiAgc2F2ZTogKHZhbHVlczogUGx1Z2luT3B0aW9uVmFsdWVzKSA9PiB2b2lkXG59XG5cbnR5cGUgUHJvcHMgPSB7XG4gIHBsdWdpbjogTG9hZGVkUGx1Z2luXG4gIC8qKiBgbmFtZUBtYXJrZXRwbGFjZWAg4oCUIHRoZSBzYXZlUGx1Z2luT3B0aW9ucyAvIHNhdmVNY3BTZXJ2ZXJVc2VyQ29uZmlnIGtleS4gKi9cbiAgcGx1Z2luSWQ6IHN0cmluZ1xuICAvKipcbiAgICogYGNvbmZpZ3VyZWRgID0gdXNlciBmaWxsZWQgYWxsIGZpZWxkcy4gYHNraXBwZWRgID0gbm90aGluZyBuZWVkZWRcbiAgICogY29uZmlndXJpbmcsIG9yIHVzZXIgaGl0IGNhbmNlbC4gYGVycm9yYCA9IHNhdmUgdGhyZXcuXG4gICAqL1xuICBvbkRvbmU6IChvdXRjb21lOiAnY29uZmlndXJlZCcgfCAnc2tpcHBlZCcgfCAnZXJyb3InLCBkZXRhaWw/OiBzdHJpbmcpID0+IHZvaWRcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFBsdWdpbk9wdGlvbnNGbG93KHtcbiAgcGx1Z2luLFxuICBwbHVnaW5JZCxcbiAgb25Eb25lLFxufTogUHJvcHMpOiBSZWFjdC5SZWFjdE5vZGUge1xuICAvLyBCdWlsZCB0aGUgc3RlcCBsaXN0IG9uY2UgYXQgbW91bnQuIFJlLWNhbGxpbmcgYWZ0ZXIgYSBzYXZlIHdvdWxkIGRyb3AgdGhlXG4gIC8vIGl0ZW0gd2UganVzdCBjb25maWd1cmVkLlxuICBjb25zdCBbc3RlcHNdID0gUmVhY3QudXNlU3RhdGU8Q29uZmlnU3RlcFtdPigoKSA9PiB7XG4gICAgY29uc3QgcmVzdWx0OiBDb25maWdTdGVwW10gPSBbXVxuXG4gICAgLy8gVG9wLWxldmVsIG1hbmlmZXN0LnVzZXJDb25maWdcbiAgICBjb25zdCB1bmNvbmZpZ3VyZWQgPSBnZXRVbmNvbmZpZ3VyZWRPcHRpb25zKHBsdWdpbilcbiAgICBpZiAoT2JqZWN0LmtleXModW5jb25maWd1cmVkKS5sZW5ndGggPiAwKSB7XG4gICAgICByZXN1bHQucHVzaCh7XG4gICAgICAgIGtleTogJ3RvcC1sZXZlbCcsXG4gICAgICAgIHRpdGxlOiBgQ29uZmlndXJlICR7cGx1Z2luLm5hbWV9YCxcbiAgICAgICAgc3VidGl0bGU6ICdQbHVnaW4gb3B0aW9ucycsXG4gICAgICAgIHNjaGVtYTogdW5jb25maWd1cmVkLFxuICAgICAgICBsb2FkOiAoKSA9PiBsb2FkUGx1Z2luT3B0aW9ucyhwbHVnaW5JZCksXG4gICAgICAgIHNhdmU6IHZhbHVlcyA9PlxuICAgICAgICAgIHNhdmVQbHVnaW5PcHRpb25zKHBsdWdpbklkLCB2YWx1ZXMsIHBsdWdpbi5tYW5pZmVzdC51c2VyQ29uZmlnISksXG4gICAgICB9KVxuICAgIH1cblxuICAgIC8vIFBlci1jaGFubmVsIHVzZXJDb25maWcgKGFzc2lzdGFudC1tb2RlIGNoYW5uZWxzKVxuICAgIGNvbnN0IGNoYW5uZWxzOiBVbmNvbmZpZ3VyZWRDaGFubmVsW10gPSBnZXRVbmNvbmZpZ3VyZWRDaGFubmVscyhwbHVnaW4pXG4gICAgZm9yIChjb25zdCBjaGFubmVsIG9mIGNoYW5uZWxzKSB7XG4gICAgICByZXN1bHQucHVzaCh7XG4gICAgICAgIGtleTogYGNoYW5uZWw6JHtjaGFubmVsLnNlcnZlcn1gLFxuICAgICAgICB0aXRsZTogYENvbmZpZ3VyZSAke2NoYW5uZWwuZGlzcGxheU5hbWV9YCxcbiAgICAgICAgc3VidGl0bGU6IGBQbHVnaW46ICR7cGx1Z2luLm5hbWV9YCxcbiAgICAgICAgc2NoZW1hOiBjaGFubmVsLmNvbmZpZ1NjaGVtYSxcbiAgICAgICAgbG9hZDogKCkgPT5cbiAgICAgICAgICBsb2FkTWNwU2VydmVyVXNlckNvbmZpZyhwbHVnaW5JZCwgY2hhbm5lbC5zZXJ2ZXIpID8/IHVuZGVmaW5lZCxcbiAgICAgICAgc2F2ZTogdmFsdWVzID0+XG4gICAgICAgICAgc2F2ZU1jcFNlcnZlclVzZXJDb25maWcoXG4gICAgICAgICAgICBwbHVnaW5JZCxcbiAgICAgICAgICAgIGNoYW5uZWwuc2VydmVyLFxuICAgICAgICAgICAgdmFsdWVzLFxuICAgICAgICAgICAgY2hhbm5lbC5jb25maWdTY2hlbWEsXG4gICAgICAgICAgKSxcbiAgICAgIH0pXG4gICAgfVxuXG4gICAgcmV0dXJuIHJlc3VsdFxuICB9KVxuXG4gIGNvbnN0IFtpbmRleCwgc2V0SW5kZXhdID0gUmVhY3QudXNlU3RhdGUoMClcblxuICAvLyBMYXRlc3QtcmVmOiBsZXRzIHRoZSBlZmZlY3QgY2xvc2Ugb3ZlciB0aGUgY3VycmVudCBvbkRvbmUgd2l0aG91dFxuICAvLyByZS1ydW5uaW5nIHdoZW4gdGhlIHBhcmVudCByZS1yZW5kZXJzLlxuICBjb25zdCBvbkRvbmVSZWYgPSBSZWFjdC51c2VSZWYob25Eb25lKVxuICBvbkRvbmVSZWYuY3VycmVudCA9IG9uRG9uZVxuXG4gIC8vIE5vdGhpbmcgdG8gY29uZmlndXJlIOKGkiB0ZWxsIHRoZSBjYWxsZXIgYW5kIHJlbmRlciBub3RoaW5nLiBFZmZlY3QsXG4gIC8vIG5vdCBpbmxpbmUgY2FsbDogY2FsbGluZyBzZXRTdGF0ZSBpbiB0aGUgcGFyZW50IGR1cmluZyBvdXIgcmVuZGVyXG4gIC8vIGlzIGEgUmVhY3QgcnVsZXMtb2YtaG9va3MgdmlvbGF0aW9uLlxuICBSZWFjdC51c2VFZmZlY3QoKCkgPT4ge1xuICAgIGlmIChzdGVwcy5sZW5ndGggPT09IDApIHtcbiAgICAgIG9uRG9uZVJlZi5jdXJyZW50KCdza2lwcGVkJylcbiAgICB9XG4gIH0sIFtzdGVwcy5sZW5ndGhdKVxuXG4gIGlmIChzdGVwcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgY29uc3QgY3VycmVudCA9IHN0ZXBzW2luZGV4XSFcblxuICBmdW5jdGlvbiBoYW5kbGVTYXZlKHZhbHVlczogUGx1Z2luT3B0aW9uVmFsdWVzKTogdm9pZCB7XG4gICAgdHJ5IHtcbiAgICAgIGN1cnJlbnQuc2F2ZSh2YWx1ZXMpXG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBvbkRvbmUoJ2Vycm9yJywgZXJyb3JNZXNzYWdlKGVycikpXG4gICAgICByZXR1cm5cbiAgICB9XG4gICAgY29uc3QgbmV4dCA9IGluZGV4ICsgMVxuICAgIGlmIChuZXh0IDwgc3RlcHMubGVuZ3RoKSB7XG4gICAgICBzZXRJbmRleChuZXh0KVxuICAgIH0gZWxzZSB7XG4gICAgICBvbkRvbmUoJ2NvbmZpZ3VyZWQnKVxuICAgIH1cbiAgfVxuXG4gIC8vIGtleSBmb3JjZXMgYSByZW1vdW50IHdoZW4gYWR2YW5jaW5nIHRvIHRoZSBuZXh0IHN0ZXAg4oCUIFJlYWN0IHdvdWxkXG4gIC8vIG90aGVyd2lzZSByZXVzZSB0aGUgaW5zdGFuY2UgYW5kIGNhcnJ5IFBsdWdpbk9wdGlvbnNEaWFsb2cnc1xuICAvLyBpbnRlcm5hbCB1c2VTdGF0ZSAoZmllbGQgaW5kZXgsIHR5cGVkIHZhbHVlcykgb3Zlci5cbiAgcmV0dXJuIChcbiAgICA8UGx1Z2luT3B0aW9uc0RpYWxvZ1xuICAgICAga2V5PXtjdXJyZW50LmtleX1cbiAgICAgIHRpdGxlPXtjdXJyZW50LnRpdGxlfVxuICAgICAgc3VidGl0bGU9e2N1cnJlbnQuc3VidGl0bGV9XG4gICAgICBjb25maWdTY2hlbWE9e2N1cnJlbnQuc2NoZW1hfVxuICAgICAgaW5pdGlhbFZhbHVlcz17Y3VycmVudC5sb2FkKCl9XG4gICAgICBvblNhdmU9e2hhbmRsZVNhdmV9XG4gICAgICBvbkNhbmNlbD17KCkgPT4gb25Eb25lKCdza2lwcGVkJyl9XG4gICAgLz5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsY0FBY0MsWUFBWSxRQUFRLHVCQUF1QjtBQUN6RCxTQUFTQyxZQUFZLFFBQVEsdUJBQXVCO0FBQ3BELFNBQ0VDLHVCQUF1QixFQUN2QkMsdUJBQXVCLFFBQ2xCLG9DQUFvQztBQUMzQyxTQUNFQyx1QkFBdUIsRUFDdkIsS0FBS0MsbUJBQW1CLFFBQ25CLDZDQUE2QztBQUNwRCxTQUFTQyxjQUFjLFFBQVEscUNBQXFDO0FBQ3BFLFNBQ0VDLHNCQUFzQixFQUN0QkMsaUJBQWlCLEVBQ2pCLEtBQUtDLGtCQUFrQixFQUN2QixLQUFLQyxrQkFBa0IsRUFDdkJDLGlCQUFpQixRQUNaLDZDQUE2QztBQUNwRCxTQUFTQyxtQkFBbUIsUUFBUSwwQkFBMEI7O0FBRTlEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPLGVBQWVDLHVCQUF1QkEsQ0FDM0NDLFFBQVEsRUFBRSxNQUFNLENBQ2pCLEVBQUVDLE9BQU8sQ0FBQ2YsWUFBWSxHQUFHLFNBQVMsQ0FBQyxDQUFDO0VBQ25DLE1BQU07SUFBRWdCLE9BQU87SUFBRUM7RUFBUyxDQUFDLEdBQUcsTUFBTVgsY0FBYyxDQUFDLENBQUM7RUFDcEQsT0FBTyxDQUFDLEdBQUdVLE9BQU8sRUFBRSxHQUFHQyxRQUFRLENBQUMsQ0FBQ0MsSUFBSSxDQUNuQ0MsQ0FBQyxJQUFJQSxDQUFDLENBQUNDLFVBQVUsS0FBS04sUUFBUSxJQUFJSyxDQUFDLENBQUNFLE1BQU0sS0FBS1AsUUFDakQsQ0FBQztBQUNIOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBS1EsVUFBVSxHQUFHO0VBQ2hCQyxHQUFHLEVBQUUsTUFBTTtFQUNYQyxLQUFLLEVB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TyxHQUFHMUIsTUFBTTs7RUFFMUI7RUFDQTtFQUNBO0VBQ0FqQyxLQUFLLENBQUM0RCxTQUFTLENBQUMsTUFBTTtJQUNwQixJQUFJdEIsS0FBSyxDQUFDTSxNQUFNLEtBQUssQ0FBQyxFQUFFO01BQ3RCYSxTQUFTLENBQUNFLE9BQU8sQ0FBQyxTQUFTLENBQUM7SUFDOUI7RUFDRixDQUFDLEVBQUUsQ0FBQ3JCLEtBQUssQ0FBQ00sTUFBTSxDQUFDLENBQUM7RUFFbEIsSUFBSU4sS0FBSyxDQUFDTSxNQUFNLEtBQUssQ0FBQyxFQUFFO0lBQ3RCLE9BQU8sSUFBSTtFQUNiO0VBRUEsTUFBTWUsT0FBTyxHQUFHckIsS0FBSyxDQUFDaUIsS0FBSyxDQUFDLENBQUM7RUFFN0IsU0FBU00sVUFBVUEsQ0FBQy9CLFFBQU0sRUFBRW5CLGtCQUFrQixDQUFDLEVBQUUsSUFBSSxDQUFDO0lBQ3BELElBQUk7TUFDRmdELE9BQU8sQ0FBQzlCLElBQUksQ0FBQ0MsUUFBTSxDQUFDO0lBQ3RCLENBQUMsQ0FBQyxPQUFPZ0MsR0FBRyxFQUFFO01BQ1o3QixNQUFNLENBQUMsT0FBTyxFQUFFL0IsWUFBWSxDQUFDNEQsR0FBRyxDQUFDLENBQUM7TUFDbEM7SUFDRjtJQUNBLE1BQU1DLElBQUksR0FBR1IsS0FBSyxHQUFHLENBQUM7SUFDdEIsSUFBSVEsSUFBSSxHQUFHekIsS0FBSyxDQUFDTSxNQUFNLEVBQUU7TUFDdkJZLFFBQVEsQ0FBQ08sSUFBSSxDQUFDO0lBQ2hCLENBQUMsTUFBTTtNQUNMOUIsTUFBTSxDQUFDLFlBQVksQ0FBQztJQUN0QjtFQUNGOztFQUVBO0VBQ0E7RUFDQTtFQUNBLE9BQ0UsQ0FBQyxtQkFBbUIsQ0FDbEIsR0FBRyxDQUFDLENBQUMwQixPQUFPLENBQUNuQyxHQUFHLENBQUMsQ0FDakIsS0FBSyxDQUFDLENBQUNtQyxPQUFPLENBQUNsQyxLQUFLLENBQUMsQ0FDckIsUUFBUSxDQUFDLENBQUNrQyxPQUFPLENBQUNqQyxRQUFRLENBQUMsQ0FDM0IsWUFBWSxDQUFDLENBQUNpQyxPQUFPLENBQUNoQyxNQUFNLENBQUMsQ0FDN0IsYUFBYSxDQUFDLENBQUNnQyxPQUFPLENBQUMvQixJQUFJLENBQUMsQ0FBQyxDQUFDLENBQzlCLE1BQU0sQ0FBQyxDQUFDaUMsVUFBVSxDQUFDLENBQ25CLFFBQVEsQ0FBQyxDQUFDLE1BQU01QixNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsR0FDbEM7QUFFTiIsImlnbm9yZUxpc3QiOltdfQ==