From 5c25ac4e9acb12ce8a7f0f89e54bb7b01a6a0b93 Mon Sep 17 00:00:00 2001 From: erdemozyol <26572407+erdemozyol@users.noreply.github.com> Date: Thu, 2 Apr 2026 17:37:07 +0300 Subject: [PATCH] Add Codex usage to /status --- src/components/Settings/CodexUsage.tsx | 211 ++++++++++++ src/components/Settings/Usage.tsx | 12 +- src/services/api/codexUsage.test.ts | 204 ++++++++++++ src/services/api/codexUsage.ts | 434 +++++++++++++++++++++++++ 4 files changed, 859 insertions(+), 2 deletions(-) create mode 100644 src/components/Settings/CodexUsage.tsx create mode 100644 src/services/api/codexUsage.test.ts create mode 100644 src/services/api/codexUsage.ts diff --git a/src/components/Settings/CodexUsage.tsx b/src/components/Settings/CodexUsage.tsx new file mode 100644 index 00000000..83a95afc --- /dev/null +++ b/src/components/Settings/CodexUsage.tsx @@ -0,0 +1,211 @@ +import * as React from 'react' +import { useEffect, useState } from 'react' + +import { useTerminalSize } from '../../hooks/useTerminalSize.js' +import { Box, Text } from '../../ink.js' +import { useKeybinding } from '../../keybindings/useKeybinding.js' +import { + buildCodexUsageRows, + fetchCodexUsage, + formatCodexPlanType, + type CodexUsageData, + type CodexUsageRow, +} from '../../services/api/codexUsage.js' +import { formatResetText } from '../../utils/format.js' +import { logError } from '../../utils/log.js' +import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js' +import { Byline } from '../design-system/Byline.js' +import { ProgressBar } from '../design-system/ProgressBar.js' + +type CodexUsageLimitBarProps = { + label: string + usedPercent: number + resetsAt?: string + maxWidth: number +} + +function CodexUsageLimitBar({ + label, + usedPercent, + resetsAt, + maxWidth, +}: CodexUsageLimitBarProps): React.ReactNode { + const normalizedUsedPercent = Math.max(0, Math.min(100, usedPercent)) + const usedText = `${Math.floor(normalizedUsedPercent)}% used` + const resetText = resetsAt + ? `Resets ${formatResetText(resetsAt, true, true)}` + : undefined + + if (maxWidth >= 62) { + return ( + + {label} + + + {usedText} + + {resetText ? {resetText} : null} + + ) + } + + return ( + + + {label} + {resetText ? ( + <> + + · {resetText} + + ) : null} + + + {usedText} + + ) +} + +function CodexUsageTextRow({ + label, + value, +}: Extract): React.ReactNode { + if (!value) { + return {label} + } + + return ( + + {label} + · {value} + + ) +} + +export function CodexUsage(): React.ReactNode { + const [usage, setUsage] = useState(null) + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(true) + const { columns } = useTerminalSize() + const availableWidth = columns - 2 + const maxWidth = Math.min(availableWidth, 80) + + const loadUsage = React.useCallback(async () => { + setIsLoading(true) + setError(null) + + try { + setUsage(await fetchCodexUsage()) + } catch (err) { + logError(err as Error) + setError(err instanceof Error ? err.message : 'Failed to load Codex usage') + } finally { + setIsLoading(false) + } + }, []) + + useEffect(() => { + void loadUsage() + }, [loadUsage]) + + useKeybinding( + 'settings:retry', + () => { + void loadUsage() + }, + { + context: 'Settings', + isActive: !!error && !isLoading, + }, + ) + + if (error) { + return ( + + Error: {error} + + + + + + + + ) + } + + if (!usage) { + return ( + + Loading Codex usage data… + + + + + ) + } + + const rows = buildCodexUsageRows(usage.snapshots) + const planType = formatCodexPlanType(usage.planType) + + return ( + + {planType ? Plan: {planType} : null} + + {rows.length === 0 ? ( + Codex usage data is not available for this account. + ) : null} + + {rows.map((row, index) => + row.kind === 'window' ? ( + + ) : ( + + ), + )} + + + + + + ) +} diff --git a/src/components/Settings/Usage.tsx b/src/components/Settings/Usage.tsx index b8c48631..3df75738 100644 --- a/src/components/Settings/Usage.tsx +++ b/src/components/Settings/Usage.tsx @@ -10,11 +10,13 @@ import { useKeybinding } from '../../keybindings/useKeybinding.js'; import { type ExtraUsage, fetchUtilization, type RateLimit, type Utilization } from '../../services/api/usage.js'; import { formatResetText } from '../../utils/format.js'; import { logError } from '../../utils/log.js'; +import { getAPIProvider } from '../../utils/model/providers.js'; import { jsonStringify } from '../../utils/slowOperations.js'; import { ConfigurableShortcutHint } from '../ConfigurableShortcutHint.js'; import { Byline } from '../design-system/Byline.js'; import { ProgressBar } from '../design-system/ProgressBar.js'; import { isEligibleForOverageCreditGrant, OverageCreditUpsell } from '../LogoV2/OverageCreditUpsell.js'; +import { CodexUsage } from './CodexUsage.js'; type LimitBarProps = { title: string; limit: RateLimit; @@ -171,7 +173,7 @@ function LimitBar(t0) { return t8; } } -export function Usage(): React.ReactNode { +function AnthropicUsage(): React.ReactNode { const [utilization, setUtilization] = useState(null); const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(true); @@ -263,6 +265,12 @@ export function Usage(): React.ReactNode { ; } +export function Usage(): React.ReactNode { + if (getAPIProvider() === 'codex') { + return ; + } + return ; +} type ExtraUsageSectionProps = { extraUsage: ExtraUsage; maxWidth: number; @@ -374,4 +382,4 @@ function ExtraUsageSection(t0) { } return t10; } -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsInVzZUVmZmVjdCIsInVzZVN0YXRlIiwiZXh0cmFVc2FnZSIsImV4dHJhVXNhZ2VDb21tYW5kIiwiZm9ybWF0Q29zdCIsImdldFN1YnNjcmlwdGlvblR5cGUiLCJ1c2VUZXJtaW5hbFNpemUiLCJCb3giLCJUZXh0IiwidXNlS2V5YmluZGluZyIsIkV4dHJhVXNhZ2UiLCJmZXRjaFV0aWxpemF0aW9uIiwiUmF0ZUxpbWl0IiwiVXRpbGl6YXRpb24iLCJmb3JtYXRSZXNldFRleHQiLCJsb2dFcnJvciIsImpzb25TdHJpbmdpZnkiLCJDb25maWd1cmFibGVTaG9ydGN1dEhpbnQiLCJCeWxpbmUiLCJQcm9ncmVzc0JhciIsImlzRWxpZ2libGVGb3JPdmVyYWdlQ3JlZGl0R3JhbnQiLCJPdmVyYWdlQ3JlZGl0VXBzZWxsIiwiTGltaXRCYXJQcm9wcyIsInRpdGxlIiwibGltaXQiLCJtYXhXaWR0aCIsInNob3dUaW1lSW5SZXNldCIsImV4dHJhU3VidGV4dCIsIkxpbWl0QmFyIiwidDAiLCIkIiwiX2MiLCJ0MSIsInVuZGVmaW5lZCIsInV0aWxpemF0aW9uIiwicmVzZXRzX2F0IiwidXNlZFRleHQiLCJNYXRoIiwiZmxvb3IiLCJzdWJ0ZXh0IiwidDIiLCJ0MyIsInQ0IiwibWF4QmFyV2lkdGgiLCJ0NSIsInQ2IiwidDciLCJ0OCIsIlVzYWdlIiwiUmVhY3ROb2RlIiwic2V0VXRpbGl6YXRpb24iLCJlcnJvciIsInNldEVycm9yIiwiaXNMb2FkaW5nIiwic2V0SXNMb2FkaW5nIiwiY29sdW1ucyIsImF2YWlsYWJsZVdpZHRoIiwibWluIiwibG9hZFV0aWxpemF0aW9uIiwidXNlQ2FsbGJhY2siLCJkYXRhIiwiZXJyIiwiRXJyb3IiLCJheGlvc0Vycm9yIiwicmVzcG9uc2UiLCJyZXNwb25zZUJvZHkiLCJjb250ZXh0IiwiaXNBY3RpdmUiLCJzdWJzY3JpcHRpb25UeXBlIiwic2hvd1Nvbm5ldEJhciIsImxpbWl0cyIsImZpdmVfaG91ciIsInNldmVuX2RheSIsInNldmVuX2RheV9zb25uZXQiLCJzb21lIiwibWFwIiwiZXh0cmFfdXNhZ2UiLCJFeHRyYVVzYWdlU2VjdGlvblByb3BzIiwiRVhUUkFfVVNBR0VfU0VDVElPTl9USVRMRSIsIkV4dHJhVXNhZ2VTZWN0aW9uIiwiaXNQcm9Pck1heCIsImlzX2VuYWJsZWQiLCJpc0VuYWJsZWQiLCJTeW1ib2wiLCJmb3IiLCJtb250aGx5X2xpbWl0IiwidXNlZF9jcmVkaXRzIiwiZm9ybWF0dGVkVXNlZENyZWRpdHMiLCJmb3JtYXR0ZWRNb250aGx5TGltaXQiLCJUMCIsIm5vdyIsIkRhdGUiLCJvbmVNb250aFJlc2V0IiwiZ2V0RnVsbFllYXIiLCJnZXRNb250aCIsInRvSVNPU3RyaW5nIiwidDkiLCJ0MTAiXSwic291cmNlcyI6WyJVc2FnZS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyB1c2VFZmZlY3QsIHVzZVN0YXRlIH0gZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBleHRyYVVzYWdlIGFzIGV4dHJhVXNhZ2VDb21tYW5kIH0gZnJvbSAnc3JjL2NvbW1hbmRzL2V4dHJhLXVzYWdlL2luZGV4LmpzJ1xuaW1wb3J0IHsgZm9ybWF0Q29zdCB9IGZyb20gJ3NyYy9jb3N0LXRyYWNrZXIuanMnXG5pbXBvcnQgeyBnZXRTdWJzY3JpcHRpb25UeXBlIH0gZnJvbSAnc3JjL3V0aWxzL2F1dGguanMnXG5pbXBvcnQgeyB1c2VUZXJtaW5hbFNpemUgfSBmcm9tICcuLi8uLi9ob29rcy91c2VUZXJtaW5hbFNpemUuanMnXG5pbXBvcnQgeyBCb3gsIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgeyB1c2VLZXliaW5kaW5nIH0gZnJvbSAnLi4vLi4va2V5YmluZGluZ3MvdXNlS2V5YmluZGluZy5qcydcbmltcG9ydCB7XG4gIHR5cGUgRXh0cmFVc2FnZSxcbiAgZmV0Y2hVdGlsaXphdGlvbixcbiAgdHlwZSBSYXRlTGltaXQsXG4gIHR5cGUgVXRpbGl6YXRpb24sXG59IGZyb20gJy4uLy4uL3NlcnZpY2VzL2FwaS91c2FnZS5qcydcbmltcG9ydCB7IGZvcm1hdFJlc2V0VGV4dCB9IGZyb20gJy4uLy4uL3V0aWxzL2Zvcm1hdC5qcydcbmltcG9ydCB7IGxvZ0Vycm9yIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nLmpzJ1xuaW1wb3J0IHsganNvblN0cmluZ2lmeSB9IGZyb20gJy4uLy4uL3V0aWxzL3Nsb3dPcGVyYXRpb25zLmpzJ1xuaW1wb3J0IHsgQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50IH0gZnJvbSAnLi4vQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50LmpzJ1xuaW1wb3J0IHsgQnlsaW5lIH0gZnJvbSAnLi4vZGVzaWduLXN5c3RlbS9CeWxpbmUuanMnXG5pbXBvcnQgeyBQcm9ncmVzc0JhciB9IGZyb20gJy4uL2Rlc2lnbi1zeXN0ZW0vUHJvZ3Jlc3NCYXIuanMnXG5pbXBvcnQge1xuICBpc0VsaWdpYmxlRm9yT3ZlcmFnZUNyZWRpdEdyYW50LFxuICBPdmVyYWdlQ3JlZGl0VXBzZWxsLFxufSBmcm9tICcuLi9Mb2dvVjIvT3ZlcmFnZUNyZWRpdFVwc2VsbC5qcydcblxudHlwZSBMaW1pdEJhclByb3BzID0ge1xuICB0aXRsZTogc3RyaW5nXG4gIGxpbWl0OiBSYXRlTGltaXRcbiAgbWF4V2lkdGg6IG51bWJlclxuICBzaG93VGltZUluUmVzZXQ/OiBib29sZWFuXG4gIGV4dHJhU3VidGV4dD86IHN0cmluZ1xufVxuXG5mdW5jdGlvbiBMaW1pdEJhcih7XG4gIHRpdGxlLFxuICBsaW1pdCxcbiAgbWF4V2lkdGgsXG4gIHNob3dUaW1lSW5SZXNldCA9IHRydWUsXG4gIGV4dHJhU3VidGV4dCxcbn06IExpbWl0QmFyUHJvcHMpOiBSZWFjdC5SZWFjdE5vZGUge1xuICBjb25zdCB7IHV0aWxpemF0aW9uLCByZXNldHNfYXQgfSA9IGxpbWl0XG4gIGlmICh1dGlsaXphdGlvbiA9PT0gbnVsbCkge1xuICAgIHJldHVybiBudWxsXG4gIH1cblxuICAvLyBDYWxjdWxhdGUgdXNhZ2UgcGVyY2VudGFnZVxuICBjb25zdCB1c2VkVGV4dCA9IGAke01hdGguZmxvb3IodXRpbGl6YXRpb24pfSUgdXNlZGBcblxuICBsZXQgc3VidGV4dDogc3RyaW5nIHwgdW5kZWZpbmVkXG4gIGlmIChyZXNldHNfYXQpIHtcbiAgICBzdWJ0ZXh0ID0gYFJlc2V0cyAke2Zvcm1hdFJlc2V0VGV4dChyZXNldHNfYXQsIHRydWUsIHNob3dUaW1lSW5SZXNldCl9YFxuICB9XG5cbiAgaWYgKGV4dHJhU3VidGV4dCkge1xuICAgIGlmIChzdWJ0ZXh0KSB7XG4gICAgICBzdWJ0ZXh0ID0gYCR7ZXh0cmFTdWJ0ZXh0fSDCtyAke3N1YnRleHR9YFxuICAgIH0gZWxzZSB7XG4gICAgICBzdWJ0ZXh0ID0gZXh0cmFTdWJ0ZXh0XG4gICAgfVxuICB9XG5cbiAgY29uc3QgbWF4QmFyV2lkdGggPSA1MFxuICBjb25zdCB1c2VkTGFiZWxTcGFjZSA9IDEyXG4gIGlmIChtYXhXaWR0aCA+PSBtYXhCYXJXaWR0aCArIHVzZWRMYWJlbFNwYWNlKSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8VGV4dCBib2xkPnt0aXRsZX08L1RleHQ+XG4gICAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cInJvd1wiIGdhcD17MX0+XG4gICAgICAgICAgPFByb2dyZXNzQmFyXG4gICAgICAgICAgICByYXRpbz17dXRpbGl6YXRpb24gLyAxMDB9XG4gICAgICAgICAgICB3aWR0aD17bWF4QmFyV2lkdGh9XG4gICAgICAgICAgICBmaWxsQ29sb3I9XCJyYXRlX2xpbWl0X2ZpbGxcIlxuICAgICAgICAgICAgZW1wdHlDb2xvcj1cInJhdGVfbGltaXRfZW1wdHlcIlxuICAgICAgICAgIC8+XG4gICAgICAgICAgPFRleHQ+e3VzZWRUZXh0fTwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICAgIHtzdWJ0ZXh0ICYmIDxUZXh0IGRpbUNvbG9yPntzdWJ0ZXh0fTwvVGV4dD59XG4gICAgICA8L0JveD5cbiAgICApXG4gIH0gZWxzZSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8VGV4dD5cbiAgICAgICAgICA8VGV4dCBib2xkPnt0aXRsZX08L1RleHQ+XG4gICAgICAgICAge3N1YnRleHQgJiYgKFxuICAgICAgICAgICAgPD5cbiAgICAgICAgICAgICAgPFRleHQ+IDwvVGV4dD5cbiAgICAgICAgICAgICAgPFRleHQgZGltQ29sb3I+wrcge3N1YnRleHR9PC9UZXh0PlxuICAgICAgICAgICAgPC8+XG4gICAgICAgICAgKX1cbiAgICAgICAgPC9UZXh0PlxuICAgICAgICA8UHJvZ3Jlc3NCYXJcbiAgICAgICAgICByYXRpbz17dXRpbGl6YXRpb24gLyAxMDB9XG4gICAgICAgICAgd2lkdGg9e21heFdpZHRofVxuICAgICAgICAgIGZpbGxDb2xvcj1cInJhdGVfbGltaXRfZmlsbFwiXG4gICAgICAgICAgZW1wdHlDb2xvcj1cInJhdGVfbGltaXRfZW1wdHlcIlxuICAgICAgICAvPlxuICAgICAgICA8VGV4dD57dXNlZFRleHR9PC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBVc2FnZSgpOiBSZWFjdC5SZWFjdE5vZGUge1xuICBjb25zdCBbdXRpbGl6YXRpb24sIHNldFV0aWxpemF0aW9uXSA9IHVzZVN0YXRlPFV0aWxpemF0aW9uIHwgbnVsbD4obnVsbClcbiAgY29uc3QgW2Vycm9yLCBzZXRFcnJvcl0gPSB1c2VTdGF0ZTxzdHJpbmcgfCBudWxsPihudWxsKVxuICBjb25zdCBbaXNMb2FkaW5nLCBzZXRJc0xvYWRpbmddID0gdXNlU3RhdGUodHJ1ZSlcbiAgY29uc3QgeyBjb2x1bW5zIH0gPSB1c2VUZXJtaW5hbFNpemUoKVxuXG4gIGNvbnN0IGF2YWlsYWJsZVdpZHRoID0gY29sdW1ucyAtIDIgLy8gMiBmb3Igc2NyZWVuIHBhZGRpbmdcbiAgY29uc3QgbWF4V2lkdGggPSBNYXRoLm1pbihhdmFpbGFibGVXaWR0aCwgODApXG5cbiAgY29uc3QgbG9hZFV0aWxpemF0aW9uID0gUmVhY3QudXNlQ2FsbGJhY2soYXN5bmMgKCkgPT4ge1xuICAgIHNldElzTG9hZGluZyh0cnVlKVxuICAgIHNldEVycm9yKG51bGwpXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGRhdGEgPSBhd2FpdCBmZXRjaFV0aWxpemF0aW9uKClcbiAgICAgIHNldFV0aWxpemF0aW9uKGRhdGEpXG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBsb2dFcnJvcihlcnIgYXMgRXJyb3IpXG4gICAgICBjb25zdCBheGlvc0Vycm9yID0gZXJyIGFzIHsgcmVzcG9uc2U/OiB7IGRhdGE/OiB1bmtub3duIH0gfVxuICAgICAgY29uc3QgcmVzcG9uc2VCb2R5ID0gYXhpb3NFcnJvci5yZXNwb25zZT8uZGF0YVxuICAgICAgICA/IGpzb25TdHJpbmdpZnkoYXhpb3NFcnJvci5yZXNwb25zZS5kYXRhKVxuICAgICAgICA6IHVuZGVmaW5lZFxuICAgICAgc2V0RXJyb3IoXG4gICAgICAgIHJlc3BvbnNlQm9keVxuICAgICAgICAgID8gYEZhaWxlZCB0byBsb2FkIHVzYWdlIGRhdGE6ICR7cmVzcG9uc2VCb2R5fWBcbiAgICAgICAgICA6ICdGYWlsZWQgdG8gbG9hZCB1c2FnZSBkYXRhJyxcbiAgICAgIClcbiAgICB9IGZpbmFsbHkge1xuICAgICAgc2V0SXNMb2FkaW5nKGZhbHNlKVxuICAgIH1cbiAgfSwgW10pXG5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICB2b2lkIGxvYWRVdGlsaXphdGlvbigpXG4gIH0sIFtsb2FkVXRpbGl6YXRpb25dKVxuXG4gIHVzZUtleWJpbmRpbmcoXG4gICAgJ3NldHRpbmdzOnJldHJ5JyxcbiAgICAoKSA9PiB7XG4gICAgICB2b2lkIGxvYWRVdGlsaXphdGlvbigpXG4gICAgfSxcbiAgICB7IGNvbnRleHQ6ICdTZXR0aW5ncycsIGlzQWN0aXZlOiAhIWVycm9yICYmICFpc0xvYWRpbmcgfSxcbiAgKVxuXG4gIGlmIChlcnJvcikge1xuICAgIHJldHVybiAoXG4gICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIiBnYXA9ezF9PlxuICAgICAgICA8VGV4dCBjb2xvcj1cImVycm9yXCI+RXJyb3I6IHtlcnJvcn08L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgIDxCeWxpbmU+XG4gICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgIGFjdGlvbj1cInNldHRpbmdzOnJldHJ5XCJcbiAgICAgICAgICAgICAgY29udGV4dD1cIlNldHRpbmdzXCJcbiAgICAgICAgICAgICAgZmFsbGJhY2s9XCJyXCJcbiAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJyZXRyeVwiXG4gICAgICAgICAgICAvPlxuICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICBhY3Rpb249XCJjb25maXJtOm5vXCJcbiAgICAgICAgICAgICAgY29udGV4dD1cIlNldHRpbmdzXCJcbiAgICAgICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cImNhbmNlbFwiXG4gICAgICAgICAgICAvPlxuICAgICAgICAgIDwvQnlsaW5lPlxuICAgICAgICA8L1RleHQ+XG4gICAgICA8L0JveD5cbiAgICApXG4gIH1cblxuICBpZiAoIXV0aWxpemF0aW9uKSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiIGdhcD17MX0+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPkxvYWRpbmcgdXNhZ2UgZGF0YeKApjwvVGV4dD5cbiAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTpub1wiXG4gICAgICAgICAgICBjb250ZXh0PVwiU2V0dGluZ3NcIlxuICAgICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgICAgZGVzY3JpcHRpb249XCJjYW5jZWxcIlxuICAgICAgICAgIC8+XG4gICAgICAgIDwvVGV4dD5cbiAgICAgIDwvQm94PlxuICAgIClcbiAgfVxuXG4gIC8vIE9ubHkgTWF4IGFuZCBUZWFtIHBsYW5zIGhhdmUgYSBTb25uZXQgbGltaXQgdGhhdCBkaWZmZXJzIGZyb20gdGhlIHdlZWtseVxuICAvLyBsaW1pdCAoc2VlIHJhdGVMaW1pdE1lc3NhZ2VzLnRzKS4gRm9yIG90aGVyIHBsYW5zIHRoZSBiYXIgaXMgcmVkdW5kYW50LlxuICAvLyBTaG93IGZvciBudWxsICh1bmtub3duIHBsYW4pIHRvIHN0YXkgY29uc2lzdGVudCB3aXRoIHJhdGVMaW1pdE1lc3NhZ2VzLnRzLFxuICAvLyB3aGljaCBsYWJlbHMgaXQgXCJTb25uZXQgbGltaXRcIiBpbiB0aGF0IGNhc2UuXG4gIGNvbnN0IHN1YnNjcmlwdGlvblR5cGUgPSBnZXRTdWJzY3JpcHRpb25UeXBlKClcbiAgY29uc3Qgc2hvd1Nvbm5ldEJhciA9XG4gICAgc3Vic2NyaXB0aW9uVHlwZSA9PT0gJ21heCcgfHxcbiAgICBzdWJzY3JpcHRpb25UeXBlID09PSAndGVhbScgfHxcbiAgICBzdWJzY3JpcHRpb25UeXBlID09PSBudWxsXG5cbiAgY29uc3QgbGltaXRzID0gW1xuICAgIHtcbiAgICAgIHRpdGxlOiAnQ3VycmVudCBzZXNzaW9uJyxcbiAgICAgIGxpbWl0OiB1dGlsaXphdGlvbi5maXZlX2hvdXIsXG4gICAgfSxcbiAgICB7XG4gICAgICB0aXRsZTogJ0N1cnJlbnQgd2VlayAoYWxsIG1vZGVscyknLFxuICAgICAgbGltaXQ6IHV0aWxpemF0aW9uLnNldmVuX2RheSxcbiAgICB9LFxuICAgIC4uLihzaG93U29ubmV0QmFyXG4gICAgICA/IFtcbiAgICAgICAgICB7XG4gICAgICAgICAgICB0aXRsZTogJ0N1cnJlbnQgd2VlayAoU29ubmV0IG9ubHkpJyxcbiAgICAgICAgICAgIGxpbWl0OiB1dGlsaXphdGlvbi5zZXZlbl9kYXlfc29ubmV0LFxuICAgICAgICAgIH0sXG4gICAgICAgIF1cbiAgICAgIDogW10pLFxuICBdXG5cbiAgcmV0dXJuIChcbiAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIiBnYXA9ezF9IHdpZHRoPVwiMTAwJVwiPlxuICAgICAge2xpbWl0cy5zb21lKCh7IGxpbWl0IH0pID0+IGxpbWl0KSB8fCAoXG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPi91c2FnZSBpcyBvbmx5IGF2YWlsYWJsZSBmb3Igc3Vic2NyaXB0aW9uIHBsYW5zLjwvVGV4dD5cbiAgICAgICl9XG5cbiAgICAgIHtsaW1pdHMubWFwKFxuICAgICAgICAoeyB0aXRsZSwgbGltaXQgfSkgPT5cbiAgICAgICAgICBsaW1pdCAmJiAoXG4gICAgICAgICAgICA8TGltaXRCYXJcbiAgICAgICAgICAgICAga2V5PXt0aXRsZX1cbiAgICAgICAgICAgICAgdGl0bGU9e3RpdGxlfVxuICAgICAgICAgICAgICBsaW1pdD17bGltaXR9XG4gICAgICAgICAgICAgIG1heFdpZHRoPXttYXhXaWR0aH1cbiAgICAgICAgICAgIC8+XG4gICAgICAgICAgKSxcbiAgICAgICl9XG5cbiAgICAgIHt1dGlsaXphdGlvbi5leHRyYV91c2FnZSAmJiAoXG4gICAgICAgIDxFeHRyYVVzYWdlU2VjdGlvblxuICAgICAgICAgIGV4dHJhVXNhZ2U9e3V0aWxpemF0aW9uLmV4dHJhX3VzYWdlfVxuICAgICAgICAgIG1heFdpZHRoPXttYXhXaWR0aH1cbiAgICAgICAgLz5cbiAgICAgICl9XG5cbiAgICAgIHtpc0VsaWdpYmxlRm9yT3ZlcmFnZUNyZWRpdEdyYW50KCkgJiYgKFxuICAgICAgICA8T3ZlcmFnZUNyZWRpdFVwc2VsbCBtYXhXaWR0aD17bWF4V2lkdGh9IC8+XG4gICAgICApfVxuXG4gICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgIGFjdGlvbj1cImNvbmZpcm06bm9cIlxuICAgICAgICAgIGNvbnRleHQ9XCJTZXR0aW5nc1wiXG4gICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgIGRlc2NyaXB0aW9uPVwiY2FuY2VsXCJcbiAgICAgICAgLz5cbiAgICAgIDwvVGV4dD5cbiAgICA8L0JveD5cbiAgKVxufVxuXG50eXBlIEV4dHJhVXNhZ2VTZWN0aW9uUHJvcHMgPSB7XG4gIGV4dHJhVXNhZ2U6IEV4dHJhVXNhZ2VcbiAgbWF4V2lkdGg6IG51bWJlclxufVxuXG5jb25zdCBFWFRSQV9VU0FHRV9TRUNUSU9OX1RJVExFID0gJ0V4dHJhIHVzYWdlJ1xuXG5mdW5jdGlvbiBFeHRyYVVzYWdlU2VjdGlvbih7XG4gIGV4dHJhVXNhZ2UsXG4gIG1heFdpZHRoLFxufTogRXh0cmFVc2FnZVNlY3Rpb25Qcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IHN1YnNjcmlwdGlvblR5cGUgPSBnZXRTdWJzY3JpcHRpb25UeXBlKClcbiAgY29uc3QgaXNQcm9Pck1heCA9IHN1YnNjcmlwdGlvblR5cGUgPT09ICdwcm8nIHx8IHN1YnNjcmlwdGlvblR5cGUgPT09ICdtYXgnXG4gIGlmICghaXNQcm9Pck1heCkge1xuICAgIC8vIE9ubHkgc2hvdyB0byBQcm8gYW5kIE1heCwgY29uc2lzdGVudCB3aXRoIGNsYXVkZS5haSBub24tYWRtaW4gdXNhZ2Ugc2V0dGluZ3NcbiAgICByZXR1cm4gZmFsc2VcbiAgfVxuXG4gIGlmICghZXh0cmFVc2FnZS5pc19lbmFibGVkKSB7XG4gICAgaWYgKGV4dHJhVXNhZ2VDb21tYW5kLmlzRW5hYmxlZCgpKSB7XG4gICAgICByZXR1cm4gKFxuICAgICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgICA8VGV4dCBib2xkPntFWFRSQV9VU0FHRV9TRUNUSU9OX1RJVExFfTwvVGV4dD5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5FeHRyYSB1c2FnZSBub3QgZW5hYmxlZCDCtyAvZXh0cmEtdXNhZ2UgdG8gZW5hYmxlPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgIClcbiAgICB9XG5cbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgaWYgKGV4dHJhVXNhZ2UubW9udGhseV9saW1pdCA9PT0gbnVsbCkge1xuICAgIHJldHVybiAoXG4gICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgPFRleHQgYm9sZD57RVhUUkFfVVNBR0VfU0VDVElPTl9USVRMRX08L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPlVubGltaXRlZDwvVGV4dD5cbiAgICAgIDwvQm94PlxuICAgIClcbiAgfVxuXG4gIGlmIChcbiAgICB0eXBlb2YgZXh0cmFVc2FnZS51c2VkX2NyZWRpdHMgIT09ICdudW1iZXInIHx8XG4gICAgdHlwZW9mIGV4dHJhVXNhZ2UudXRpbGl6YXRpb24gIT09ICdudW1iZXInXG4gICkge1xuICAgIHJldHVybiBudWxsXG4gIH1cblxuICBjb25zdCBmb3JtYXR0ZWRVc2VkQ3JlZGl0cyA9IGZvcm1hdENvc3QoZXh0cmFVc2FnZS51c2VkX2NyZWRpdHMgLyAxMDAsIDIpXG4gIGNvbnN0IGZvcm1hdHRlZE1vbnRobHlMaW1pdCA9IGZvcm1hdENvc3QoZXh0cmFVc2FnZS5tb250aGx5X2xpbWl0IC8gMTAwLCAyKVxuICBjb25zdCBub3cgPSBuZXcgRGF0ZSgpXG4gIGNvbnN0IG9uZU1vbnRoUmVzZXQgPSBuZXcgRGF0ZShub3cuZ2V0RnVsbFllYXIoKSwgbm93LmdldE1vbnRoKCkgKyAxLCAxKVxuXG4gIHJldHVybiAoXG4gICAgPExpbWl0QmFyXG4gICAgICB0aXRsZT17RVhUUkFfVVNBR0VfU0VDVElPTl9USVRMRX1cbiAgICAgIGxpbWl0PXt7XG4gICAgICAgIHV0aWxpemF0aW9uOiBleHRyYVVzYWdlLnV0aWxpemF0aW9uLFxuICAgICAgICAvLyBOb3QgYXBwbGljYWJsZSBmb3IgZW50ZXJwcmlzZXMsIGJ1dCBmb3Igbm93IHdlIGRvbid0IHJlbmRlciB0aGlzIGZvciB0aGVtXG4gICAgICAgIHJlc2V0c19hdDogb25lTW9udGhSZXNldC50b0lTT1N0cmluZygpLFxuICAgICAgfX1cbiAgICAgIHNob3dUaW1lSW5SZXNldD17ZmFsc2V9XG4gICAgICBleHRyYVN1YnRleHQ9e2Ake2Zvcm1hdHRlZFVzZWRDcmVkaXRzfSAvICR7Zm9ybWF0dGVkTW9udGhseUxpbWl0fSBzcGVudGB9XG4gICAgICBtYXhXaWR0aD17bWF4V2lkdGh9XG4gICAgLz5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxTQUFTLEVBQUVDLFFBQVEsUUFBUSxPQUFPO0FBQzNDLFNBQVNDLFVBQVUsSUFBSUMsaUJBQWlCLFFBQVEsbUNBQW1DO0FBQ25GLFNBQVNDLFVBQVUsUUFBUSxxQkFBcUI7QUFDaEQsU0FBU0MsbUJBQW1CLFFBQVEsbUJBQW1CO0FBQ3ZELFNBQVNDLGVBQWUsUUFBUSxnQ0FBZ0M7QUFDaEUsU0FBU0MsR0FBRyxFQUFFQyxJQUFJLFFBQVEsY0FBYztBQUN4QyxTQUFTQyxhQUFhLFFBQVEsb0NBQW9DO0FBQ2xFLFNBQ0UsS0FBS0MsVUFBVSxFQUNmQyxnQkFBZ0IsRUFDaEIsS0FBS0MsU0FBUyxFQUNkLEtBQUtDLFdBQVcsUUFDWCw2QkFBNkI7QUFDcEMsU0FBU0MsZUFBZSxRQUFRLHVCQUF1QjtBQUN2RCxTQUFTQyxRQUFRLFFBQVEsb0JBQW9CO0FBQzdDLFNBQVNDLGFBQWEsUUFBUSwrQkFBK0I7QUFDN0QsU0FBU0Msd0JBQXdCLFFBQVEsZ0NBQWdDO0FBQ3pFLFNBQVNDLE1BQU0sUUFBUSw0QkFBNEI7QUFDbkQsU0FBU0MsV0FBVyxRQUFRLGlDQUFpQztBQUM3RCxTQUNFQywrQkFBK0IsRUFDL0JDLG1CQUFtQixRQUNkLGtDQUFrQztBQUV6QyxLQUFLQyxhQUFhLEdBQUc7RUFDbkJDLEtBQUssRUFBRSxNQUFNO0VBQ2JDLEtBQUssRUFBRVosU0FBUztFQUNoQmEsUUFBUSxFQUFFLE1BQU07RUFDaEJDLGVBQWUsQ0FBQyxFQUFFLE9BQU87RUFDekJDLFlBQVksQ0FBQyxFQUFFLE1BQU07QUFDdkIsQ0FBQztBQUVELFNBQUFDLFNBQUFDLEVBQUE7RUFBQSxNQUFBQyxDQUFBLEdBQUFDLEVBQUE7RUFBa0I7SUFBQVIsS0FBQTtJQUFBQyxLQUFBO0lBQUFDLFFBQUE7SUFBQUMsZUFBQSxFQUFBTSxFQUFBO0lBQUFMO0VBQUEsSUFBQUUsRUFNRjtFQUZkLE1BQUFILGVBQUEsR0FBQU0sRUFBc0IsS0FBdEJDLFNBQXNCLEdBQXRCLElBQXNCLEdBQXRCRCxFQUFzQjtFQUd0QjtJQUFBRSxXQUFBO0lBQUFDO0VBQUEsSUFBbUNYLEtBQUs7RUFDeEMsSUFBSVUsV0FBVyxLQUFLLElBQUk7SUFBQSxPQUNmLElBQUk7RUFBQTtFQUliLE1BQUFFLFFBQUEsR0FBaUIsR0FBR0MsSUFBSSxDQUFBQyxLQUFNLENBQUNKLFdBQVcsQ0FBQyxRQUFRO0VBRS9DSyxHQUFBLENBQUFBLE9BQUE7RUFDSixJQUFJSixTQUFTO0lBQUEsSUFBQUssRUFBQTtJQUFBLElBQUFWLENBQUEsUUFBQUssU0FBQSxJQUFBTCxDQUFBLFFBQUFKLGVBQUE7TUFDU2MsRUFBQSxHQUFBMUIsZUFBZSxDQUFDcUIsU0FBUyxFQUFFLElBQUksRUFBRVQsZUFBZSxDQUFDO01BQUFJLENBQUEsTUFBQUssU0FBQTtNQUFBTCxDQUFBLE1BQUFKLGVBQUE7TUFBQUksQ0FBQSxNQUFBVSxFQUFBO0lBQUE7TUFBQUEsRUFBQSxHQUFBVixDQUFBO0lBQUE7SUFBckVTLE9BQUEsQ0FBQUEsQ0FBQSxDQUFVQSxVQUFVQSxFQUFpREEsRUFBRTtFQUFoRTtFQUdULElBQUlaLFlBQVk7SUFDZCxJQUFJWSxPQUFPO01BQ1RBLE9BQUEsQ0FBQUEsQ0FBQSxDQUFVQSxHQUFHWixZQUFZLE1BQU1ZLE9BQU8sRUFBRTtJQUFqQztNQUVQQSxPQUFBLENBQUFBLENBQUEsQ0FBVVosWUFBWTtJQUFmO0VBQ1I7RUFLSCxJQUFJRixRQUFRLElBQUksRUFBNEI7SUFBQSxJQUFBZSxFQUFBO0lBQUEsSUFBQVYsQ0FBQSxRQUFBUCxLQUFBO01BR3RDaUIsRUFBQSxJQUFDLElBQUksQ0FBQyxJQUFJLENBQUosS0FBRyxDQUFDLENBQUVqQixNQUFJLENBQUUsRUFBakIsSUFBSSxDQUFvQjtNQUFBTyxDQUFBLE1BQUFQLEtBQUE7TUFBQU8sQ0FBQSxNQUFBVSxFQUFBO0lBQUE7TUFBQUEsRUFBQSxHQUFBVixDQUFBO0lBQUE7SUFHZCxNQUFBVyxFQUFBLEdBQUFQLFdBQVcsR0FBRyxHQUFHO0lBQUEsSUFBQVEsRUFBQTtJQUFBLElBQUFaLENBQUEsUUFBQVcsRUFBQTtNQUQxQkMsRUFBQSxJQUFDLFdBQVcsQ0FDSCxLQUFpQixDQUFqQixDQUFBRCxFQUFnQixDQUFDLENBQ2pCRSxLQUFXLENBQVhBLENBVEdBLEVBU09BLENBQUMsQ0FDUixTQUFpQixDQUFqQixpQkFBaUIsQ0FDaEIsVUFBa0IsQ0FBbEIsa0JBQWtCLEdBQzdCO01BQUFiLENBQUEsTUFBQVcsRUFBQTtNQUFBWCxDQUFBLE1BQUFZLEVBQUE7SUFBQTtNQUFBQSxFQUFBLEdBQUFaLENBQUE7SUFBQTtJQUFBLElBQUFjLEVBQUE7SUFBQSxJQUFBZCxDQUFBLFFBQUFNLFFBQUE7TUFDRlEsRUFBQSxJQUFDLElBQUksQ0FBRVIsU0FBTyxDQUFFLEVBQWYsSUFBSSxDQUFrQjtNQUFBTixDQUFBLE1BQUFNLFFBQUE7TUFBQU4sQ0FBQSxNQUFBYyxFQUFBO0lBQUE7TUFBQUEsRUFBQSxHQUFBZCxDQUFBO0lBQUE7SUFBQSxJQUFBZSxFQUFBO0lBQUEsSUFBQWYsQ0FBQSxRQUFBWSxFQUFBLElBQUFaLENBQUEsU0FBQWMsRUFBQTtNQVB6QkMsRUFBQSxJQUFDLEdBQUcsQ0FBZSxhQUFLLENBQUwsS0FBSyxDQUFNLEdBQUMsQ0FBRCxHQUFDLENBQzdCLENBQUFILEVBS0MsQ0FDRCxDQUFBRSxFQUFzQixDQUN4QixFQVJDLEdBQUcsQ0FRRTtNQUFBZCxDQUFBLE1BQUFZLEVBQUE7TUFBQVosQ0FBQSxPQUFBYyxFQUFBO01BQUFkLENBQUEsT0FBQWUsRUFBQTtJQUFBO01BQUFBLEVBQUEsR0FBQWYsQ0FBQTtJQUFBO0lBQUEsSUFBQWdCLEVBQUE7SUFBQSxJQUFBaEIsQ0FBQSxTQUFBUyxPQUFBO01BQ0xPLEVBQUEsR0FBQVAsT0FBMEMsSUFBL0IsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFSLEtBQU8sQ0FBQyxDQUFFQSxRQUFNLENBQUUsRUFBdkIsSUFBSSxDQUEwQjtNQUFBVCxDQUFBLE9BQUFTLE9BQUE7TUFBQVQsQ0FBQSxPQUFBZ0IsRUFBQTtJQUFBO01BQUFBLEVBQUEsR0FBQWhCLENBQUE7SUFBQTtJQUFBLElBQUFpQixFQUFBO0lBQUEsSUFBQWpCLENBQUEsU0FBQVUsRUFBQSxJQUFBVixDQUFBLFNBQUFlLEVBQUEsSUFBQWYsQ0FBQSxTQUFBZ0IsRUFBQTtNQVg3Q0MsRUFBQSxJQUFDLEdBQUcsQ0FBZSxhQUFRLENBQVIsUUFBUSxDQUN6QixDQUFBUCxFQUF3QixDQUN4QixDQUFBSyxFQVFLLENBQ0osQ0FBQUMsRUFBeUMsQ0FDNUMsRUFaQyxHQUFHLENBWUU7TUFBQWhCLENBQUEsT0FBQVUsRUFBQTtNQUFBVixDQUFBLE9BQUFlLEVBQUE7TUFBQWYsQ0FBQSxPQUFBZ0IsRUFBQTtNQUFBaEIsQ0FBQSxPQUFBaUIsRUFBQTtJQUFBO01BQUFBLEVBQUEsR0FBQWpCLENBQUE7SUFBQTtJQUFBLE9BWk5pQixFQVlNO0VBQUE7SUFBQSxJQUFBUCxFQUFBO0lBQUEsSUFBQVYsQ0FBQSxTQUFBUCxLQUFBO01BTUZpQixFQUFBLElBQUMsSUFBSSxDQUFDLElBQUksQ0FBSixLQUFHLENBQUMsQ0FBRWpCLE1BQUksQ0FBRSxFQUFqQixJQUFJLENBQW9CO01BQUFPLENBQUEsT0FBQVAsS0FBQTtNQUFBTyxDQUFBLE9BQUFVLEVBQUE7SUFBQTtNQUFBQSxFQUFBLEdBQUFWLENBQUE7SUFBQTtJQUFBLElBQUFXLEVBQUE7SUFBQSxJQUFBWCxDQUFBLFNBQUFTLE9BQUE7TUFDeEJFLEVBQUEsR0FBQUYsT0FLQSxJQUxBLEVBRUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxFQUFOLElBQUksQ0FDTCxDQUFDLElBQUksQ0FBQyxRQUFRLENBQVIsS0FBTyxDQUFDLENBQUMsRUFBR0EsUUFBTSxDQUFFLEVBQXpCLElBQUksQ0FBNEIsR0FFcEM7TUFBQVQsQ0FBQSxPQUFBUyxPQUFBO01BQUFULENBQUEsT0FBQVcsRUFBQTtJQUFBO01BQUFBLEVBQUEsR0FBQVgsQ0FBQTtJQUFBO0lBQUEsSUFBQVksRUFBQTtJQUFBLElBQUFaLENBQUEsU0FBQVUsRUFBQSxJQUFBVixDQUFBLFNBQUFXLEVBQUE7TUFQSEMsRUFBQSxJQUFDLElBQUksQ0FDSCxDQUFBRixFQUF3QixDQUN2QixDQUFBQyxFQUtELENBQ0YsRUFSQyxJQUFJLENBUUU7TUFBQVgsQ0FBQSxPQUFBVSxFQUFBO01BQUFWLENBQUEsT0FBQVcsRUFBQTtNQUFBWCxDQUFBLE9BQUFZLEVBQUE7SUFBQTtNQUFBQSxFQUFBLEdBQUFaLENBQUE7SUFBQTtJQUVFLE1BQUFjLEVBQUEsR0FBQVYsV0FBVyxHQUFHLEdBQUc7SUFBQSxJQUFBVyxFQUFBO0lBQUEsSUFBQWYsQ0FBQSxTQUFBTCxRQUFBLElBQUFLLENBQUEsU0FBQWMsRUFBQTtNQUQxQkMsRUFBQSxJQUFDLFdBQVcsQ0FDSCxLQUFpQixDQUFqQixDQUFBRCxFQUFnQixDQUFDLENBQ2pCbkIsS0FBUSxDQUFSQSxTQUFPLENBQUMsQ0FDTCxTQUFpQixDQUFqQixpQkFBaUIsQ0FDaEIsVUFBa0IsQ0FBbEIsa0JBQWtCLEdBQzdCO01BQUFLLENBQUEsT0FBQUwsUUFBQTtNQUFBSyxDQUFBLE9BQUFjLEVBQUE7TUFBQWQsQ0FBQSxPQUFBZSxFQUFBO0lBQUE7TUFBQUEsRUFBQSxHQUFBZixDQUFBO0lBQUE7SUFBQSxJQUFBZ0IsRUFBQTtJQUFBLElBQUFoQixDQUFBLFNBQUFNLFFBQUE7TUFDRlUsRUFBQSxJQUFDLElBQUksQ0FBRVYsU0FBTyxDQUFFLEVBQWYsSUFBSSxDQUFrQjtNQUFBTixDQUFBLE9BQUFNLFFBQUE7TUFBQU4sQ0FBQSxPQUFBZ0IsRUFBQTtJQUFBO01BQUFBLEVBQUEsR0FBQWhCLENBQUE7SUFBQTtJQUFBLElBQUFpQixFQUFBO0lBQUEsSUFBQWpCLENBQUEsU0FBQVksRUFBQSxJQUFBWixDQUFBLFNBQUFlLEVBQUEsSUFBQWYsQ0FBQSxTQUFBZ0IsRUFBQTtNQWhCekJDLEVBQUEsSUFBQyxHQUFHLENBQWUsYUFBUSxDQUFSLFFBQVEsQ0FDekIsQ0FBQUwsRUFRTSxDQUNOLENBQUFHLEVBS0MsQ0FDRCxDQUFBQyxFQUFzQixDQUN4QixFQWpCQyxHQUFHLENBaUJFO01BQUFoQixDQUFBLE9BQUFZLEVBQUE7TUFBQVosQ0FBQSxPQUFBZSxFQUFBO01BQUFmLENBQUEsT0FBQWdCLEVBQUE7TUFBQWhCLENBQUEsT0FBQWlCLEVBQUE7SUFBQTtNQUFBQSxFQUFBLEdBQUFqQixDQUFBO0lBQUE7SUFBQSxPQWpCTmlCLEVBaUJNO0VBQUE7QUFFVDtBQUdILE9BQU8sU0FBU0MsS0FBS0EsQ0FBQSxDQUFFLEVBQUVqRCxLQUFLLENBQUNrRCxTQUFTLENBQUM7RUFDdkMsTUFBTSxDQUFDZixXQUFXLEVBQUVnQixjQUFjLENBQUMsR0FBR2pELFFBQVEsQ0FBQ1ksV0FBVyxHQUFHLElBQUksQ0FBQyxDQUFDLElBQUksQ0FBQztFQUN4RSxNQUFNLENBQUNzQyxLQUFLLEVBQUVDLFFBQVEsQ0FBQyxHQUFHbkQsUUFBUSxDQUFDLE1BQU0sR0FBRyxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUM7RUFDdkQsTUFBTSxDQUFDb0QsU0FBUyxFQUFFQyxZQUFZLENBQUMsR0FBR3JELFFBQVEsQ0FBQyxJQUFJLENBQUM7RUFDaEQsTUFBTTtJQUFFc0Q7RUFBUSxDQUFDLEdBQUdqRCxlQUFlLENBQUMsQ0FBQztFQUVyQyxNQUFNa0QsY0FBYyxHQUFHRCxPQUFPLEdBQUcsQ0FBQyxFQUFDO0VBQ25DLE1BQU05QixRQUFRLEdBQUdZLElBQUksQ0FBQ29CLEdBQUcsQ0FBQ0QsY0FBYyxFQUFFLEVBQUUsQ0FBQztFQUU3QyxNQUFNRSxlQUFlLEdBQUczRCxLQUFLLENBQUM0RCxXQUFXLENBQUMsWUFBWTtJQUNwREwsWUFBWSxDQUFDLElBQUksQ0FBQztJQUNsQkYsUUFBUSxDQUFDLElBQUksQ0FBQztJQUNkLElBQUk7TUFDRixNQUFNUSxJQUFJLEdBQUcsTUFBTWpELGdCQUFnQixDQUFDLENBQUM7TUFDckN1QyxjQUFjLENBQUNVLElBQUksQ0FBQztJQUN0QixDQUFDLENBQUMsT0FBT0MsR0FBRyxFQUFFO01BQ1o5QyxRQUFRLENBQUM4QyxHQUFHLElBQUlDLEtBQUssQ0FBQztNQUN0QixNQUFNQyxVQUFVLEdBQUdGLEdBQUcsSUFBSTtRQUFFRyxRQUFRLENBQUMsRUFBRTtVQUFFSixJQUFJLENBQUMsRUFBRSxPQUFPO1FBQUMsQ0FBQztNQUFDLENBQUM7TUFDM0QsTUFBTUssWUFBWSxHQUFHRixVQUFVLENBQUNDLFFBQVEsRUFBRUosSUFBSSxHQUMxQzVDLGFBQWEsQ0FBQytDLFVBQVUsQ0FBQ0MsUUFBUSxDQUFDSixJQUFJLENBQUMsR0FDdkMzQixTQUFTO01BQ2JtQixRQUFRLENBQ05hLFlBQVksR0FDUiw4QkFBOEJBLFlBQVksRUFBRSxHQUM1QywyQkFDTixDQUFDO0lBQ0gsQ0FBQyxTQUFTO01BQ1JYLFlBQVksQ0FBQyxLQUFLLENBQUM7SUFDckI7RUFDRixDQUFDLEVBQUUsRUFBRSxDQUFDO0VBRU50RCxTQUFTLENBQUMsTUFBTTtJQUNkLEtBQUswRCxlQUFlLENBQUMsQ0FBQztFQUN4QixDQUFDLEVBQUUsQ0FBQ0EsZUFBZSxDQUFDLENBQUM7RUFFckJqRCxhQUFhLENBQ1gsZ0JBQWdCLEVBQ2hCLE1BQU07SUFDSixLQUFLaUQsZUFBZSxDQUFDLENBQUM7RUFDeEIsQ0FBQyxFQUNEO0lBQUVRLE9BQU8sRUFBRSxVQUFVO0lBQUVDLFFBQVEsRUFBRSxDQUFDLENBQUNoQixLQUFLLElBQUksQ0FBQ0U7RUFBVSxDQUN6RCxDQUFDO0VBRUQsSUFBSUYsS0FBSyxFQUFFO0lBQ1QsT0FDRSxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUN6QyxRQUFRLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDQSxLQUFLLENBQUMsRUFBRSxJQUFJO0FBQ2hELFFBQVEsQ0FBQyxJQUFJLENBQUMsUUFBUTtBQUN0QixVQUFVLENBQUMsTUFBTTtBQUNqQixZQUFZLENBQUMsd0JBQXdCLENBQ3ZCLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FDdkIsT0FBTyxDQUFDLFVBQVUsQ0FDbEIsUUFBUSxDQUFDLEdBQUcsQ0FDWixXQUFXLENBQUMsT0FBTztBQUVqQyxZQUFZLENBQUMsd0JBQXdCLENBQ3ZCLE1BQU0sQ0FBQyxZQUFZLENBQ25CLE9BQU8sQ0FBQyxVQUFVLENBQ2xCLFFBQVEsQ0FBQyxLQUFLLENBQ2QsV0FBVyxDQUFDLFFBQVE7QUFFbEMsVUFBVSxFQUFFLE1BQU07QUFDbEIsUUFBUSxFQUFFLElBQUk7QUFDZCxNQUFNLEVBQUUsR0FBRyxDQUFDO0VBRVY7RUFFQSxJQUFJLENBQUNqQixXQUFXLEVBQUU7SUFDaEIsT0FDRSxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUN6QyxRQUFRLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxtQkFBbUIsRUFBRSxJQUFJO0FBQ2hELFFBQVEsQ0FBQyxJQUFJLENBQUMsUUFBUTtBQUN0QixVQUFVLENBQUMsd0JBQXdCLENBQ3ZCLE1BQU0sQ0FBQyxZQUFZLENBQ25CLE9BQU8sQ0FBQyxVQUFVLENBQ2xCLFFBQVEsQ0FBQyxLQUFLLENBQ2QsV0FBVyxDQUFDLFFBQVE7QUFFaEMsUUFBUSxFQUFFLElBQUk7QUFDZCxNQUFNLEVBQUUsR0FBRyxDQUFDO0VBRVY7O0VBRUE7RUFDQTtFQUNBO0VBQ0E7RUFDQSxNQUFNa0MsZ0JBQWdCLEdBQUcvRCxtQkFBbUIsQ0FBQyxDQUFDO0VBQzlDLE1BQU1nRSxhQUFhLEdBQ2pCRCxnQkFBZ0IsS0FBSyxLQUFLLElBQzFCQSxnQkFBZ0IsS0FBSyxNQUFNLElBQzNCQSxnQkFBZ0IsS0FBSyxJQUFJO0VBRTNCLE1BQU1FLE1BQU0sR0FBRyxDQUNiO0lBQ0UvQyxLQUFLLEVBQUUsaUJBQWlCO0lBQ3hCQyxLQUFLLEVBQUVVLFdBQVcsQ0FBQ3FDO0VBQ3JCLENBQUMsRUFDRDtJQUNFaEQsS0FBSyxFQUFFLDJCQUEyQjtJQUNsQ0MsS0FBSyxFQUFFVSxXQUFXLENBQUNzQztFQUNyQixDQUFDLEVBQ0QsSUFBSUgsYUFBYSxHQUNiLENBQ0U7SUFDRTlDLEtBQUssRUFBRSw0QkFBNEI7SUFDbkNDLEtBQUssRUFBRVUsV0FBVyxDQUFDdUM7RUFDckIsQ0FBQyxDQUNGLEdBQ0QsRUFBRSxDQUFDLENBQ1I7RUFFRCxPQUNFLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE1BQU07QUFDcEQsTUFBTSxDQUFDSCxNQUFNLENBQUNJLElBQUksQ0FBQyxDQUFDO01BQUVsRDtJQUFNLENBQUMsS0FBS0EsS0FBSyxDQUFDLElBQ2hDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxnREFBZ0QsRUFBRSxJQUFJLENBQ3RFO0FBQ1A7QUFDQSxNQUFNLENBQUM4QyxNQUFNLENBQUNLLEdBQUcsQ0FDVCxDQUFDO01BQUVwRCxLQUFLO01BQUVDLEtBQUssRUFBTEE7SUFBTSxDQUFDLEtBQ2ZBLE9BQUssSUFDSCxDQUFDLFFBQVEsQ0FDUCxHQUFHLENBQUMsQ0FBQ0QsS0FBSyxDQUFDLENBQ1gsS0FBSyxDQUFDLENBQUNBLEtBQUssQ0FBQyxDQUNiLEtBQUssQ0FBQyxDQUFDQyxPQUFLLENBQUMsQ0FDYixRQUFRLENBQUMsQ0FBQ0MsUUFBUSxDQUFDLEdBRzNCLENBQUM7QUFDUDtBQUNBLE1BQU0sQ0FBQ1MsV0FBVyxDQUFDMEMsV0FBVyxJQUN0QixDQUFDLGlCQUFpQixDQUNoQixVQUFVLENBQUMsQ0FBQzFDLFdBQVcsQ0FBQzBDLFdBQVcsQ0FBQyxDQUNwQyxRQUFRLENBQUMsQ0FBQ25ELFFBQVEsQ0FBQyxHQUV0QjtBQUNQO0FBQ0EsTUFBTSxDQUFDTCwrQkFBK0IsQ0FBQyxDQUFDLElBQ2hDLENBQUMsbUJBQW1CLENBQUMsUUFBUSxDQUFDLENBQUNLLFFBQVEsQ0FBQyxHQUN6QztBQUNQO0FBQ0EsTUFBTSxDQUFDLElBQUksQ0FBQyxRQUFRO0FBQ3BCLFFBQVEsQ0FBQyx3QkFBd0IsQ0FDdkIsTUFBTSxDQUFDLFlBQVksQ0FDbkIsT0FBTyxDQUFDLFVBQVUsQ0FDbEIsUUFBUSxDQUFDLEtBQUssQ0FDZCxXQUFXLENBQUMsUUFBUTtBQUU5QixNQUFNLEVBQUUsSUFBSTtBQUNaLElBQUksRUFBRSxHQUFHLENBQUM7QUFFVjtBQUVBLEtBQUtvRCxzQkFBc0IsR0FBRztFQUM1QjNFLFVBQVUsRUFBRVEsVUFBVTtFQUN0QmUsUUFBUSxFQUFFLE1BQU07QUFDbEIsQ0FBQztBQUVELE1BQU1xRCx5QkFBeUIsR0FBRyxhQUFhO0FBRS9DLFNBQUFDLGtCQUFBbEQsRUFBQTtFQUFBLE1BQUFDLENBQUEsR0FBQUMsRUFBQTtFQUEyQjtJQUFBN0IsVUFBQTtJQUFBdUI7RUFBQSxJQUFBSSxFQUdGO0VBQ3ZCLE1BQUF1QyxnQkFBQSxHQUF5Qi9ELG1CQUFtQixDQUFDLENBQUM7RUFDOUMsTUFBQTJFLFVBQUEsR0FBbUJaLGdCQUFnQixLQUFLLEtBQW1DLElBQTFCQSxnQkFBZ0IsS0FBSyxLQUFLO0VBQzNFLElBQUksQ0FBQ1ksVUFBVTtJQUFBLE9BRU4sS0FBSztFQUFBO0VBR2QsSUFBSSxDQUFDOUUsVUFBVSxDQUFBK0UsVUFBVztJQUN4QixJQUFJOUUsaUJBQWlCLENBQUErRSxTQUFVLENBQUMsQ0FBQztNQUFBLElBQUFsRCxFQUFBO01BQUEsSUFBQUYsQ0FBQSxRQUFBcUQsTUFBQSxDQUFBQyxHQUFBO1FBRTdCcEQsRUFBQSxJQUFDLEdBQUcsQ0FBZSxhQUFRLENBQVIsUUFBUSxDQUN6QixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUosS0FBRyxDQUFDLENBQUU4QywwQkFBd0IsQ0FBRSxFQUFyQyxJQUFJLENBQ0wsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFSLEtBQU8sQ0FBQyxDQUFDLGdEQUFnRCxFQUE5RCxJQUFJLENBQ1AsRUFIQyxHQUFHLENBR0U7UUFBQWhELENBQUEsTUFBQUUsRUFBQTtNQUFBO1FBQUFBLEVBQUEsR0FBQUYsQ0FBQTtNQUFBO01BQUEsT0FITkUsRUFHTTtJQUFBO0lBRVQsT0FFTSxJQUFJO0VBQUE7RUFHYixJQUFJOUIsVUFBVSxDQUFBbUYsYUFBYyxLQUFLLElBQUk7SUFBQSxJQUFBckQsRUFBQTtJQUFBLElBQUFGLENBQUEsUUFBQXFELE1BQUEsQ0FBQUMsR0FBQTtNQUVqQ3BELEVBQUEsSUFBQyxHQUFHLENBQWUsYUFBUSxDQUFSLFFBQVEsQ0FDekIsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFKLEtBQUcsQ0FBQyxDQUFFOEMsMEJBQXdCLENBQUUsRUFBckMsSUFBSSxDQUNMLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBUixLQUFPLENBQUMsQ0FBQyxTQUFTLEVBQXZCLElBQUksQ0FDUCxFQUhDLEdBQUcsQ0FHRTtNQUFBaEQsQ0FBQSxNQUFBRSxFQUFBO0lBQUE7TUFBQUEsRUFBQSxHQUFBRixDQUFBO0lBQUE7SUFBQSxPQUhORSxFQUdNO0VBQUE7RUFJVixJQUNFLE9BQU85QixVQUFVLENBQUFvRixZQUFhLEtBQUssUUFDTyxJQUExQyxPQUFPcEYsVUFBVSxDQUFBZ0MsV0FBWSxLQUFLLFFBQVE7SUFBQSxPQUVuQyxJQUFJO0VBQUE7RUFHMkIsTUFBQUYsRUFBQSxHQUFBOUIsVUFBVSxDQUFBb0YsWUFBYSxHQUFHLEdBQUc7RUFBQSxJQUFBOUMsRUFBQTtFQUFBLElBQUFWLENBQUEsUUFBQUUsRUFBQTtJQUF4Q1EsRUFBQSxHQUFBcEMsVUFBVSxDQUFDNEIsRUFBNkIsRUFBRSxDQUFDLENBQUM7SUFBQUYsQ0FBQSxNQUFBRSxFQUFBO0lBQUFGLENBQUEsTUFBQVUsRUFBQTtFQUFBO0lBQUFBLEVBQUEsR0FBQVYsQ0FBQTtFQUFBO0VBQXpFLE1BQUF5RCxvQkFBQSxHQUE2Qi9DLEVBQTRDO0VBQ2hDLE1BQUFDLEVBQUEsR0FBQXZDLFVBQVUsQ0FBQW1GLGFBQWMsR0FBRyxHQUFHO0VBQUEsSUFBQTNDLEVBQUE7RUFBQSxJQUFBWixDQUFBLFFBQUFXLEVBQUE7SUFBekNDLEVBQUEsR0FBQXRDLFVBQVUsQ0FBQ3FDLEVBQThCLEVBQUUsQ0FBQyxDQUFDO0lBQUFYLENBQUEsTUFBQVcsRUFBQTtJQUFBWCxDQUFBLE1BQUFZLEVBQUE7RUFBQTtJQUFBQSxFQUFBLEdBQUFaLENBQUE7RUFBQTtFQUEzRSxNQUFBMEQscUJBQUEsR0FBOEI5QyxFQUE2QztFQUFBLElBQUErQyxFQUFBO0VBQUEsSUFBQTdDLEVBQUE7RUFBQSxJQUFBQyxFQUFBO0VBQUEsSUFBQUMsRUFBQTtFQUFBLElBQUFoQixDQUFBLFFBQUE1QixVQUFBLENBQUFnQyxXQUFBO0lBQzNFLE1BQUF3RCxHQUFBLEdBQVksSUFBSUMsSUFBSSxDQUFDLENBQUM7SUFDdEIsTUFBQUMsYUFBQSxHQUFzQixJQUFJRCxJQUFJLENBQUNELEdBQUcsQ0FBQUcsV0FBWSxDQUFDLENBQUMsRUFBRUgsR0FBRyxDQUFBSSxRQUFTLENBQUMsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUM7SUFHckVMLEVBQUEsR0FBQTdELFFBQVE7SUFDQWtELEVBQUEsQ0FBQUEsQ0FBQSxDQUFBQSx5QkFBeUI7SUFFakJsQyxFQUFBLEdBQUExQyxVQUFVLENBQUFnQyxXQUFZO0lBRXhCVyxFQUFBLEdBQUErQyxhQUFhLENBQUFHLFdBQVksQ0FBQyxDQUFDO0lBQUFqRSxDQUFBLE1BQUE1QixVQUFBLENBQUFnQyxXQUFBO0lBQUFKLENBQUEsTUFBQTJELEVBQUE7SUFBQTNELENBQUEsTUFBQWMsRUFBQTtJQUFBZCxDQUFBLE1BQUFlLEVBQUE7SUFBQWYsQ0FBQSxPQUFBZ0IsRUFBQTtFQUFBO0lBQUEyQyxFQUFBLEdBQUEzRCxDQUFBO0lBQUFjLEVBQUEsR0FBQWQsQ0FBQTtJQUFBZSxFQUFBLEdBQUFmLENBQUE7SUFBQWdCLEVBQUEsR0FBQWhCLENBQUE7RUFBQTtFQUFBLElBQUFpQixFQUFBO0VBQUEsSUFBQWpCLENBQUEsU0FBQWMsRUFBQSxJQUFBZCxDQUFBLFNBQUFlLEVBQUE7SUFIakNFLEVBQUE7TUFBQWIsV0FBQSxFQUNRVSxFQUFzQjtNQUFBVCxTQUFBLEVBRXhCVTtJQUNiLENBQUM7SUFBQWYsQ0FBQSxPQUFBYyxFQUFBO0lBQUFkLENBQUEsT0FBQWUsRUFBQTtJQUFBZixDQUFBLE9BQUFpQixFQUFBO0VBQUE7SUFBQUEsRUFBQSxHQUFBakIsQ0FBQTtFQUFBO0VBRWEsTUFBQWtFLEVBQUEsTUFBR1Qsb0JBQW9CLE1BQU1DLHFCQUFxQixRQUFRO0VBQUEsSUFBQVMsR0FBQTtFQUFBLElBQUFuRSxDQUFBLFNBQUEyRCxFQUFBLElBQUEzRCxDQUFBLFNBQUFMLFFBQUEsSUFBQUssQ0FBQSxTQUFBZ0IsRUFBQSxJQUFBaEIsQ0FBQSxTQUFBaUIsRUFBQSxJQUFBakIsQ0FBQSxTQUFBa0UsRUFBQTtJQVIxRUMsR0FBQSxJQUFDLEVBQVEsQ0FDQW5CLEtBQXlCLENBQXpCQSxHQUF3QixDQUFDLENBQ3pCLEtBSU4sQ0FKTSxDQUFBL0IsRUFJUCxDQUFDLENBQ2dCLGVBQUssQ0FBTCxNQUFJLENBQUMsQ0FDUixZQUEwRCxDQUExRCxDQUFBaUQsRUFBeUQsQ0FBQyxDQUM5RHZFLFFBQVEsQ0FBUkEsU0FBTyxDQUFDLEdBQ2xCO0lBQUFLLENBQUEsT0FBQTJELEVBQUE7SUFBQTNELENBQUEsT0FBQUwsUUFBQTtJQUFBSyxDQUFBLE9BQUFnQixFQUFBO0lBQUFoQixDQUFBLE9BQUFpQixFQUFBO0lBQUFqQixDQUFBLE9BQUFrRSxFQUFBO0lBQUFsRSxDQUFBLE9BQUFtRSxHQUFBO0VBQUE7SUFBQUEsR0FBQSxHQUFBbkUsQ0FBQTtFQUFBO0VBQUEsT0FWRm1FLEdBVUU7QUFBQSIsImlnbm9yZUxpc3QiOltdfQ== \ No newline at end of file +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsInVzZUVmZmVjdCIsInVzZVN0YXRlIiwiZXh0cmFVc2FnZSIsImV4dHJhVXNhZ2VDb21tYW5kIiwiZm9ybWF0Q29zdCIsImdldFN1YnNjcmlwdGlvblR5cGUiLCJ1c2VUZXJtaW5hbFNpemUiLCJCb3giLCJUZXh0IiwidXNlS2V5YmluZGluZyIsIkV4dHJhVXNhZ2UiLCJmZXRjaFV0aWxpemF0aW9uIiwiUmF0ZUxpbWl0IiwiVXRpbGl6YXRpb24iLCJmb3JtYXRSZXNldFRleHQiLCJsb2dFcnJvciIsImpzb25TdHJpbmdpZnkiLCJDb25maWd1cmFibGVTaG9ydGN1dEhpbnQiLCJCeWxpbmUiLCJQcm9ncmVzc0JhciIsImlzRWxpZ2libGVGb3JPdmVyYWdlQ3JlZGl0R3JhbnQiLCJPdmVyYWdlQ3JlZGl0VXBzZWxsIiwiTGltaXRCYXJQcm9wcyIsInRpdGxlIiwibGltaXQiLCJtYXhXaWR0aCIsInNob3dUaW1lSW5SZXNldCIsImV4dHJhU3VidGV4dCIsIkxpbWl0QmFyIiwidDAiLCIkIiwiX2MiLCJ0MSIsInVuZGVmaW5lZCIsInV0aWxpemF0aW9uIiwicmVzZXRzX2F0IiwidXNlZFRleHQiLCJNYXRoIiwiZmxvb3IiLCJzdWJ0ZXh0IiwidDIiLCJ0MyIsInQ0IiwibWF4QmFyV2lkdGgiLCJ0NSIsInQ2IiwidDciLCJ0OCIsIlVzYWdlIiwiUmVhY3ROb2RlIiwic2V0VXRpbGl6YXRpb24iLCJlcnJvciIsInNldEVycm9yIiwiaXNMb2FkaW5nIiwic2V0SXNMb2FkaW5nIiwiY29sdW1ucyIsImF2YWlsYWJsZVdpZHRoIiwibWluIiwibG9hZFV0aWxpemF0aW9uIiwidXNlQ2FsbGJhY2siLCJkYXRhIiwiZXJyIiwiRXJyb3IiLCJheGlvc0Vycm9yIiwicmVzcG9uc2UiLCJyZXNwb25zZUJvZHkiLCJjb250ZXh0IiwiaXNBY3RpdmUiLCJzdWJzY3JpcHRpb25UeXBlIiwic2hvd1Nvbm5ldEJhciIsImxpbWl0cyIsImZpdmVfaG91ciIsInNldmVuX2RheSIsInNldmVuX2RheV9zb25uZXQiLCJzb21lIiwibWFwIiwiZXh0cmFfdXNhZ2UiLCJFeHRyYVVzYWdlU2VjdGlvblByb3BzIiwiRVhUUkFfVVNBR0VfU0VDVElPTl9USVRMRSIsIkV4dHJhVXNhZ2VTZWN0aW9uIiwiaXNQcm9Pck1heCIsImlzX2VuYWJsZWQiLCJpc0VuYWJsZWQiLCJTeW1ib2wiLCJmb3IiLCJtb250aGx5X2xpbWl0IiwidXNlZF9jcmVkaXRzIiwiZm9ybWF0dGVkVXNlZENyZWRpdHMiLCJmb3JtYXR0ZWRNb250aGx5TGltaXQiLCJUMCIsIm5vdyIsIkRhdGUiLCJvbmVNb250aFJlc2V0IiwiZ2V0RnVsbFllYXIiLCJnZXRNb250aCIsInRvSVNPU3RyaW5nIiwidDkiLCJ0MTAiXSwic291cmNlcyI6WyJVc2FnZS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyB1c2VFZmZlY3QsIHVzZVN0YXRlIH0gZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBleHRyYVVzYWdlIGFzIGV4dHJhVXNhZ2VDb21tYW5kIH0gZnJvbSAnc3JjL2NvbW1hbmRzL2V4dHJhLXVzYWdlL2luZGV4LmpzJ1xuaW1wb3J0IHsgZm9ybWF0Q29zdCB9IGZyb20gJ3NyYy9jb3N0LXRyYWNrZXIuanMnXG5pbXBvcnQgeyBnZXRTdWJzY3JpcHRpb25UeXBlIH0gZnJvbSAnc3JjL3V0aWxzL2F1dGguanMnXG5pbXBvcnQgeyB1c2VUZXJtaW5hbFNpemUgfSBmcm9tICcuLi8uLi9ob29rcy91c2VUZXJtaW5hbFNpemUuanMnXG5pbXBvcnQgeyBCb3gsIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgeyB1c2VLZXliaW5kaW5nIH0gZnJvbSAnLi4vLi4va2V5YmluZGluZ3MvdXNlS2V5YmluZGluZy5qcydcbmltcG9ydCB7XG4gIHR5cGUgRXh0cmFVc2FnZSxcbiAgZmV0Y2hVdGlsaXphdGlvbixcbiAgdHlwZSBSYXRlTGltaXQsXG4gIHR5cGUgVXRpbGl6YXRpb24sXG59IGZyb20gJy4uLy4uL3NlcnZpY2VzL2FwaS91c2FnZS5qcydcbmltcG9ydCB7IGZvcm1hdFJlc2V0VGV4dCB9IGZyb20gJy4uLy4uL3V0aWxzL2Zvcm1hdC5qcydcbmltcG9ydCB7IGxvZ0Vycm9yIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nLmpzJ1xuaW1wb3J0IHsganNvblN0cmluZ2lmeSB9IGZyb20gJy4uLy4uL3V0aWxzL3Nsb3dPcGVyYXRpb25zLmpzJ1xuaW1wb3J0IHsgQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50IH0gZnJvbSAnLi4vQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50LmpzJ1xuaW1wb3J0IHsgQnlsaW5lIH0gZnJvbSAnLi4vZGVzaWduLXN5c3RlbS9CeWxpbmUuanMnXG5pbXBvcnQgeyBQcm9ncmVzc0JhciB9IGZyb20gJy4uL2Rlc2lnbi1zeXN0ZW0vUHJvZ3Jlc3NCYXIuanMnXG5pbXBvcnQge1xuICBpc0VsaWdpYmxlRm9yT3ZlcmFnZUNyZWRpdEdyYW50LFxuICBPdmVyYWdlQ3JlZGl0VXBzZWxsLFxufSBmcm9tICcuLi9Mb2dvVjIvT3ZlcmFnZUNyZWRpdFVwc2VsbC5qcydcblxudHlwZSBMaW1pdEJhclByb3BzID0ge1xuICB0aXRsZTogc3RyaW5nXG4gIGxpbWl0OiBSYXRlTGltaXRcbiAgbWF4V2lkdGg6IG51bWJlclxuICBzaG93VGltZUluUmVzZXQ/OiBib29sZWFuXG4gIGV4dHJhU3VidGV4dD86IHN0cmluZ1xufVxuXG5mdW5jdGlvbiBMaW1pdEJhcih7XG4gIHRpdGxlLFxuICBsaW1pdCxcbiAgbWF4V2lkdGgsXG4gIHNob3dUaW1lSW5SZXNldCA9IHRydWUsXG4gIGV4dHJhU3VidGV4dCxcbn06IExpbWl0QmFyUHJvcHMpOiBSZWFjdC5SZWFjdE5vZGUge1xuICBjb25zdCB7IHV0aWxpemF0aW9uLCByZXNldHNfYXQgfSA9IGxpbWl0XG4gIGlmICh1dGlsaXphdGlvbiA9PT0gbnVsbCkge1xuICAgIHJldHVybiBudWxsXG4gIH1cblxuICAvLyBDYWxjdWxhdGUgdXNhZ2UgcGVyY2VudGFnZVxuICBjb25zdCB1c2VkVGV4dCA9IGAke01hdGguZmxvb3IodXRpbGl6YXRpb24pfSUgdXNlZGBcblxuICBsZXQgc3VidGV4dDogc3RyaW5nIHwgdW5kZWZpbmVkXG4gIGlmIChyZXNldHNfYXQpIHtcbiAgICBzdWJ0ZXh0ID0gYFJlc2V0cyAke2Zvcm1hdFJlc2V0VGV4dChyZXNldHNfYXQsIHRydWUsIHNob3dUaW1lSW5SZXNldCl9YFxuICB9XG5cbiAgaWYgKGV4dHJhU3VidGV4dCkge1xuICAgIGlmIChzdWJ0ZXh0KSB7XG4gICAgICBzdWJ0ZXh0ID0gYCR7ZXh0cmFTdWJ0ZXh0fSDCtyAke3N1YnRleHR9YFxuICAgIH0gZWxzZSB7XG4gICAgICBzdWJ0ZXh0ID0gZXh0cmFTdWJ0ZXh0XG4gICAgfVxuICB9XG5cbiAgY29uc3QgbWF4QmFyV2lkdGggPSA1MFxuICBjb25zdCB1c2VkTGFiZWxTcGFjZSA9IDEyXG4gIGlmIChtYXhXaWR0aCA+PSBtYXhCYXJXaWR0aCArIHVzZWRMYWJlbFNwYWNlKSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8VGV4dCBib2xkPnt0aXRsZX08L1RleHQ+XG4gICAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cInJvd1wiIGdhcD17MX0+XG4gICAgICAgICAgPFByb2dyZXNzQmFyXG4gICAgICAgICAgICByYXRpbz17dXRpbGl6YXRpb24gLyAxMDB9XG4gICAgICAgICAgICB3aWR0aD17bWF4QmFyV2lkdGh9XG4gICAgICAgICAgICBmaWxsQ29sb3I9XCJyYXRlX2xpbWl0X2ZpbGxcIlxuICAgICAgICAgICAgZW1wdHlDb2xvcj1cInJhdGVfbGltaXRfZW1wdHlcIlxuICAgICAgICAgIC8+XG4gICAgICAgICAgPFRleHQ+e3VzZWRUZXh0fTwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICAgIHtzdWJ0ZXh0ICYmIDxUZXh0IGRpbUNvbG9yPntzdWJ0ZXh0fTwvVGV4dD59XG4gICAgICA8L0JveD5cbiAgICApXG4gIH0gZWxzZSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8VGV4dD5cbiAgICAgICAgICA8VGV4dCBib2xkPnt0aXRsZX08L1RleHQ+XG4gICAgICAgICAge3N1YnRleHQgJiYgKFxuICAgICAgICAgICAgPD5cbiAgICAgICAgICAgICAgPFRleHQ+IDwvVGV4dD5cbiAgICAgICAgICAgICAgPFRleHQgZGltQ29sb3I+wrcge3N1YnRleHR9PC9UZXh0PlxuICAgICAgICAgICAgPC8+XG4gICAgICAgICAgKX1cbiAgICAgICAgPC9UZXh0PlxuICAgICAgICA8UHJvZ3Jlc3NCYXJcbiAgICAgICAgICByYXRpbz17dXRpbGl6YXRpb24gLyAxMDB9XG4gICAgICAgICAgd2lkdGg9e21heFdpZHRofVxuICAgICAgICAgIGZpbGxDb2xvcj1cInJhdGVfbGltaXRfZmlsbFwiXG4gICAgICAgICAgZW1wdHlDb2xvcj1cInJhdGVfbGltaXRfZW1wdHlcIlxuICAgICAgICAvPlxuICAgICAgICA8VGV4dD57dXNlZFRleHR9PC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBVc2FnZSgpOiBSZWFjdC5SZWFjdE5vZGUge1xuICBjb25zdCBbdXRpbGl6YXRpb24sIHNldFV0aWxpemF0aW9uXSA9IHVzZVN0YXRlPFV0aWxpemF0aW9uIHwgbnVsbD4obnVsbClcbiAgY29uc3QgW2Vycm9yLCBzZXRFcnJvcl0gPSB1c2VTdGF0ZTxzdHJpbmcgfCBudWxsPihudWxsKVxuICBjb25zdCBbaXNMb2FkaW5nLCBzZXRJc0xvYWRpbmddID0gdXNlU3RhdGUodHJ1ZSlcbiAgY29uc3QgeyBjb2x1bW5zIH0gPSB1c2VUZXJtaW5hbFNpemUoKVxuXG4gIGNvbnN0IGF2YWlsYWJsZVdpZHRoID0gY29sdW1ucyAtIDIgLy8gMiBmb3Igc2NyZWVuIHBhZGRpbmdcbiAgY29uc3QgbWF4V2lkdGggPSBNYXRoLm1pbihhdmFpbGFibGVXaWR0aCwgODApXG5cbiAgY29uc3QgbG9hZFV0aWxpemF0aW9uID0gUmVhY3QudXNlQ2FsbGJhY2soYXN5bmMgKCkgPT4ge1xuICAgIHNldElzTG9hZGluZyh0cnVlKVxuICAgIHNldEVycm9yKG51bGwpXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IGRhdGEgPSBhd2FpdCBmZXRjaFV0aWxpemF0aW9uKClcbiAgICAgIHNldFV0aWxpemF0aW9uKGRhdGEpXG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBsb2dFcnJvcihlcnIgYXMgRXJyb3IpXG4gICAgICBjb25zdCBheGlvc0Vycm9yID0gZXJyIGFzIHsgcmVzcG9uc2U/OiB7IGRhdGE/OiB1bmtub3duIH0gfVxuICAgICAgY29uc3QgcmVzcG9uc2VCb2R5ID0gYXhpb3NFcnJvci5yZXNwb25zZT8uZGF0YVxuICAgICAgICA/IGpzb25TdHJpbmdpZnkoYXhpb3NFcnJvci5yZXNwb25zZS5kYXRhKVxuICAgICAgICA6IHVuZGVmaW5lZFxuICAgICAgc2V0RXJyb3IoXG4gICAgICAgIHJlc3BvbnNlQm9keVxuICAgICAgICAgID8gYEZhaWxlZCB0byBsb2FkIHVzYWdlIGRhdGE6ICR7cmVzcG9uc2VCb2R5fWBcbiAgICAgICAgICA6ICdGYWlsZWQgdG8gbG9hZCB1c2FnZSBkYXRhJyxcbiAgICAgIClcbiAgICB9IGZpbmFsbHkge1xuICAgICAgc2V0SXNMb2FkaW5nKGZhbHNlKVxuICAgIH1cbiAgfSwgW10pXG5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICB2b2lkIGxvYWRVdGlsaXphdGlvbigpXG4gIH0sIFtsb2FkVXRpbGl6YXRpb25dKVxuXG4gIHVzZUtleWJpbmRpbmcoXG4gICAgJ3NldHRpbmdzOnJldHJ5JyxcbiAgICAoKSA9PiB7XG4gICAgICB2b2lkIGxvYWRVdGlsaXphdGlvbigpXG4gICAgfSxcbiAgICB7IGNvbnRleHQ6ICdTZXR0aW5ncycsIGlzQWN0aXZlOiAhIWVycm9yICYmICFpc0xvYWRpbmcgfSxcbiAgKVxuXG4gIGlmIChlcnJvcikge1xuICAgIHJldHVybiAoXG4gICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIiBnYXA9ezF9PlxuICAgICAgICA8VGV4dCBjb2xvcj1cImVycm9yXCI+RXJyb3I6IHtlcnJvcn08L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgIDxCeWxpbmU+XG4gICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgIGFjdGlvbj1cInNldHRpbmdzOnJldHJ5XCJcbiAgICAgICAgICAgICAgY29udGV4dD1cIlNldHRpbmdzXCJcbiAgICAgICAgICAgICAgZmFsbGJhY2s9XCJyXCJcbiAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJyZXRyeVwiXG4gICAgICAgICAgICAvPlxuICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICBhY3Rpb249XCJjb25maXJtOm5vXCJcbiAgICAgICAgICAgICAgY29udGV4dD1cIlNldHRpbmdzXCJcbiAgICAgICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cImNhbmNlbFwiXG4gICAgICAgICAgICAvPlxuICAgICAgICAgIDwvQnlsaW5lPlxuICAgICAgICA8L1RleHQ+XG4gICAgICA8L0JveD5cbiAgICApXG4gIH1cblxuICBpZiAoIXV0aWxpemF0aW9uKSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiIGdhcD17MX0+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPkxvYWRpbmcgdXNhZ2UgZGF0YeKApjwvVGV4dD5cbiAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTpub1wiXG4gICAgICAgICAgICBjb250ZXh0PVwiU2V0dGluZ3NcIlxuICAgICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgICAgZGVzY3JpcHRpb249XCJjYW5jZWxcIlxuICAgICAgICAgIC8+XG4gICAgICAgIDwvVGV4dD5cbiAgICAgIDwvQm94PlxuICAgIClcbiAgfVxuXG4gIC8vIE9ubHkgTWF4IGFuZCBUZWFtIHBsYW5zIGhhdmUgYSBTb25uZXQgbGltaXQgdGhhdCBkaWZmZXJzIGZyb20gdGhlIHdlZWtseVxuICAvLyBsaW1pdCAoc2VlIHJhdGVMaW1pdE1lc3NhZ2VzLnRzKS4gRm9yIG90aGVyIHBsYW5zIHRoZSBiYXIgaXMgcmVkdW5kYW50LlxuICAvLyBTaG93IGZvciBudWxsICh1bmtub3duIHBsYW4pIHRvIHN0YXkgY29uc2lzdGVudCB3aXRoIHJhdGVMaW1pdE1lc3NhZ2VzLnRzLFxuICAvLyB3aGljaCBsYWJlbHMgaXQgXCJTb25uZXQgbGltaXRcIiBpbiB0aGF0IGNhc2UuXG4gIGNvbnN0IHN1YnNjcmlwdGlvblR5cGUgPSBnZXRTdWJzY3JpcHRpb25UeXBlKClcbiAgY29uc3Qgc2hvd1Nvbm5ldEJhciA9XG4gICAgc3Vic2NyaXB0aW9uVHlwZSA9PT0gJ21heCcgfHxcbiAgICBzdWJzY3JpcHRpb25UeXBlID09PSAndGVhbScgfHxcbiAgICBzdWJzY3JpcHRpb25UeXBlID09PSBudWxsXG5cbiAgY29uc3QgbGltaXRzID0gW1xuICAgIHtcbiAgICAgIHRpdGxlOiAnQ3VycmVudCBzZXNzaW9uJyxcbiAgICAgIGxpbWl0OiB1dGlsaXphdGlvbi5maXZlX2hvdXIsXG4gICAgfSxcbiAgICB7XG4gICAgICB0aXRsZTogJ0N1cnJlbnQgd2VlayAoYWxsIG1vZGVscyknLFxuICAgICAgbGltaXQ6IHV0aWxpemF0aW9uLnNldmVuX2RheSxcbiAgICB9LFxuICAgIC4uLihzaG93U29ubmV0QmFyXG4gICAgICA/IFtcbiAgICAgICAgICB7XG4gICAgICAgICAgICB0aXRsZTogJ0N1cnJlbnQgd2VlayAoU29ubmV0IG9ubHkpJyxcbiAgICAgICAgICAgIGxpbWl0OiB1dGlsaXphdGlvbi5zZXZlbl9kYXlfc29ubmV0LFxuICAgICAgICAgIH0sXG4gICAgICAgIF1cbiAgICAgIDogW10pLFxuICBdXG5cbiAgcmV0dXJuIChcbiAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIiBnYXA9ezF9IHdpZHRoPVwiMTAwJVwiPlxuICAgICAge2xpbWl0cy5zb21lKCh7IGxpbWl0IH0pID0+IGxpbWl0KSB8fCAoXG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPi91c2FnZSBpcyBvbmx5IGF2YWlsYWJsZSBmb3Igc3Vic2NyaXB0aW9uIHBsYW5zLjwvVGV4dD5cbiAgICAgICl9XG5cbiAgICAgIHtsaW1pdHMubWFwKFxuICAgICAgICAoeyB0aXRsZSwgbGltaXQgfSkgPT5cbiAgICAgICAgICBsaW1pdCAmJiAoXG4gICAgICAgICAgICA8TGltaXRCYXJcbiAgICAgICAgICAgICAga2V5PXt0aXRsZX1cbiAgICAgICAgICAgICAgdGl0bGU9e3RpdGxlfVxuICAgICAgICAgICAgICBsaW1pdD17bGltaXR9XG4gICAgICAgICAgICAgIG1heFdpZHRoPXttYXhXaWR0aH1cbiAgICAgICAgICAgIC8+XG4gICAgICAgICAgKSxcbiAgICAgICl9XG5cbiAgICAgIHt1dGlsaXphdGlvbi5leHRyYV91c2FnZSAmJiAoXG4gICAgICAgIDxFeHRyYVVzYWdlU2VjdGlvblxuICAgICAgICAgIGV4dHJhVXNhZ2U9e3V0aWxpemF0aW9uLmV4dHJhX3VzYWdlfVxuICAgICAgICAgIG1heFdpZHRoPXttYXhXaWR0aH1cbiAgICAgICAgLz5cbiAgICAgICl9XG5cbiAgICAgIHtpc0VsaWdpYmxlRm9yT3ZlcmFnZUNyZWRpdEdyYW50KCkgJiYgKFxuICAgICAgICA8T3ZlcmFnZUNyZWRpdFVwc2VsbCBtYXhXaWR0aD17bWF4V2lkdGh9IC8+XG4gICAgICApfVxuXG4gICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgIGFjdGlvbj1cImNvbmZpcm06bm9cIlxuICAgICAgICAgIGNvbnRleHQ9XCJTZXR0aW5nc1wiXG4gICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgIGRlc2NyaXB0aW9uPVwiY2FuY2VsXCJcbiAgICAgICAgLz5cbiAgICAgIDwvVGV4dD5cbiAgICA8L0JveD5cbiAgKVxufVxuXG50eXBlIEV4dHJhVXNhZ2VTZWN0aW9uUHJvcHMgPSB7XG4gIGV4dHJhVXNhZ2U6IEV4dHJhVXNhZ2VcbiAgbWF4V2lkdGg6IG51bWJlclxufVxuXG5jb25zdCBFWFRSQV9VU0FHRV9TRUNUSU9OX1RJVExFID0gJ0V4dHJhIHVzYWdlJ1xuXG5mdW5jdGlvbiBFeHRyYVVzYWdlU2VjdGlvbih7XG4gIGV4dHJhVXNhZ2UsXG4gIG1heFdpZHRoLFxufTogRXh0cmFVc2FnZVNlY3Rpb25Qcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IHN1YnNjcmlwdGlvblR5cGUgPSBnZXRTdWJzY3JpcHRpb25UeXBlKClcbiAgY29uc3QgaXNQcm9Pck1heCA9IHN1YnNjcmlwdGlvblR5cGUgPT09ICdwcm8nIHx8IHN1YnNjcmlwdGlvblR5cGUgPT09ICdtYXgnXG4gIGlmICghaXNQcm9Pck1heCkge1xuICAgIC8vIE9ubHkgc2hvdyB0byBQcm8gYW5kIE1heCwgY29uc2lzdGVudCB3aXRoIGNsYXVkZS5haSBub24tYWRtaW4gdXNhZ2Ugc2V0dGluZ3NcbiAgICByZXR1cm4gZmFsc2VcbiAgfVxuXG4gIGlmICghZXh0cmFVc2FnZS5pc19lbmFibGVkKSB7XG4gICAgaWYgKGV4dHJhVXNhZ2VDb21tYW5kLmlzRW5hYmxlZCgpKSB7XG4gICAgICByZXR1cm4gKFxuICAgICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgICA8VGV4dCBib2xkPntFWFRSQV9VU0FHRV9TRUNUSU9OX1RJVExFfTwvVGV4dD5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5FeHRyYSB1c2FnZSBub3QgZW5hYmxlZCDCtyAvZXh0cmEtdXNhZ2UgdG8gZW5hYmxlPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgIClcbiAgICB9XG5cbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgaWYgKGV4dHJhVXNhZ2UubW9udGhseV9saW1pdCA9PT0gbnVsbCkge1xuICAgIHJldHVybiAoXG4gICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgPFRleHQgYm9sZD57RVhUUkFfVVNBR0VfU0VDVElPTl9USVRMRX08L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPlVubGltaXRlZDwvVGV4dD5cbiAgICAgIDwvQm94PlxuICAgIClcbiAgfVxuXG4gIGlmIChcbiAgICB0eXBlb2YgZXh0cmFVc2FnZS51c2VkX2NyZWRpdHMgIT09ICdudW1iZXInIHx8XG4gICAgdHlwZW9mIGV4dHJhVXNhZ2UudXRpbGl6YXRpb24gIT09ICdudW1iZXInXG4gICkge1xuICAgIHJldHVybiBudWxsXG4gIH1cblxuICBjb25zdCBmb3JtYXR0ZWRVc2VkQ3JlZGl0cyA9IGZvcm1hdENvc3QoZXh0cmFVc2FnZS51c2VkX2NyZWRpdHMgLyAxMDAsIDIpXG4gIGNvbnN0IGZvcm1hdHRlZE1vbnRobHlMaW1pdCA9IGZvcm1hdENvc3QoZXh0cmFVc2FnZS5tb250aGx5X2xpbWl0IC8gMTAwLCAyKVxuICBjb25zdCBub3cgPSBuZXcgRGF0ZSgpXG4gIGNvbnN0IG9uZU1vbnRoUmVzZXQgPSBuZXcgRGF0ZShub3cuZ2V0RnVsbFllYXIoKSwgbm93LmdldE1vbnRoKCkgKyAxLCAxKVxuXG4gIHJldHVybiAoXG4gICAgPExpbWl0QmFyXG4gICAgICB0aXRsZT17RVhUUkFfVVNBR0VfU0VDVElPTl9USVRMRX1cbiAgICAgIGxpbWl0PXt7XG4gICAgICAgIHV0aWxpemF0aW9uOiBleHRyYVVzYWdlLnV0aWxpemF0aW9uLFxuICAgICAgICAvLyBOb3QgYXBwbGljYWJsZSBmb3IgZW50ZXJwcmlzZXMsIGJ1dCBmb3Igbm93IHdlIGRvbid0IHJlbmRlciB0aGlzIGZvciB0aGVtXG4gICAgICAgIHJlc2V0c19hdDogb25lTW9udGhSZXNldC50b0lTT1N0cmluZygpLFxuICAgICAgfX1cbiAgICAgIHNob3dUaW1lSW5SZXNldD17ZmFsc2V9XG4gICAgICBleHRyYVN1YnRleHQ9e2Ake2Zvcm1hdHRlZFVzZWRDcmVkaXRzfSAvICR7Zm9ybWF0dGVkTW9udGhseUxpbWl0fSBzcGVudGB9XG4gICAgICBtYXhXaWR0aD17bWF4V2lkdGh9XG4gICAgLz5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxTQUFTLEVBQUVDLFFBQVEsUUFBUSxPQUFPO0FBQzNDLFNBQVNDLFVBQVUsSUFBSUMsaUJBQWlCLFFBQVEsbUNBQW1DO0FBQ25GLFNBQVNDLFVBQVUsUUFBUSxxQkFBcUI7QUFDaEQsU0FBU0MsbUJBQW1CLFFBQVEsbUJBQW1CO0FBQ3ZELFNBQVNDLGVBQWUsUUFBUSxnQ0FBZ0M7QUFDaEUsU0FBU0MsR0FBRyxFQUFFQyxJQUFJLFFBQVEsY0FBYztBQUN4QyxTQUFTQyxhQUFhLFFBQVEsb0NBQW9DO0FBQ2xFLFNBQ0UsS0FBS0MsVUFBVSxFQUNmQyxnQkFBZ0IsRUFDaEIsS0FBS0MsU0FBUyxFQUNkLEtBQUtDLFdBQVcsUUFDWCw2QkFBNkI7QUFDcEMsU0FBU0MsZUFBZSxRQUFRLHVCQUF1QjtBQUN2RCxTQUFTQyxRQUFRLFFBQVEsb0JBQW9CO0FBQzdDLFNBQVNDLGFBQWEsUUFBUSwrQkFBK0I7QUFDN0QsU0FBU0Msd0JBQXdCLFFBQVEsZ0NBQWdDO0FBQ3pFLFNBQVNDLE1BQU0sUUFBUSw0QkFBNEI7QUFDbkQsU0FBU0MsV0FBVyxRQUFRLGlDQUFpQztBQUM3RCxTQUNFQywrQkFBK0IsRUFDL0JDLG1CQUFtQixRQUNkLGtDQUFrQztBQUV6QyxLQUFLQyxhQUFhLEdB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 diff --git a/src/services/api/codexUsage.test.ts b/src/services/api/codexUsage.test.ts new file mode 100644 index 00000000..7d775ffe --- /dev/null +++ b/src/services/api/codexUsage.test.ts @@ -0,0 +1,204 @@ +import { describe, expect, test } from 'bun:test' + +import { + buildCodexUsageRows, + formatCodexPlanType, + getCodexUsageUrl, + normalizeCodexUsagePayload, +} from './codexUsage.js' + +describe('normalizeCodexUsagePayload', () => { + test('normalizes live Codex usage payloads from /backend-api/wham/usage', () => { + const usage = normalizeCodexUsagePayload({ + plan_type: 'plus', + rate_limit: { + primary_window: { + used_percent: 38, + limit_window_seconds: 18_000, + reset_at: 1_775_154_358, + }, + secondary_window: { + used_percent: 32, + limit_window_seconds: 604_800, + reset_at: 1_775_685_041, + }, + }, + code_review_rate_limit: { + primary_window: { + used_percent: 0, + limit_window_seconds: 604_800, + reset_at: 1_775_744_471, + }, + secondary_window: null, + }, + credits: { + has_credits: false, + unlimited: false, + balance: '0', + }, + }) + + expect(usage.planType).toBe('plus') + expect(usage.snapshots).toHaveLength(2) + expect(usage.snapshots[0]).toMatchObject({ + limitName: 'codex', + primary: { + usedPercent: 38, + windowMinutes: 300, + }, + secondary: { + usedPercent: 32, + windowMinutes: 10_080, + }, + }) + expect(usage.snapshots[1]).toMatchObject({ + limitName: 'code review', + primary: { + usedPercent: 0, + windowMinutes: 10_080, + }, + }) + }) + + test('supports direct protocol-style snapshot collections', () => { + const usage = normalizeCodexUsagePayload({ + rateLimitsByLimitId: { + codex: { + limit_name: 'codex', + primary: { + used_percent: 12, + window_minutes: 300, + resets_at: 1_700_000_000, + }, + credits: { + has_credits: true, + unlimited: false, + balance: '25', + }, + }, + }, + }) + + expect(usage.snapshots).toEqual([ + { + limitName: 'codex', + primary: { + usedPercent: 12, + windowMinutes: 300, + resetsAt: new Date(1_700_000_000 * 1000).toISOString(), + }, + secondary: undefined, + credits: { + hasCredits: true, + unlimited: false, + balance: '25', + }, + }, + ]) + }) +}) + +describe('buildCodexUsageRows', () => { + test('builds Codex-like labels for primary and secondary windows', () => { + const rows = buildCodexUsageRows([ + { + limitName: 'codex', + primary: { + usedPercent: 38, + windowMinutes: 300, + resetsAt: '2026-04-02T10:00:00.000Z', + }, + secondary: { + usedPercent: 32, + windowMinutes: 10_080, + resetsAt: '2026-04-09T10:00:00.000Z', + }, + }, + { + limitName: 'code review', + primary: { + usedPercent: 0, + windowMinutes: 10_080, + resetsAt: '2026-04-09T10:00:00.000Z', + }, + }, + ]) + + expect(rows).toEqual([ + { + kind: 'window', + label: '5h limit', + usedPercent: 38, + resetsAt: '2026-04-02T10:00:00.000Z', + }, + { + kind: 'window', + label: 'Weekly limit', + usedPercent: 32, + resetsAt: '2026-04-09T10:00:00.000Z', + }, + { + kind: 'window', + label: 'Code review Weekly limit', + usedPercent: 0, + resetsAt: '2026-04-09T10:00:00.000Z', + }, + ]) + }) + + test('renders credits rows only when credits are available', () => { + const rows = buildCodexUsageRows([ + { + limitName: 'codex', + credits: { + hasCredits: true, + unlimited: false, + balance: '25.2', + }, + }, + { + limitName: 'code review', + credits: { + hasCredits: true, + unlimited: true, + }, + }, + { + limitName: 'other', + credits: { + hasCredits: true, + unlimited: false, + balance: '0', + }, + }, + ]) + + expect(rows).toEqual([ + { + kind: 'text', + label: 'Credits', + value: '25 credits', + }, + { + kind: 'text', + label: 'Code review limit', + value: '', + }, + { + kind: 'text', + label: 'Credits', + value: 'Unlimited', + }, + ]) + }) +}) + +describe('Codex usage helpers', () => { + test('formats plan labels and usage endpoint url', () => { + expect(formatCodexPlanType('team_max')).toBe('Team Max') + expect(getCodexUsageUrl()).toBe('https://chatgpt.com/backend-api/wham/usage') + expect(getCodexUsageUrl('https://chatgpt.com/backend-api/codex')).toBe( + 'https://chatgpt.com/backend-api/wham/usage', + ) + }) +}) diff --git a/src/services/api/codexUsage.ts b/src/services/api/codexUsage.ts new file mode 100644 index 00000000..06a43290 --- /dev/null +++ b/src/services/api/codexUsage.ts @@ -0,0 +1,434 @@ +import { + DEFAULT_CODEX_BASE_URL, + isCodexBaseUrl, + resolveCodexApiCredentials, + resolveProviderRequest, +} from './providerConfig.js' + +export type CodexUsageWindow = { + usedPercent: number + windowMinutes?: number + resetsAt?: string +} + +export type CodexUsageCredits = { + hasCredits: boolean + unlimited: boolean + balance?: string +} + +export type CodexUsageSnapshot = { + limitName: string + primary?: CodexUsageWindow + secondary?: CodexUsageWindow + credits?: CodexUsageCredits +} + +export type CodexUsageData = { + planType?: string + snapshots: CodexUsageSnapshot[] +} + +export type CodexUsageRow = + | { + kind: 'window' + label: string + usedPercent: number + resetsAt?: string + } + | { + kind: 'text' + label: string + value: string + } + +type RecordLike = Record + +function isRecord(value: unknown): value is RecordLike { + return typeof value === 'object' && value !== null +} + +function asString(value: unknown): string | undefined { + return typeof value === 'string' && value.trim() ? value.trim() : undefined +} + +function asNumber(value: unknown): number | undefined { + return typeof value === 'number' && Number.isFinite(value) ? value : undefined +} + +function asBoolean(value: unknown): boolean | undefined { + return typeof value === 'boolean' ? value : undefined +} + +function toIsoFromUnixSeconds(value: unknown): string | undefined { + const seconds = asNumber(value) + if (seconds === undefined) return undefined + return new Date(seconds * 1000).toISOString() +} + +function normalizeWindow(value: unknown): CodexUsageWindow | undefined { + if (!isRecord(value)) return undefined + + const usedPercent = + asNumber(value.used_percent) ?? asNumber(value.usedPercent) + if (usedPercent === undefined) return undefined + + const windowMinutes = + asNumber(value.window_minutes) ?? + asNumber(value.windowDurationMins) ?? + (() => { + const seconds = asNumber(value.limit_window_seconds) + return seconds === undefined ? undefined : Math.round(seconds / 60) + })() + + const resetsAt = + toIsoFromUnixSeconds(value.resets_at) ?? + toIsoFromUnixSeconds(value.resetsAt) ?? + toIsoFromUnixSeconds(value.reset_at) + + return { + usedPercent, + windowMinutes, + resetsAt, + } +} + +function normalizeCredits(value: unknown): CodexUsageCredits | undefined { + if (!isRecord(value)) return undefined + + const hasCredits = + asBoolean(value.has_credits) ?? asBoolean(value.hasCredits) ?? false + const unlimited = asBoolean(value.unlimited) ?? false + const balance = asString(value.balance) + + if (!hasCredits && !unlimited && !balance) { + return undefined + } + + return { + hasCredits, + unlimited, + balance, + } +} + +function normalizeSnapshot( + value: unknown, + fallbackLimitName: string, +): CodexUsageSnapshot | undefined { + if (!isRecord(value)) return undefined + + const limitName = + asString(value.limit_name) ?? + asString(value.limitName) ?? + asString(value.limit_id) ?? + asString(value.limitId) ?? + fallbackLimitName + + const primary = + normalizeWindow(value.primary) ?? normalizeWindow(value.primary_window) + const secondary = + normalizeWindow(value.secondary) ?? normalizeWindow(value.secondary_window) + const credits = normalizeCredits(value.credits) + + if (!primary && !secondary && !credits) { + return undefined + } + + return { + limitName, + primary, + secondary, + credits, + } +} + +function normalizeSnapshotsFromCollection( + value: unknown, + defaultLimitName = 'codex', +): CodexUsageSnapshot[] { + if (Array.isArray(value)) { + return value + .map((item, index) => + normalizeSnapshot( + item, + index === 0 ? defaultLimitName : `${defaultLimitName}-${index + 1}`, + ), + ) + .filter((item): item is CodexUsageSnapshot => item !== undefined) + } + + if (!isRecord(value)) return [] + + return Object.entries(value) + .map(([key, entry]) => normalizeSnapshot(entry, key)) + .filter((item): item is CodexUsageSnapshot => item !== undefined) +} + +function normalizeLiveUsagePayload(payload: RecordLike): CodexUsageData { + const planType = asString(payload.plan_type) ?? asString(payload.planType) + const snapshots: CodexUsageSnapshot[] = [] + const codexCredits = normalizeCredits(payload.credits) + + const codexSnapshot = normalizeSnapshot(payload.rate_limit, 'codex') + if (codexSnapshot) { + codexSnapshot.credits ??= codexCredits + snapshots.push(codexSnapshot) + } else if (codexCredits) { + snapshots.push({ + limitName: 'codex', + credits: codexCredits, + }) + } + + const codeReviewSnapshot = normalizeSnapshot( + payload.code_review_rate_limit, + 'code review', + ) + if (codeReviewSnapshot) { + snapshots.push(codeReviewSnapshot) + } + + snapshots.push( + ...normalizeSnapshotsFromCollection( + payload.additional_rate_limits ?? payload.additionalRateLimits, + 'additional', + ), + ) + + return { + planType, + snapshots, + } +} + +export function normalizeCodexUsagePayload(payload: unknown): CodexUsageData { + if (Array.isArray(payload)) { + return { + snapshots: normalizeSnapshotsFromCollection(payload), + } + } + + if (!isRecord(payload)) { + return { snapshots: [] } + } + + if ( + 'rate_limit' in payload || + 'code_review_rate_limit' in payload || + 'additional_rate_limits' in payload || + 'credits' in payload + ) { + return normalizeLiveUsagePayload(payload) + } + + const collection = + payload.rate_limits ?? + payload.rateLimits ?? + payload.rate_limits_by_limit_id ?? + payload.rateLimitsByLimitId + + if (collection !== undefined) { + return { + planType: asString(payload.plan_type) ?? asString(payload.planType), + snapshots: normalizeSnapshotsFromCollection(collection), + } + } + + const snapshot = normalizeSnapshot(payload, 'codex') + return { + planType: asString(payload.plan_type) ?? asString(payload.planType), + snapshots: snapshot ? [snapshot] : [], + } +} + +function capitalizeFirst(value: string): string { + if (!value) return value + return value[0]!.toUpperCase() + value.slice(1) +} + +function formatWindowDuration( + windowMinutes: number | undefined, + fallback: string, +): string { + if (windowMinutes === undefined || windowMinutes <= 0) { + return fallback + } + + if (windowMinutes === 60 * 24 * 7) { + return 'weekly' + } + + if (windowMinutes % (60 * 24) === 0) { + return `${windowMinutes / (60 * 24)}d` + } + + if (windowMinutes % 60 === 0) { + return `${windowMinutes / 60}h` + } + + return `${windowMinutes}m` +} + +function formatCreditBalance(rawBalance: string | undefined): string | undefined { + const balance = rawBalance?.trim() + if (!balance) return undefined + + const intValue = Number.parseInt(balance, 10) + if (Number.isFinite(intValue) && `${intValue}` === balance && intValue > 0) { + return `${intValue}` + } + + const floatValue = Number.parseFloat(balance) + if (Number.isFinite(floatValue) && floatValue > 0) { + return `${Math.round(floatValue)}` + } + + return undefined +} + +function buildCreditsRow( + credits: CodexUsageCredits | undefined, +): CodexUsageRow | undefined { + if (!credits?.hasCredits) return undefined + if (credits.unlimited) { + return { + kind: 'text', + label: 'Credits', + value: 'Unlimited', + } + } + + const displayBalance = formatCreditBalance(credits.balance) + if (!displayBalance) return undefined + + return { + kind: 'text', + label: 'Credits', + value: `${displayBalance} credits`, + } +} + +export function buildCodexUsageRows( + snapshots: CodexUsageSnapshot[], +): CodexUsageRow[] { + const rows: CodexUsageRow[] = [] + + for (const snapshot of snapshots) { + const limitBucketLabel = snapshot.limitName.trim() || 'codex' + const creditsRow = buildCreditsRow(snapshot.credits) + const hasRenderableContent = + snapshot.primary !== undefined || + snapshot.secondary !== undefined || + creditsRow !== undefined + if (!hasRenderableContent) { + continue + } + + const showLimitPrefix = limitBucketLabel.toLowerCase() !== 'codex' + const windowCount = + Number(snapshot.primary !== undefined) + + Number(snapshot.secondary !== undefined) + const combineNonCodexSingleLimit = showLimitPrefix && windowCount === 1 + + if (showLimitPrefix && !combineNonCodexSingleLimit) { + rows.push({ + kind: 'text', + label: `${capitalizeFirst(limitBucketLabel)} limit`, + value: '', + }) + } + + if (snapshot.primary) { + const durationLabel = capitalizeFirst( + formatWindowDuration(snapshot.primary.windowMinutes, '5h'), + ) + rows.push({ + kind: 'window', + label: combineNonCodexSingleLimit + ? `${capitalizeFirst(limitBucketLabel)} ${durationLabel} limit` + : `${durationLabel} limit`, + usedPercent: snapshot.primary.usedPercent, + resetsAt: snapshot.primary.resetsAt, + }) + } + + if (snapshot.secondary) { + const durationLabel = capitalizeFirst( + formatWindowDuration(snapshot.secondary.windowMinutes, 'weekly'), + ) + rows.push({ + kind: 'window', + label: combineNonCodexSingleLimit + ? `${capitalizeFirst(limitBucketLabel)} ${durationLabel} limit` + : `${durationLabel} limit`, + usedPercent: snapshot.secondary.usedPercent, + resetsAt: snapshot.secondary.resetsAt, + }) + } + + if (creditsRow) { + rows.push(creditsRow) + } + } + + return rows +} + +export function formatCodexPlanType( + planType: string | undefined, +): string | undefined { + if (!planType) return undefined + return planType + .split(/[_\s-]+/) + .filter(Boolean) + .map(part => capitalizeFirst(part.toLowerCase())) + .join(' ') +} + +export function getCodexUsageUrl(baseUrl = DEFAULT_CODEX_BASE_URL): string { + return new URL('/backend-api/wham/usage', baseUrl).toString() +} + +export async function fetchCodexUsage(): Promise { + const request = resolveProviderRequest({ + model: process.env.OPENAI_MODEL, + baseUrl: process.env.OPENAI_BASE_URL, + }) + if (!isCodexBaseUrl(request.baseUrl)) { + throw new Error( + 'Codex usage is only available with the official ChatGPT Codex backend.', + ) + } + + const credentials = resolveCodexApiCredentials() + if (!credentials.apiKey) { + const authHint = credentials.authPath + ? ` or place a Codex auth.json at ${credentials.authPath}` + : '' + throw new Error(`Codex auth is required. Set CODEX_API_KEY${authHint}.`) + } + if (!credentials.accountId) { + throw new Error( + 'Codex auth is missing chatgpt_account_id. Re-login with the Codex CLI or set CHATGPT_ACCOUNT_ID/CODEX_ACCOUNT_ID.', + ) + } + + const response = await fetch(getCodexUsageUrl(request.baseUrl), { + method: 'GET', + headers: { + Accept: 'application/json', + Authorization: `Bearer ${credentials.apiKey}`, + 'chatgpt-account-id': credentials.accountId, + originator: 'openclaude', + }, + signal: AbortSignal.timeout(5000), + }) + + if (!response.ok) { + const errorBody = await response.text().catch(() => 'unknown error') + throw new Error(`Codex usage error ${response.status}: ${errorBody}`) + } + + return normalizeCodexUsagePayload(await response.json()) +}