diff --git a/src/commands/add-dir/add-dir.tsx b/src/commands/add-dir/add-dir.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b1a2b4de1f2d24185e389bd0982450de33cee68d --- /dev/null +++ b/src/commands/add-dir/add-dir.tsx @@ -0,0 +1,126 @@ +import { c as _c } from "react/compiler-runtime"; +import chalk from 'chalk'; +import figures from 'figures'; +import React, { useEffect } from 'react'; +import { getAdditionalDirectoriesForClaudeMd, setAdditionalDirectoriesForClaudeMd } from '../../bootstrap/state.js'; +import type { LocalJSXCommandContext } from '../../commands.js'; +import { MessageResponse } from '../../components/MessageResponse.js'; +import { AddWorkspaceDirectory } from '../../components/permissions/rules/AddWorkspaceDirectory.js'; +import { Box, Text } from '../../ink.js'; +import type { LocalJSXCommandOnDone } from '../../types/command.js'; +import { applyPermissionUpdate, persistPermissionUpdate } from '../../utils/permissions/PermissionUpdate.js'; +import type { PermissionUpdateDestination } from '../../utils/permissions/PermissionUpdateSchema.js'; +import { SandboxManager } from '../../utils/sandbox/sandbox-adapter.js'; +import { addDirHelpMessage, validateDirectoryForWorkspace } from './validation.js'; +function AddDirError(t0) { + const $ = _c(10); + const { + message, + args, + onDone + } = t0; + let t1; + let t2; + if ($[0] !== onDone) { + t1 = () => { + const timer = setTimeout(onDone, 0); + return () => clearTimeout(timer); + }; + t2 = [onDone]; + $[0] = onDone; + $[1] = t1; + $[2] = t2; + } else { + t1 = $[1]; + t2 = $[2]; + } + useEffect(t1, t2); + let t3; + if ($[3] !== args) { + t3 = {figures.pointer} /add-dir {args}; + $[3] = args; + $[4] = t3; + } else { + t3 = $[4]; + } + let t4; + if ($[5] !== message) { + t4 = {message}; + $[5] = message; + $[6] = t4; + } else { + t4 = $[6]; + } + let t5; + if ($[7] !== t3 || $[8] !== t4) { + t5 = {t3}{t4}; + $[7] = t3; + $[8] = t4; + $[9] = t5; + } else { + t5 = $[9]; + } + return t5; +} +export async function call(onDone: LocalJSXCommandOnDone, context: LocalJSXCommandContext, args?: string): Promise { + const directoryPath = (args ?? '').trim(); + const appState = context.getAppState(); + + // Helper to handle adding a directory (shared by both with-path and no-path cases) + const handleAddDirectory = async (path: string, remember = false) => { + const destination: PermissionUpdateDestination = remember ? 'localSettings' : 'session'; + const permissionUpdate = { + type: 'addDirectories' as const, + directories: [path], + destination + }; + + // Apply to session context + const latestAppState = context.getAppState(); + const updatedContext = applyPermissionUpdate(latestAppState.toolPermissionContext, permissionUpdate); + context.setAppState(prev => ({ + ...prev, + toolPermissionContext: updatedContext + })); + + // Update sandbox config so Bash commands can access the new directory. + // Bootstrap state is the source of truth for session-only dirs; persisted + // dirs are picked up via the settings subscription, but we refresh + // eagerly here to avoid a race when the user acts immediately. + const currentDirs = getAdditionalDirectoriesForClaudeMd(); + if (!currentDirs.includes(path)) { + setAdditionalDirectoriesForClaudeMd([...currentDirs, path]); + } + SandboxManager.refreshConfig(); + let message: string; + if (remember) { + try { + persistPermissionUpdate(permissionUpdate); + message = `Added ${chalk.bold(path)} as a working directory and saved to local settings`; + } catch (error) { + message = `Added ${chalk.bold(path)} as a working directory. Failed to save to local settings: ${error instanceof Error ? error.message : 'Unknown error'}`; + } + } else { + message = `Added ${chalk.bold(path)} as a working directory for this session`; + } + const messageWithHint = `${message} ${chalk.dim('· /permissions to manage')}`; + onDone(messageWithHint); + }; + + // When no path is provided, show AddWorkspaceDirectory input form directly + // and return to REPL after confirmation + if (!directoryPath) { + return { + onDone('Did not add a working directory.'); + }} />; + } + const result = await validateDirectoryForWorkspace(directoryPath, appState.toolPermissionContext); + if (result.resultType !== 'success') { + const message = addDirHelpMessage(result); + return onDone(message)} />; + } + return { + onDone(`Did not add ${chalk.bold(result.absolutePath)} as a working directory.`); + }} />; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJjaGFsayIsImZpZ3VyZXMiLCJSZWFjdCIsInVzZUVmZmVjdCIsImdldEFkZGl0aW9uYWxEaXJlY3Rvcmllc0ZvckNsYXVkZU1kIiwic2V0QWRkaXRpb25hbERpcmVjdG9yaWVzRm9yQ2xhdWRlTWQiLCJMb2NhbEpTWENvbW1hbmRDb250ZXh0IiwiTWVzc2FnZVJlc3BvbnNlIiwiQWRkV29ya3NwYWNlRGlyZWN0b3J5IiwiQm94IiwiVGV4dCIsIkxvY2FsSlNYQ29tbWFuZE9uRG9uZSIsImFwcGx5UGVybWlzc2lvblVwZGF0ZSIsInBlcnNpc3RQZXJtaXNzaW9uVXBkYXRlIiwiUGVybWlzc2lvblVwZGF0ZURlc3RpbmF0aW9uIiwiU2FuZGJveE1hbmFnZXIiLCJhZGREaXJIZWxwTWVzc2FnZSIsInZhbGlkYXRlRGlyZWN0b3J5Rm9yV29ya3NwYWNlIiwiQWRkRGlyRXJyb3IiLCJ0MCIsIiQiLCJfYyIsIm1lc3NhZ2UiLCJhcmdzIiwib25Eb25lIiwidDEiLCJ0MiIsInRpbWVyIiwic2V0VGltZW91dCIsImNsZWFyVGltZW91dCIsInQzIiwicG9pbnRlciIsInQ0IiwidDUiLCJjYWxsIiwiY29udGV4dCIsIlByb21pc2UiLCJSZWFjdE5vZGUiLCJkaXJlY3RvcnlQYXRoIiwidHJpbSIsImFwcFN0YXRlIiwiZ2V0QXBwU3RhdGUiLCJoYW5kbGVBZGREaXJlY3RvcnkiLCJwYXRoIiwicmVtZW1iZXIiLCJkZXN0aW5hdGlvbiIsInBlcm1pc3Npb25VcGRhdGUiLCJ0eXBlIiwiY29uc3QiLCJkaXJlY3RvcmllcyIsImxhdGVzdEFwcFN0YXRlIiwidXBkYXRlZENvbnRleHQiLCJ0b29sUGVybWlzc2lvbkNvbnRleHQiLCJzZXRBcHBTdGF0ZSIsInByZXYiLCJjdXJyZW50RGlycyIsImluY2x1ZGVzIiwicmVmcmVzaENvbmZpZyIsImJvbGQiLCJlcnJvciIsIkVycm9yIiwibWVzc2FnZVdpdGhIaW50IiwiZGltIiwicmVzdWx0IiwicmVzdWx0VHlwZSIsImFic29sdXRlUGF0aCJdLCJzb3VyY2VzIjpbImFkZC1kaXIudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjaGFsayBmcm9tICdjaGFsaydcbmltcG9ydCBmaWd1cmVzIGZyb20gJ2ZpZ3VyZXMnXG5pbXBvcnQgUmVhY3QsIHsgdXNlRWZmZWN0IH0gZnJvbSAncmVhY3QnXG5pbXBvcnQge1xuICBnZXRBZGRpdGlvbmFsRGlyZWN0b3JpZXNGb3JDbGF1ZGVNZCxcbiAgc2V0QWRkaXRpb25hbERpcmVjdG9yaWVzRm9yQ2xhdWRlTWQsXG59IGZyb20gJy4uLy4uL2Jvb3RzdHJhcC9zdGF0ZS5qcydcbmltcG9ydCB0eXBlIHsgTG9jYWxKU1hDb21tYW5kQ29udGV4dCB9IGZyb20gJy4uLy4uL2NvbW1hbmRzLmpzJ1xuaW1wb3J0IHsgTWVzc2FnZVJlc3BvbnNlIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9NZXNzYWdlUmVzcG9uc2UuanMnXG5pbXBvcnQgeyBBZGRXb3Jrc3BhY2VEaXJlY3RvcnkgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL3Blcm1pc3Npb25zL3J1bGVzL0FkZFdvcmtzcGFjZURpcmVjdG9yeS5qcydcbmltcG9ydCB7IEJveCwgVGV4dCB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB0eXBlIHsgTG9jYWxKU1hDb21tYW5kT25Eb25lIH0gZnJvbSAnLi4vLi4vdHlwZXMvY29tbWFuZC5qcydcbmltcG9ydCB7XG4gIGFwcGx5UGVybWlzc2lvblVwZGF0ZSxcbiAgcGVyc2lzdFBlcm1pc3Npb25VcGRhdGUsXG59IGZyb20gJy4uLy4uL3V0aWxzL3Blcm1pc3Npb25zL1Blcm1pc3Npb25VcGRhdGUuanMnXG5pbXBvcnQgdHlwZSB7IFBlcm1pc3Npb25VcGRhdGVEZXN0aW5hdGlvbiB9IGZyb20gJy4uLy4uL3V0aWxzL3Blcm1pc3Npb25zL1Blcm1pc3Npb25VcGRhdGVTY2hlbWEuanMnXG5pbXBvcnQgeyBTYW5kYm94TWFuYWdlciB9IGZyb20gJy4uLy4uL3V0aWxzL3NhbmRib3gvc2FuZGJveC1hZGFwdGVyLmpzJ1xuaW1wb3J0IHtcbiAgYWRkRGlySGVscE1lc3NhZ2UsXG4gIHZhbGlkYXRlRGlyZWN0b3J5Rm9yV29ya3NwYWNlLFxufSBmcm9tICcuL3ZhbGlkYXRpb24uanMnXG5cbmZ1bmN0aW9uIEFkZERpckVycm9yKHtcbiAgbWVzc2FnZSxcbiAgYXJncyxcbiAgb25Eb25lLFxufToge1xuICBtZXNzYWdlOiBzdHJpbmdcbiAgYXJnczogc3RyaW5nXG4gIG9uRG9uZTogKCkgPT4gdm9pZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgLy8gV2UgbmVlZCB0byBkZWZlciBjYWxsaW5nIG9uRG9uZSB0byBhdm9pZCB0aGUgXCJyZXR1cm4gbnVsbFwiIGJ1ZyB3aGVyZVxuICAgIC8vIHRoZSBjb21wb25lbnQgdW5tb3VudHMgYmVmb3JlIFJlYWN0IGNhbiByZW5kZXIgdGhlIGVycm9yIG1lc3NhZ2UuXG4gICAgLy8gVXNpbmcgc2V0VGltZW91dCBlbnN1cmVzIHRoZSBlcnJvciBkaXNwbGF5cyBiZWZvcmUgdGhlIGNvbW1hbmQgZXhpdHMuXG4gICAgY29uc3QgdGltZXIgPSBzZXRUaW1lb3V0KG9uRG9uZSwgMClcbiAgICByZXR1cm4gKCkgPT4gY2xlYXJUaW1lb3V0KHRpbWVyKVxuICB9LCBbb25Eb25lXSlcblxuICByZXR1cm4gKFxuICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgIHtmaWd1cmVzLnBvaW50ZXJ9IC9hZGQtZGlyIHthcmdzfVxuICAgICAgPC9UZXh0PlxuICAgICAgPE1lc3NhZ2VSZXNwb25zZT5cbiAgICAgICAgPFRleHQ+e21lc3NhZ2V9PC9UZXh0PlxuICAgICAgPC9NZXNzYWdlUmVzcG9uc2U+XG4gICAgPC9Cb3g+XG4gIClcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNhbGwoXG4gIG9uRG9uZTogTG9jYWxKU1hDb21tYW5kT25Eb25lLFxuICBjb250ZXh0OiBMb2NhbEpTWENvbW1hbmRDb250ZXh0LFxuICBhcmdzPzogc3RyaW5nLFxuKTogUHJvbWlzZTxSZWFjdC5SZWFjdE5vZGU+IHtcbiAgY29uc3QgZGlyZWN0b3J5UGF0aCA9IChhcmdzID8/ICcnKS50cmltKClcbiAgY29uc3QgYXBwU3RhdGUgPSBjb250ZXh0LmdldEFwcFN0YXRlKClcblxuICAvLyBIZWxwZXIgdG8gaGFuZGxlIGFkZGluZyBhIGRpcmVjdG9yeSAoc2hhcmVkIGJ5IGJvdGggd2l0aC1wYXRoIGFuZCBuby1wYXRoIGNhc2VzKVxuICBjb25zdCBoYW5kbGVBZGREaXJlY3RvcnkgPSBhc3luYyAocGF0aDogc3RyaW5nLCByZW1lbWJlciA9IGZhbHNlKSA9PiB7XG4gICAgY29uc3QgZGVzdGluYXRpb246IFBlcm1pc3Npb25VcGRhdGVEZXN0aW5hdGlvbiA9IHJlbWVtYmVyXG4gICAgICA/ICdsb2NhbFNldHRpbmdzJ1xuICAgICAgOiAnc2Vzc2lvbidcblxuICAgIGNvbnN0IHBlcm1pc3Npb25VcGRhdGUgPSB7XG4gICAgICB0eXBlOiAnYWRkRGlyZWN0b3JpZXMnIGFzIGNvbnN0LFxuICAgICAgZGlyZWN0b3JpZXM6IFtwYXRoXSxcbiAgICAgIGRlc3RpbmF0aW9uLFxuICAgIH1cblxuICAgIC8vIEFwcGx5IHRvIHNlc3Npb24gY29udGV4dFxuICAgIGNvbnN0IGxhdGVzdEFwcFN0YXRlID0gY29udGV4dC5nZXRBcHBTdGF0ZSgpXG4gICAgY29uc3QgdXBkYXRlZENvbnRleHQgPSBhcHBseVBlcm1pc3Npb25VcGRhdGUoXG4gICAgICBsYXRlc3RBcHBTdGF0ZS50b29sUGVybWlzc2lvbkNvbnRleHQsXG4gICAgICBwZXJtaXNzaW9uVXBkYXRlLFxuICAgIClcbiAgICBjb250ZXh0LnNldEFwcFN0YXRlKHByZXYgPT4gKHtcbiAgICAgIC4uLnByZXYsXG4gICAgICB0b29sUGVybWlzc2lvbkNvbnRleHQ6IHVwZGF0ZWRDb250ZXh0LFxuICAgIH0pKVxuXG4gICAgLy8gVXBkYXRlIHNhbmRib3ggY29uZmlnIHNvIEJhc2ggY29tbWFuZHMgY2FuIGFjY2VzcyB0aGUgbmV3IGRpcmVjdG9yeS5cbiAgICAvLyBCb290c3RyYXAgc3RhdGUgaXMgdGhlIHNvdXJjZSBvZiB0cnV0aCBmb3Igc2Vzc2lvbi1vbmx5IGRpcnM7IHBlcnNpc3RlZFxuICAgIC8vIGRpcnMgYXJlIHBpY2tlZCB1cCB2aWEgdGhlIHNldHRpbmdzIHN1YnNjcmlwdGlvbiwgYnV0IHdlIHJlZnJlc2hcbiAgICAvLyBlYWdlcmx5IGhlcmUgdG8gYXZvaWQgYSByYWNlIHdoZW4gdGhlIHVzZXIgYWN0cyBpbW1lZGlhdGVseS5cbiAgICBjb25zdCBjdXJyZW50RGlycyA9IGdldEFkZGl0aW9uYWxEaXJlY3Rvcmllc0ZvckNsYXVkZU1kKClcbiAgICBpZiAoIWN1cnJlbnREaXJzLmluY2x1ZGVzKHBhdGgpKSB7XG4gICAgICBzZXRBZGRpdGlvbmFsRGlyZWN0b3JpZXNGb3JDbGF1ZGVNZChbLi4uY3VycmVudERpcnMsIHBhdGhdKVxuICAgIH1cbiAgICBTYW5kYm94TWFuYWdlci5yZWZyZXNoQ29uZmlnKClcblxuICAgIGxldCBtZXNzYWdlOiBzdHJpbmdcblxuICAgIGlmIChyZW1lbWJlcikge1xuICAgICAgdHJ5IHtcbiAgICAgICAgcGVyc2lzdFBlcm1pc3Npb25VcGRhdGUocGVybWlzc2lvblVwZGF0ZSlcbiAgICAgICAgbWVzc2FnZSA9IGBBZGRlZCAke2NoYWxrLmJvbGQocGF0aCl9IGFzIGEgd29ya2luZyBkaXJlY3RvcnkgYW5kIHNhdmVkIHRvIGxvY2FsIHNldHRpbmdzYFxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgbWVzc2FnZSA9IGBBZGRlZCAke2NoYWxrLmJvbGQocGF0aCl9IGFzIGEgd29ya2luZyBkaXJlY3RvcnkuIEZhaWxlZCB0byBzYXZlIHRvIGxvY2FsIHNldHRpbmdzOiAke2Vycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogJ1Vua25vd24gZXJyb3InfWBcbiAgICAgIH1cbiAgICB9IGVsc2Uge1xuICAgICAgbWVzc2FnZSA9IGBBZGRlZCAke2NoYWxrLmJvbGQocGF0aCl9IGFzIGEgd29ya2luZyBkaXJlY3RvcnkgZm9yIHRoaXMgc2Vzc2lvbmBcbiAgICB9XG5cbiAgICBjb25zdCBtZXNzYWdlV2l0aEhpbnQgPSBgJHttZXNzYWdlfSAke2NoYWxrLmRpbSgnwrcgL3Blcm1pc3Npb25zIHRvIG1hbmFnZScpfWBcbiAgICBvbkRvbmUobWVzc2FnZVdpdGhIaW50KVxuICB9XG5cbiAgLy8gV2hlbiBubyBwYXRoIGlzIHByb3ZpZGVkLCBzaG93IEFkZFdvcmtzcGFjZURpcmVjdG9yeSBpbnB1dCBmb3JtIGRpcmVjdGx5XG4gIC8vIGFuZCByZXR1cm4gdG8gUkVQTCBhZnRlciBjb25maXJtYXRpb25cbiAgaWYgKCFkaXJlY3RvcnlQYXRoKSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxBZGRXb3Jrc3BhY2VEaXJlY3RvcnlcbiAgICAgICAgcGVybWlzc2lvbkNvbnRleHQ9e2FwcFN0YXRlLnRvb2xQZXJtaXNzaW9uQ29udGV4dH1cbiAgICAgICAgb25BZGREaXJlY3Rvcnk9e2hhbmRsZUFkZERpcmVjdG9yeX1cbiAgICAgICAgb25DYW5jZWw9eygpID0+IHtcbiAgICAgICAgICBvbkRvbmUoJ0RpZCBub3QgYWRkIGEgd29ya2luZyBkaXJlY3RvcnkuJylcbiAgICAgICAgfX1cbiAgICAgIC8+XG4gICAgKVxuICB9XG5cbiAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdmFsaWRhdGVEaXJlY3RvcnlGb3JXb3Jrc3BhY2UoXG4gICAgZGlyZWN0b3J5UGF0aCxcbiAgICBhcHBTdGF0ZS50b29sUGVybWlzc2lvbkNvbnRleHQsXG4gIClcblxuICBpZiAocmVzdWx0LnJlc3VsdFR5cGUgIT09ICdzdWNjZXNzJykge1xuICAgIGNvbnN0IG1lc3NhZ2UgPSBhZGREaXJIZWxwTWVzc2FnZShyZXN1bHQpXG5cbiAgICByZXR1cm4gKFxuICAgICAgPEFkZERpckVycm9yXG4gICAgICAgIG1lc3NhZ2U9e21lc3NhZ2V9XG4gICAgICAgIGFyZ3M9e2FyZ3MgPz8gJyd9XG4gICAgICAgIG9uRG9uZT17KCkgPT4gb25Eb25lKG1lc3NhZ2UpfVxuICAgICAgLz5cbiAgICApXG4gIH1cblxuICByZXR1cm4gKFxuICAgIDxBZGRXb3Jrc3BhY2VEaXJlY3RvcnlcbiAgICAgIGRpcmVjdG9yeVBhdGg9e3Jlc3VsdC5hYnNvbHV0ZVBhdGh9XG4gICAgICBwZXJtaXNzaW9uQ29udGV4dD17YXBwU3RhdGUudG9vbFBlcm1pc3Npb25Db250ZXh0fVxuICAgICAgb25BZGREaXJlY3Rvcnk9e2hhbmRsZUFkZERpcmVjdG9yeX1cbiAgICAgIG9uQ2FuY2VsPXsoKSA9PiB7XG4gICAgICAgIG9uRG9uZShcbiAgICAgICAgICBgRGlkIG5vdCBhZGQgJHtjaGFsay5ib2xkKHJlc3VsdC5hYnNvbHV0ZVBhdGgpfSBhcyBhIHdvcmtpbmcgZGlyZWN0b3J5LmAsXG4gICAgICAgIClcbiAgICAgIH19XG4gICAgLz5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBT0EsS0FBSyxNQUFNLE9BQU87QUFDekIsT0FBT0MsT0FBTyxNQUFNLFNBQVM7QUFDN0IsT0FBT0MsS0FBSyxJQUFJQyxTQUFTLFFBQVEsT0FBTztBQUN4QyxTQUNFQyxtQ0FBbUMsRUFDbkNDLG1DQUFtQyxRQUM5QiwwQkFBMEI7QUFDakMsY0FBY0Msc0JBQXNCLFFBQVEsbUJBQW1CO0FBQy9ELFNBQVNDLGVBQWUsUUFBUSxxQ0FBcUM7QUFDckUsU0FBU0MscUJBQXFCLFFBQVEsNkRBQTZEO0FBQ25HLFNBQVNDLEdBQUcsRUFBRUMsSUFBSSxRQUFRLGNBQWM7QUFDeEMsY0FBY0MscUJBQXFCLFFBQVEsd0JBQXdCO0FBQ25FLFNBQ0VDLHFCQUFxQixFQUNyQkMsdUJBQXVCLFFBQ2xCLDZDQUE2QztBQUNwRCxjQUFjQywyQkFBMkIsUUFBUSxtREFBbUQ7QUFDcEcsU0FBU0MsY0FBYyxRQUFRLHdDQUF3QztBQUN2RSxTQUNFQyxpQkFBaUIsRUFDakJDLDZCQUE2QixRQUN4QixpQkFBaUI7QUFFeEIsU0FBQUMsWUFBQUMsRUFBQTtFQUFBLE1BQUFDLENBQUEsR0FBQUMsRUFBQTtFQUFxQjtJQUFBQyxPQUFBO0lBQUFDLElBQUE7SUFBQUM7RUFBQSxJQUFBTCxFQVFwQjtFQUFBLElBQUFNLEVBQUE7RUFBQSxJQUFBQyxFQUFBO0VBQUEsSUFBQU4sQ0FBQSxRQUFBSSxNQUFBO0lBQ1dDLEVBQUEsR0FBQUEsQ0FBQTtNQUlSLE1BQUFFLEtBQUEsR0FBY0MsVUFBVSxDQUFDSixNQUFNLEVBQUUsQ0FBQyxDQUFDO01BQUEsT0FDNUIsTUFBTUssWUFBWSxDQUFDRixLQUFLLENBQUM7SUFBQSxDQUNqQztJQUFFRCxFQUFBLElBQUNGLE1BQU0sQ0FBQztJQUFBSixDQUFBLE1BQUFJLE1BQUE7SUFBQUosQ0FBQSxNQUFBSyxFQUFBO0lBQUFMLENBQUEsTUFBQU0sRUFBQTtFQUFBO0lBQUFELEVBQUEsR0FBQUwsQ0FBQTtJQUFBTSxFQUFBLEdBQUFOLENBQUE7RUFBQTtFQU5YakIsU0FBUyxDQUFDc0IsRUFNVCxFQUFFQyxFQUFRLENBQUM7RUFBQSxJQUFBSSxFQUFBO0VBQUEsSUFBQVYsQ0FBQSxRQUFBRyxJQUFBO0lBSVJPLEVBQUEsSUFBQyxJQUFJLENBQUMsUUFBUSxDQUFSLEtBQU8sQ0FBQyxDQUNYLENBQUE3QixPQUFPLENBQUE4QixPQUFPLENBQUUsVUFBV1IsS0FBRyxDQUNqQyxFQUZDLElBQUksQ0FFRTtJQUFBSCxDQUFBLE1BQUFHLElBQUE7SUFBQUgsQ0FBQSxNQUFBVSxFQUFBO0VBQUE7SUFBQUEsRUFBQSxHQUFBVixDQUFBO0VBQUE7RUFBQSxJQUFBWSxFQUFBO0VBQUEsSUFBQVosQ0FBQSxRQUFBRSxPQUFBO0lBQ1BVLEVBQUEsSUFBQyxlQUFlLENBQ2QsQ0FBQyxJQUFJLENBQUVWLFFBQU0sQ0FBRSxFQUFkLElBQUksQ0FDUCxFQUZDLGVBQWUsQ0FFRTtJQUFBRixDQUFBLE1BQUFFLE9BQUE7SUFBQUYsQ0FBQSxNQUFBWSxFQUFBO0VBQUE7SUFBQUEsRUFBQSxHQUFBWixDQUFBO0VBQUE7RUFBQSxJQUFBYSxFQUFBO0VBQUEsSUFBQWIsQ0FBQSxRQUFBVSxFQUFBLElBQUFWLENBQUEsUUFBQVksRUFBQTtJQU5wQkMsRUFBQSxJQUFDLEdBQUcsQ0FBZSxhQUFRLENBQVIsUUFBUSxDQUN6QixDQUFBSCxFQUVNLENBQ04sQ0FBQUUsRUFFaUIsQ0FDbkIsRUFQQyxHQUFHLENBT0U7SUFBQVosQ0FBQSxNQUFBVSxFQUFBO0lBQUFWLENBQUEsTUFBQVksRUFBQTtJQUFBWixDQUFBLE1BQUFhLEVBQUE7RUFBQTtJQUFBQSxFQUFBLEdBQUFiLENBQUE7RUFBQTtFQUFBLE9BUE5hLEVBT007QUFBQTtBQUlWLE9BQU8sZUFBZUMsSUFBSUEsQ0FDeEJWLE1BQU0sRUFBRWIscUJBQXFCLEVBQzdCd0IsT0FBTyxFQUFFN0Isc0JBQXNCLEVBQy9CaUIsSUFBYSxDQUFSLEVBQUUsTUFBTSxDQUNkLEVBQUVhLE9BQU8sQ0FBQ2xDLEtBQUssQ0FBQ21DLFNBQVMsQ0FBQyxDQUFDO0VBQzFCLE1BQU1DLGFBQWEsR0FBRyxDQUFDZixJQUFJLElBQUksRUFBRSxFQUFFZ0IsSUFBSSxDQUFDLENBQUM7RUFDekMsTUFBTUMsUUFBUSxHQUFHTCxPQUFPLENBQUNNLFdBQVcsQ0FBQyxDQUFDOztFQUV0QztFQUNBLE1BQU1DLGtCQUFrQixHQUFHLE1BQUFBLENBQU9DLElBQUksRUFBRSxNQUFNLEVBQUVDLFFBQVEsR0FBRyxLQUFLLEtBQUs7SUFDbkUsTUFBTUMsV0FBVyxFQUFFL0IsMkJBQTJCLEdBQUc4QixRQUFRLEdBQ3JELGVBQWUsR0FDZixTQUFTO0lBRWIsTUFBTUUsZ0JBQWdCLEdBQUc7TUFDdkJDLElBQUksRUFBRSxnQkFBZ0IsSUFBSUMsS0FBSztNQUMvQkMsV0FBVyxFQUFFLENBQUNOLElBQUksQ0FBQztNQUNuQkU7SUFDRixDQUFDOztJQUVEO0lBQ0EsTUFBTUssY0FBYyxHQUFHZixPQUFPLENBQUNNLFdBQVcsQ0FBQyxDQUFDO0lBQzVDLE1BQU1VLGNBQWMsR0FBR3ZDLHFCQUFxQixDQUMxQ3NDLGNBQWMsQ0FBQ0UscUJBQXFCLEVBQ3BDTixnQkFDRixDQUFDO0lBQ0RYLE9BQU8sQ0FBQ2tCLFdBQVcsQ0FBQ0MsSUFBSSxLQUFLO01BQzNCLEdBQUdBLElBQUk7TUFDUEYscUJBQXFCLEVBQUVEO0lBQ3pCLENBQUMsQ0FBQyxDQUFDOztJQUVIO0lBQ0E7SUFDQTtJQUNBO0lBQ0EsTUFBTUksV0FBVyxHQUFHbkQsbUNBQW1DLENBQUMsQ0FBQztJQUN6RCxJQUFJLENBQUNtRCxXQUFXLENBQUNDLFFBQVEsQ0FBQ2IsSUFBSSxDQUFDLEVBQUU7TUFDL0J0QyxtQ0FBbUMsQ0FBQyxDQUFDLEdBQUdrRCxXQUFXLEVBQUVaLElBQUksQ0FBQyxDQUFDO0lBQzdEO0lBQ0E1QixjQUFjLENBQUMwQyxhQUFhLENBQUMsQ0FBQztJQUU5QixJQUFJbkMsT0FBTyxFQUFFLE1BQU07SUFFbkIsSUFBSXNCLFFBQVEsRUFBRTtNQUNaLElBQUk7UUFDRi9CLHVCQUF1QixDQUFDaUMsZ0JBQWdCLENBQUM7UUFDekN4QixPQUFPLEdBQUcsU0FBU3RCLEtBQUssQ0FBQzBELElBQUksQ0FBQ2YsSUFBSSxDQUFDLHFEQUFxRDtNQUMxRixDQUFDLENBQUMsT0FBT2dCLEtBQUssRUFBRTtRQUNkckMsT0FBTyxHQUFHLFNBQVN0QixLQUFLLENBQUMwRCxJQUFJLENBQUNmLElBQUksQ0FBQyw4REFBOERnQixLQUFLLFlBQVlDLEtBQUssR0FBR0QsS0FBSyxDQUFDckMsT0FBTyxHQUFHLGVBQWUsRUFBRTtNQUM3SjtJQUNGLENBQUMsTUFBTTtNQUNMQSxPQUFPLEdBQUcsU0FBU3RCLEtBQUssQ0FBQzBELElBQUksQ0FBQ2YsSUFBSSxDQUFDLDBDQUEwQztJQUMvRTtJQUVBLE1BQU1rQixlQUFlLEdBQUcsR0FBR3ZDLE9BQU8sSUFBSXRCLEtBQUssQ0FBQzhELEdBQUcsQ0FBQywwQkFBMEIsQ0FBQyxFQUFFO0lBQzdFdEMsTUFBTSxDQUFDcUMsZUFBZSxDQUFDO0VBQ3pCLENBQUM7O0VBRUQ7RUFDQTtFQUNBLElBQUksQ0FBQ3ZCLGFBQWEsRUFBRTtJQUNsQixPQUNFLENBQUMscUJBQXFCLENBQ3BCLGlCQUFpQixDQUFDLENBQUNFLFFBQVEsQ0FBQ1kscUJBQXFCLENBQUMsQ0FDbEQsY0FBYyxDQUFDLENBQUNWLGtCQUFrQixDQUFDLENBQ25DLFFBQVEsQ0FBQyxDQUFDLE1BQU07TUFDZGxCLE1BQU0sQ0FBQyxrQ0FBa0MsQ0FBQztJQUM1QyxDQUFDLENBQUMsR0FDRjtFQUVOO0VBRUEsTUFBTXVDLE1BQU0sR0FBRyxNQUFNOUMsNkJBQTZCLENBQ2hEcUIsYUFBYSxFQUNiRSxRQUFRLENBQUNZLHFCQUNYLENBQUM7RUFFRCxJQUFJVyxNQUFNLENBQUNDLFVBQVUsS0FBSyxTQUFTLEVBQUU7SUFDbkMsTUFBTTFDLE9BQU8sR0FBR04saUJBQWlCLENBQUMrQyxNQUFNLENBQUM7SUFFekMsT0FDRSxDQUFDLFdBQVcsQ0FDVixPQUFPLENBQUMsQ0FBQ3pDLE9BQU8sQ0FBQyxDQUNqQixJQUFJLENBQUMsQ0FBQ0MsSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUNqQixNQUFNLENBQUMsQ0FBQyxNQUFNQyxNQUFNLENBQUNGLE9BQU8sQ0FBQyxDQUFDLEdBQzlCO0VBRU47RUFFQSxPQUNFLENBQUMscUJBQXFCLENBQ3BCLGFBQWEsQ0FBQyxDQUFDeUMsTUFBTSxDQUFDRSxZQUFZLENBQUMsQ0FDbkMsaUJBQWlCLENBQUMsQ0FBQ3pCLFFBQVEsQ0FBQ1kscUJBQXFCLENBQUMsQ0FDbEQsY0FBYyxDQUFDLENBQUNWLGtCQUFrQixDQUFDLENBQ25DLFFBQVEsQ0FBQyxDQUFDLE1BQU07SUFDZGxCLE1BQU0sQ0FDSixlQUFleEIsS0FBSyxDQUFDMEQsSUFBSSxDQUFDSyxNQUFNLENBQUNFLFlBQVksQ0FBQywwQkFDaEQsQ0FBQztFQUNILENBQUMsQ0FBQyxHQUNGO0FBRU4iLCJpZ25vcmVMaXN0IjpbXX0= \ No newline at end of file diff --git a/src/commands/add-dir/index.ts b/src/commands/add-dir/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e3475492bc7531d262ad442e8a66aa24dfe36d93 --- /dev/null +++ b/src/commands/add-dir/index.ts @@ -0,0 +1,11 @@ +import type { Command } from '../../commands.js' + +const addDir = { + type: 'local-jsx', + name: 'add-dir', + description: 'Add a new working directory', + argumentHint: '', + load: () => import('./add-dir.js'), +} satisfies Command + +export default addDir diff --git a/src/commands/agents/agents.tsx b/src/commands/agents/agents.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3af6273712ca291e7c30586a8031fb843b1912d1 --- /dev/null +++ b/src/commands/agents/agents.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +import { AgentsMenu } from '../../components/agents/AgentsMenu.js'; +import type { ToolUseContext } from '../../Tool.js'; +import { getTools } from '../../tools.js'; +import type { LocalJSXCommandOnDone } from '../../types/command.js'; +export async function call(onDone: LocalJSXCommandOnDone, context: ToolUseContext): Promise { + const appState = context.getAppState(); + const permissionContext = appState.toolPermissionContext; + const tools = getTools(permissionContext); + return ; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIkFnZW50c01lbnUiLCJUb29sVXNlQ29udGV4dCIsImdldFRvb2xzIiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwiY2FsbCIsIm9uRG9uZSIsImNvbnRleHQiLCJQcm9taXNlIiwiUmVhY3ROb2RlIiwiYXBwU3RhdGUiLCJnZXRBcHBTdGF0ZSIsInBlcm1pc3Npb25Db250ZXh0IiwidG9vbFBlcm1pc3Npb25Db250ZXh0IiwidG9vbHMiXSwic291cmNlcyI6WyJhZ2VudHMudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgQWdlbnRzTWVudSB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvYWdlbnRzL0FnZW50c01lbnUuanMnXG5pbXBvcnQgdHlwZSB7IFRvb2xVc2VDb250ZXh0IH0gZnJvbSAnLi4vLi4vVG9vbC5qcydcbmltcG9ydCB7IGdldFRvb2xzIH0gZnJvbSAnLi4vLi4vdG9vbHMuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjYWxsKFxuICBvbkRvbmU6IExvY2FsSlNYQ29tbWFuZE9uRG9uZSxcbiAgY29udGV4dDogVG9vbFVzZUNvbnRleHQsXG4pOiBQcm9taXNlPFJlYWN0LlJlYWN0Tm9kZT4ge1xuICBjb25zdCBhcHBTdGF0ZSA9IGNvbnRleHQuZ2V0QXBwU3RhdGUoKVxuICBjb25zdCBwZXJtaXNzaW9uQ29udGV4dCA9IGFwcFN0YXRlLnRvb2xQZXJtaXNzaW9uQ29udGV4dFxuICBjb25zdCB0b29scyA9IGdldFRvb2xzKHBlcm1pc3Npb25Db250ZXh0KVxuXG4gIHJldHVybiA8QWdlbnRzTWVudSB0b29scz17dG9vbHN9IG9uRXhpdD17b25Eb25lfSAvPlxufVxuIl0sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUtBLEtBQUssTUFBTSxPQUFPO0FBQzlCLFNBQVNDLFVBQVUsUUFBUSx1Q0FBdUM7QUFDbEUsY0FBY0MsY0FBYyxRQUFRLGVBQWU7QUFDbkQsU0FBU0MsUUFBUSxRQUFRLGdCQUFnQjtBQUN6QyxjQUFjQyxxQkFBcUIsUUFBUSx3QkFBd0I7QUFFbkUsT0FBTyxlQUFlQyxJQUFJQSxDQUN4QkMsTUFBTSxFQUFFRixxQkFBcUIsRUFDN0JHLE9BQU8sRUFBRUwsY0FBYyxDQUN4QixFQUFFTSxPQUFPLENBQUNSLEtBQUssQ0FBQ1MsU0FBUyxDQUFDLENBQUM7RUFDMUIsTUFBTUMsUUFBUSxHQUFHSCxPQUFPLENBQUNJLFdBQVcsQ0FBQyxDQUFDO0VBQ3RDLE1BQU1DLGlCQUFpQixHQUFHRixRQUFRLENBQUNHLHFCQUFxQjtFQUN4RCxNQUFNQyxLQUFLLEdBQUdYLFFBQVEsQ0FBQ1MsaUJBQWlCLENBQUM7RUFFekMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQ0UsS0FBSyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUNSLE1BQU0sQ0FBQyxHQUFHO0FBQ3JEIiwiaWdub3JlTGlzdCI6W119 \ No newline at end of file diff --git a/src/commands/agents/index.ts b/src/commands/agents/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ac43d2ec95258f2a7badafd9497c09e513e808b3 --- /dev/null +++ b/src/commands/agents/index.ts @@ -0,0 +1,10 @@ +import type { Command } from '../../commands.js' + +const agents = { + type: 'local-jsx', + name: 'agents', + description: 'Manage agent configurations', + load: () => import('./agents.js'), +} satisfies Command + +export default agents diff --git a/src/commands/ant-trace/index.js b/src/commands/ant-trace/index.js new file mode 100644 index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e --- /dev/null +++ b/src/commands/ant-trace/index.js @@ -0,0 +1 @@ +export default { isEnabled: () => false, isHidden: true, name: 'stub' }; diff --git a/src/commands/autofix-pr/index.js b/src/commands/autofix-pr/index.js new file mode 100644 index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e --- /dev/null +++ b/src/commands/autofix-pr/index.js @@ -0,0 +1 @@ +export default { isEnabled: () => false, isHidden: true, name: 'stub' }; diff --git a/src/commands/branch/branch.ts b/src/commands/branch/branch.ts new file mode 100644 index 0000000000000000000000000000000000000000..4a7c2779a408830c4fb5aa67463205fa03c342f9 --- /dev/null +++ b/src/commands/branch/branch.ts @@ -0,0 +1,296 @@ +import { randomUUID, type UUID } from 'crypto' +import { mkdir, readFile, writeFile } from 'fs/promises' +import { getOriginalCwd, getSessionId } from '../../bootstrap/state.js' +import type { LocalJSXCommandContext } from '../../commands.js' +import { logEvent } from '../../services/analytics/index.js' +import type { LocalJSXCommandOnDone } from '../../types/command.js' +import type { + ContentReplacementEntry, + Entry, + LogOption, + SerializedMessage, + TranscriptMessage, +} from '../../types/logs.js' +import { parseJSONL } from '../../utils/json.js' +import { + getProjectDir, + getTranscriptPath, + getTranscriptPathForSession, + isTranscriptMessage, + saveCustomTitle, + searchSessionsByCustomTitle, +} from '../../utils/sessionStorage.js' +import { jsonStringify } from '../../utils/slowOperations.js' +import { escapeRegExp } from '../../utils/stringUtils.js' + +type TranscriptEntry = TranscriptMessage & { + forkedFrom?: { + sessionId: string + messageUuid: UUID + } +} + +/** + * Derive a single-line title base from the first user message. + * Collapses whitespace — multiline first messages (pasted stacks, code) + * otherwise flow into the saved title and break the resume hint. + */ +export function deriveFirstPrompt( + firstUserMessage: Extract | undefined, +): string { + const content = firstUserMessage?.message?.content + if (!content) return 'Branched conversation' + const raw = + typeof content === 'string' + ? content + : content.find( + (block): block is { type: 'text'; text: string } => + block.type === 'text', + )?.text + if (!raw) return 'Branched conversation' + return ( + raw.replace(/\s+/g, ' ').trim().slice(0, 100) || 'Branched conversation' + ) +} + +/** + * Creates a fork of the current conversation by copying from the transcript file. + * Preserves all original metadata (timestamps, gitBranch, etc.) while updating + * sessionId and adding forkedFrom traceability. + */ +async function createFork(customTitle?: string): Promise<{ + sessionId: UUID + title: string | undefined + forkPath: string + serializedMessages: SerializedMessage[] + contentReplacementRecords: ContentReplacementEntry['replacements'] +}> { + const forkSessionId = randomUUID() as UUID + const originalSessionId = getSessionId() + const projectDir = getProjectDir(getOriginalCwd()) + const forkSessionPath = getTranscriptPathForSession(forkSessionId) + const currentTranscriptPath = getTranscriptPath() + + // Ensure project directory exists + await mkdir(projectDir, { recursive: true, mode: 0o700 }) + + // Read current transcript file + let transcriptContent: Buffer + try { + transcriptContent = await readFile(currentTranscriptPath) + } catch { + throw new Error('No conversation to branch') + } + + if (transcriptContent.length === 0) { + throw new Error('No conversation to branch') + } + + // Parse all transcript entries (messages + metadata entries like content-replacement) + const entries = parseJSONL(transcriptContent) + + // Filter to only main conversation messages (exclude sidechains and non-message entries) + const mainConversationEntries = entries.filter( + (entry): entry is TranscriptMessage => + isTranscriptMessage(entry) && !entry.isSidechain, + ) + + // Content-replacement entries for the original session. These record which + // tool_result blocks were replaced with previews by the per-message budget. + // Without them in the fork JSONL, `claude -r {forkId}` reconstructs state + // with an empty replacements Map → previously-replaced results are classified + // as FROZEN and sent as full content (prompt cache miss + permanent overage). + // sessionId must be rewritten since loadTranscriptFile keys lookup by the + // session's messages' sessionId. + const contentReplacementRecords = entries + .filter( + (entry): entry is ContentReplacementEntry => + entry.type === 'content-replacement' && + entry.sessionId === originalSessionId, + ) + .flatMap(entry => entry.replacements) + + if (mainConversationEntries.length === 0) { + throw new Error('No messages to branch') + } + + // Build forked entries with new sessionId and preserved metadata + let parentUuid: UUID | null = null + const lines: string[] = [] + const serializedMessages: SerializedMessage[] = [] + + for (const entry of mainConversationEntries) { + // Create forked transcript entry preserving all original metadata + const forkedEntry: TranscriptEntry = { + ...entry, + sessionId: forkSessionId, + parentUuid, + isSidechain: false, + forkedFrom: { + sessionId: originalSessionId, + messageUuid: entry.uuid, + }, + } + + // Build serialized message for LogOption + const serialized: SerializedMessage = { + ...entry, + sessionId: forkSessionId, + } + + serializedMessages.push(serialized) + lines.push(jsonStringify(forkedEntry)) + if (entry.type !== 'progress') { + parentUuid = entry.uuid + } + } + + // Append content-replacement entry (if any) with the fork's sessionId. + // Written as a SINGLE entry (same shape as insertContentReplacement) so + // loadTranscriptFile's content-replacement branch picks it up. + if (contentReplacementRecords.length > 0) { + const forkedReplacementEntry: ContentReplacementEntry = { + type: 'content-replacement', + sessionId: forkSessionId, + replacements: contentReplacementRecords, + } + lines.push(jsonStringify(forkedReplacementEntry)) + } + + // Write the fork session file + await writeFile(forkSessionPath, lines.join('\n') + '\n', { + encoding: 'utf8', + mode: 0o600, + }) + + return { + sessionId: forkSessionId, + title: customTitle, + forkPath: forkSessionPath, + serializedMessages, + contentReplacementRecords, + } +} + +/** + * Generates a unique fork name by checking for collisions with existing session names. + * If "baseName (Branch)" already exists, tries "baseName (Branch 2)", "baseName (Branch 3)", etc. + */ +async function getUniqueForkName(baseName: string): Promise { + const candidateName = `${baseName} (Branch)` + + // Check if this exact name already exists + const existingWithExactName = await searchSessionsByCustomTitle( + candidateName, + { exact: true }, + ) + + if (existingWithExactName.length === 0) { + return candidateName + } + + // Name collision - find a unique numbered suffix + // Search for all sessions that start with the base pattern + const existingForks = await searchSessionsByCustomTitle(`${baseName} (Branch`) + + // Extract existing fork numbers to find the next available + const usedNumbers = new Set([1]) // Consider " (Branch)" as number 1 + const forkNumberPattern = new RegExp( + `^${escapeRegExp(baseName)} \\(Branch(?: (\\d+))?\\)$`, + ) + + for (const session of existingForks) { + const match = session.customTitle?.match(forkNumberPattern) + if (match) { + if (match[1]) { + usedNumbers.add(parseInt(match[1], 10)) + } else { + usedNumbers.add(1) // " (Branch)" without number is treated as 1 + } + } + } + + // Find the next available number + let nextNumber = 2 + while (usedNumbers.has(nextNumber)) { + nextNumber++ + } + + return `${baseName} (Branch ${nextNumber})` +} + +export async function call( + onDone: LocalJSXCommandOnDone, + context: LocalJSXCommandContext, + args: string, +): Promise { + const customTitle = args?.trim() || undefined + + const originalSessionId = getSessionId() + + try { + const { + sessionId, + title, + forkPath, + serializedMessages, + contentReplacementRecords, + } = await createFork(customTitle) + + // Build LogOption for resume + const now = new Date() + const firstPrompt = deriveFirstPrompt( + serializedMessages.find(m => m.type === 'user'), + ) + + // Save custom title - use provided title or firstPrompt as default + // This ensures /status and /resume show the same session name + // Always add " (Branch)" suffix to make it clear this is a branched session + // Handle collisions by adding a number suffix (e.g., " (Branch 2)", " (Branch 3)") + const baseName = title ?? firstPrompt + const effectiveTitle = await getUniqueForkName(baseName) + await saveCustomTitle(sessionId, effectiveTitle, forkPath) + + logEvent('tengu_conversation_forked', { + message_count: serializedMessages.length, + has_custom_title: !!title, + }) + + const forkLog: LogOption = { + date: now.toISOString().split('T')[0]!, + messages: serializedMessages, + fullPath: forkPath, + value: now.getTime(), + created: now, + modified: now, + firstPrompt, + messageCount: serializedMessages.length, + isSidechain: false, + sessionId, + customTitle: effectiveTitle, + contentReplacements: contentReplacementRecords, + } + + // Resume into the fork + const titleInfo = title ? ` "${title}"` : '' + const resumeHint = `\nTo resume the original: claude -r ${originalSessionId}` + const successMessage = `Branched conversation${titleInfo}. You are now in the branch.${resumeHint}` + + if (context.resume) { + await context.resume(sessionId, forkLog, 'fork') + onDone(successMessage, { display: 'system' }) + } else { + // Fallback if resume not available + onDone( + `Branched conversation${titleInfo}. Resume with: /resume ${sessionId}`, + ) + } + + return null + } catch (error) { + const message = + error instanceof Error ? error.message : 'Unknown error occurred' + onDone(`Failed to branch conversation: ${message}`) + return null + } +} diff --git a/src/commands/branch/index.ts b/src/commands/branch/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..731ff39fe572417053fceebe850fe118472b977f --- /dev/null +++ b/src/commands/branch/index.ts @@ -0,0 +1,14 @@ +import { feature } from 'bun:bundle' +import type { Command } from '../../commands.js' + +const branch = { + type: 'local-jsx', + name: 'branch', + // 'fork' alias only when /fork doesn't exist as its own command + aliases: feature('FORK_SUBAGENT') ? [] : ['fork'], + description: 'Create a branch of the current conversation at this point', + argumentHint: '[name]', + load: () => import('./branch.js'), +} satisfies Command + +export default branch diff --git a/src/commands/break-cache/index.js b/src/commands/break-cache/index.js new file mode 100644 index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e --- /dev/null +++ b/src/commands/break-cache/index.js @@ -0,0 +1 @@ +export default { isEnabled: () => false, isHidden: true, name: 'stub' }; diff --git a/src/commands/btw/index.ts b/src/commands/btw/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..d4888711d3b0779b7119e7d052b6b267998a6a39 --- /dev/null +++ b/src/commands/btw/index.ts @@ -0,0 +1,13 @@ +import type { Command } from '../../commands.js' + +const btw = { + type: 'local-jsx', + name: 'btw', + description: + 'Ask a quick side question without interrupting the main conversation', + immediate: true, + argumentHint: '', + load: () => import('./btw.js'), +} satisfies Command + +export default btw diff --git a/src/commands/chrome/index.ts b/src/commands/chrome/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..0e9af5ff3c871e06879d217ec920b011a192f1cd --- /dev/null +++ b/src/commands/chrome/index.ts @@ -0,0 +1,13 @@ +import { getIsNonInteractiveSession } from '../../bootstrap/state.js' +import type { Command } from '../../commands.js' + +const command: Command = { + name: 'chrome', + description: 'Claude in Chrome (Beta) settings', + availability: ['claude-ai'], + isEnabled: () => !getIsNonInteractiveSession(), + type: 'local-jsx', + load: () => import('./chrome.js'), +} + +export default command diff --git a/src/commands/clear/clear.ts b/src/commands/clear/clear.ts new file mode 100644 index 0000000000000000000000000000000000000000..24c6295a1d4e36283582eaef179eb00c3bdc3cd3 --- /dev/null +++ b/src/commands/clear/clear.ts @@ -0,0 +1,7 @@ +import type { LocalCommandCall } from '../../types/command.js' +import { clearConversation } from './conversation.js' + +export const call: LocalCommandCall = async (_, context) => { + await clearConversation(context) + return { type: 'text', value: '' } +} diff --git a/src/commands/clear/index.ts b/src/commands/clear/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..a426de6fa2048b3696ebfecf188e2431d7a39c10 --- /dev/null +++ b/src/commands/clear/index.ts @@ -0,0 +1,19 @@ +/** + * Clear command - minimal metadata only. + * Implementation is lazy-loaded from clear.ts to reduce startup time. + * Utility functions: + * - clearSessionCaches: import from './clear/caches.js' + * - clearConversation: import from './clear/conversation.js' + */ +import type { Command } from '../../commands.js' + +const clear = { + type: 'local', + name: 'clear', + description: 'Clear conversation history and free up context', + aliases: ['reset', 'new'], + supportsNonInteractive: false, // Should just create a new session + load: () => import('./clear.js'), +} satisfies Command + +export default clear diff --git a/src/commands/color/color.ts b/src/commands/color/color.ts new file mode 100644 index 0000000000000000000000000000000000000000..e4420b7f941e0b64e37347191b77632cff815641 --- /dev/null +++ b/src/commands/color/color.ts @@ -0,0 +1,93 @@ +import type { UUID } from 'crypto' +import { getSessionId } from '../../bootstrap/state.js' +import type { ToolUseContext } from '../../Tool.js' +import { + AGENT_COLORS, + type AgentColorName, +} from '../../tools/AgentTool/agentColorManager.js' +import type { + LocalJSXCommandContext, + LocalJSXCommandOnDone, +} from '../../types/command.js' +import { + getTranscriptPath, + saveAgentColor, +} from '../../utils/sessionStorage.js' +import { isTeammate } from '../../utils/teammate.js' + +const RESET_ALIASES = ['default', 'reset', 'none', 'gray', 'grey'] as const + +export async function call( + onDone: LocalJSXCommandOnDone, + context: ToolUseContext & LocalJSXCommandContext, + args: string, +): Promise { + // Teammates cannot set their own color + if (isTeammate()) { + onDone( + 'Cannot set color: This session is a swarm teammate. Teammate colors are assigned by the team leader.', + { display: 'system' }, + ) + return null + } + + if (!args || args.trim() === '') { + const colorList = AGENT_COLORS.join(', ') + onDone(`Please provide a color. Available colors: ${colorList}, default`, { + display: 'system', + }) + return null + } + + const colorArg = args.trim().toLowerCase() + + // Handle reset to default (gray) + if (RESET_ALIASES.includes(colorArg as (typeof RESET_ALIASES)[number])) { + const sessionId = getSessionId() as UUID + const fullPath = getTranscriptPath() + + // Use "default" sentinel (not empty string) so truthiness guards + // in sessionStorage.ts persist the reset across session restarts + await saveAgentColor(sessionId, 'default', fullPath) + + context.setAppState(prev => ({ + ...prev, + standaloneAgentContext: { + ...prev.standaloneAgentContext, + name: prev.standaloneAgentContext?.name ?? '', + color: undefined, + }, + })) + + onDone('Session color reset to default', { display: 'system' }) + return null + } + + if (!AGENT_COLORS.includes(colorArg as AgentColorName)) { + const colorList = AGENT_COLORS.join(', ') + onDone( + `Invalid color "${colorArg}". Available colors: ${colorList}, default`, + { display: 'system' }, + ) + return null + } + + const sessionId = getSessionId() as UUID + const fullPath = getTranscriptPath() + + // Save to transcript for persistence across sessions + await saveAgentColor(sessionId, colorArg, fullPath) + + // Update AppState for immediate effect + context.setAppState(prev => ({ + ...prev, + standaloneAgentContext: { + ...prev.standaloneAgentContext, + name: prev.standaloneAgentContext?.name ?? '', + color: colorArg as AgentColorName, + }, + })) + + onDone(`Session color set to: ${colorArg}`, { display: 'system' }) + return null +} diff --git a/src/commands/mcp/addCommand.ts b/src/commands/mcp/addCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..aeb3bc0537a6f266845e2b0d28c9971d8fd733c9 --- /dev/null +++ b/src/commands/mcp/addCommand.ts @@ -0,0 +1,280 @@ +/** + * MCP add CLI subcommand + * + * Extracted from main.tsx to enable direct testing. + */ +import { type Command, Option } from '@commander-js/extra-typings' +import { cliError, cliOk } from '../../cli/exit.js' +import { + type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + logEvent, +} from '../../services/analytics/index.js' +import { + readClientSecret, + saveMcpClientSecret, +} from '../../services/mcp/auth.js' +import { addMcpConfig } from '../../services/mcp/config.js' +import { + describeMcpConfigFilePath, + ensureConfigScope, + ensureTransport, + parseHeaders, +} from '../../services/mcp/utils.js' +import { + getXaaIdpSettings, + isXaaEnabled, +} from '../../services/mcp/xaaIdpLogin.js' +import { parseEnvVars } from '../../utils/envUtils.js' +import { jsonStringify } from '../../utils/slowOperations.js' + +/** + * Registers the `mcp add` subcommand on the given Commander command. + */ +export function registerMcpAddCommand(mcp: Command): void { + mcp + .command('add [args...]') + .description( + 'Add an MCP server to Claude Code.\n\n' + + 'Examples:\n' + + ' # Add HTTP server:\n' + + ' claude mcp add --transport http sentry https://mcp.sentry.dev/mcp\n\n' + + ' # Add HTTP server with headers:\n' + + ' claude mcp add --transport http corridor https://app.corridor.dev/api/mcp --header "Authorization: Bearer ..."\n\n' + + ' # Add stdio server with environment variables:\n' + + ' claude mcp add -e API_KEY=xxx my-server -- npx my-mcp-server\n\n' + + ' # Add stdio server with subprocess flags:\n' + + ' claude mcp add my-server -- my-command --some-flag arg1', + ) + .option( + '-s, --scope ', + 'Configuration scope (local, user, or project)', + 'local', + ) + .option( + '-t, --transport ', + 'Transport type (stdio, sse, http). Defaults to stdio if not specified.', + ) + .option( + '-e, --env ', + 'Set environment variables (e.g. -e KEY=value)', + ) + .option( + '-H, --header ', + 'Set WebSocket headers (e.g. -H "X-Api-Key: abc123" -H "X-Custom: value")', + ) + .option('--client-id ', 'OAuth client ID for HTTP/SSE servers') + .option( + '--client-secret', + 'Prompt for OAuth client secret (or set MCP_CLIENT_SECRET env var)', + ) + .option( + '--callback-port ', + 'Fixed port for OAuth callback (for servers requiring pre-registered redirect URIs)', + ) + .helpOption('-h, --help', 'Display help for command') + .addOption( + new Option( + '--xaa', + "Enable XAA (SEP-990) for this server. Requires 'claude mcp xaa setup' first. Also requires --client-id and --client-secret (for the MCP server's AS).", + ).hideHelp(!isXaaEnabled()), + ) + .action(async (name, commandOrUrl, args, options) => { + // Commander.js handles -- natively: it consumes -- and everything after becomes args + const actualCommand = commandOrUrl + const actualArgs = args + + // If no name is provided, error + if (!name) { + cliError( + 'Error: Server name is required.\n' + + 'Usage: claude mcp add [args...]', + ) + } else if (!actualCommand) { + cliError( + 'Error: Command is required when server name is provided.\n' + + 'Usage: claude mcp add [args...]', + ) + } + + try { + const scope = ensureConfigScope(options.scope) + const transport = ensureTransport(options.transport) + + // XAA fail-fast: validate at add-time, not auth-time. + if (options.xaa && !isXaaEnabled()) { + cliError( + 'Error: --xaa requires CLAUDE_CODE_ENABLE_XAA=1 in your environment', + ) + } + const xaa = Boolean(options.xaa) + if (xaa) { + const missing: string[] = [] + if (!options.clientId) missing.push('--client-id') + if (!options.clientSecret) missing.push('--client-secret') + if (!getXaaIdpSettings()) { + missing.push( + "'claude mcp xaa setup' (settings.xaaIdp not configured)", + ) + } + if (missing.length) { + cliError(`Error: --xaa requires: ${missing.join(', ')}`) + } + } + + // Check if transport was explicitly provided + const transportExplicit = options.transport !== undefined + + // Check if the command looks like a URL (likely incorrect usage) + const looksLikeUrl = + actualCommand.startsWith('http://') || + actualCommand.startsWith('https://') || + actualCommand.startsWith('localhost') || + actualCommand.endsWith('/sse') || + actualCommand.endsWith('/mcp') + + logEvent('tengu_mcp_add', { + type: transport as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + scope: + scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + source: + 'command' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + transport: + transport as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + transportExplicit: transportExplicit, + looksLikeUrl: looksLikeUrl, + }) + + if (transport === 'sse') { + if (!actualCommand) { + cliError('Error: URL is required for SSE transport.') + } + + const headers = options.header + ? parseHeaders(options.header) + : undefined + + const callbackPort = options.callbackPort + ? parseInt(options.callbackPort, 10) + : undefined + const oauth = + options.clientId || callbackPort || xaa + ? { + ...(options.clientId ? { clientId: options.clientId } : {}), + ...(callbackPort ? { callbackPort } : {}), + ...(xaa ? { xaa: true } : {}), + } + : undefined + + const clientSecret = + options.clientSecret && options.clientId + ? await readClientSecret() + : undefined + + const serverConfig = { + type: 'sse' as const, + url: actualCommand, + headers, + oauth, + } + await addMcpConfig(name, serverConfig, scope) + + if (clientSecret) { + saveMcpClientSecret(name, serverConfig, clientSecret) + } + + process.stdout.write( + `Added SSE MCP server ${name} with URL: ${actualCommand} to ${scope} config\n`, + ) + if (headers) { + process.stdout.write( + `Headers: ${jsonStringify(headers, null, 2)}\n`, + ) + } + } else if (transport === 'http') { + if (!actualCommand) { + cliError('Error: URL is required for HTTP transport.') + } + + const headers = options.header + ? parseHeaders(options.header) + : undefined + + const callbackPort = options.callbackPort + ? parseInt(options.callbackPort, 10) + : undefined + const oauth = + options.clientId || callbackPort || xaa + ? { + ...(options.clientId ? { clientId: options.clientId } : {}), + ...(callbackPort ? { callbackPort } : {}), + ...(xaa ? { xaa: true } : {}), + } + : undefined + + const clientSecret = + options.clientSecret && options.clientId + ? await readClientSecret() + : undefined + + const serverConfig = { + type: 'http' as const, + url: actualCommand, + headers, + oauth, + } + await addMcpConfig(name, serverConfig, scope) + + if (clientSecret) { + saveMcpClientSecret(name, serverConfig, clientSecret) + } + + process.stdout.write( + `Added HTTP MCP server ${name} with URL: ${actualCommand} to ${scope} config\n`, + ) + if (headers) { + process.stdout.write( + `Headers: ${jsonStringify(headers, null, 2)}\n`, + ) + } + } else { + if ( + options.clientId || + options.clientSecret || + options.callbackPort || + options.xaa + ) { + process.stderr.write( + `Warning: --client-id, --client-secret, --callback-port, and --xaa are only supported for HTTP/SSE transports and will be ignored for stdio.\n`, + ) + } + + // Warn if this looks like a URL but transport wasn't explicitly specified + if (!transportExplicit && looksLikeUrl) { + process.stderr.write( + `\nWarning: The command "${actualCommand}" looks like a URL, but is being interpreted as a stdio server as --transport was not specified.\n`, + ) + process.stderr.write( + `If this is an HTTP server, use: claude mcp add --transport http ${name} ${actualCommand}\n`, + ) + process.stderr.write( + `If this is an SSE server, use: claude mcp add --transport sse ${name} ${actualCommand}\n`, + ) + } + + const env = parseEnvVars(options.env) + await addMcpConfig( + name, + { type: 'stdio', command: actualCommand, args: actualArgs, env }, + scope, + ) + + process.stdout.write( + `Added stdio MCP server ${name} with command: ${actualCommand} ${actualArgs.join(' ')} to ${scope} config\n`, + ) + } + cliOk(`File modified: ${describeMcpConfigFilePath(scope)}`) + } catch (error) { + cliError((error as Error).message) + } + }) +} diff --git a/src/commands/mcp/index.ts b/src/commands/mcp/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..153ac5b2c6393d695393b45d8b060be443bfc69a --- /dev/null +++ b/src/commands/mcp/index.ts @@ -0,0 +1,12 @@ +import type { Command } from '../../commands.js' + +const mcp = { + type: 'local-jsx', + name: 'mcp', + description: 'Manage MCP servers', + immediate: true, + argumentHint: '[enable|disable [server-name]]', + load: () => import('./mcp.js'), +} satisfies Command + +export default mcp diff --git a/src/commands/mcp/mcp.tsx b/src/commands/mcp/mcp.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f5cf40daf997e8f9530e519ebfb64a8a190c03a6 --- /dev/null +++ b/src/commands/mcp/mcp.tsx @@ -0,0 +1,85 @@ +import { c as _c } from "react/compiler-runtime"; +import React, { useEffect, useRef } from 'react'; +import { MCPSettings } from '../../components/mcp/index.js'; +import { MCPReconnect } from '../../components/mcp/MCPReconnect.js'; +import { useMcpToggleEnabled } from '../../services/mcp/MCPConnectionManager.js'; +import { useAppState } from '../../state/AppState.js'; +import type { LocalJSXCommandOnDone } from '../../types/command.js'; +import { PluginSettings } from '../plugin/PluginSettings.js'; + +// TODO: This is a hack to get the context value from toggleMcpServer (useContext only works in a component) +// Ideally, all MCP state and functions would be in global state. +function MCPToggle(t0) { + const $ = _c(7); + const { + action, + target, + onComplete + } = t0; + const mcpClients = useAppState(_temp); + const toggleMcpServer = useMcpToggleEnabled(); + const didRun = useRef(false); + let t1; + let t2; + if ($[0] !== action || $[1] !== mcpClients || $[2] !== onComplete || $[3] !== target || $[4] !== toggleMcpServer) { + t1 = () => { + if (didRun.current) { + return; + } + didRun.current = true; + const isEnabling = action === "enable"; + const clients = mcpClients.filter(_temp2); + const toToggle = target === "all" ? clients.filter(c_0 => isEnabling ? c_0.type === "disabled" : c_0.type !== "disabled") : clients.filter(c_1 => c_1.name === target); + if (toToggle.length === 0) { + onComplete(target === "all" ? `All MCP servers are already ${isEnabling ? "enabled" : "disabled"}` : `MCP server "${target}" not found`); + return; + } + for (const s_0 of toToggle) { + toggleMcpServer(s_0.name); + } + onComplete(target === "all" ? `${isEnabling ? "Enabled" : "Disabled"} ${toToggle.length} MCP server(s)` : `MCP server "${target}" ${isEnabling ? "enabled" : "disabled"}`); + }; + t2 = [action, target, mcpClients, toggleMcpServer, onComplete]; + $[0] = action; + $[1] = mcpClients; + $[2] = onComplete; + $[3] = target; + $[4] = toggleMcpServer; + $[5] = t1; + $[6] = t2; + } else { + t1 = $[5]; + t2 = $[6]; + } + useEffect(t1, t2); + return null; +} +function _temp2(c) { + return c.name !== "ide"; +} +function _temp(s) { + return s.mcp.clients; +} +export async function call(onDone: LocalJSXCommandOnDone, _context: unknown, args?: string): Promise { + if (args) { + const parts = args.trim().split(/\s+/); + + // Allow /mcp no-redirect to bypass the redirect for testing + if (parts[0] === 'no-redirect') { + return ; + } + if (parts[0] === 'reconnect' && parts[1]) { + return ; + } + if (parts[0] === 'enable' || parts[0] === 'disable') { + return 1 ? parts.slice(1).join(' ') : 'all'} onComplete={onDone} />; + } + } + + // Redirect base /mcp command to /plugins installed tab for ant users + if ("external" === 'ant') { + return ; + } + return ; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsInVzZUVmZmVjdCIsInVzZVJlZiIsIk1DUFNldHRpbmdzIiwiTUNQUmVjb25uZWN0IiwidXNlTWNwVG9nZ2xlRW5hYmxlZCIsInVzZUFwcFN0YXRlIiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwiUGx1Z2luU2V0dGluZ3MiLCJNQ1BUb2dnbGUiLCJ0MCIsIiQiLCJfYyIsImFjdGlvbiIsInRhcmdldCIsIm9uQ29tcGxldGUiLCJtY3BDbGllbnRzIiwiX3RlbXAiLCJ0b2dnbGVNY3BTZXJ2ZXIiLCJkaWRSdW4iLCJ0MSIsInQyIiwiY3VycmVudCIsImlzRW5hYmxpbmciLCJjbGllbnRzIiwiZmlsdGVyIiwiX3RlbXAyIiwidG9Ub2dnbGUiLCJjXzAiLCJjIiwidHlwZSIsImNfMSIsIm5hbWUiLCJsZW5ndGgiLCJzXzAiLCJzIiwibWNwIiwiY2FsbCIsIm9uRG9uZSIsIl9jb250ZXh0IiwiYXJncyIsIlByb21pc2UiLCJSZWFjdE5vZGUiLCJwYXJ0cyIsInRyaW0iLCJzcGxpdCIsInNsaWNlIiwiam9pbiJdLCJzb3VyY2VzIjpbIm1jcC50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFJlYWN0LCB7IHVzZUVmZmVjdCwgdXNlUmVmIH0gZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBNQ1BTZXR0aW5ncyB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvbWNwL2luZGV4LmpzJ1xuaW1wb3J0IHsgTUNQUmVjb25uZWN0IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9tY3AvTUNQUmVjb25uZWN0LmpzJ1xuaW1wb3J0IHsgdXNlTWNwVG9nZ2xlRW5hYmxlZCB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL21jcC9NQ1BDb25uZWN0aW9uTWFuYWdlci5qcydcbmltcG9ydCB7IHVzZUFwcFN0YXRlIH0gZnJvbSAnLi4vLi4vc3RhdGUvQXBwU3RhdGUuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5pbXBvcnQgeyBQbHVnaW5TZXR0aW5ncyB9IGZyb20gJy4uL3BsdWdpbi9QbHVnaW5TZXR0aW5ncy5qcydcblxuLy8gVE9ETzogVGhpcyBpcyBhIGhhY2sgdG8gZ2V0IHRoZSBjb250ZXh0IHZhbHVlIGZyb20gdG9nZ2xlTWNwU2VydmVyICh1c2VDb250ZXh0IG9ubHkgd29ya3MgaW4gYSBjb21wb25lbnQpXG4vLyBJZGVhbGx5LCBhbGwgTUNQIHN0YXRlIGFuZCBmdW5jdGlvbnMgd291bGQgYmUgaW4gZ2xvYmFsIHN0YXRlLlxuZnVuY3Rpb24gTUNQVG9nZ2xlKHtcbiAgYWN0aW9uLFxuICB0YXJnZXQsXG4gIG9uQ29tcGxldGUsXG59OiB7XG4gIGFjdGlvbjogJ2VuYWJsZScgfCAnZGlzYWJsZSdcbiAgdGFyZ2V0OiBzdHJpbmdcbiAgb25Db21wbGV0ZTogKHJlc3VsdDogc3RyaW5nKSA9PiB2b2lkXG59KTogbnVsbCB7XG4gIGNvbnN0IG1jcENsaWVudHMgPSB1c2VBcHBTdGF0ZShzID0+IHMubWNwLmNsaWVudHMpXG4gIGNvbnN0IHRvZ2dsZU1jcFNlcnZlciA9IHVzZU1jcFRvZ2dsZUVuYWJsZWQoKVxuICBjb25zdCBkaWRSdW4gPSB1c2VSZWYoZmFsc2UpXG5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICBpZiAoZGlkUnVuLmN1cnJlbnQpIHJldHVyblxuICAgIGRpZFJ1bi5jdXJyZW50ID0gdHJ1ZVxuXG4gICAgY29uc3QgaXNFbmFibGluZyA9IGFjdGlvbiA9PT0gJ2VuYWJsZSdcbiAgICBjb25zdCBjbGllbnRzID0gbWNwQ2xpZW50cy5maWx0ZXIoYyA9PiBjLm5hbWUgIT09ICdpZGUnKVxuICAgIGNvbnN0IHRvVG9nZ2xlID1cbiAgICAgIHRhcmdldCA9PT0gJ2FsbCdcbiAgICAgICAgPyBjbGllbnRzLmZpbHRlcihjID0+XG4gICAgICAgICAgICBpc0VuYWJsaW5nID8gYy50eXBlID09PSAnZGlzYWJsZWQnIDogYy50eXBlICE9PSAnZGlzYWJsZWQnLFxuICAgICAgICAgIClcbiAgICAgICAgOiBjbGllbnRzLmZpbHRlcihjID0+IGMubmFtZSA9PT0gdGFyZ2V0KVxuXG4gICAgaWYgKHRvVG9nZ2xlLmxlbmd0aCA9PT0gMCkge1xuICAgICAgb25Db21wbGV0ZShcbiAgICAgICAgdGFyZ2V0ID09PSAnYWxsJ1xuICAgICAgICAgID8gYEFsbCBNQ1Agc2VydmVycyBhcmUgYWxyZWFkeSAke2lzRW5hYmxpbmcgPyAnZW5hYmxlZCcgOiAnZGlzYWJsZWQnfWBcbiAgICAgICAgICA6IGBNQ1Agc2VydmVyIFwiJHt0YXJnZXR9XCIgbm90IGZvdW5kYCxcbiAgICAgIClcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGZvciAoY29uc3QgcyBvZiB0b1RvZ2dsZSkge1xuICAgICAgdm9pZCB0b2dnbGVNY3BTZXJ2ZXIocy5uYW1lKVxuICAgIH1cblxuICAgIG9uQ29tcGxldGUoXG4gICAgICB0YXJnZXQgPT09ICdhbGwnXG4gICAgICAgID8gYCR7aXNFbmFibGluZyA/ICdFbmFibGVkJyA6ICdEaXNhYmxlZCd9ICR7dG9Ub2dnbGUubGVuZ3RofSBNQ1Agc2VydmVyKHMpYFxuICAgICAgICA6IGBNQ1Agc2VydmVyIFwiJHt0YXJnZXR9XCIgJHtpc0VuYWJsaW5nID8gJ2VuYWJsZWQnIDogJ2Rpc2FibGVkJ31gLFxuICAgIClcbiAgfSwgW2FjdGlvbiwgdGFyZ2V0LCBtY3BDbGllbnRzLCB0b2dnbGVNY3BTZXJ2ZXIsIG9uQ29tcGxldGVdKVxuXG4gIHJldHVybiBudWxsXG59XG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjYWxsKFxuICBvbkRvbmU6IExvY2FsSlNYQ29tbWFuZE9uRG9uZSxcbiAgX2NvbnRleHQ6IHVua25vd24sXG4gIGFyZ3M/OiBzdHJpbmcsXG4pOiBQcm9taXNlPFJlYWN0LlJlYWN0Tm9kZT4ge1xuICBpZiAoYXJncykge1xuICAgIGNvbnN0IHBhcnRzID0gYXJncy50cmltKCkuc3BsaXQoL1xccysvKVxuXG4gICAgLy8gQWxsb3cgL21jcCBuby1yZWRpcmVjdCB0byBieXBhc3MgdGhlIHJlZGlyZWN0IGZvciB0ZXN0aW5nXG4gICAgaWYgKHBhcnRzWzBdID09PSAnbm8tcmVkaXJlY3QnKSB7XG4gICAgICByZXR1cm4gPE1DUFNldHRpbmdzIG9uQ29tcGxldGU9e29uRG9uZX0gLz5cbiAgICB9XG5cbiAgICBpZiAocGFydHNbMF0gPT09ICdyZWNvbm5lY3QnICYmIHBhcnRzWzFdKSB7XG4gICAgICByZXR1cm4gKFxuICAgICAgICA8TUNQUmVjb25uZWN0XG4gICAgICAgICAgc2VydmVyTmFtZT17cGFydHMuc2xpY2UoMSkuam9pbignICcpfVxuICAgICAgICAgIG9uQ29tcGxldGU9e29uRG9uZX1cbiAgICAgICAgLz5cbiAgICAgIClcbiAgICB9XG5cbiAgICBpZiAocGFydHNbMF0gPT09ICdlbmFibGUnIHx8IHBhcnRzWzBdID09PSAnZGlzYWJsZScpIHtcbiAgICAgIHJldHVybiAoXG4gICAgICAgIDxNQ1BUb2dnbGVcbiAgICAgICAgICBhY3Rpb249e3BhcnRzWzBdfVxuICAgICAgICAgIHRhcmdldD17cGFydHMubGVuZ3RoID4gMSA/IHBhcnRzLnNsaWNlKDEpLmpvaW4oJyAnKSA6ICdhbGwnfVxuICAgICAgICAgIG9uQ29tcGxldGU9e29uRG9uZX1cbiAgICAgICAgLz5cbiAgICAgIClcbiAgICB9XG4gIH1cblxuICAvLyBSZWRpcmVjdCBiYXNlIC9tY3AgY29tbWFuZCB0byAvcGx1Z2lucyBpbnN0YWxsZWQgdGFiIGZvciBhbnQgdXNlcnNcbiAgaWYgKFwiZXh0ZXJuYWxcIiA9PT0gJ2FudCcpIHtcbiAgICByZXR1cm4gKFxuICAgICAgPFBsdWdpblNldHRpbmdzXG4gICAgICAgIG9uQ29tcGxldGU9e29uRG9uZX1cbiAgICAgICAgYXJncz1cIm1hbmFnZVwiXG4gICAgICAgIHNob3dNY3BSZWRpcmVjdE1lc3NhZ2VcbiAgICAgIC8+XG4gICAgKVxuICB9XG5cbiAgcmV0dXJuIDxNQ1BTZXR0aW5ncyBvbkNvbXBsZXRlPXtvbkRvbmV9IC8+XG59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 \ No newline at end of file diff --git a/src/commands/mcp/xaaIdpCommand.ts b/src/commands/mcp/xaaIdpCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..df8a8881bf6eecfdcbff5b373601da5239b5d720 --- /dev/null +++ b/src/commands/mcp/xaaIdpCommand.ts @@ -0,0 +1,266 @@ +/** + * `claude mcp xaa` — manage the XAA (SEP-990) IdP connection. + * + * The IdP connection is user-level: configure once, all XAA-enabled MCP + * servers reuse it. Lives in settings.xaaIdp (non-secret) + a keychain slot + * keyed by issuer (secret). Separate trust domain from per-server AS secrets. + */ +import type { Command } from '@commander-js/extra-typings' +import { cliError, cliOk } from '../../cli/exit.js' +import { + acquireIdpIdToken, + clearIdpClientSecret, + clearIdpIdToken, + getCachedIdpIdToken, + getIdpClientSecret, + getXaaIdpSettings, + issuerKey, + saveIdpClientSecret, + saveIdpIdTokenFromJwt, +} from '../../services/mcp/xaaIdpLogin.js' +import { errorMessage } from '../../utils/errors.js' +import { updateSettingsForSource } from '../../utils/settings/settings.js' + +export function registerMcpXaaIdpCommand(mcp: Command): void { + const xaaIdp = mcp + .command('xaa') + .description('Manage the XAA (SEP-990) IdP connection') + + xaaIdp + .command('setup') + .description( + 'Configure the IdP connection (one-time setup for all XAA-enabled servers)', + ) + .requiredOption('--issuer ', 'IdP issuer URL (OIDC discovery)') + .requiredOption('--client-id ', "Claude Code's client_id at the IdP") + .option( + '--client-secret', + 'Read IdP client secret from MCP_XAA_IDP_CLIENT_SECRET env var', + ) + .option( + '--callback-port ', + 'Fixed loopback callback port (only if IdP does not honor RFC 8252 port-any matching)', + ) + .action(options => { + // Validate everything BEFORE any writes. An exit(1) mid-write leaves + // settings configured but keychain missing — confusing state. + // updateSettingsForSource doesn't schema-check on write; a non-URL + // issuer lands on disk and then poisons the whole userSettings source + // on next launch (SettingsSchema .url() fails → parseSettingsFile + // returns { settings: null }, dropping everything, not just xaaIdp). + let issuerUrl: URL + try { + issuerUrl = new URL(options.issuer) + } catch { + return cliError( + `Error: --issuer must be a valid URL (got "${options.issuer}")`, + ) + } + // OIDC discovery + token exchange run against this host. Allow http:// + // only for loopback (conformance harness mock IdP); anything else leaks + // the client secret and authorization code over plaintext. + if ( + issuerUrl.protocol !== 'https:' && + !( + issuerUrl.protocol === 'http:' && + (issuerUrl.hostname === 'localhost' || + issuerUrl.hostname === '127.0.0.1' || + issuerUrl.hostname === '[::1]') + ) + ) { + return cliError( + `Error: --issuer must use https:// (got "${issuerUrl.protocol}//${issuerUrl.host}")`, + ) + } + const callbackPort = options.callbackPort + ? parseInt(options.callbackPort, 10) + : undefined + // callbackPort <= 0 fails Zod's .positive() on next launch — same + // settings-poisoning failure mode as the issuer check above. + if ( + callbackPort !== undefined && + (!Number.isInteger(callbackPort) || callbackPort <= 0) + ) { + return cliError('Error: --callback-port must be a positive integer') + } + const secret = options.clientSecret + ? process.env.MCP_XAA_IDP_CLIENT_SECRET + : undefined + if (options.clientSecret && !secret) { + return cliError( + 'Error: --client-secret requires MCP_XAA_IDP_CLIENT_SECRET env var', + ) + } + + // Read old config now (before settings overwrite) so we can clear stale + // keychain slots after a successful write. `clear` can't do this after + // the fact — it reads the *current* settings.xaaIdp, which by then is + // the new one. + const old = getXaaIdpSettings() + const oldIssuer = old?.issuer + const oldClientId = old?.clientId + + // callbackPort MUST be present (even as undefined) — mergeWith deep-merges + // and only deletes on explicit `undefined`, not on absent key. A conditional + // spread would leak a prior fixed port into a new IdP's config. + const { error } = updateSettingsForSource('userSettings', { + xaaIdp: { + issuer: options.issuer, + clientId: options.clientId, + callbackPort, + }, + }) + if (error) { + return cliError(`Error writing settings: ${error.message}`) + } + + // Clear stale keychain slots only after settings write succeeded — + // otherwise a write failure leaves settings pointing at oldIssuer with + // its secret already gone. Compare via issuerKey(): trailing-slash or + // host-case differences normalize to the same keychain slot. + if (oldIssuer) { + if (issuerKey(oldIssuer) !== issuerKey(options.issuer)) { + clearIdpIdToken(oldIssuer) + clearIdpClientSecret(oldIssuer) + } else if (oldClientId !== options.clientId) { + // Same issuer slot but different OAuth client registration — the + // cached id_token's aud claim and the stored secret are both for the + // old client. `xaa login` would send {new clientId, old secret} and + // fail with opaque `invalid_client`; downstream SEP-990 exchange + // would fail aud validation. Keep both when clientId is unchanged: + // re-setup without --client-secret means "tweak port, keep secret". + clearIdpIdToken(oldIssuer) + clearIdpClientSecret(oldIssuer) + } + } + + if (secret) { + const { success, warning } = saveIdpClientSecret(options.issuer, secret) + if (!success) { + return cliError( + `Error: settings written but keychain save failed${warning ? ` — ${warning}` : ''}. ` + + `Re-run with --client-secret once keychain is available.`, + ) + } + } + + cliOk(`XAA IdP connection configured for ${options.issuer}`) + }) + + xaaIdp + .command('login') + .description( + 'Cache an IdP id_token so XAA-enabled MCP servers authenticate ' + + 'silently. Default: run the OIDC browser login. With --id-token: ' + + 'write a pre-obtained JWT directly (used by conformance/e2e tests ' + + 'where the mock IdP does not serve /authorize).', + ) + .option( + '--force', + 'Ignore any cached id_token and re-login (useful after IdP-side revocation)', + ) + // TODO(paulc): read the JWT from stdin instead of argv to keep it out of + // shell history. Fine for conformance (docker exec uses argv directly, + // no shell parser), but a real user would want `echo $TOKEN | ... --stdin`. + .option( + '--id-token ', + 'Write this pre-obtained id_token directly to cache, skipping the OIDC browser login', + ) + .action(async options => { + const idp = getXaaIdpSettings() + if (!idp) { + return cliError( + "Error: no XAA IdP connection. Run 'claude mcp xaa setup' first.", + ) + } + + // Direct-inject path: skip cache check, skip OIDC. Writing IS the + // operation. Issuer comes from settings (single source of truth), not + // a separate flag — one less thing to desync. + if (options.idToken) { + const expiresAt = saveIdpIdTokenFromJwt(idp.issuer, options.idToken) + return cliOk( + `id_token cached for ${idp.issuer} (expires ${new Date(expiresAt).toISOString()})`, + ) + } + + if (options.force) { + clearIdpIdToken(idp.issuer) + } + + const wasCached = getCachedIdpIdToken(idp.issuer) !== undefined + if (wasCached) { + return cliOk( + `Already logged in to ${idp.issuer} (cached id_token still valid). Use --force to re-login.`, + ) + } + + process.stdout.write(`Opening browser for IdP login at ${idp.issuer}…\n`) + try { + await acquireIdpIdToken({ + idpIssuer: idp.issuer, + idpClientId: idp.clientId, + idpClientSecret: getIdpClientSecret(idp.issuer), + callbackPort: idp.callbackPort, + onAuthorizationUrl: url => { + process.stdout.write( + `If the browser did not open, visit:\n ${url}\n`, + ) + }, + }) + cliOk( + `Logged in. MCP servers with --xaa will now authenticate silently.`, + ) + } catch (e) { + cliError(`IdP login failed: ${errorMessage(e)}`) + } + }) + + xaaIdp + .command('show') + .description('Show the current IdP connection config') + .action(() => { + const idp = getXaaIdpSettings() + if (!idp) { + return cliOk('No XAA IdP connection configured.') + } + const hasSecret = getIdpClientSecret(idp.issuer) !== undefined + const hasIdToken = getCachedIdpIdToken(idp.issuer) !== undefined + process.stdout.write(`Issuer: ${idp.issuer}\n`) + process.stdout.write(`Client ID: ${idp.clientId}\n`) + if (idp.callbackPort !== undefined) { + process.stdout.write(`Callback port: ${idp.callbackPort}\n`) + } + process.stdout.write( + `Client secret: ${hasSecret ? '(stored in keychain)' : '(not set — PKCE-only)'}\n`, + ) + process.stdout.write( + `Logged in: ${hasIdToken ? 'yes (id_token cached)' : "no — run 'claude mcp xaa login'"}\n`, + ) + cliOk() + }) + + xaaIdp + .command('clear') + .description('Clear the IdP connection config and cached id_token') + .action(() => { + // Read issuer first so we can clear the right keychain slots. + const idp = getXaaIdpSettings() + // updateSettingsForSource uses mergeWith: set to undefined (not delete) + // to signal key removal. + const { error } = updateSettingsForSource('userSettings', { + xaaIdp: undefined, + }) + if (error) { + return cliError(`Error writing settings: ${error.message}`) + } + // Clear keychain only after settings write succeeded — otherwise a + // write failure leaves settings pointing at the IdP with its secrets + // already gone (same pattern as `setup`'s old-issuer cleanup). + if (idp) { + clearIdpIdToken(idp.issuer) + clearIdpClientSecret(idp.issuer) + } + cliOk('XAA IdP connection cleared') + }) +} diff --git a/src/commands/memory/index.ts b/src/commands/memory/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..31f254c95d63206431d3f1a72dbedcc2433c009c --- /dev/null +++ b/src/commands/memory/index.ts @@ -0,0 +1,10 @@ +import type { Command } from '../../commands.js' + +const memory: Command = { + type: 'local-jsx', + name: 'memory', + description: 'Edit Claude memory files', + load: () => import('./memory.js'), +} + +export default memory diff --git a/src/commands/memory/memory.tsx b/src/commands/memory/memory.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5ca53ae450e935bfdbf569a06c80321051fef42e --- /dev/null +++ b/src/commands/memory/memory.tsx @@ -0,0 +1,90 @@ +import { mkdir, writeFile } from 'fs/promises'; +import * as React from 'react'; +import type { CommandResultDisplay } from '../../commands.js'; +import { Dialog } from '../../components/design-system/Dialog.js'; +import { MemoryFileSelector } from '../../components/memory/MemoryFileSelector.js'; +import { getRelativeMemoryPath } from '../../components/memory/MemoryUpdateNotification.js'; +import { Box, Link, Text } from '../../ink.js'; +import type { LocalJSXCommandCall } from '../../types/command.js'; +import { clearMemoryFileCaches, getMemoryFiles } from '../../utils/claudemd.js'; +import { getClaudeConfigHomeDir } from '../../utils/envUtils.js'; +import { getErrnoCode } from '../../utils/errors.js'; +import { logError } from '../../utils/log.js'; +import { editFileInEditor } from '../../utils/promptEditor.js'; +function MemoryCommand({ + onDone +}: { + onDone: (result?: string, options?: { + display?: CommandResultDisplay; + }) => void; +}): React.ReactNode { + const handleSelectMemoryFile = async (memoryPath: string) => { + try { + // Create claude directory if it doesn't exist (idempotent with recursive) + if (memoryPath.includes(getClaudeConfigHomeDir())) { + await mkdir(getClaudeConfigHomeDir(), { + recursive: true + }); + } + + // Create file if it doesn't exist (wx flag fails if file exists, + // which we catch to preserve existing content) + try { + await writeFile(memoryPath, '', { + encoding: 'utf8', + flag: 'wx' + }); + } catch (e: unknown) { + if (getErrnoCode(e) !== 'EEXIST') { + throw e; + } + } + await editFileInEditor(memoryPath); + + // Determine which environment variable controls the editor + let editorSource = 'default'; + let editorValue = ''; + if (process.env.VISUAL) { + editorSource = '$VISUAL'; + editorValue = process.env.VISUAL; + } else if (process.env.EDITOR) { + editorSource = '$EDITOR'; + editorValue = process.env.EDITOR; + } + const editorInfo = editorSource !== 'default' ? `Using ${editorSource}="${editorValue}".` : ''; + const editorHint = editorInfo ? `> ${editorInfo} To change editor, set $EDITOR or $VISUAL environment variable.` : `> To use a different editor, set the $EDITOR or $VISUAL environment variable.`; + onDone(`Opened memory file at ${getRelativeMemoryPath(memoryPath)}\n\n${editorHint}`, { + display: 'system' + }); + } catch (error) { + logError(error); + onDone(`Error opening memory file: ${error}`); + } + }; + const handleCancel = () => { + onDone('Cancelled memory editing', { + display: 'system' + }); + }; + return + + + + + + + + Learn more: + + + + ; +} +export const call: LocalJSXCommandCall = async onDone => { + // Clear + prime before rendering — Suspense handles the unprimed case, + // but awaiting here avoids a fallback flash on initial open. + clearMemoryFileCaches(); + await getMemoryFiles(); + return ; +}; +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJta2RpciIsIndyaXRlRmlsZSIsIlJlYWN0IiwiQ29tbWFuZFJlc3VsdERpc3BsYXkiLCJEaWFsb2ciLCJNZW1vcnlGaWxlU2VsZWN0b3IiLCJnZXRSZWxhdGl2ZU1lbW9yeVBhdGgiLCJCb3giLCJMaW5rIiwiVGV4dCIsIkxvY2FsSlNYQ29tbWFuZENhbGwiLCJjbGVhck1lbW9yeUZpbGVDYWNoZXMiLCJnZXRNZW1vcnlGaWxlcyIsImdldENsYXVkZUNvbmZpZ0hvbWVEaXIiLCJnZXRFcnJub0NvZGUiLCJsb2dFcnJvciIsImVkaXRGaWxlSW5FZGl0b3IiLCJNZW1vcnlDb21tYW5kIiwib25Eb25lIiwicmVzdWx0Iiwib3B0aW9ucyIsImRpc3BsYXkiLCJSZWFjdE5vZGUiLCJoYW5kbGVTZWxlY3RNZW1vcnlGaWxlIiwibWVtb3J5UGF0aCIsImluY2x1ZGVzIiwicmVjdXJzaXZlIiwiZW5jb2RpbmciLCJmbGFnIiwiZSIsImVkaXRvclNvdXJjZSIsImVkaXRvclZhbHVlIiwicHJvY2VzcyIsImVudiIsIlZJU1VBTCIsIkVESVRPUiIsImVkaXRvckluZm8iLCJlZGl0b3JIaW50IiwiZXJyb3IiLCJoYW5kbGVDYW5jZWwiLCJjYWxsIl0sInNvdXJjZXMiOlsibWVtb3J5LnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBta2Rpciwgd3JpdGVGaWxlIH0gZnJvbSAnZnMvcHJvbWlzZXMnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB0eXBlIHsgQ29tbWFuZFJlc3VsdERpc3BsYXkgfSBmcm9tICcuLi8uLi9jb21tYW5kcy5qcydcbmltcG9ydCB7IERpYWxvZyB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvZGVzaWduLXN5c3RlbS9EaWFsb2cuanMnXG5pbXBvcnQgeyBNZW1vcnlGaWxlU2VsZWN0b3IgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL21lbW9yeS9NZW1vcnlGaWxlU2VsZWN0b3IuanMnXG5pbXBvcnQgeyBnZXRSZWxhdGl2ZU1lbW9yeVBhdGggfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL21lbW9yeS9NZW1vcnlVcGRhdGVOb3RpZmljYXRpb24uanMnXG5pbXBvcnQgeyBCb3gsIExpbmssIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZENhbGwgfSBmcm9tICcuLi8uLi90eXBlcy9jb21tYW5kLmpzJ1xuaW1wb3J0IHsgY2xlYXJNZW1vcnlGaWxlQ2FjaGVzLCBnZXRNZW1vcnlGaWxlcyB9IGZyb20gJy4uLy4uL3V0aWxzL2NsYXVkZW1kLmpzJ1xuaW1wb3J0IHsgZ2V0Q2xhdWRlQ29uZmlnSG9tZURpciB9IGZyb20gJy4uLy4uL3V0aWxzL2VudlV0aWxzLmpzJ1xuaW1wb3J0IHsgZ2V0RXJybm9Db2RlIH0gZnJvbSAnLi4vLi4vdXRpbHMvZXJyb3JzLmpzJ1xuaW1wb3J0IHsgbG9nRXJyb3IgfSBmcm9tICcuLi8uLi91dGlscy9sb2cuanMnXG5pbXBvcnQgeyBlZGl0RmlsZUluRWRpdG9yIH0gZnJvbSAnLi4vLi4vdXRpbHMvcHJvbXB0RWRpdG9yLmpzJ1xuXG5mdW5jdGlvbiBNZW1vcnlDb21tYW5kKHtcbiAgb25Eb25lLFxufToge1xuICBvbkRvbmU6IChcbiAgICByZXN1bHQ/OiBzdHJpbmcsXG4gICAgb3B0aW9ucz86IHsgZGlzcGxheT86IENvbW1hbmRSZXN1bHREaXNwbGF5IH0sXG4gICkgPT4gdm9pZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IGhhbmRsZVNlbGVjdE1lbW9yeUZpbGUgPSBhc3luYyAobWVtb3J5UGF0aDogc3RyaW5nKSA9PiB7XG4gICAgdHJ5IHtcbiAgICAgIC8vIENyZWF0ZSBjbGF1ZGUgZGlyZWN0b3J5IGlmIGl0IGRvZXNuJ3QgZXhpc3QgKGlkZW1wb3RlbnQgd2l0aCByZWN1cnNpdmUpXG4gICAgICBpZiAobWVtb3J5UGF0aC5pbmNsdWRlcyhnZXRDbGF1ZGVDb25maWdIb21lRGlyKCkpKSB7XG4gICAgICAgIGF3YWl0IG1rZGlyKGdldENsYXVkZUNvbmZpZ0hvbWVEaXIoKSwgeyByZWN1cnNpdmU6IHRydWUgfSlcbiAgICAgIH1cblxuICAgICAgLy8gQ3JlYXRlIGZpbGUgaWYgaXQgZG9lc24ndCBleGlzdCAod3ggZmxhZyBmYWlscyBpZiBmaWxlIGV4aXN0cyxcbiAgICAgIC8vIHdoaWNoIHdlIGNhdGNoIHRvIHByZXNlcnZlIGV4aXN0aW5nIGNvbnRlbnQpXG4gICAgICB0cnkge1xuICAgICAgICBhd2FpdCB3cml0ZUZpbGUobWVtb3J5UGF0aCwgJycsIHsgZW5jb2Rpbmc6ICd1dGY4JywgZmxhZzogJ3d4JyB9KVxuICAgICAgfSBjYXRjaCAoZTogdW5rbm93bikge1xuICAgICAgICBpZiAoZ2V0RXJybm9Db2RlKGUpICE9PSAnRUVYSVNUJykge1xuICAgICAgICAgIHRocm93IGVcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBhd2FpdCBlZGl0RmlsZUluRWRpdG9yKG1lbW9yeVBhdGgpXG5cbiAgICAgIC8vIERldGVybWluZSB3aGljaCBlbnZpcm9ubWVudCB2YXJpYWJsZSBjb250cm9scyB0aGUgZWRpdG9yXG4gICAgICBsZXQgZWRpdG9yU291cmNlID0gJ2RlZmF1bHQnXG4gICAgICBsZXQgZWRpdG9yVmFsdWUgPSAnJ1xuICAgICAgaWYgKHByb2Nlc3MuZW52LlZJU1VBTCkge1xuICAgICAgICBlZGl0b3JTb3VyY2UgPSAnJFZJU1VBTCdcbiAgICAgICAgZWRpdG9yVmFsdWUgPSBwcm9jZXNzLmVudi5WSVNVQUxcbiAgICAgIH0gZWxzZSBpZiAocHJvY2Vzcy5lbnYuRURJVE9SKSB7XG4gICAgICAgIGVkaXRvclNvdXJjZSA9ICckRURJVE9SJ1xuICAgICAgICBlZGl0b3JWYWx1ZSA9IHByb2Nlc3MuZW52LkVESVRPUlxuICAgICAgfVxuXG4gICAgICBjb25zdCBlZGl0b3JJbmZvID1cbiAgICAgICAgZWRpdG9yU291cmNlICE9PSAnZGVmYXVsdCdcbiAgICAgICAgICA/IGBVc2luZyAke2VkaXRvclNvdXJjZX09XCIke2VkaXRvclZhbHVlfVwiLmBcbiAgICAgICAgICA6ICcnXG5cbiAgICAgIGNvbnN0IGVkaXRvckhpbnQgPSBlZGl0b3JJbmZvXG4gICAgICAgID8gYD4gJHtlZGl0b3JJbmZvfSBUbyBjaGFuZ2UgZWRpdG9yLCBzZXQgJEVESVRPUiBvciAkVklTVUFMIGVudmlyb25tZW50IHZhcmlhYmxlLmBcbiAgICAgICAgOiBgPiBUbyB1c2UgYSBkaWZmZXJlbnQgZWRpdG9yLCBzZXQgdGhlICRFRElUT1Igb3IgJFZJU1VBTCBlbnZpcm9ubWVudCB2YXJpYWJsZS5gXG5cbiAgICAgIG9uRG9uZShcbiAgICAgICAgYE9wZW5lZCBtZW1vcnkgZmlsZSBhdCAke2dldFJlbGF0aXZlTWVtb3J5UGF0aChtZW1vcnlQYXRoKX1cXG5cXG4ke2VkaXRvckhpbnR9YCxcbiAgICAgICAgeyBkaXNwbGF5OiAnc3lzdGVtJyB9LFxuICAgICAgKVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dFcnJvcihlcnJvcilcbiAgICAgIG9uRG9uZShgRXJyb3Igb3BlbmluZyBtZW1vcnkgZmlsZTogJHtlcnJvcn1gKVxuICAgIH1cbiAgfVxuXG4gIGNvbnN0IGhhbmRsZUNhbmNlbCA9ICgpID0+IHtcbiAgICBvbkRvbmUoJ0NhbmNlbGxlZCBtZW1vcnkgZWRpdGluZycsIHsgZGlzcGxheTogJ3N5c3RlbScgfSlcbiAgfVxuXG4gIHJldHVybiAoXG4gICAgPERpYWxvZyB0aXRsZT1cIk1lbW9yeVwiIG9uQ2FuY2VsPXtoYW5kbGVDYW5jZWx9IGNvbG9yPVwicmVtZW1iZXJcIj5cbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8UmVhY3QuU3VzcGVuc2UgZmFsbGJhY2s9e251bGx9PlxuICAgICAgICAgIDxNZW1vcnlGaWxlU2VsZWN0b3JcbiAgICAgICAgICAgIG9uU2VsZWN0PXtoYW5kbGVTZWxlY3RNZW1vcnlGaWxlfVxuICAgICAgICAgICAgb25DYW5jZWw9e2hhbmRsZUNhbmNlbH1cbiAgICAgICAgICAvPlxuICAgICAgICA8L1JlYWN0LlN1c3BlbnNlPlxuXG4gICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICAgIExlYXJuIG1vcmU6IDxMaW5rIHVybD1cImh0dHBzOi8vY29kZS5jbGF1ZGUuY29tL2RvY3MvZW4vbWVtb3J5XCIgLz5cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgPC9Cb3g+XG4gICAgPC9EaWFsb2c+XG4gIClcbn1cblxuZXhwb3J0IGNvbnN0IGNhbGw6IExvY2FsSlNYQ29tbWFuZENhbGwgPSBhc3luYyBvbkRvbmUgPT4ge1xuICAvLyBDbGVhciArIHByaW1lIGJlZm9yZSByZW5kZXJpbmcg4oCUIFN1c3BlbnNlIGhhbmRsZXMgdGhlIHVucHJpbWVkIGNhc2UsXG4gIC8vIGJ1dCBhd2FpdGluZyBoZXJlIGF2b2lkcyBhIGZhbGxiYWNrIGZsYXNoIG9uIGluaXRpYWwgb3Blbi5cbiAgY2xlYXJNZW1vcnlGaWxlQ2FjaGVzKClcbiAgYXdhaXQgZ2V0TWVtb3J5RmlsZXMoKVxuICByZXR1cm4gPE1lbW9yeUNvbW1hbmQgb25Eb25lPXtvbkRvbmV9IC8+XG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLFNBQVNBLEtBQUssRUFBRUMsU0FBUyxRQUFRLGFBQWE7QUFDOUMsT0FBTyxLQUFLQyxLQUFLLE1BQU0sT0FBTztBQUM5QixjQUFjQyxvQkFBb0IsUUFBUSxtQkFBbUI7QUFDN0QsU0FBU0MsTUFBTSxRQUFRLDBDQUEwQztBQUNqRSxTQUFTQyxrQkFBa0IsUUFBUSwrQ0FBK0M7QUFDbEYsU0FBU0MscUJBQXFCLFFBQVEscURBQXFEO0FBQzNGLFNBQVNDLEdBQUcsRUFBRUMsSUFBSSxFQUFFQyxJQUFJLFFBQVEsY0FBYztBQUM5QyxjQUFjQyxtQkFBbUIsUUFBUSx3QkFBd0I7QUFDakUsU0FBU0MscUJBQXFCLEVBQUVDLGNBQWMsUUFBUSx5QkFBeUI7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 \ No newline at end of file diff --git a/src/commands/mobile/index.ts b/src/commands/mobile/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..2657aa8429532c00252cbf5f498e9117a0961f1e --- /dev/null +++ b/src/commands/mobile/index.ts @@ -0,0 +1,11 @@ +import type { Command } from '../../commands.js' + +const mobile = { + type: 'local-jsx', + name: 'mobile', + aliases: ['ios', 'android'], + description: 'Show QR code to download the Claude mobile app', + load: () => import('./mobile.js'), +} satisfies Command + +export default mobile diff --git a/src/commands/mobile/mobile.tsx b/src/commands/mobile/mobile.tsx new file mode 100644 index 0000000000000000000000000000000000000000..40968c19ec83dad03a686c613dce3cfd517d7099 --- /dev/null +++ b/src/commands/mobile/mobile.tsx @@ -0,0 +1,274 @@ +import { c as _c } from "react/compiler-runtime"; +import { toString as qrToString } from 'qrcode'; +import * as React from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { Pane } from '../../components/design-system/Pane.js'; +import type { KeyboardEvent } from '../../ink/events/keyboard-event.js'; +import { Box, Text } from '../../ink.js'; +import { useKeybinding } from '../../keybindings/useKeybinding.js'; +import type { LocalJSXCommandOnDone } from '../../types/command.js'; +type Platform = 'ios' | 'android'; +type Props = { + onDone: () => void; +}; +const PLATFORMS: Record = { + ios: { + url: 'https://apps.apple.com/app/claude-by-anthropic/id6473753684' + }, + android: { + url: 'https://play.google.com/store/apps/details?id=com.anthropic.claude' + } +}; +function MobileQRCode(t0) { + const $ = _c(52); + const { + onDone + } = t0; + const [platform, setPlatform] = useState("ios"); + let t1; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t1 = { + ios: "", + android: "" + }; + $[0] = t1; + } else { + t1 = $[0]; + } + const [qrCodes, setQrCodes] = useState(t1); + const { + url + } = PLATFORMS[platform]; + const qrCode = qrCodes[platform]; + let t2; + let t3; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t2 = () => { + const generateQRCodes = async function generateQRCodes() { + const [ios, android] = await Promise.all([qrToString(PLATFORMS.ios.url, { + type: "utf8", + errorCorrectionLevel: "L" + }), qrToString(PLATFORMS.android.url, { + type: "utf8", + errorCorrectionLevel: "L" + })]); + setQrCodes({ + ios, + android + }); + }; + generateQRCodes().catch(_temp); + }; + t3 = []; + $[1] = t2; + $[2] = t3; + } else { + t2 = $[1]; + t3 = $[2]; + } + useEffect(t2, t3); + let t4; + if ($[3] !== onDone) { + t4 = () => { + onDone(); + }; + $[3] = onDone; + $[4] = t4; + } else { + t4 = $[4]; + } + const handleClose = t4; + let t5; + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { + t5 = { + context: "Confirmation" + }; + $[5] = t5; + } else { + t5 = $[5]; + } + useKeybinding("confirm:no", handleClose, t5); + let t6; + if ($[6] !== onDone) { + t6 = function handleKeyDown(e) { + if (e.key === "q" || e.ctrl && e.key === "c") { + e.preventDefault(); + onDone(); + return; + } + if (e.key === "tab" || e.key === "left" || e.key === "right") { + e.preventDefault(); + setPlatform(_temp2); + } + }; + $[6] = onDone; + $[7] = t6; + } else { + t6 = $[7]; + } + const handleKeyDown = t6; + let T0; + let T1; + let t10; + let t11; + let t12; + let t13; + let t7; + let t8; + let t9; + if ($[8] !== handleKeyDown || $[9] !== qrCode) { + const lines = qrCode.split("\n").filter(_temp3); + T1 = Pane; + T0 = Box; + t7 = "column"; + t8 = 0; + t9 = true; + t10 = handleKeyDown; + if ($[19] === Symbol.for("react.memo_cache_sentinel")) { + t11 = ; + t12 = ; + $[19] = t11; + $[20] = t12; + } else { + t11 = $[19]; + t12 = $[20]; + } + t13 = lines.map(_temp4); + $[8] = handleKeyDown; + $[9] = qrCode; + $[10] = T0; + $[11] = T1; + $[12] = t10; + $[13] = t11; + $[14] = t12; + $[15] = t13; + $[16] = t7; + $[17] = t8; + $[18] = t9; + } else { + T0 = $[10]; + T1 = $[11]; + t10 = $[12]; + t11 = $[13]; + t12 = $[14]; + t13 = $[15]; + t7 = $[16]; + t8 = $[17]; + t9 = $[18]; + } + let t14; + let t15; + if ($[21] === Symbol.for("react.memo_cache_sentinel")) { + t14 = ; + t15 = ; + $[21] = t14; + $[22] = t15; + } else { + t14 = $[21]; + t15 = $[22]; + } + const t16 = platform === "ios"; + const t17 = platform === "ios"; + let t18; + if ($[23] !== t16 || $[24] !== t17) { + t18 = iOS; + $[23] = t16; + $[24] = t17; + $[25] = t18; + } else { + t18 = $[25]; + } + let t19; + if ($[26] === Symbol.for("react.memo_cache_sentinel")) { + t19 = {" / "}; + $[26] = t19; + } else { + t19 = $[26]; + } + const t20 = platform === "android"; + const t21 = platform === "android"; + let t22; + if ($[27] !== t20 || $[28] !== t21) { + t22 = Android; + $[27] = t20; + $[28] = t21; + $[29] = t22; + } else { + t22 = $[29]; + } + let t23; + if ($[30] !== t18 || $[31] !== t22) { + t23 = {t18}{t19}{t22}; + $[30] = t18; + $[31] = t22; + $[32] = t23; + } else { + t23 = $[32]; + } + let t24; + if ($[33] === Symbol.for("react.memo_cache_sentinel")) { + t24 = (tab to switch, esc to close); + $[33] = t24; + } else { + t24 = $[33]; + } + let t25; + if ($[34] !== t23) { + t25 = {t23}{t24}; + $[34] = t23; + $[35] = t25; + } else { + t25 = $[35]; + } + let t26; + if ($[36] !== url) { + t26 = {url}; + $[36] = url; + $[37] = t26; + } else { + t26 = $[37]; + } + let t27; + if ($[38] !== T0 || $[39] !== t10 || $[40] !== t11 || $[41] !== t12 || $[42] !== t13 || $[43] !== t25 || $[44] !== t26 || $[45] !== t7 || $[46] !== t8 || $[47] !== t9) { + t27 = {t11}{t12}{t13}{t14}{t15}{t25}{t26}; + $[38] = T0; + $[39] = t10; + $[40] = t11; + $[41] = t12; + $[42] = t13; + $[43] = t25; + $[44] = t26; + $[45] = t7; + $[46] = t8; + $[47] = t9; + $[48] = t27; + } else { + t27 = $[48]; + } + let t28; + if ($[49] !== T1 || $[50] !== t27) { + t28 = {t27}; + $[49] = T1; + $[50] = t27; + $[51] = t28; + } else { + t28 = $[51]; + } + return t28; +} +function _temp4(line_0, i) { + return {line_0}; +} +function _temp3(line) { + return line.length > 0; +} +function _temp2(prev) { + return prev === "ios" ? "android" : "ios"; +} +function _temp() {} +export async function call(onDone: LocalJSXCommandOnDone): Promise { + return ; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJ0b1N0cmluZyIsInFyVG9TdHJpbmciLCJSZWFjdCIsInVzZUNhbGxiYWNrIiwidXNlRWZmZWN0IiwidXNlU3RhdGUiLCJQYW5lIiwiS2V5Ym9hcmRFdmVudCIsIkJveCIsIlRleHQiLCJ1c2VLZXliaW5kaW5nIiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwiUGxhdGZvcm0iLCJQcm9wcyIsIm9uRG9uZSIsIlBMQVRGT1JNUyIsIlJlY29yZCIsInVybCIsImlvcyIsImFuZHJvaWQiLCJNb2JpbGVRUkNvZGUiLCJ0MCIsIiQiLCJfYyIsInBsYXRmb3JtIiwic2V0UGxhdGZvcm0iLCJ0MSIsIlN5bWJvbCIsImZvciIsInFyQ29kZXMiLCJzZXRRckNvZGVzIiwicXJDb2RlIiwidDIiLCJ0MyIsImdlbmVyYXRlUVJDb2RlcyIsIlByb21pc2UiLCJhbGwiLCJ0eXBlIiwiZXJyb3JDb3JyZWN0aW9uTGV2ZWwiLCJjYXRjaCIsIl90ZW1wIiwidDQiLCJoYW5kbGVDbG9zZSIsInQ1IiwiY29udGV4dCIsInQ2IiwiaGFuZGxlS2V5RG93biIsImUiLCJrZXkiLCJjdHJsIiwicHJldmVudERlZmF1bHQiLCJfdGVtcDIiLCJUMCIsIlQxIiwidDEwIiwidDExIiwidDEyIiwidDEzIiwidDciLCJ0OCIsInQ5IiwibGluZXMiLCJzcGxpdCIsImZpbHRlciIsIl90ZW1wMyIsIm1hcCIsIl90ZW1wNCIsInQxNCIsInQxNSIsInQxNiIsInQxNyIsInQxOCIsInQxOSIsInQyMCIsInQyMSIsInQyMiIsInQyMyIsInQyNCIsInQyNSIsInQyNiIsInQyNyIsInQyOCIsImxpbmVfMCIsImkiLCJsaW5lIiwibGVuZ3RoIiwicHJldiIsImNhbGwiLCJSZWFjdE5vZGUiXSwic291cmNlcyI6WyJtb2JpbGUudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IHRvU3RyaW5nIGFzIHFyVG9TdHJpbmcgfSBmcm9tICdxcmNvZGUnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IHVzZUNhbGxiYWNrLCB1c2VFZmZlY3QsIHVzZVN0YXRlIH0gZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBQYW5lIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL1BhbmUuanMnXG5pbXBvcnQgdHlwZSB7IEtleWJvYXJkRXZlbnQgfSBmcm9tICcuLi8uLi9pbmsvZXZlbnRzL2tleWJvYXJkLWV2ZW50LmpzJ1xuaW1wb3J0IHsgQm94LCBUZXh0IH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHsgdXNlS2V5YmluZGluZyB9IGZyb20gJy4uLy4uL2tleWJpbmRpbmdzL3VzZUtleWJpbmRpbmcuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5cbnR5cGUgUGxhdGZvcm0gPSAnaW9zJyB8ICdhbmRyb2lkJ1xuXG50eXBlIFByb3BzID0ge1xuICBvbkRvbmU6ICgpID0+IHZvaWRcbn1cblxuY29uc3QgUExBVEZPUk1TOiBSZWNvcmQ8UGxhdGZvcm0sIHsgdXJsOiBzdHJpbmcgfT4gPSB7XG4gIGlvczoge1xuICAgIHVybDogJ2h0dHBzOi8vYXBwcy5hcHBsZS5jb20vYXBwL2NsYXVkZS1ieS1hbnRocm9waWMvaWQ2NDczNzUzNjg0JyxcbiAgfSxcbiAgYW5kcm9pZDoge1xuICAgIHVybDogJ2h0dHBzOi8vcGxheS5nb29nbGUuY29tL3N0b3JlL2FwcHMvZGV0YWlscz9pZD1jb20uYW50aHJvcGljLmNsYXVkZScsXG4gIH0sXG59XG5cbmZ1bmN0aW9uIE1vYmlsZVFSQ29kZSh7IG9uRG9uZSB9OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IFtwbGF0Zm9ybSwgc2V0UGxhdGZvcm1dID0gdXNlU3RhdGU8UGxhdGZvcm0+KCdpb3MnKVxuICBjb25zdCBbcXJDb2Rlcywgc2V0UXJDb2Rlc10gPSB1c2VTdGF0ZTxSZWNvcmQ8UGxhdGZvcm0sIHN0cmluZz4+KHtcbiAgICBpb3M6ICcnLFxuICAgIGFuZHJvaWQ6ICcnLFxuICB9KVxuXG4gIGNvbnN0IHsgdXJsIH0gPSBQTEFURk9STVNbcGxhdGZvcm1dXG4gIGNvbnN0IHFyQ29kZSA9IHFyQ29kZXNbcGxhdGZvcm1dXG5cbiAgLy8gR2VuZXJhdGUgYm90aCBRUiBjb2RlcyB1cGZyb250IHRvIGF2b2lkIGZsaWNrZXIgd2hlbiBzd2l0Y2hpbmdcbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICBhc3luYyBmdW5jdGlvbiBnZW5lcmF0ZVFSQ29kZXMoKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICBjb25zdCBbaW9zLCBhbmRyb2lkXSA9IGF3YWl0IFByb21pc2UuYWxsKFtcbiAgICAgICAgcXJUb1N0cmluZyhQTEFURk9STVMuaW9zLnVybCwge1xuICAgICAgICAgIHR5cGU6ICd1dGY4JyxcbiAgICAgICAgICBlcnJvckNvcnJlY3Rpb25MZXZlbDogJ0wnLFxuICAgICAgICB9KSxcbiAgICAgICAgcXJUb1N0cmluZyhQTEFURk9STVMuYW5kcm9pZC51cmwsIHtcbiAgICAgICAgICB0eXBlOiAndXRmOCcsXG4gICAgICAgICAgZXJyb3JDb3JyZWN0aW9uTGV2ZWw6ICdMJyxcbiAgICAgICAgfSksXG4gICAgICBdKVxuICAgICAgc2V0UXJDb2Rlcyh7IGlvcywgYW5kcm9pZCB9KVxuICAgIH1cbiAgICBnZW5lcmF0ZVFSQ29kZXMoKS5jYXRjaCgoKSA9PiB7XG4gICAgICAvLyBRUiBnZW5lcmF0aW9uIGZhaWxlZCwgbGVhdmUgZW1wdHlcbiAgICB9KVxuICB9LCBbXSlcblxuICBjb25zdCBoYW5kbGVDbG9zZSA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICBvbkRvbmUoKVxuICB9LCBbb25Eb25lXSlcblxuICB1c2VLZXliaW5kaW5nKCdjb25maXJtOm5vJywgaGFuZGxlQ2xvc2UsIHsgY29udGV4dDogJ0NvbmZpcm1hdGlvbicgfSlcblxuICBmdW5jdGlvbiBoYW5kbGVLZXlEb3duKGU6IEtleWJvYXJkRXZlbnQpOiB2b2lkIHtcbiAgICBpZiAoZS5rZXkgPT09ICdxJyB8fCAoZS5jdHJsICYmIGUua2V5ID09PSAnYycpKSB7XG4gICAgICBlLnByZXZlbnREZWZhdWx0KClcbiAgICAgIG9uRG9uZSgpXG4gICAgICByZXR1cm5cbiAgICB9XG4gICAgaWYgKGUua2V5ID09PSAndGFiJyB8fCBlLmtleSA9PT0gJ2xlZnQnIHx8IGUua2V5ID09PSAncmlnaHQnKSB7XG4gICAgICBlLnByZXZlbnREZWZhdWx0KClcbiAgICAgIHNldFBsYXRmb3JtKHByZXYgPT4gKHByZXYgPT09ICdpb3MnID8gJ2FuZHJvaWQnIDogJ2lvcycpKVxuICAgIH1cbiAgfVxuXG4gIGNvbnN0IGxpbmVzID0gcXJDb2RlLnNwbGl0KCdcXG4nKS5maWx0ZXIobGluZSA9PiBsaW5lLmxlbmd0aCA+IDApXG5cbiAgcmV0dXJuIChcbiAgICA8UGFuZT5cbiAgICAgIDxCb3hcbiAgICAgICAgZmxleERpcmVjdGlvbj1cImNvbHVtblwiXG4gICAgICAgIHRhYkluZGV4PXswfVxuICAgICAgICBhdXRvRm9jdXNcbiAgICAgICAgb25LZXlEb3duPXtoYW5kbGVLZXlEb3dufVxuICAgICAgPlxuICAgICAgICA8VGV4dD4gPC9UZXh0PlxuICAgICAgICA8VGV4dD4gPC9UZXh0PlxuICAgICAgICB7bGluZXMubWFwKChsaW5lLCBpKSA9PiAoXG4gICAgICAgICAgPFRleHQga2V5PXtpfT57bGluZX08L1RleHQ+XG4gICAgICAgICkpfVxuICAgICAgICA8VGV4dD4gPC9UZXh0PlxuICAgICAgICA8VGV4dD4gPC9UZXh0PlxuXG4gICAgICAgIHsvKiBDb250cm9scyAqL31cbiAgICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwicm93XCIgZ2FwPXsyfT5cbiAgICAgICAgICA8VGV4dD5cbiAgICAgICAgICAgIDxUZXh0IGJvbGQ9e3BsYXRmb3JtID09PSAnaW9zJ30gdW5kZXJsaW5lPXtwbGF0Zm9ybSA9PT0gJ2lvcyd9PlxuICAgICAgICAgICAgICBpT1NcbiAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPnsnIC8gJ308L1RleHQ+XG4gICAgICAgICAgICA8VGV4dFxuICAgICAgICAgICAgICBib2xkPXtwbGF0Zm9ybSA9PT0gJ2FuZHJvaWQnfVxuICAgICAgICAgICAgICB1bmRlcmxpbmU9e3BsYXRmb3JtID09PSAnYW5kcm9pZCd9XG4gICAgICAgICAgICA+XG4gICAgICAgICAgICAgIEFuZHJvaWRcbiAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+KHRhYiB0byBzd2l0Y2gsIGVzYyB0byBjbG9zZSk8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgICA8VGV4dCBkaW1Db2xvcj57dXJsfTwvVGV4dD5cbiAgICAgIDwvQm94PlxuICAgIDwvUGFuZT5cbiAgKVxufVxuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY2FsbChcbiAgb25Eb25lOiBMb2NhbEpTWENvbW1hbmRPbkRvbmUsXG4pOiBQcm9taXNlPFJlYWN0LlJlYWN0Tm9kZT4ge1xuICByZXR1cm4gPE1vYmlsZVFSQ29kZSBvbkRvbmU9e29uRG9uZX0gLz5cbn1cbiJdLCJtYXBwaW5ncyI6IjtBQUFBLFNBQVNBLFFBQVEsSUFBSUMsVUFBVSxRQUFRLFFBQVE7QUFDL0MsT0FBTyxLQUFLQyxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxXQUFXLEVBQUVDLFNBQVMsRUFBRUMsUUFBUSxRQUFRLE9BQU87QUFDeEQsU0FBU0MsSUFBSSxRQUFRLHdDQUF3QztBQUM3RCxjQUFjQyxhQUFhLFFBQVEsb0NBQW9DO0FBQ3ZFLFNBQVNDLEdBQUcsRUFBRUMsSUFBSSxRQUFRLGNBQWM7QUFDeEMsU0FBU0MsYUFBYSxRQUFRLG9DQUFvQztBQUNsRSxjQUFjQyxxQkFBcUIsUUFBUSx3QkFBd0I7QUFFbkUsS0FBS0MsUUFBUSxHQUFHLEtBQUssR0FBRyxTQUFTO0FBRWpDLEtBQUtDLEtBQUssR0FBRztFQUNYQyxNQUFN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 \ No newline at end of file diff --git a/src/commands/mock-limits/index.js b/src/commands/mock-limits/index.js new file mode 100644 index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e --- /dev/null +++ b/src/commands/mock-limits/index.js @@ -0,0 +1 @@ +export default { isEnabled: () => false, isHidden: true, name: 'stub' }; diff --git a/src/commands/model/index.ts b/src/commands/model/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..5554906e65a01482ecba040a9d182cf8b6a513f8 --- /dev/null +++ b/src/commands/model/index.ts @@ -0,0 +1,16 @@ +import type { Command } from '../../commands.js' +import { shouldInferenceConfigCommandBeImmediate } from '../../utils/immediateCommand.js' +import { getMainLoopModel, renderModelName } from '../../utils/model/model.js' + +export default { + type: 'local-jsx', + name: 'model', + get description() { + return `Set the AI model for Claude Code (currently ${renderModelName(getMainLoopModel())})` + }, + argumentHint: '[model]', + get immediate() { + return shouldInferenceConfigCommandBeImmediate() + }, + load: () => import('./model.js'), +} satisfies Command diff --git a/src/commands/model/model.tsx b/src/commands/model/model.tsx new file mode 100644 index 0000000000000000000000000000000000000000..65c084d9fb959e43044d1806983b4a2fa6e9fb6a --- /dev/null +++ b/src/commands/model/model.tsx @@ -0,0 +1,297 @@ +import { c as _c } from "react/compiler-runtime"; +import chalk from 'chalk'; +import * as React from 'react'; +import type { CommandResultDisplay } from '../../commands.js'; +import { ModelPicker } from '../../components/ModelPicker.js'; +import { COMMON_HELP_ARGS, COMMON_INFO_ARGS } from '../../constants/xml.js'; +import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from '../../services/analytics/index.js'; +import { useAppState, useSetAppState } from '../../state/AppState.js'; +import type { LocalJSXCommandCall } from '../../types/command.js'; +import type { EffortLevel } from '../../utils/effort.js'; +import { isBilledAsExtraUsage } from '../../utils/extraUsage.js'; +import { clearFastModeCooldown, isFastModeAvailable, isFastModeEnabled, isFastModeSupportedByModel } from '../../utils/fastMode.js'; +import { MODEL_ALIASES } from '../../utils/model/aliases.js'; +import { checkOpus1mAccess, checkSonnet1mAccess } from '../../utils/model/check1mAccess.js'; +import { getDefaultMainLoopModelSetting, isOpus1mMergeEnabled, renderDefaultModelSetting } from '../../utils/model/model.js'; +import { isModelAllowed } from '../../utils/model/modelAllowlist.js'; +import { validateModel } from '../../utils/model/validateModel.js'; +function ModelPickerWrapper(t0) { + const $ = _c(17); + const { + onDone + } = t0; + const mainLoopModel = useAppState(_temp); + const mainLoopModelForSession = useAppState(_temp2); + const isFastMode = useAppState(_temp3); + const setAppState = useSetAppState(); + let t1; + if ($[0] !== mainLoopModel || $[1] !== onDone) { + t1 = function handleCancel() { + logEvent("tengu_model_command_menu", { + action: "cancel" as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS + }); + const displayModel = renderModelLabel(mainLoopModel); + onDone(`Kept model as ${chalk.bold(displayModel)}`, { + display: "system" + }); + }; + $[0] = mainLoopModel; + $[1] = onDone; + $[2] = t1; + } else { + t1 = $[2]; + } + const handleCancel = t1; + let t2; + if ($[3] !== isFastMode || $[4] !== mainLoopModel || $[5] !== onDone || $[6] !== setAppState) { + t2 = function handleSelect(model, effort) { + logEvent("tengu_model_command_menu", { + action: model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + from_model: mainLoopModel as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + to_model: model as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS + }); + setAppState(prev => ({ + ...prev, + mainLoopModel: model, + mainLoopModelForSession: null + })); + let message = `Set model to ${chalk.bold(renderModelLabel(model))}`; + if (effort !== undefined) { + message = message + ` with ${chalk.bold(effort)} effort`; + } + let wasFastModeToggledOn = undefined; + if (isFastModeEnabled()) { + clearFastModeCooldown(); + if (!isFastModeSupportedByModel(model) && isFastMode) { + setAppState(_temp4); + wasFastModeToggledOn = false; + } else { + if (isFastModeSupportedByModel(model) && isFastModeAvailable() && isFastMode) { + message = message + " \xB7 Fast mode ON"; + wasFastModeToggledOn = true; + } + } + } + if (isBilledAsExtraUsage(model, wasFastModeToggledOn === true, isOpus1mMergeEnabled())) { + message = message + " \xB7 Billed as extra usage"; + } + if (wasFastModeToggledOn === false) { + message = message + " \xB7 Fast mode OFF"; + } + onDone(message); + }; + $[3] = isFastMode; + $[4] = mainLoopModel; + $[5] = onDone; + $[6] = setAppState; + $[7] = t2; + } else { + t2 = $[7]; + } + const handleSelect = t2; + let t3; + if ($[8] !== isFastMode || $[9] !== mainLoopModel) { + t3 = isFastModeEnabled() && isFastMode && isFastModeSupportedByModel(mainLoopModel) && isFastModeAvailable(); + $[8] = isFastMode; + $[9] = mainLoopModel; + $[10] = t3; + } else { + t3 = $[10]; + } + let t4; + if ($[11] !== handleCancel || $[12] !== handleSelect || $[13] !== mainLoopModel || $[14] !== mainLoopModelForSession || $[15] !== t3) { + t4 = ; + $[11] = handleCancel; + $[12] = handleSelect; + $[13] = mainLoopModel; + $[14] = mainLoopModelForSession; + $[15] = t3; + $[16] = t4; + } else { + t4 = $[16]; + } + return t4; +} +function _temp4(prev_0) { + return { + ...prev_0, + fastMode: false + }; +} +function _temp3(s_1) { + return s_1.fastMode; +} +function _temp2(s_0) { + return s_0.mainLoopModelForSession; +} +function _temp(s) { + return s.mainLoopModel; +} +function SetModelAndClose({ + args, + onDone +}: { + args: string; + onDone: (result?: string, options?: { + display?: CommandResultDisplay; + }) => void; +}): React.ReactNode { + const isFastMode = useAppState(s => s.fastMode); + const setAppState = useSetAppState(); + const model = args === 'default' ? null : args; + React.useEffect(() => { + async function handleModelChange(): Promise { + if (model && !isModelAllowed(model)) { + onDone(`Model '${model}' is not available. Your organization restricts model selection.`, { + display: 'system' + }); + return; + } + + // @[MODEL LAUNCH]: Update check for 1M access. + if (model && isOpus1mUnavailable(model)) { + onDone(`Opus 4.6 with 1M context is not available for your account. Learn more: https://code.claude.com/docs/en/model-config#extended-context-with-1m`, { + display: 'system' + }); + return; + } + if (model && isSonnet1mUnavailable(model)) { + onDone(`Sonnet 4.6 with 1M context is not available for your account. Learn more: https://code.claude.com/docs/en/model-config#extended-context-with-1m`, { + display: 'system' + }); + return; + } + + // Skip validation for default model + if (!model) { + setModel(null); + return; + } + + // Skip validation for known aliases - they're predefined and should work + if (isKnownAlias(model)) { + setModel(model); + return; + } + + // Validate and set custom model + try { + // Don't use parseUserSpecifiedModel for non-aliases since it lowercases the input + // and model names are case-sensitive + const { + valid, + error: error_0 + } = await validateModel(model); + if (valid) { + setModel(model); + } else { + onDone(error_0 || `Model '${model}' not found`, { + display: 'system' + }); + } + } catch (error) { + onDone(`Failed to validate model: ${(error as Error).message}`, { + display: 'system' + }); + } + } + function setModel(modelValue: string | null): void { + setAppState(prev => ({ + ...prev, + mainLoopModel: modelValue, + mainLoopModelForSession: null + })); + let message = `Set model to ${chalk.bold(renderModelLabel(modelValue))}`; + let wasFastModeToggledOn = undefined; + if (isFastModeEnabled()) { + clearFastModeCooldown(); + if (!isFastModeSupportedByModel(modelValue) && isFastMode) { + setAppState(prev_0 => ({ + ...prev_0, + fastMode: false + })); + wasFastModeToggledOn = false; + // Do not update fast mode in settings since this is an automatic downgrade + } else if (isFastModeSupportedByModel(modelValue) && isFastMode) { + message += ` · Fast mode ON`; + wasFastModeToggledOn = true; + } + } + if (isBilledAsExtraUsage(modelValue, wasFastModeToggledOn === true, isOpus1mMergeEnabled())) { + message += ` · Billed as extra usage`; + } + if (wasFastModeToggledOn === false) { + // Fast mode was toggled off, show suffix after extra usage billing + message += ` · Fast mode OFF`; + } + onDone(message); + } + void handleModelChange(); + }, [model, onDone, setAppState]); + return null; +} +function isKnownAlias(model: string): boolean { + return (MODEL_ALIASES as readonly string[]).includes(model.toLowerCase().trim()); +} +function isOpus1mUnavailable(model: string): boolean { + const m = model.toLowerCase(); + return !checkOpus1mAccess() && !isOpus1mMergeEnabled() && m.includes('opus') && m.includes('[1m]'); +} +function isSonnet1mUnavailable(model: string): boolean { + const m = model.toLowerCase(); + // Warn about Sonnet and Sonnet 4.6, but not Sonnet 4.5 since that had + // a different access criteria. + return !checkSonnet1mAccess() && (m.includes('sonnet[1m]') || m.includes('sonnet-4-6[1m]')); +} +function ShowModelAndClose(t0) { + const { + onDone + } = t0; + const mainLoopModel = useAppState(_temp7); + const mainLoopModelForSession = useAppState(_temp8); + const effortValue = useAppState(_temp9); + const displayModel = renderModelLabel(mainLoopModel); + const effortInfo = effortValue !== undefined ? ` (effort: ${effortValue})` : ""; + if (mainLoopModelForSession) { + onDone(`Current model: ${chalk.bold(renderModelLabel(mainLoopModelForSession))} (session override from plan mode)\nBase model: ${displayModel}${effortInfo}`); + } else { + onDone(`Current model: ${displayModel}${effortInfo}`); + } + return null; +} +function _temp9(s_1) { + return s_1.effortValue; +} +function _temp8(s_0) { + return s_0.mainLoopModelForSession; +} +function _temp7(s) { + return s.mainLoopModel; +} +export const call: LocalJSXCommandCall = async (onDone, _context, args) => { + args = args?.trim() || ''; + if (COMMON_INFO_ARGS.includes(args)) { + logEvent('tengu_model_command_inline_help', { + args: args as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS + }); + return ; + } + if (COMMON_HELP_ARGS.includes(args)) { + onDone('Run /model to open the model selection menu, or /model [modelName] to set the model.', { + display: 'system' + }); + return; + } + if (args) { + logEvent('tengu_model_command_inline', { + args: args as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS + }); + return ; + } + return ; +}; +function renderModelLabel(model: string | null): string { + const rendered = renderDefaultModelSetting(model ?? getDefaultMainLoopModelSetting()); + return model === null ? `${rendered} (default)` : rendered; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJjaGFsayIsIlJlYWN0IiwiQ29tbWFuZFJlc3VsdERpc3BsYXkiLCJNb2RlbFBpY2tlciIsIkNPTU1PTl9IRUxQX0FSR1MiLCJDT01NT05fSU5GT19BUkdTIiwiQW5hbHl0aWNzTWV0YWRhdGFfSV9WRVJJRklFRF9USElTX0lTX05PVF9DT0RFX09SX0ZJTEVQQVRIUyIsImxvZ0V2ZW50IiwidXNlQXBwU3RhdGUiLCJ1c2VTZXRBcHBTdGF0ZSIsIkxvY2FsSlNYQ29tbWFuZENhbGwiLCJFZmZvcnRMZXZlbCIsImlzQmlsbGVkQXNFeHRyYVVzYWdlIiwiY2xlYXJGYXN0TW9kZUNvb2xkb3duIiwiaXNGYXN0TW9kZUF2YWlsYWJsZSIsImlzRmFzdE1vZGVFbmFibGVkIiwiaXNGYXN0TW9kZVN1cHBvcnRlZEJ5TW9kZWwiLCJNT0RFTF9BTElBU0VTIiwiY2hlY2tPcHVzMW1BY2Nlc3MiLCJjaGVja1Nvbm5ldDFtQWNjZXNzIiwiZ2V0RGVmYXVsdE1haW5Mb29wTW9kZWxTZXR0aW5nIiwiaXNPcHVzMW1NZXJnZUVuYWJsZWQiLCJyZW5kZXJEZWZhdWx0TW9kZWxTZXR0aW5nIiwiaXNNb2RlbEFsbG93ZWQiLCJ2YWxpZGF0ZU1vZGVsIiwiTW9kZWxQaWNrZXJXcmFwcGVyIiwidDAiLCIkIiwiX2MiLCJvbkRvbmUiLCJtYWluTG9vcE1vZGVsIiwiX3RlbXAiLCJtYWluTG9vcE1vZGVsRm9yU2Vzc2lvbiIsIl90ZW1wMiIsImlzRmFzdE1vZGUiLCJfdGVtcDMiLCJzZXRBcHBTdGF0ZSIsInQxIiwiaGFuZGxlQ2FuY2VsIiwiYWN0aW9uIiwiZGlzcGxheU1vZGVsIiwicmVuZGVyTW9kZWxMYWJlbCIsImJvbGQiLCJkaXNwbGF5IiwidDIiLCJoYW5kbGVTZWxlY3QiLCJtb2RlbCIsImVmZm9ydCIsImZyb21fbW9kZWwiLCJ0b19tb2RlbCIsInByZXYiLCJtZXNzYWdlIiwidW5kZWZpbmVkIiwid2FzRmFzdE1vZGVUb2dnbGVkT24iLCJfdGVtcDQiLCJ0MyIsInQ0IiwicHJldl8wIiwiZmFzdE1vZGUiLCJzXzEiLCJzIiwic18wIiwiU2V0TW9kZWxBbmRDbG9zZSIsImFyZ3MiLCJyZXN1bHQiLCJvcHRpb25zIiwiUmVhY3ROb2RlIiwidXNlRWZmZWN0IiwiaGFuZGxlTW9kZWxDaGFuZ2UiLCJQcm9taXNlIiwiaXNPcHVzMW1VbmF2YWlsYWJsZSIsImlzU29ubmV0MW1VbmF2YWlsYWJsZSIsInNldE1vZGVsIiwiaXNLbm93bkFsaWFzIiwidmFsaWQiLCJlcnJvciIsIkVycm9yIiwibW9kZWxWYWx1ZSIsImluY2x1ZGVzIiwidG9Mb3dlckNhc2UiLCJ0cmltIiwibSIsIlNob3dNb2RlbEFuZENsb3NlIiwiX3RlbXA3IiwiX3RlbXA4IiwiZWZmb3J0VmFsdWUiLCJfdGVtcDkiLCJlZmZvcnRJbmZvIiwiY2FsbCIsIl9jb250ZXh0IiwicmVuZGVyZWQiXSwic291cmNlcyI6WyJtb2RlbC50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGNoYWxrIGZyb20gJ2NoYWxrJ1xuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgdHlwZSB7IENvbW1hbmRSZXN1bHREaXNwbGF5IH0gZnJvbSAnLi4vLi4vY29tbWFuZHMuanMnXG5pbXBvcnQgeyBNb2RlbFBpY2tlciB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvTW9kZWxQaWNrZXIuanMnXG5pbXBvcnQgeyBDT01NT05fSEVMUF9BUkdTLCBDT01NT05fSU5GT19BUkdTIH0gZnJvbSAnLi4vLi4vY29uc3RhbnRzL3htbC5qcydcbmltcG9ydCB7XG4gIHR5cGUgQW5hbHl0aWNzTWV0YWRhdGFfSV9WRVJJRklFRF9USElTX0lTX05PVF9DT0RFX09SX0ZJTEVQQVRIUyxcbiAgbG9nRXZlbnQsXG59IGZyb20gJy4uLy4uL3NlcnZpY2VzL2FuYWx5dGljcy9pbmRleC5qcydcbmltcG9ydCB7IHVzZUFwcFN0YXRlLCB1c2VTZXRBcHBTdGF0ZSB9IGZyb20gJy4uLy4uL3N0YXRlL0FwcFN0YXRlLmpzJ1xuaW1wb3J0IHR5cGUgeyBMb2NhbEpTWENvbW1hbmRDYWxsIH0gZnJvbSAnLi4vLi4vdHlwZXMvY29tbWFuZC5qcydcbmltcG9ydCB0eXBlIHsgRWZmb3J0TGV2ZWwgfSBmcm9tICcuLi8uLi91dGlscy9lZmZvcnQuanMnXG5pbXBvcnQgeyBpc0JpbGxlZEFzRXh0cmFVc2FnZSB9IGZyb20gJy4uLy4uL3V0aWxzL2V4dHJhVXNhZ2UuanMnXG5pbXBvcnQge1xuICBjbGVhckZhc3RNb2RlQ29vbGRvd24sXG4gIGlzRmFzdE1vZGVBdmFpbGFibGUsXG4gIGlzRmFzdE1vZGVFbmFibGVkLFxuICBpc0Zhc3RNb2RlU3VwcG9ydGVkQnlNb2RlbCxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvZmFzdE1vZGUuanMnXG5pbXBvcnQgeyBNT0RFTF9BTElBU0VTIH0gZnJvbSAnLi4vLi4vdXRpbHMvbW9kZWwvYWxpYXNlcy5qcydcbmltcG9ydCB7XG4gIGNoZWNrT3B1czFtQWNjZXNzLFxuICBjaGVja1Nvbm5ldDFtQWNjZXNzLFxufSBmcm9tICcuLi8uLi91dGlscy9tb2RlbC9jaGVjazFtQWNjZXNzLmpzJ1xuaW1wb3J0IHtcbiAgZ2V0RGVmYXVsdE1haW5Mb29wTW9kZWxTZXR0aW5nLFxuICBpc09wdXMxbU1lcmdlRW5hYmxlZCxcbiAgcmVuZGVyRGVmYXVsdE1vZGVsU2V0dGluZyxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvbW9kZWwvbW9kZWwuanMnXG5pbXBvcnQgeyBpc01vZGVsQWxsb3dlZCB9IGZyb20gJy4uLy4uL3V0aWxzL21vZGVsL21vZGVsQWxsb3dsaXN0LmpzJ1xuaW1wb3J0IHsgdmFsaWRhdGVNb2RlbCB9IGZyb20gJy4uLy4uL3V0aWxzL21vZGVsL3ZhbGlkYXRlTW9kZWwuanMnXG5cbmZ1bmN0aW9uIE1vZGVsUGlja2VyV3JhcHBlcih7XG4gIG9uRG9uZSxcbn06IHtcbiAgb25Eb25lOiAoXG4gICAgcmVzdWx0Pzogc3RyaW5nLFxuICAgIG9wdGlvbnM/OiB7IGRpc3BsYXk/OiBDb21tYW5kUmVzdWx0RGlzcGxheSB9LFxuICApID0+IHZvaWRcbn0pOiBSZWFjdC5SZWFjdE5vZGUge1xuICBjb25zdCBtYWluTG9vcE1vZGVsID0gdXNlQXBwU3RhdGUocyA9PiBzLm1haW5Mb29wTW9kZWwpXG4gIGNvbnN0IG1haW5Mb29wTW9kZWxGb3JTZXNzaW9uID0gdXNlQXBwU3RhdGUocyA9PiBzLm1haW5Mb29wTW9kZWxGb3JTZXNzaW9uKVxuICBjb25zdCBpc0Zhc3RNb2RlID0gdXNlQXBwU3RhdGUocyA9PiBzLmZhc3RNb2RlKVxuICBjb25zdCBzZXRBcHBTdGF0ZSA9IHVzZVNldEFwcFN0YXRlKClcblxuICBmdW5jdGlvbiBoYW5kbGVDYW5jZWwoKTogdm9pZCB7XG4gICAgbG9nRXZlbnQoJ3Rlbmd1X21vZGVsX2NvbW1hbmRfbWVudScsIHtcbiAgICAgIGFjdGlvbjpcbiAgICAgICAgJ2NhbmNlbCcgYXMgQW5hbHl0aWNzTWV0YWRhdGFfSV9WRVJJRklFRF9USElTX0lTX05PVF9DT0RFX09SX0ZJTEVQQVRIUyxcbiAgICB9KVxuICAgIGNvbnN0IGRpc3BsYXlNb2RlbCA9IHJlbmRlck1vZGVsTGFiZWwobWFpbkxvb3BNb2RlbClcbiAgICBvbkRvbmUoYEtlcHQgbW9kZWwgYXMgJHtjaGFsay5ib2xkKGRpc3BsYXlNb2RlbCl9YCwge1xuICAgICAgZGlzcGxheTogJ3N5c3RlbScsXG4gICAgfSlcbiAgfVxuXG4gIGZ1bmN0aW9uIGhhbmRsZVNlbGVjdChcbiAgICBtb2RlbDogc3RyaW5nIHwgbnVsbCxcbiAgICBlZmZvcnQ6IEVmZm9ydExldmVsIHwgdW5kZWZpbmVkLFxuICApOiB2b2lkIHtcbiAgICBsb2dFdmVudCgndGVuZ3VfbW9kZWxfY29tbWFuZF9tZW51Jywge1xuICAgICAgYWN0aW9uOlxuICAgICAgICBtb2RlbCBhcyBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICAgICAgZnJvbV9tb2RlbDpcbiAgICAgICAgbWFpbkxvb3BNb2RlbCBhcyBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICAgICAgdG9fbW9kZWw6XG4gICAgICAgIG1vZGVsIGFzIEFuYWx5dGljc01ldGFkYXRhX0lfVkVSSUZJRURfVEhJU19JU19OT1RfQ09ERV9PUl9GSUxFUEFUSFMsXG4gICAgfSlcbiAgICBzZXRBcHBTdGF0ZShwcmV2ID0+ICh7XG4gICAgICAuLi5wcmV2LFxuICAgICAgbWFpbkxvb3BNb2RlbDogbW9kZWwsXG4gICAgICBtYWluTG9vcE1vZGVsRm9yU2Vzc2lvbjogbnVsbCxcbiAgICB9KSlcblxuICAgIGxldCBtZXNzYWdlID0gYFNldCBtb2RlbCB0byAke2NoYWxrLmJvbGQocmVuZGVyTW9kZWxMYWJlbChtb2RlbCkpfWBcbiAgICBpZiAoZWZmb3J0ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgIG1lc3NhZ2UgKz0gYCB3aXRoICR7Y2hhbGsuYm9sZChlZmZvcnQpfSBlZmZvcnRgXG4gICAgfVxuXG4gICAgLy8gVHVybiBvZmYgZmFzdCBtb2RlIGlmIHN3aXRjaGluZyB0byB1bnN1cHBvcnRlZCBtb2RlbFxuICAgIGxldCB3YXNGYXN0TW9kZVRvZ2dsZWRPbiA9IHVuZGVmaW5lZFxuICAgIGlmIChpc0Zhc3RNb2RlRW5hYmxlZCgpKSB7XG4gICAgICBjbGVhckZhc3RNb2RlQ29vbGRvd24oKVxuICAgICAgaWYgKCFpc0Zhc3RNb2RlU3VwcG9ydGVkQnlNb2RlbChtb2RlbCkgJiYgaXNGYXN0TW9kZSkge1xuICAgICAgICBzZXRBcHBTdGF0ZShwcmV2ID0+ICh7XG4gICAgICAgICAgLi4ucHJldixcbiAgICAgICAgICBmYXN0TW9kZTogZmFsc2UsXG4gICAgICAgIH0pKVxuICAgICAgICB3YXNGYXN0TW9kZVRvZ2dsZWRPbiA9IGZhbHNlXG4gICAgICAgIC8vIERvIG5vdCB1cGRhdGUgZmFzdCBtb2RlIGluIHNldHRpbmdzIHNpbmNlIHRoaXMgaXMgYW4gYXV0b21hdGljIGRvd25ncmFkZVxuICAgICAgfSBlbHNlIGlmIChcbiAgICAgICAgaXNGYXN0TW9kZVN1cHBvcnRlZEJ5TW9kZWwobW9kZWwpICYmXG4gICAgICAgIGlzRmFzdE1vZGVBdmFpbGFibGUoKSAmJlxuICAgICAgICBpc0Zhc3RNb2RlXG4gICAgICApIHtcbiAgICAgICAgbWVzc2FnZSArPSBgIMK3IEZhc3QgbW9kZSBPTmBcbiAgICAgICAgd2FzRmFzdE1vZGVUb2dnbGVkT24gPSB0cnVlXG4gICAgICB9XG4gICAgfVxuXG4gICAgaWYgKFxuICAgICAgaXNCaWxsZWRBc0V4dHJhVXNhZ2UoXG4gICAgICAgIG1vZGVsLFxuICAgICAgICB3YXNGYXN0TW9kZVRvZ2dsZWRPbiA9PT0gdHJ1ZSxcbiAgICAgICAgaXNPcHVzMW1NZXJnZUVuYWJsZWQoKSxcbiAgICAgIClcbiAgICApIHtcbiAgICAgIG1lc3NhZ2UgKz0gYCDCtyBCaWxsZWQgYXMgZXh0cmEgdXNhZ2VgXG4gICAgfVxuXG4gICAgaWYgKHdhc0Zhc3RNb2RlVG9nZ2xlZE9uID09PSBmYWxzZSkge1xuICAgICAgLy8gRmFzdCBtb2RlIHdhcyB0b2dnbGVkIG9mZiwgc2hvdyBzdWZmaXggYWZ0ZXIgZXh0cmEgdXNhZ2UgYmlsbGluZ1xuICAgICAgbWVzc2FnZSArPSBgIMK3IEZhc3QgbW9kZSBPRkZgXG4gICAgfVxuXG4gICAgb25Eb25lKG1lc3NhZ2UpXG4gIH1cblxuICByZXR1cm4gKFxuICAgIDxNb2RlbFBpY2tlclxuICAgICAgaW5pdGlhbD17bWFpbkxvb3BNb2RlbH1cbiAgICAgIHNlc3Npb25Nb2RlbD17bWFpbkxvb3BNb2RlbEZvclNlc3Npb259XG4gICAgICBvblNlbGVjdD17aGFuZGxlU2VsZWN0fVxuICAgICAgb25DYW5jZWw9e2hhbmRsZUNhbmNlbH1cbiAgICAgIGlzU3RhbmRhbG9uZUNvbW1hbmRcbiAgICAgIHNob3dGYXN0TW9kZU5vdGljZT17XG4gICAgICAgIGlzRmFzdE1vZGVFbmFibGVkKCkgJiZcbiAgICAgICAgaXNGYXN0TW9kZSAmJlxuICAgICAgICBpc0Zhc3RNb2RlU3VwcG9ydGVkQnlNb2RlbChtYWluTG9vcE1vZGVsKSAmJlxuICAgICAgICBpc0Zhc3RNb2RlQXZhaWxhYmxlKClcbiAgICAgIH1cbiAgICAvPlxuICApXG59XG5cbmZ1bmN0aW9uIFNldE1vZGVsQW5kQ2xvc2Uoe1xuICBhcmdzLFxuICBvbkRvbmUsXG59OiB7XG4gIGFyZ3M6IHN0cmluZ1xuICBvbkRvbmU6IChcbiAgICByZXN1bHQ/OiBzdHJpbmcsXG4gICAgb3B0aW9ucz86IHsgZGlzcGxheT86IENvbW1hbmRSZXN1bHREaXNwbGF5IH0sXG4gICkgPT4gdm9pZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IGlzRmFzdE1vZGUgPSB1c2VBcHBTdGF0ZShzID0+IHMuZmFzdE1vZGUpXG4gIGNvbnN0IHNldEFwcFN0YXRlID0gdXNlU2V0QXBwU3RhdGUoKVxuICBjb25zdCBtb2RlbCA9IGFyZ3MgPT09ICdkZWZhdWx0JyA/IG51bGwgOiBhcmdzXG5cbiAgUmVhY3QudXNlRWZmZWN0KCgpID0+IHtcbiAgICBhc3luYyBmdW5jdGlvbiBoYW5kbGVNb2RlbENoYW5nZSgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICAgIGlmIChtb2RlbCAmJiAhaXNNb2RlbEFsbG93ZWQobW9kZWwpKSB7XG4gICAgICAgIG9uRG9uZShcbiAgICAgICAgICBgTW9kZWwgJyR7bW9kZWx9JyBpcyBub3QgYXZhaWxhYmxlLiBZb3VyIG9yZ2FuaXphdGlvbiByZXN0cmljdHMgbW9kZWwgc2VsZWN0aW9uLmAsXG4gICAgICAgICAgeyBkaXNwbGF5OiAnc3lzdGVtJyB9LFxuICAgICAgICApXG4gICAgICAgIHJldHVyblxuICAgICAgfVxuXG4gICAgICAvLyBAW01PREVMIExBVU5DSF06IFVwZGF0ZSBjaGVjayBmb3IgMU0gYWNjZXNzLlxuICAgICAgaWYgKG1vZGVsICYmIGlzT3B1czFtVW5hdmFpbGFibGUobW9kZWwpKSB7XG4gICAgICAgIG9uRG9uZShcbiAgICAgICAgICBgT3B1cyA0LjYgd2l0aCAxTSBjb250ZXh0IGlzIG5vdCBhdmFpbGFibGUgZm9yIHlvdXIgYWNjb3VudC4gTGVhcm4gbW9yZTogaHR0cHM6Ly9jb2RlLmNsYXVkZS5jb20vZG9jcy9lbi9tb2RlbC1jb25maWcjZXh0ZW5kZWQtY29udGV4dC13aXRoLTFtYCxcbiAgICAgICAgICB7IGRpc3BsYXk6ICdzeXN0ZW0nIH0sXG4gICAgICAgIClcbiAgICAgICAgcmV0dXJuXG4gICAgICB9XG5cbiAgICAgIGlmIChtb2RlbCAmJiBpc1Nvbm5ldDFtVW5hdmFpbGFibGUobW9kZWwpKSB7XG4gICAgICAgIG9uRG9uZShcbiAgICAgICAgICBgU29ubmV0IDQuNiB3aXRoIDFNIGNvbnRleHQgaXMgbm90IGF2YWlsYWJsZSBmb3IgeW91ciBhY2NvdW50LiBMZWFybiBtb3JlOiBodHRwczovL2NvZGUuY2xhdWRlLmNvbS9kb2NzL2VuL21vZGVsLWNvbmZpZyNleHRlbmRlZC1jb250ZXh0LXdpdGgtMW1gLFxuICAgICAgICAgIHsgZGlzcGxheTogJ3N5c3RlbScgfSxcbiAgICAgICAgKVxuICAgICAgICByZXR1cm5cbiAgICAgIH1cblxuICAgICAgLy8gU2tpcCB2YWxpZGF0aW9uIGZvciBkZWZhdWx0IG1vZGVsXG4gICAgICBpZiAoIW1vZGVsKSB7XG4gICAgICAgIHNldE1vZGVsKG51bGwpXG4gICAgICAgIHJldHVyblxuICAgICAgfVxuXG4gICAgICAvLyBTa2lwIHZhbGlkYXRpb24gZm9yIGtub3duIGFsaWFzZXMgLSB0aGV5J3JlIHByZWRlZmluZWQgYW5kIHNob3VsZCB3b3JrXG4gICAgICBpZiAoaXNLbm93bkFsaWFzKG1vZGVsKSkge1xuICAgICAgICBzZXRNb2RlbChtb2RlbClcbiAgICAgICAgcmV0dXJuXG4gICAgICB9XG5cbiAgICAgIC8vIFZhbGlkYXRlIGFuZCBzZXQgY3VzdG9tIG1vZGVsXG4gICAgICB0cnkge1xuICAgICAgICAvLyBEb24ndCB1c2UgcGFyc2VVc2VyU3BlY2lmaWVkTW9kZWwgZm9yIG5vbi1hbGlhc2VzIHNpbmNlIGl0IGxvd2VyY2FzZXMgdGhlIGlucHV0XG4gICAgICAgIC8vIGFuZCBtb2RlbCBuYW1lcyBhcmUgY2FzZS1zZW5zaXRpdmVcbiAgICAgICAgY29uc3QgeyB2YWxpZCwgZXJyb3IgfSA9IGF3YWl0IHZhbGlkYXRlTW9kZWwobW9kZWwpXG5cbiAgICAgICAgaWYgKHZhbGlkKSB7XG4gICAgICAgICAgc2V0TW9kZWwobW9kZWwpXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgb25Eb25lKGVycm9yIHx8IGBNb2RlbCAnJHttb2RlbH0nIG5vdCBmb3VuZGAsIHtcbiAgICAgICAgICAgIGRpc3BsYXk6ICdzeXN0ZW0nLFxuICAgICAgICAgIH0pXG4gICAgICAgIH1cbiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIG9uRG9uZShgRmFpbGVkIHRvIHZhbGlkYXRlIG1vZGVsOiAkeyhlcnJvciBhcyBFcnJvcikubWVzc2FnZX1gLCB7XG4gICAgICAgICAgZGlzcGxheTogJ3N5c3RlbScsXG4gICAgICAgIH0pXG4gICAgICB9XG4gICAgfVxuXG4gICAgZnVuY3Rpb24gc2V0TW9kZWwobW9kZWxWYWx1ZTogc3RyaW5nIHwgbnVsbCk6IHZvaWQge1xuICAgICAgc2V0QXBwU3RhdGUocHJldiA9PiAoe1xuICAgICAgICAuLi5wcmV2LFxuICAgICAgICBtYWluTG9vcE1vZGVsOiBtb2RlbFZhbHVlLFxuICAgICAgICBtYWluTG9vcE1vZGVsRm9yU2Vzc2lvbjogbnVsbCxcbiAgICAgIH0pKVxuICAgICAgbGV0IG1lc3NhZ2UgPSBgU2V0IG1vZGVsIHRvICR7Y2hhbGsuYm9sZChyZW5kZXJNb2RlbExhYmVsKG1vZGVsVmFsdWUpKX1gXG5cbiAgICAgIGxldCB3YXNGYXN0TW9kZVRvZ2dsZWRPbiA9IHVuZGVmaW5lZFxuICAgICAgaWYgKGlzRmFzdE1vZGVFbmFibGVkKCkpIHtcbiAgICAgICAgY2xlYXJGYXN0TW9kZUNvb2xkb3duKClcbiAgICAgICAgaWYgKCFpc0Zhc3RNb2RlU3VwcG9ydGVkQnlNb2RlbChtb2RlbFZhbHVlKSAmJiBpc0Zhc3RNb2RlKSB7XG4gICAgICAgICAgc2V0QXBwU3RhdGUocHJldiA9PiAoe1xuICAgICAgICAgICAgLi4ucHJldixcbiAgICAgICAgICAgIGZhc3RNb2RlOiBmYWxzZSxcbiAgICAgICAgICB9KSlcbiAgICAgICAgICB3YXNGYXN0TW9kZVRvZ2dsZWRPbiA9IGZhbHNlXG4gICAgICAgICAgLy8gRG8gbm90IHVwZGF0ZSBmYXN0IG1vZGUgaW4gc2V0dGluZ3Mgc2luY2UgdGhpcyBpcyBhbiBhdXRvbWF0aWMgZG93bmdyYWRlXG4gICAgICAgIH0gZWxzZSBpZiAoaXNGYXN0TW9kZVN1cHBvcnRlZEJ5TW9kZWwobW9kZWxWYWx1ZSkgJiYgaXNGYXN0TW9kZSkge1xuICAgICAgICAgIG1lc3NhZ2UgKz0gYCDCtyBGYXN0IG1vZGUgT05gXG4gICAgICAgICAgd2FzRmFzdE1vZGVUb2dnbGVkT24gPSB0cnVlXG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgaWYgKFxuICAgICAgICBpc0JpbGxlZEFzRXh0cmFVc2FnZShcbiAgICAgICAgICBtb2RlbFZhbHVlLFxuICAgICAgICAgIHdhc0Zhc3RNb2RlVG9nZ2xlZE9uID09PSB0cnVlLFxuICAgICAgICAgIGlzT3B1czFtTWVyZ2VFbmFibGVkKCksXG4gICAgICAgIClcbiAgICAgICkge1xuICAgICAgICBtZXNzYWdlICs9IGAgwrcgQmlsbGVkIGFzIGV4dHJhIHVzYWdlYFxuICAgICAgfVxuXG4gICAgICBpZiAod2FzRmFzdE1vZGVUb2dnbGVkT24gPT09IGZhbHNlKSB7XG4gICAgICAgIC8vIEZhc3QgbW9kZSB3YXMgdG9nZ2xlZCBvZmYsIHNob3cgc3VmZml4IGFmdGVyIGV4dHJhIHVzYWdlIGJpbGxpbmdcbiAgICAgICAgbWVzc2FnZSArPSBgIMK3IEZhc3QgbW9kZSBPRkZgXG4gICAgICB9XG5cbiAgICAgIG9uRG9uZShtZXNzYWdlKVxuICAgIH1cblxuICAgIHZvaWQgaGFuZGxlTW9kZWxDaGFuZ2UoKVxuICB9LCBbbW9kZWwsIG9uRG9uZSwgc2V0QXBwU3RhdGVdKVxuXG4gIHJldHVybiBudWxsXG59XG5cbmZ1bmN0aW9uIGlzS25vd25BbGlhcyhtb2RlbDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIHJldHVybiAoTU9ERUxfQUxJQVNFUyBhcyByZWFkb25seSBzdHJpbmdbXSkuaW5jbHVkZXMoXG4gICAgbW9kZWwudG9Mb3dlckNhc2UoKS50cmltKCksXG4gIClcbn1cblxuZnVuY3Rpb24gaXNPcHVzMW1VbmF2YWlsYWJsZShtb2RlbDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIGNvbnN0IG0gPSBtb2RlbC50b0xvd2VyQ2FzZSgpXG4gIHJldHVybiAoXG4gICAgIWNoZWNrT3B1czFtQWNjZXNzKCkgJiZcbiAgICAhaXNPcHVzMW1NZXJnZUVuYWJsZWQoKSAmJlxuICAgIG0uaW5jbHVkZXMoJ29wdXMnKSAmJlxuICAgIG0uaW5jbHVkZXMoJ1sxbV0nKVxuICApXG59XG5cbmZ1bmN0aW9uIGlzU29ubmV0MW1VbmF2YWlsYWJsZShtb2RlbDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIGNvbnN0IG0gPSBtb2RlbC50b0xvd2VyQ2FzZSgpXG4gIC8vIFdhcm4gYWJvdXQgU29ubmV0IGFuZCBTb25uZXQgNC42LCBidXQgbm90IFNvbm5ldCA0LjUgc2luY2UgdGhhdCBoYWRcbiAgLy8gYSBkaWZmZXJlbnQgYWNjZXNzIGNyaXRlcmlhLlxuICByZXR1cm4gKFxuICAgICFjaGVja1Nvbm5ldDFtQWNjZXNzKCkgJiZcbiAgICAobS5pbmNsdWRlcygnc29ubmV0WzFtXScpIHx8IG0uaW5jbHVkZXMoJ3Nvbm5ldC00LTZbMW1dJykpXG4gIClcbn1cblxuZnVuY3Rpb24gU2hvd01vZGVsQW5kQ2xvc2Uoe1xuICBvbkRvbmUsXG59OiB7XG4gIG9uRG9uZTogKHJlc3VsdD86IHN0cmluZykgPT4gdm9pZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IG1haW5Mb29wTW9kZWwgPSB1c2VBcHBTdGF0ZShzID0+IHMubWFpbkxvb3BNb2RlbClcbiAgY29uc3QgbWFpbkxvb3BNb2RlbEZvclNlc3Npb24gPSB1c2VBcHBTdGF0ZShzID0+IHMubWFpbkxvb3BNb2RlbEZvclNlc3Npb24pXG4gIGNvbnN0IGVmZm9ydFZhbHVlID0gdXNlQXBwU3RhdGUocyA9PiBzLmVmZm9ydFZhbHVlKVxuICBjb25zdCBkaXNwbGF5TW9kZWwgPSByZW5kZXJNb2RlbExhYmVsKG1haW5Mb29wTW9kZWwpXG4gIGNvbnN0IGVmZm9ydEluZm8gPVxuICAgIGVmZm9ydFZhbHVlICE9PSB1bmRlZmluZWQgPyBgIChlZmZvcnQ6ICR7ZWZmb3J0VmFsdWV9KWAgOiAnJ1xuXG4gIGlmIChtYWluTG9vcE1vZGVsRm9yU2Vzc2lvbikge1xuICAgIG9uRG9uZShcbiAgICAgIGBDdXJyZW50IG1vZGVsOiAke2NoYWxrLmJvbGQocmVuZGVyTW9kZWxMYWJlbChtYWluTG9vcE1vZGVsRm9yU2Vzc2lvbikpfSAoc2Vzc2lvbiBvdmVycmlkZSBmcm9tIHBsYW4gbW9kZSlcXG5CYXNlIG1vZGVsOiAke2Rpc3BsYXlNb2RlbH0ke2VmZm9ydEluZm99YCxcbiAgICApXG4gIH0gZWxzZSB7XG4gICAgb25Eb25lKGBDdXJyZW50IG1vZGVsOiAke2Rpc3BsYXlNb2RlbH0ke2VmZm9ydEluZm99YClcbiAgfVxuXG4gIHJldHVybiBudWxsXG59XG5cbmV4cG9ydCBjb25zdCBjYWxsOiBMb2NhbEpTWENvbW1hbmRDYWxsID0gYXN5bmMgKG9uRG9uZSwgX2NvbnRleHQsIGFyZ3MpID0+IHtcbiAgYXJncyA9IGFyZ3M/LnRyaW0oKSB8fCAnJ1xuICBpZiAoQ09NTU9OX0lORk9fQVJHUy5pbmNsdWRlcyhhcmdzKSkge1xuICAgIGxvZ0V2ZW50KCd0ZW5ndV9tb2RlbF9jb21tYW5kX2lubGluZV9oZWxwJywge1xuICAgICAgYXJnczogYXJncyBhcyBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICAgIH0pXG4gICAgcmV0dXJuIDxTaG93TW9kZWxBbmRDbG9zZSBvbkRvbmU9e29uRG9uZX0gLz5cbiAgfVxuICBpZiAoQ09NTU9OX0hFTFBfQVJHUy5pbmNsdWRlcyhhcmdzKSkge1xuICAgIG9uRG9uZShcbiAgICAgICdSdW4gL21vZGVsIHRvIG9wZW4gdGhlIG1vZGVsIHNlbGVjdGlvbiBtZW51LCBvciAvbW9kZWwgW21vZGVsTmFtZV0gdG8gc2V0IHRoZSBtb2RlbC4nLFxuICAgICAgeyBkaXNwbGF5OiAnc3lzdGVtJyB9LFxuICAgIClcbiAgICByZXR1cm5cbiAgfVxuXG4gIGlmIChhcmdzKSB7XG4gICAgbG9nRXZlbnQoJ3Rlbmd1X21vZGVsX2NvbW1hbmRfaW5saW5lJywge1xuICAgICAgYXJnczogYXJncyBhcyBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICAgIH0pXG4gICAgcmV0dXJuIDxTZXRNb2RlbEFuZENsb3NlIGFyZ3M9e2FyZ3N9IG9uRG9uZT17b25Eb25lfSAvPlxuICB9XG5cbiAgcmV0dXJuIDxNb2RlbFBpY2tlcldyYXBwZXIgb25Eb25lPXtvbkRvbmV9IC8+XG59XG5cbmZ1bmN0aW9uIHJlbmRlck1vZGVsTGFiZWwobW9kZWw6IHN0cmluZyB8IG51bGwpOiBzdHJpbmcge1xuICBjb25zdCByZW5kZXJlZCA9IHJlbmRlckRlZmF1bHRNb2RlbFNldHRpbmcoXG4gICAgbW9kZWwgPz8gZ2V0RGVmYXVsdE1haW5Mb29wTW9kZWxTZXR0aW5nKCksXG4gIClcbiAgcmV0dXJuIG1vZGVsID09PSBudWxsID8gYCR7cmVuZGVyZWR9IChkZWZhdWx0KWAgOiByZW5kZXJlZFxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBT0EsS0FBSyxNQUFNLE9BQU87QUFDekIsT0FBTyxLQUFLQyxLQUFLLE1BQU0sT0FBTztBQUM5QixjQUFjQyxvQkFBb0IsUUFBUSxtQkFBbUI7QUFDN0QsU0FBU0MsV0FBVyxRQUFRLGlDQUFpQztBQUM3RCxTQUFT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 \ No newline at end of file diff --git a/src/commands/oauth-refresh/index.js b/src/commands/oauth-refresh/index.js new file mode 100644 index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e --- /dev/null +++ b/src/commands/oauth-refresh/index.js @@ -0,0 +1 @@ +export default { isEnabled: () => false, isHidden: true, name: 'stub' }; diff --git a/src/commands/onboarding/index.js b/src/commands/onboarding/index.js new file mode 100644 index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e --- /dev/null +++ b/src/commands/onboarding/index.js @@ -0,0 +1 @@ +export default { isEnabled: () => false, isHidden: true, name: 'stub' }; diff --git a/src/commands/output-style/index.ts b/src/commands/output-style/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..5cb0e25e62094f477e8fe052c0a7db65842217cc --- /dev/null +++ b/src/commands/output-style/index.ts @@ -0,0 +1,11 @@ +import type { Command } from '../../commands.js' + +const outputStyle = { + type: 'local-jsx', + name: 'output-style', + description: 'Deprecated: use /config to change output style', + isHidden: true, + load: () => import('./output-style.js'), +} satisfies Command + +export default outputStyle diff --git a/src/commands/output-style/output-style.tsx b/src/commands/output-style/output-style.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9e26bf38271aa34831198486cd6a281b3837e7ed --- /dev/null +++ b/src/commands/output-style/output-style.tsx @@ -0,0 +1,7 @@ +import type { LocalJSXCommandOnDone } from '../../types/command.js'; +export async function call(onDone: LocalJSXCommandOnDone): Promise { + onDone('/output-style has been deprecated. Use /config to change your output style, or set it in your settings file. Changes take effect on the next session.', { + display: 'system' + }); +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJMb2NhbEpTWENvbW1hbmRPbkRvbmUiLCJjYWxsIiwib25Eb25lIiwiUHJvbWlzZSIsImRpc3BsYXkiXSwic291cmNlcyI6WyJvdXRwdXQtc3R5bGUudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIHsgTG9jYWxKU1hDb21tYW5kT25Eb25lIH0gZnJvbSAnLi4vLi4vdHlwZXMvY29tbWFuZC5qcydcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNhbGwob25Eb25lOiBMb2NhbEpTWENvbW1hbmRPbkRvbmUpOiBQcm9taXNlPHVuZGVmaW5lZD4ge1xuICBvbkRvbmUoXG4gICAgJy9vdXRwdXQtc3R5bGUgaGFzIGJlZW4gZGVwcmVjYXRlZC4gVXNlIC9jb25maWcgdG8gY2hhbmdlIHlvdXIgb3V0cHV0IHN0eWxlLCBvciBzZXQgaXQgaW4geW91ciBzZXR0aW5ncyBmaWxlLiBDaGFuZ2VzIHRha2UgZWZmZWN0IG9uIHRoZSBuZXh0IHNlc3Npb24uJyxcbiAgICB7IGRpc3BsYXk6ICdzeXN0ZW0nIH0sXG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IkFBQUEsY0FBY0EscUJBQXFCLFFBQVEsd0JBQXdCO0FBRW5FLE9BQU8sZUFBZUMsSUFBSUEsQ0FBQ0MsTUFBTSxFQUFFRixxQkFBcUIsQ0FBQyxFQUFFRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7RUFDNUVELE1BQU0sQ0FDSix1SkFBdUosRUFDdko7SUFBRUUsT0FBTyxFQUFFO0VBQVMsQ0FDdEIsQ0FBQztBQUNIIiwiaWdub3JlTGlzdCI6W119 \ No newline at end of file diff --git a/src/commands/passes/index.ts b/src/commands/passes/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc160132296f2fb58af3998a9caf8fd263cd0f0b --- /dev/null +++ b/src/commands/passes/index.ts @@ -0,0 +1,22 @@ +import type { Command } from '../../commands.js' +import { + checkCachedPassesEligibility, + getCachedReferrerReward, +} from '../../services/api/referral.js' + +export default { + type: 'local-jsx', + name: 'passes', + get description() { + const reward = getCachedReferrerReward() + if (reward) { + return 'Share a free week of Claude Code with friends and earn extra usage' + } + return 'Share a free week of Claude Code with friends' + }, + get isHidden() { + const { eligible, hasCache } = checkCachedPassesEligibility() + return !eligible || !hasCache + }, + load: () => import('./passes.js'), +} satisfies Command diff --git a/src/commands/passes/passes.tsx b/src/commands/passes/passes.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c17cb59471931801247123bf435029f4f2e94479 --- /dev/null +++ b/src/commands/passes/passes.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import { Passes } from '../../components/Passes/Passes.js'; +import { logEvent } from '../../services/analytics/index.js'; +import { getCachedRemainingPasses } from '../../services/api/referral.js'; +import type { LocalJSXCommandOnDone } from '../../types/command.js'; +import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js'; +export async function call(onDone: LocalJSXCommandOnDone): Promise { + // Mark that user has visited /passes so we stop showing the upsell + const config = getGlobalConfig(); + const isFirstVisit = !config.hasVisitedPasses; + if (isFirstVisit) { + const remaining = getCachedRemainingPasses(); + saveGlobalConfig(current => ({ + ...current, + hasVisitedPasses: true, + passesLastSeenRemaining: remaining ?? current.passesLastSeenRemaining + })); + } + logEvent('tengu_guest_passes_visited', { + is_first_visit: isFirstVisit + }); + return ; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlBhc3NlcyIsImxvZ0V2ZW50IiwiZ2V0Q2FjaGVkUmVtYWluaW5nUGFzc2VzIiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwiZ2V0R2xvYmFsQ29uZmlnIiwic2F2ZUdsb2JhbENvbmZpZyIsImNhbGwiLCJvbkRvbmUiLCJQcm9taXNlIiwiUmVhY3ROb2RlIiwiY29uZmlnIiwiaXNGaXJzdFZpc2l0IiwiaGFzVmlzaXRlZFBhc3NlcyIsInJlbWFpbmluZyIsImN1cnJlbnQiLCJwYXNzZXNMYXN0U2VlblJlbWFpbmluZyIsImlzX2ZpcnN0X3Zpc2l0Il0sInNvdXJjZXMiOlsicGFzc2VzLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IFBhc3NlcyB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvUGFzc2VzL1Bhc3Nlcy5qcydcbmltcG9ydCB7IGxvZ0V2ZW50IH0gZnJvbSAnLi4vLi4vc2VydmljZXMvYW5hbHl0aWNzL2luZGV4LmpzJ1xuaW1wb3J0IHsgZ2V0Q2FjaGVkUmVtYWluaW5nUGFzc2VzIH0gZnJvbSAnLi4vLi4vc2VydmljZXMvYXBpL3JlZmVycmFsLmpzJ1xuaW1wb3J0IHR5cGUgeyBMb2NhbEpTWENvbW1hbmRPbkRvbmUgfSBmcm9tICcuLi8uLi90eXBlcy9jb21tYW5kLmpzJ1xuaW1wb3J0IHsgZ2V0R2xvYmFsQ29uZmlnLCBzYXZlR2xvYmFsQ29uZmlnIH0gZnJvbSAnLi4vLi4vdXRpbHMvY29uZmlnLmpzJ1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY2FsbChcbiAgb25Eb25lOiBMb2NhbEpTWENvbW1hbmRPbkRvbmUsXG4pOiBQcm9taXNlPFJlYWN0LlJlYWN0Tm9kZT4ge1xuICAvLyBNYXJrIHRoYXQgdXNlciBoYXMgdmlzaXRlZCAvcGFzc2VzIHNvIHdlIHN0b3Agc2hvd2luZyB0aGUgdXBzZWxsXG4gIGNvbnN0IGNvbmZpZyA9IGdldEdsb2JhbENvbmZpZygpXG4gIGNvbnN0IGlzRmlyc3RWaXNpdCA9ICFjb25maWcuaGFzVmlzaXRlZFBhc3Nlc1xuICBpZiAoaXNGaXJzdFZpc2l0KSB7XG4gICAgY29uc3QgcmVtYWluaW5nID0gZ2V0Q2FjaGVkUmVtYWluaW5nUGFzc2VzKClcbiAgICBzYXZlR2xvYmFsQ29uZmlnKGN1cnJlbnQgPT4gKHtcbiAgICAgIC4uLmN1cnJlbnQsXG4gICAgICBoYXNWaXNpdGVkUGFzc2VzOiB0cnVlLFxuICAgICAgcGFzc2VzTGFzdFNlZW5SZW1haW5pbmc6IHJlbWFpbmluZyA/PyBjdXJyZW50LnBhc3Nlc0xhc3RTZWVuUmVtYWluaW5nLFxuICAgIH0pKVxuICB9XG4gIGxvZ0V2ZW50KCd0ZW5ndV9ndWVzdF9wYXNzZXNfdmlzaXRlZCcsIHsgaXNfZmlyc3RfdmlzaXQ6IGlzRmlyc3RWaXNpdCB9KVxuICByZXR1cm4gPFBhc3NlcyBvbkRvbmU9e29uRG9uZX0gLz5cbn1cbiJdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxNQUFNLFFBQVEsbUNBQW1DO0FBQzFELFNBQVNDLFFBQVEsUUFBUSxtQ0FBbUM7QUFDNUQsU0FBU0Msd0JBQXdCLFFBQVEsZ0NBQWdDO0FBQ3pFLGNBQWNDLHFCQUFxQixRQUFRLHdCQUF3QjtBQUNuRSxTQUFTQyxlQUFlLEVBQUVDLGdCQUFnQixRQUFRLHVCQUF1QjtBQUV6RSxPQUFPLGVBQWVDLElBQUlBLENBQ3hCQyxNQUFNLEVBQUVKLHFCQUFxQixDQUM5QixFQUFFSyxPQUFPLENBQUNULEtBQUssQ0FBQ1UsU0FBUyxDQUFDLENBQUM7RUFDMUI7RUFDQSxNQUFNQyxNQUFNLEdBQUdOLGVBQWUsQ0FBQyxDQUFDO0VBQ2hDLE1BQU1PLFlBQVksR0FBRyxDQUFDRCxNQUFNLENBQUNFLGdCQUFnQjtFQUM3QyxJQUFJRCxZQUFZLEVBQUU7SUFDaEIsTUFBTUUsU0FBUyxHQUFHWCx3QkFBd0IsQ0FBQyxDQUFDO0lBQzVDRyxnQkFBZ0IsQ0FBQ1MsT0FBTyxLQUFLO01BQzNCLEdBQUdBLE9BQU87TUFDVkYsZ0JBQWdCLEVBQUUsSUFBSTtNQUN0QkcsdUJBQXVCLEVBQUVGLFNBQVMsSUFBSUMsT0FBTyxDQUFDQztJQUNoRCxDQUFDLENBQUMsQ0FBQztFQUNMO0VBQ0FkLFFBQVEsQ0FBQyw0QkFBNEIsRUFBRTtJQUFFZSxjQUFjLEVBQUVMO0VBQWEsQ0FBQyxDQUFDO0VBQ3hFLE9BQU8sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUNKLE1BQU0sQ0FBQyxHQUFHO0FBQ25DIiwiaWdub3JlTGlzdCI6W119 \ No newline at end of file diff --git a/src/commands/perf-issue/index.js b/src/commands/perf-issue/index.js new file mode 100644 index 0000000000000000000000000000000000000000..e1a619d254332b7e0c662931b91aae3a9f9c3b0e --- /dev/null +++ b/src/commands/perf-issue/index.js @@ -0,0 +1 @@ +export default { isEnabled: () => false, isHidden: true, name: 'stub' }; diff --git a/src/commands/permissions/index.ts b/src/commands/permissions/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ef4641f0e342b2ff65ce92725648fab0ef9de14e --- /dev/null +++ b/src/commands/permissions/index.ts @@ -0,0 +1,11 @@ +import type { Command } from '../../commands.js' + +const permissions = { + type: 'local-jsx', + name: 'permissions', + aliases: ['allowed-tools'], + description: 'Manage allow & deny tool permission rules', + load: () => import('./permissions.js'), +} satisfies Command + +export default permissions diff --git a/src/commands/permissions/permissions.tsx b/src/commands/permissions/permissions.tsx new file mode 100644 index 0000000000000000000000000000000000000000..6248fd96b8e10b7fc067855bdeed5bb52f09f042 --- /dev/null +++ b/src/commands/permissions/permissions.tsx @@ -0,0 +1,10 @@ +import * as React from 'react'; +import { PermissionRuleList } from '../../components/permissions/rules/PermissionRuleList.js'; +import type { LocalJSXCommandCall } from '../../types/command.js'; +import { createPermissionRetryMessage } from '../../utils/messages.js'; +export const call: LocalJSXCommandCall = async (onDone, context) => { + return { + context.setMessages(prev => [...prev, createPermissionRetryMessage(commands)]); + }} />; +}; +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlBlcm1pc3Npb25SdWxlTGlzdCIsIkxvY2FsSlNYQ29tbWFuZENhbGwiLCJjcmVhdGVQZXJtaXNzaW9uUmV0cnlNZXNzYWdlIiwiY2FsbCIsIm9uRG9uZSIsImNvbnRleHQiLCJjb21tYW5kcyIsInNldE1lc3NhZ2VzIiwicHJldiJdLCJzb3VyY2VzIjpbInBlcm1pc3Npb25zLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IFBlcm1pc3Npb25SdWxlTGlzdCB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvcGVybWlzc2lvbnMvcnVsZXMvUGVybWlzc2lvblJ1bGVMaXN0LmpzJ1xuaW1wb3J0IHR5cGUgeyBMb2NhbEpTWENvbW1hbmRDYWxsIH0gZnJvbSAnLi4vLi4vdHlwZXMvY29tbWFuZC5qcydcbmltcG9ydCB7IGNyZWF0ZVBlcm1pc3Npb25SZXRyeU1lc3NhZ2UgfSBmcm9tICcuLi8uLi91dGlscy9tZXNzYWdlcy5qcydcblxuZXhwb3J0IGNvbnN0IGNhbGw6IExvY2FsSlNYQ29tbWFuZENhbGwgPSBhc3luYyAob25Eb25lLCBjb250ZXh0KSA9PiB7XG4gIHJldHVybiAoXG4gICAgPFBlcm1pc3Npb25SdWxlTGlzdFxuICAgICAgb25FeGl0PXtvbkRvbmV9XG4gICAgICBvblJldHJ5RGVuaWFscz17Y29tbWFuZHMgPT4ge1xuICAgICAgICBjb250ZXh0LnNldE1lc3NhZ2VzKHByZXYgPT4gW1xuICAgICAgICAgIC4uLnByZXYsXG4gICAgICAgICAgY3JlYXRlUGVybWlzc2lvblJldHJ5TWVzc2FnZShjb21tYW5kcyksXG4gICAgICAgIF0pXG4gICAgICB9fVxuICAgIC8+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxrQkFBa0IsUUFBUSwwREFBMEQ7QUFDN0YsY0FBY0MsbUJBQW1CLFFBQVEsd0JBQXdCO0FBQ2pFLFNBQVNDLDRCQUE0QixRQUFRLHlCQUF5QjtBQUV0RSxPQUFPLE1BQU1DLElBQUksRUFBRUYsbUJBQW1CLEdBQUcsTUFBQUUsQ0FBT0MsTUFBTSxFQUFFQyxPQUFPLEtBQUs7RUFDbEUsT0FDRSxDQUFDLGtCQUFrQixDQUNqQixNQUFNLENBQUMsQ0FBQ0QsTUFBTSxDQUFDLENBQ2YsY0FBYyxDQUFDLENBQUNFLFFBQVEsSUFBSTtJQUMxQkQsT0FBTyxDQUFDRSxXQUFXLENBQUNDLElBQUksSUFBSSxDQUMxQixHQUFHQSxJQUFJLEVBQ1BOLDRCQUE0QixDQUFDSSxRQUFRLENBQUMsQ0FDdkMsQ0FBQztFQUNKLENBQUMsQ0FBQyxHQUNGO0FBRU4sQ0FBQyIsImlnbm9yZUxpc3QiOltdfQ== \ No newline at end of file diff --git a/src/commands/plan/index.ts b/src/commands/plan/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c710899c9f634c18dc6964a9340c968969325efa --- /dev/null +++ b/src/commands/plan/index.ts @@ -0,0 +1,11 @@ +import type { Command } from '../../commands.js' + +const plan = { + type: 'local-jsx', + name: 'plan', + description: 'Enable plan mode or view the current session plan', + argumentHint: '[open|]', + load: () => import('./plan.js'), +} satisfies Command + +export default plan diff --git a/src/commands/plan/plan.tsx b/src/commands/plan/plan.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c8597b0c2f6852f04a975cf2dffa74348b03f55a --- /dev/null +++ b/src/commands/plan/plan.tsx @@ -0,0 +1,122 @@ +import { c as _c } from "react/compiler-runtime"; +import * as React from 'react'; +import { handlePlanModeTransition } from '../../bootstrap/state.js'; +import type { LocalJSXCommandContext } from '../../commands.js'; +import { Box, Text } from '../../ink.js'; +import type { LocalJSXCommandOnDone } from '../../types/command.js'; +import { getExternalEditor } from '../../utils/editor.js'; +import { toIDEDisplayName } from '../../utils/ide.js'; +import { applyPermissionUpdate } from '../../utils/permissions/PermissionUpdate.js'; +import { prepareContextForPlanMode } from '../../utils/permissions/permissionSetup.js'; +import { getPlan, getPlanFilePath } from '../../utils/plans.js'; +import { editFileInEditor } from '../../utils/promptEditor.js'; +import { renderToString } from '../../utils/staticRender.js'; +function PlanDisplay(t0) { + const $ = _c(11); + const { + planContent, + planPath, + editorName + } = t0; + let t1; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t1 = Current Plan; + $[0] = t1; + } else { + t1 = $[0]; + } + let t2; + if ($[1] !== planPath) { + t2 = {planPath}; + $[1] = planPath; + $[2] = t2; + } else { + t2 = $[2]; + } + let t3; + if ($[3] !== planContent) { + t3 = {planContent}; + $[3] = planContent; + $[4] = t3; + } else { + t3 = $[4]; + } + let t4; + if ($[5] !== editorName) { + t4 = editorName && "/plan open" to edit this plan in {editorName}; + $[5] = editorName; + $[6] = t4; + } else { + t4 = $[6]; + } + let t5; + if ($[7] !== t2 || $[8] !== t3 || $[9] !== t4) { + t5 = {t1}{t2}{t3}{t4}; + $[7] = t2; + $[8] = t3; + $[9] = t4; + $[10] = t5; + } else { + t5 = $[10]; + } + return t5; +} +export async function call(onDone: LocalJSXCommandOnDone, context: LocalJSXCommandContext, args: string): Promise { + const { + getAppState, + setAppState + } = context; + const appState = getAppState(); + const currentMode = appState.toolPermissionContext.mode; + + // If not in plan mode, enable it + if (currentMode !== 'plan') { + handlePlanModeTransition(currentMode, 'plan'); + setAppState(prev => ({ + ...prev, + toolPermissionContext: applyPermissionUpdate(prepareContextForPlanMode(prev.toolPermissionContext), { + type: 'setMode', + mode: 'plan', + destination: 'session' + }) + })); + const description = args.trim(); + if (description && description !== 'open') { + onDone('Enabled plan mode', { + shouldQuery: true + }); + } else { + onDone('Enabled plan mode'); + } + return null; + } + + // Already in plan mode - show the current plan + const planContent = getPlan(); + const planPath = getPlanFilePath(); + if (!planContent) { + onDone('Already in plan mode. No plan written yet.'); + return null; + } + + // If user typed "/plan open", open in editor + const argList = args.trim().split(/\s+/); + if (argList[0] === 'open') { + const result = await editFileInEditor(planPath); + if (result.error) { + onDone(`Failed to open plan in editor: ${result.error}`); + } else { + onDone(`Opened plan in editor: ${planPath}`); + } + return null; + } + const editor = getExternalEditor(); + const editorName = editor ? toIDEDisplayName(editor) : undefined; + const display = ; + + // Render to string and pass to onDone like local commands do + const output = await renderToString(display); + onDone(output); + return null; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsImhhbmRsZVBsYW5Nb2RlVHJhbnNpdGlvbiIsIkxvY2FsSlNYQ29tbWFuZENvbnRleHQiLCJCb3giLCJUZXh0IiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwiZ2V0RXh0ZXJuYWxFZGl0b3IiLCJ0b0lERURpc3BsYXlOYW1lIiwiYXBwbHlQZXJtaXNzaW9uVXBkYXRlIiwicHJlcGFyZUNvbnRleHRGb3JQbGFuTW9kZSIsImdldFBsYW4iLCJnZXRQbGFuRmlsZVBhdGgiLCJlZGl0RmlsZUluRWRpdG9yIiwicmVuZGVyVG9TdHJpbmciLCJQbGFuRGlzcGxheSIsInQwIiwiJCIsIl9jIiwicGxhbkNvbnRlbnQiLCJwbGFuUGF0aCIsImVkaXRvck5hbWUiLCJ0MSIsIlN5bWJvbCIsImZvciIsInQyIiwidDMiLCJ0NCIsInQ1IiwiY2FsbCIsIm9uRG9uZSIsImNvbnRleHQiLCJhcmdzIiwiUHJvbWlzZSIsIlJlYWN0Tm9kZSIsImdldEFwcFN0YXRlIiwic2V0QXBwU3RhdGUiLCJhcHBTdGF0ZSIsImN1cnJlbnRNb2RlIiwidG9vbFBlcm1pc3Npb25Db250ZXh0IiwibW9kZSIsInByZXYiLCJ0eXBlIiwiZGVzdGluYXRpb24iLCJkZXNjcmlwdGlvbiIsInRyaW0iLCJzaG91bGRRdWVyeSIsImFyZ0xpc3QiLCJzcGxpdCIsInJlc3VsdCIsImVycm9yIiwiZWRpdG9yIiwidW5kZWZpbmVkIiwiZGlzcGxheSIsIm91dHB1dCJdLCJzb3VyY2VzIjpbInBsYW4udHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgaGFuZGxlUGxhbk1vZGVUcmFuc2l0aW9uIH0gZnJvbSAnLi4vLi4vYm9vdHN0cmFwL3N0YXRlLmpzJ1xuaW1wb3J0IHR5cGUgeyBMb2NhbEpTWENvbW1hbmRDb250ZXh0IH0gZnJvbSAnLi4vLi4vY29tbWFuZHMuanMnXG5pbXBvcnQgeyBCb3gsIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5pbXBvcnQgeyBnZXRFeHRlcm5hbEVkaXRvciB9IGZyb20gJy4uLy4uL3V0aWxzL2VkaXRvci5qcydcbmltcG9ydCB7IHRvSURFRGlzcGxheU5hbWUgfSBmcm9tICcuLi8uLi91dGlscy9pZGUuanMnXG5pbXBvcnQgeyBhcHBseVBlcm1pc3Npb25VcGRhdGUgfSBmcm9tICcuLi8uLi91dGlscy9wZXJtaXNzaW9ucy9QZXJtaXNzaW9uVXBkYXRlLmpzJ1xuaW1wb3J0IHsgcHJlcGFyZUNvbnRleHRGb3JQbGFuTW9kZSB9IGZyb20gJy4uLy4uL3V0aWxzL3Blcm1pc3Npb25zL3Blcm1pc3Npb25TZXR1cC5qcydcbmltcG9ydCB7IGdldFBsYW4sIGdldFBsYW5GaWxlUGF0aCB9IGZyb20gJy4uLy4uL3V0aWxzL3BsYW5zLmpzJ1xuaW1wb3J0IHsgZWRpdEZpbGVJbkVkaXRvciB9IGZyb20gJy4uLy4uL3V0aWxzL3Byb21wdEVkaXRvci5qcydcbmltcG9ydCB7IHJlbmRlclRvU3RyaW5nIH0gZnJvbSAnLi4vLi4vdXRpbHMvc3RhdGljUmVuZGVyLmpzJ1xuXG5mdW5jdGlvbiBQbGFuRGlzcGxheSh7XG4gIHBsYW5Db250ZW50LFxuICBwbGFuUGF0aCxcbiAgZWRpdG9yTmFtZSxcbn06IHtcbiAgcGxhbkNvbnRlbnQ6IHN0cmluZ1xuICBwbGFuUGF0aDogc3RyaW5nXG4gIGVkaXRvck5hbWU6IHN0cmluZyB8IHVuZGVmaW5lZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIHJldHVybiAoXG4gICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICA8VGV4dCBib2xkPkN1cnJlbnQgUGxhbjwvVGV4dD5cbiAgICAgIDxUZXh0IGRpbUNvbG9yPntwbGFuUGF0aH08L1RleHQ+XG4gICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgIDxUZXh0PntwbGFuQ29udGVudH08L1RleHQ+XG4gICAgICA8L0JveD5cbiAgICAgIHtlZGl0b3JOYW1lICYmIChcbiAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPiZxdW90Oy9wbGFuIG9wZW4mcXVvdDs8L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IHRvIGVkaXQgdGhpcyBwbGFuIGluIDwvVGV4dD5cbiAgICAgICAgICA8VGV4dCBib2xkIGRpbUNvbG9yPlxuICAgICAgICAgICAge2VkaXRvck5hbWV9XG4gICAgICAgICAgPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICl9XG4gICAgPC9Cb3g+XG4gIClcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNhbGwoXG4gIG9uRG9uZTogTG9jYWxKU1hDb21tYW5kT25Eb25lLFxuICBjb250ZXh0OiBMb2NhbEpTWENvbW1hbmRDb250ZXh0LFxuICBhcmdzOiBzdHJpbmcsXG4pOiBQcm9taXNlPFJlYWN0LlJlYWN0Tm9kZT4ge1xuICBjb25zdCB7IGdldEFwcFN0YXRlLCBzZXRBcHBTdGF0ZSB9ID0gY29udGV4dFxuICBjb25zdCBhcHBTdGF0ZSA9IGdldEFwcFN0YXRlKClcbiAgY29uc3QgY3VycmVudE1vZGUgPSBhcHBTdGF0ZS50b29sUGVybWlzc2lvbkNvbnRleHQubW9kZVxuXG4gIC8vIElmIG5vdCBpbiBwbGFuIG1vZGUsIGVuYWJsZSBpdFxuICBpZiAoY3VycmVudE1vZGUgIT09ICdwbGFuJykge1xuICAgIGhhbmRsZVBsYW5Nb2RlVHJhbnNpdGlvbihjdXJyZW50TW9kZSwgJ3BsYW4nKVxuICAgIHNldEFwcFN0YXRlKHByZXYgPT4gKHtcbiAgICAgIC4uLnByZXYsXG4gICAgICB0b29sUGVybWlzc2lvbkNvbnRleHQ6IGFwcGx5UGVybWlzc2lvblVwZGF0ZShcbiAgICAgICAgcHJlcGFyZUNvbnRleHRGb3JQbGFuTW9kZShwcmV2LnRvb2xQZXJtaXNzaW9uQ29udGV4dCksXG4gICAgICAgIHsgdHlwZTogJ3NldE1vZGUnLCBtb2RlOiAncGxhbicsIGRlc3RpbmF0aW9uOiAnc2Vzc2lvbicgfSxcbiAgICAgICksXG4gICAgfSkpXG4gICAgY29uc3QgZGVzY3JpcHRpb24gPSBhcmdzLnRyaW0oKVxuICAgIGlmIChkZXNjcmlwdGlvbiAmJiBkZXNjcmlwdGlvbiAhPT0gJ29wZW4nKSB7XG4gICAgICBvbkRvbmUoJ0VuYWJsZWQgcGxhbiBtb2RlJywgeyBzaG91bGRRdWVyeTogdHJ1ZSB9KVxuICAgIH0gZWxzZSB7XG4gICAgICBvbkRvbmUoJ0VuYWJsZWQgcGxhbiBtb2RlJylcbiAgICB9XG4gICAgcmV0dXJuIG51bGxcbiAgfVxuXG4gIC8vIEFscmVhZHkgaW4gcGxhbiBtb2RlIC0gc2hvdyB0aGUgY3VycmVudCBwbGFuXG4gIGNvbnN0IHBsYW5Db250ZW50ID0gZ2V0UGxhbigpXG4gIGNvbnN0IHBsYW5QYXRoID0gZ2V0UGxhbkZpbGVQYXRoKClcblxuICBpZiAoIXBsYW5Db250ZW50KSB7XG4gICAgb25Eb25lKCdBbHJlYWR5IGluIHBsYW4gbW9kZS4gTm8gcGxhbiB3cml0dGVuIHlldC4nKVxuICAgIHJldHVybiBudWxsXG4gIH1cblxuICAvLyBJZiB1c2VyIHR5cGVkIFwiL3BsYW4gb3BlblwiLCBvcGVuIGluIGVkaXRvclxuICBjb25zdCBhcmdMaXN0ID0gYXJncy50cmltKCkuc3BsaXQoL1xccysvKVxuICBpZiAoYXJnTGlzdFswXSA9PT0gJ29wZW4nKSB7XG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgZWRpdEZpbGVJbkVkaXRvcihwbGFuUGF0aClcbiAgICBpZiAocmVzdWx0LmVycm9yKSB7XG4gICAgICBvbkRvbmUoYEZhaWxlZCB0byBvcGVuIHBsYW4gaW4gZWRpdG9yOiAke3Jlc3VsdC5lcnJvcn1gKVxuICAgIH0gZWxzZSB7XG4gICAgICBvbkRvbmUoYE9wZW5lZCBwbGFuIGluIGVkaXRvcjogJHtwbGFuUGF0aH1gKVxuICAgIH1cbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgY29uc3QgZWRpdG9yID0gZ2V0RXh0ZXJuYWxFZGl0b3IoKVxuICBjb25zdCBlZGl0b3JOYW1lID0gZWRpdG9yID8gdG9JREVEaXNwbGF5TmFtZShlZGl0b3IpIDogdW5kZWZpbmVkXG5cbiAgY29uc3QgZGlzcGxheSA9IChcbiAgICA8UGxhbkRpc3BsYXlcbiAgICAgIHBsYW5Db250ZW50PXtwbGFuQ29udGVudH1cbiAgICAgIHBsYW5QYXRoPXtwbGFuUGF0aH1cbiAgICAgIGVkaXRvck5hbWU9e2VkaXRvck5hbWV9XG4gICAgLz5cbiAgKVxuXG4gIC8vIFJlbmRlciB0byBzdHJpbmcgYW5kIHBhc3MgdG8gb25Eb25lIGxpa2UgbG9jYWwgY29tbWFuZHMgZG9cbiAgY29uc3Qgb3V0cHV0ID0gYXdhaXQgcmVuZGVyVG9TdHJpbmcoZGlzcGxheSlcbiAgb25Eb25lKG91dHB1dClcbiAgcmV0dXJuIG51bGxcbn1cbiJdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0Msd0JBQXdCLFFBQVEsMEJBQTBCO0FBQ25FLGNBQWNDLHNCQUFzQixRQUFRLG1CQUFtQjtBQUMvRCxTQUFTQyxHQUFHLEVBQUVDLElBQUksUUFBUSxjQUFjO0FBQ3hDLGNBQWNDLHFCQUFxQixRQUFRLHdCQUF3QjtBQUNuRSxTQUFTQyxpQkFBaUIsUUFBUSx1QkFBdUI7QUFDekQsU0FBU0MsZ0JBQWdCLFFBQVEsb0JBQW9CO0FBQ3JELFNBQVNDLHFCQUFxQixRQUFRLDZDQUE2QztBQUNuRixTQUFTQyx5QkFBeUIsUUFBUSw0Q0FBNEM7QUFDdEYsU0FBU0MsT0FBTyxFQUFFQyxlQUFlLFFBQVEsc0JBQXNCO0FBQy9ELFNBQVNDLGdCQUFnQixRQUFRLDZCQUE2QjtBQUM5RCxTQUFTQyxjQUFjLFFBQVEsNkJBQTZCO0FBRTVELFNBQUFDLFlBQUFDLEVBQUE7RUFBQSxNQUFBQyxDQUFBLEdBQUFDLEVBQUE7RUFBcUI7SUFBQUMsV0FBQTtJQUFBQyxRQUFBO0lBQUFDO0VBQUEsSUFB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 \ No newline at end of file diff --git a/src/commands/plugin/AddMarketplace.tsx b/src/commands/plugin/AddMarketplace.tsx new file mode 100644 index 0000000000000000000000000000000000000000..79b8188db376c397ac1df2030010d0f692fe67f9 --- /dev/null +++ b/src/commands/plugin/AddMarketplace.tsx @@ -0,0 +1,162 @@ +import * as React from 'react'; +import { useEffect, useRef, useState } from 'react'; +import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from 'src/services/analytics/index.js'; +import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js'; +import { Byline } from '../../components/design-system/Byline.js'; +import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js'; +import { Spinner } from '../../components/Spinner.js'; +import TextInput from '../../components/TextInput.js'; +import { Box, Text } from '../../ink.js'; +import { toError } from '../../utils/errors.js'; +import { logError } from '../../utils/log.js'; +import { clearAllCaches } from '../../utils/plugins/cacheUtils.js'; +import { addMarketplaceSource, saveMarketplaceToSettings } from '../../utils/plugins/marketplaceManager.js'; +import { parseMarketplaceInput } from '../../utils/plugins/parseMarketplaceInput.js'; +import type { ViewState } from './types.js'; +type Props = { + inputValue: string; + setInputValue: (value: string) => void; + cursorOffset: number; + setCursorOffset: (offset: number) => void; + error: string | null; + setError: (error: string | null) => void; + result: string | null; + setResult: (result: string | null) => void; + setViewState: (state: ViewState) => void; + onAddComplete?: () => void | Promise; + cliMode?: boolean; +}; +export function AddMarketplace({ + inputValue, + setInputValue, + cursorOffset, + setCursorOffset, + error, + setError, + result, + setResult, + setViewState, + onAddComplete, + cliMode = false +}: Props): React.ReactNode { + const hasAttemptedAutoAdd = useRef(false); + const [isLoading, setLoading] = useState(false); + const [progressMessage, setProgressMessage] = useState(''); + const handleAdd = async () => { + const input = inputValue.trim(); + if (!input) { + setError('Please enter a marketplace source'); + return; + } + const parsed = await parseMarketplaceInput(input); + if (!parsed) { + setError('Invalid marketplace source format. Try: owner/repo, https://..., or ./path'); + return; + } + + // Check if parseMarketplaceInput returned an error + if ('error' in parsed) { + setError(parsed.error); + return; + } + setError(null); + try { + setLoading(true); + setProgressMessage(''); + const { + name, + resolvedSource + } = await addMarketplaceSource(parsed, message => { + setProgressMessage(message); + }); + saveMarketplaceToSettings(name, { + source: resolvedSource + }); + clearAllCaches(); + let sourceType = parsed.source; + if (parsed.source === 'github') { + sourceType = parsed.repo as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS; + } + logEvent('tengu_marketplace_added', { + source_type: sourceType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS + }); + if (onAddComplete) { + await onAddComplete(); + } + setProgressMessage(''); + setLoading(false); + if (cliMode) { + // In CLI mode, set result to trigger completion + setResult(`Successfully added marketplace: ${name}`); + } else { + // In interactive mode, switch to browse view + setViewState({ + type: 'browse-marketplace', + targetMarketplace: name + }); + } + } catch (err) { + const error = toError(err); + logError(error); + setError(error.message); + setProgressMessage(''); + setLoading(false); + if (cliMode) { + // In CLI mode, set result with error to trigger completion + setResult(`Error: ${error.message}`); + } else { + setResult(null); + } + } + }; + + // Auto-add if inputValue is provided + useEffect(() => { + if (inputValue && !hasAttemptedAutoAdd.current && !error && !result) { + hasAttemptedAutoAdd.current = true; + void handleAdd(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + // biome-ignore lint/correctness/useExhaustiveDependencies: intentional + }, []); // Only run once on mount + + return + + + Add Marketplace + + + Enter marketplace source: + Examples: + · owner/repo (GitHub) + · git@github.com:owner/repo.git (SSH) + · https://example.com/marketplace.json + · ./path/to/marketplace + + + + + {isLoading && + + + {progressMessage || 'Adding marketplace to configuration…'} + + } + {error && + {error} + } + {result && + {result} + } + + + + + + + + + + ; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsInVzZUVmZmVjdCIsInVzZVJlZiIsInVzZVN0YXRlIiwiQW5hbHl0aWNzTWV0YWRhdGFfSV9WRVJJRklFRF9USElTX0lTX05PVF9DT0RFX09SX0ZJTEVQQVRIUyIsImxvZ0V2ZW50IiwiQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50IiwiQnlsaW5lIiwiS2V5Ym9hcmRTaG9ydGN1dEhpbnQiLCJTcGlubmVyIiwiVGV4dElucHV0IiwiQm94IiwiVGV4dCIsInRvRXJyb3IiLCJsb2dFcnJvciIsImNsZWFyQWxsQ2FjaGVzIiwiYWRkTWFya2V0cGxhY2VTb3VyY2UiLCJzYXZlTWFya2V0cGxhY2VUb1NldHRpbmdzIiwicGFyc2VNYXJrZXRwbGFjZUlucHV0IiwiVmlld1N0YXRlIiwiUHJvcHMiLCJpbnB1dFZhbHVlIiwic2V0SW5wdXRWYWx1ZSIsInZhbHVlIiwiY3Vyc29yT2Zmc2V0Iiwic2V0Q3Vyc29yT2Zmc2V0Iiwib2Zmc2V0IiwiZXJyb3IiLCJzZXRFcnJvciIsInJlc3VsdCIsInNldFJlc3VsdCIsInNldFZpZXdTdGF0ZSIsInN0YXRlIiwib25BZGRDb21wbGV0ZSIsIlByb21pc2UiLCJjbGlNb2RlIiwiQWRkTWFya2V0cGxhY2UiLCJSZWFjdE5vZGUiLCJoYXNBdHRlbXB0ZWRBdXRvQWRkIiwiaXNMb2FkaW5nIiwic2V0TG9hZGluZyIsInByb2dyZXNzTWVzc2FnZSIsInNldFByb2dyZXNzTWVzc2FnZSIsImhhbmRsZUFkZCIsImlucHV0IiwidHJpbSIsInBhcnNlZCIsIm5hbWUiLCJyZXNvbHZlZFNvdXJjZSIsIm1lc3NhZ2UiLCJzb3VyY2UiLCJzb3VyY2VUeXBlIiwicmVwbyIsInNvdXJjZV90eXBlIiwidHlwZSIsInRhcmdldE1hcmtldHBsYWNlIiwiZXJyIiwiY3VycmVudCJdLCJzb3VyY2VzIjpbIkFkZE1hcmtldHBsYWNlLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IHVzZUVmZmVjdCwgdXNlUmVmLCB1c2VTdGF0ZSB9IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHtcbiAgdHlwZSBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICBsb2dFdmVudCxcbn0gZnJvbSAnc3JjL3NlcnZpY2VzL2FuYWx5dGljcy9pbmRleC5qcydcbmltcG9ydCB7IENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludCB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50LmpzJ1xuaW1wb3J0IHsgQnlsaW5lIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL0J5bGluZS5qcydcbmltcG9ydCB7IEtleWJvYXJkU2hvcnRjdXRIaW50IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL0tleWJvYXJkU2hvcnRjdXRIaW50LmpzJ1xuaW1wb3J0IHsgU3Bpbm5lciB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvU3Bpbm5lci5qcydcbmltcG9ydCBUZXh0SW5wdXQgZnJvbSAnLi4vLi4vY29tcG9uZW50cy9UZXh0SW5wdXQuanMnXG5pbXBvcnQgeyBCb3gsIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgeyB0b0Vycm9yIH0gZnJvbSAnLi4vLi4vdXRpbHMvZXJyb3JzLmpzJ1xuaW1wb3J0IHsgbG9nRXJyb3IgfSBmcm9tICcuLi8uLi91dGlscy9sb2cuanMnXG5pbXBvcnQgeyBjbGVhckFsbENhY2hlcyB9IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvY2FjaGVVdGlscy5qcydcbmltcG9ydCB7XG4gIGFkZE1hcmtldHBsYWNlU291cmNlLFxuICBzYXZlTWFya2V0cGxhY2VUb1NldHRpbmdzLFxufSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL21hcmtldHBsYWNlTWFuYWdlci5qcydcbmltcG9ydCB7IHBhcnNlTWFya2V0cGxhY2VJbnB1dCB9IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvcGFyc2VNYXJrZXRwbGFjZUlucHV0LmpzJ1xuaW1wb3J0IHR5cGUgeyBWaWV3U3RhdGUgfSBmcm9tICcuL3R5cGVzLmpzJ1xuXG50eXBlIFByb3BzID0ge1xuICBpbnB1dFZhbHVlOiBzdHJpbmdcbiAgc2V0SW5wdXRWYWx1ZTogKHZhbHVlOiBzdHJpbmcpID0+IHZvaWRcbiAgY3Vyc29yT2Zmc2V0OiBudW1iZXJcbiAgc2V0Q3Vyc29yT2Zmc2V0OiAob2Zmc2V0OiBudW1iZXIpID0+IHZvaWRcbiAgZXJyb3I6IHN0cmluZyB8IG51bGxcbiAgc2V0RXJyb3I6IChlcnJvcjogc3RyaW5nIHwgbnVsbCkgPT4gdm9pZFxuICByZXN1bHQ6IHN0cmluZyB8IG51bGxcbiAgc2V0UmVzdWx0OiAocmVzdWx0OiBzdHJpbmcgfCBudWxsKSA9PiB2b2lkXG4gIHNldFZpZXdTdGF0ZTogKHN0YXRlOiBWaWV3U3RhdGUpID0+IHZvaWRcbiAgb25BZGRDb21wbGV0ZT86ICgpID0+IHZvaWQgfCBQcm9taXNlPHZvaWQ+XG4gIGNsaU1vZGU/OiBib29sZWFuXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBBZGRNYXJrZXRwbGFjZSh7XG4gIGlucHV0VmFsdWUsXG4gIHNldElucHV0VmFsdWUsXG4gIGN1cnNvck9mZnNldCxcbiAgc2V0Q3Vyc29yT2Zmc2V0LFxuICBlcnJvcixcbiAgc2V0RXJyb3IsXG4gIHJlc3VsdCxcbiAgc2V0UmVzdWx0LFxuICBzZXRWaWV3U3RhdGUsXG4gIG9uQWRkQ29tcGxldGUsXG4gIGNsaU1vZGUgPSBmYWxzZSxcbn06IFByb3BzKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgY29uc3QgaGFzQXR0ZW1wdGVkQXV0b0FkZCA9IHVzZVJlZihmYWxzZSlcbiAgY29uc3QgW2lzTG9hZGluZywgc2V0TG9hZGluZ10gPSB1c2VTdGF0ZShmYWxzZSlcbiAgY29uc3QgW3Byb2dyZXNzTWVzc2FnZSwgc2V0UHJvZ3Jlc3NNZXNzYWdlXSA9IHVzZVN0YXRlPHN0cmluZz4oJycpXG5cbiAgY29uc3QgaGFuZGxlQWRkID0gYXN5bmMgKCkgPT4ge1xuICAgIGNvbnN0IGlucHV0ID0gaW5wdXRWYWx1ZS50cmltKClcbiAgICBpZiAoIWlucHV0KSB7XG4gICAgICBzZXRFcnJvcignUGxlYXNlIGVudGVyIGEgbWFya2V0cGxhY2Ugc291cmNlJylcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGNvbnN0IHBhcnNlZCA9IGF3YWl0IHBhcnNlTWFya2V0cGxhY2VJbnB1dChpbnB1dClcbiAgICBpZiAoIXBhcnNlZCkge1xuICAgICAgc2V0RXJyb3IoXG4gICAgICAgICdJbnZhbGlkIG1hcmtldHBsYWNlIHNvdXJjZSBmb3JtYXQuIFRyeTogb3duZXIvcmVwbywgaHR0cHM6Ly8uLi4sIG9yIC4vcGF0aCcsXG4gICAgICApXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICAvLyBDaGVjayBpZiBwYXJzZU1hcmtldHBsYWNlSW5wdXQgcmV0dXJuZWQgYW4gZXJyb3JcbiAgICBpZiAoJ2Vycm9yJyBpbiBwYXJzZWQpIHtcbiAgICAgIHNldEVycm9yKHBhcnNlZC5lcnJvcilcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIHNldEVycm9yKG51bGwpXG5cbiAgICB0cnkge1xuICAgICAgc2V0TG9hZGluZyh0cnVlKVxuICAgICAgc2V0UHJvZ3Jlc3NNZXNzYWdlKCcnKVxuICAgICAgY29uc3QgeyBuYW1lLCByZXNvbHZlZFNvdXJjZSB9ID0gYXdhaXQgYWRkTWFya2V0cGxhY2VTb3VyY2UoXG4gICAgICAgIHBhcnNlZCxcbiAgICAgICAgbWVzc2FnZSA9PiB7XG4gICAgICAgICAgc2V0UHJvZ3Jlc3NNZXNzYWdlKG1lc3NhZ2UpXG4gICAgICAgIH0sXG4gICAgICApXG4gICAgICBzYXZlTWFya2V0cGxhY2VUb1NldHRpbmdzKG5hbWUsIHsgc291cmNlOiByZXNvbHZlZFNvdXJjZSB9KVxuICAgICAgY2xlYXJBbGxDYWNoZXMoKVxuXG4gICAgICBsZXQgc291cmNlVHlwZSA9IHBhcnNlZC5zb3VyY2VcbiAgICAgIGlmIChwYXJzZWQuc291cmNlID09PSAnZ2l0aHViJykge1xuICAgICAgICBzb3VyY2VUeXBlID1cbiAgICAgICAgICBwYXJzZWQucmVwbyBhcyBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTXG4gICAgICB9XG5cbiAgICAgIGxvZ0V2ZW50KCd0ZW5ndV9tYXJrZXRwbGFjZV9hZGRlZCcsIHtcbiAgICAgICAgc291cmNlX3R5cGU6XG4gICAgICAgICAgc291cmNlVHlwZSBhcyBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICAgICAgfSlcblxuICAgICAgaWYgKG9uQWRkQ29tcGxldGUpIHtcbiAgICAgICAgYXdhaXQgb25BZGRDb21wbGV0ZSgpXG4gICAgICB9XG5cbiAgICAgIHNldFByb2dyZXNzTWVzc2FnZSgnJylcbiAgICAgIHNldExvYWRpbmcoZmFsc2UpXG5cbiAgICAgIGlmIChjbGlNb2RlKSB7XG4gICAgICAgIC8vIEluIENMSSBtb2RlLCBzZXQgcmVzdWx0IHRvIHRyaWdnZXIgY29tcGxldGlvblxuICAgICAgICBzZXRSZXN1bHQoYFN1Y2Nlc3NmdWxseSBhZGRlZCBtYXJrZXRwbGFjZTogJHtuYW1lfWApXG4gICAgICB9IGVsc2Uge1xuICAgICAgICAvLyBJbiBpbnRlcmFjdGl2ZSBtb2RlLCBzd2l0Y2ggdG8gYnJvd3NlIHZpZXdcbiAgICAgICAgc2V0Vmlld1N0YXRlKHsgdHlwZTogJ2Jyb3dzZS1tYXJrZXRwbGFjZScsIHRhcmdldE1hcmtldHBsYWNlOiBuYW1lIH0pXG4gICAgICB9XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBjb25zdCBlcnJvciA9IHRvRXJyb3IoZXJyKVxuICAgICAgbG9nRXJyb3IoZXJyb3IpXG4gICAgICBzZXRFcnJvcihlcnJvci5tZXNzYWdlKVxuICAgICAgc2V0UHJvZ3Jlc3NNZXNzYWdlKCcnKVxuICAgICAgc2V0TG9hZGluZyhmYWxzZSlcblxuICAgICAgaWYgKGNsaU1vZGUpIHtcbiAgICAgICAgLy8gSW4gQ0xJIG1vZGUsIHNldCByZXN1bHQgd2l0aCBlcnJvciB0byB0cmlnZ2VyIGNvbXBsZXRpb25cbiAgICAgICAgc2V0UmVzdWx0KGBFcnJvcjogJHtlcnJvci5tZXNzYWdlfWApXG4gICAgICB9IGVsc2Uge1xuICAgICAgICBzZXRSZXN1bHQobnVsbClcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvLyBBdXRvLWFkZCBpZiBpbnB1dFZhbHVlIGlzIHByb3ZpZGVkXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgaWYgKGlucHV0VmFsdWUgJiYgIWhhc0F0dGVtcHRlZEF1dG9BZGQuY3VycmVudCAmJiAhZXJyb3IgJiYgIXJlc3VsdCkge1xuICAgICAgaGFzQXR0ZW1wdGVkQXV0b0FkZC5jdXJyZW50ID0gdHJ1ZVxuICAgICAgdm9pZCBoYW5kbGVBZGQoKVxuICAgIH1cbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgcmVhY3QtaG9va3MvZXhoYXVzdGl2ZS1kZXBzXG4gICAgLy8gYmlvbWUtaWdub3JlIGxpbnQvY29ycmVjdG5lc3MvdXNlRXhoYXVzdGl2ZURlcGVuZGVuY2llczogaW50ZW50aW9uYWxcbiAgfSwgW10pIC8vIE9ubHkgcnVuIG9uY2Ugb24gbW91bnRcblxuICByZXR1cm4gKFxuICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCIgcGFkZGluZ1g9ezF9IGJvcmRlclN0eWxlPVwicm91bmRcIj5cbiAgICAgICAgPEJveCBtYXJnaW5Cb3R0b209ezF9PlxuICAgICAgICAgIDxUZXh0IGJvbGQ+QWRkIE1hcmtldHBsYWNlPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgICAgPFRleHQ+RW50ZXIgbWFya2V0cGxhY2Ugc291cmNlOjwvVGV4dD5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5FeGFtcGxlczo8L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IMK3IG93bmVyL3JlcG8gKEdpdEh1Yik8L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IMK3IGdpdEBnaXRodWIuY29tOm93bmVyL3JlcG8uZ2l0IChTU0gpPC9UZXh0PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPiDCtyBodHRwczovL2V4YW1wbGUuY29tL21hcmtldHBsYWNlLmpzb248L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IMK3IC4vcGF0aC90by9tYXJrZXRwbGFjZTwvVGV4dD5cbiAgICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgICA8VGV4dElucHV0XG4gICAgICAgICAgICAgIHZhbHVlPXtpbnB1dFZhbHVlfVxuICAgICAgICAgICAgICBvbkNoYW5nZT17c2V0SW5wdXRWYWx1ZX1cbiAgICAgICAgICAgICAgb25TdWJtaXQ9e2hhbmRsZUFkZH1cbiAgICAgICAgICAgICAgY29sdW1ucz17ODB9XG4gICAgICAgICAgICAgIGN1cnNvck9mZnNldD17Y3Vyc29yT2Zmc2V0fVxuICAgICAgICAgICAgICBvbkNoYW5nZUN1cnNvck9mZnNldD17c2V0Q3Vyc29yT2Zmc2V0fVxuICAgICAgICAgICAgICBmb2N1c1xuICAgICAgICAgICAgICBzaG93Q3Vyc29yXG4gICAgICAgICAgICAvPlxuICAgICAgICAgIDwvQm94PlxuICAgICAgICA8L0JveD5cbiAgICAgICAge2lzTG9hZGluZyAmJiAoXG4gICAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgICAgPFNwaW5uZXIgLz5cbiAgICAgICAgICAgIDxUZXh0PlxuICAgICAgICAgICAgICB7cHJvZ3Jlc3NNZXNzYWdlIHx8ICdBZGRpbmcgbWFya2V0cGxhY2UgdG8gY29uZmlndXJhdGlvbuKApid9XG4gICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICl9XG4gICAgICAgIHtlcnJvciAmJiAoXG4gICAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgICAgPFRleHQgY29sb3I9XCJlcnJvclwiPntlcnJvcn08L1RleHQ+XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICl9XG4gICAgICAgIHtyZXN1bHQgJiYgKFxuICAgICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICAgIDxUZXh0PntyZXN1bHR9PC9UZXh0PlxuICAgICAgICAgIDwvQm94PlxuICAgICAgICApfVxuICAgICAgPC9Cb3g+XG4gICAgICA8Qm94IG1hcmdpbkxlZnQ9ezN9PlxuICAgICAgICA8VGV4dCBkaW1Db2xvciBpdGFsaWM+XG4gICAgICAgICAgPEJ5bGluZT5cbiAgICAgICAgICAgIDxLZXlib2FyZFNob3J0Y3V0SGludCBzaG9ydGN1dD1cIkVudGVyXCIgYWN0aW9uPVwiYWRkXCIgLz5cbiAgICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTpub1wiXG4gICAgICAgICAgICAgIGNvbnRleHQ9XCJTZXR0aW5nc1wiXG4gICAgICAgICAgICAgIGZhbGxiYWNrPVwiRXNjXCJcbiAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJjYW5jZWxcIlxuICAgICAgICAgICAgLz5cbiAgICAgICAgICA8L0J5bGluZT5cbiAgICAgICAgPC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgPC9Cb3g+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxTQUFTLEVBQUVDLE1BQU0sRUFBRUMsUUFBUSxRQUFRLE9BQU87QUFDbkQsU0FDRSxLQUFLQywwREFBMEQsRUFDL0RDLFFBQVEsUUFDSCxpQ0FBaUM7QUFDeEMsU0FBU0Msd0JBQXdCLFFBQVEsOENBQThD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 \ No newline at end of file diff --git a/src/commands/plugin/BrowseMarketplace.tsx b/src/commands/plugin/BrowseMarketplace.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4d1a24d42f5a02b172863c454ee4dcd36ab7b389 --- /dev/null +++ b/src/commands/plugin/BrowseMarketplace.tsx @@ -0,0 +1,802 @@ +import figures from 'figures'; +import * as React from 'react'; +import { useEffect, useState } from 'react'; +import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js'; +import { Byline } from '../../components/design-system/Byline.js'; +import { Box, Text } from '../../ink.js'; +import { useKeybinding, useKeybindings } from '../../keybindings/useKeybinding.js'; +import type { LoadedPlugin } from '../../types/plugin.js'; +import { count } from '../../utils/array.js'; +import { openBrowser } from '../../utils/browser.js'; +import { logForDebugging } from '../../utils/debug.js'; +import { errorMessage } from '../../utils/errors.js'; +import { clearAllCaches } from '../../utils/plugins/cacheUtils.js'; +import { formatInstallCount, getInstallCounts } from '../../utils/plugins/installCounts.js'; +import { isPluginGloballyInstalled, isPluginInstalled } from '../../utils/plugins/installedPluginsManager.js'; +import { createPluginId, formatFailureDetails, formatMarketplaceLoadingErrors, getMarketplaceSourceDisplay, loadMarketplacesWithGracefulDegradation } from '../../utils/plugins/marketplaceHelpers.js'; +import { getMarketplace, loadKnownMarketplacesConfig } from '../../utils/plugins/marketplaceManager.js'; +import { OFFICIAL_MARKETPLACE_NAME } from '../../utils/plugins/officialMarketplace.js'; +import { installPluginFromMarketplace } from '../../utils/plugins/pluginInstallationHelpers.js'; +import { isPluginBlockedByPolicy } from '../../utils/plugins/pluginPolicy.js'; +import { plural } from '../../utils/stringUtils.js'; +import { truncateToWidth } from '../../utils/truncate.js'; +import { findPluginOptionsTarget, PluginOptionsFlow } from './PluginOptionsFlow.js'; +import { PluginTrustWarning } from './PluginTrustWarning.js'; +import { buildPluginDetailsMenuOptions, extractGitHubRepo, type InstallablePlugin, PluginSelectionKeyHint } from './pluginDetailsHelpers.js'; +import type { ViewState as ParentViewState } from './types.js'; +import { usePagination } from './usePagination.js'; +type Props = { + error: string | null; + setError: (error: string | null) => void; + result: string | null; + setResult: (result: string | null) => void; + setViewState: (state: ParentViewState) => void; + onInstallComplete?: () => void | Promise; + targetMarketplace?: string; + targetPlugin?: string; +}; +type ViewState = 'marketplace-list' | 'plugin-list' | 'plugin-details' | { + type: 'plugin-options'; + plugin: LoadedPlugin; + pluginId: string; +}; +type MarketplaceInfo = { + name: string; + totalPlugins: number; + installedCount: number; + source?: string; +}; +export function BrowseMarketplace({ + error, + setError, + result: _result, + setResult, + setViewState: setParentViewState, + onInstallComplete, + targetMarketplace, + targetPlugin +}: Props): React.ReactNode { + // View state + const [viewState, setViewState] = useState('marketplace-list'); + const [selectedMarketplace, setSelectedMarketplace] = useState(null); + const [selectedPlugin, setSelectedPlugin] = useState(null); + + // Data state + const [marketplaces, setMarketplaces] = useState([]); + const [availablePlugins, setAvailablePlugins] = useState([]); + const [loading, setLoading] = useState(true); + const [installCounts, setInstallCounts] = useState | null>(null); + + // Selection state + const [selectedIndex, setSelectedIndex] = useState(0); + const [selectedForInstall, setSelectedForInstall] = useState>(new Set()); + const [installingPlugins, setInstallingPlugins] = useState>(new Set()); + + // Pagination for plugin list (continuous scrolling) + const pagination = usePagination({ + totalItems: availablePlugins.length, + selectedIndex + }); + + // Details view state + const [detailsMenuIndex, setDetailsMenuIndex] = useState(0); + const [isInstalling, setIsInstalling] = useState(false); + const [installError, setInstallError] = useState(null); + + // Warning state for non-critical errors (e.g., some marketplaces failed to load) + const [warning, setWarning] = useState(null); + + // Handle escape to go back - viewState-dependent navigation + const handleBack = React.useCallback(() => { + if (viewState === 'plugin-list') { + // If navigated directly to a specific marketplace via targetMarketplace, + // go back to manage-marketplaces showing that marketplace's details + if (targetMarketplace) { + setParentViewState({ + type: 'manage-marketplaces', + targetMarketplace + }); + } else if (marketplaces.length === 1) { + // If there's only one marketplace, skip the marketplace-list view + // since we auto-navigated past it on load + setParentViewState({ + type: 'menu' + }); + } else { + setViewState('marketplace-list'); + setSelectedMarketplace(null); + setSelectedForInstall(new Set()); + } + } else if (viewState === 'plugin-details') { + setViewState('plugin-list'); + setSelectedPlugin(null); + } else { + // At root level (marketplace-list), exit the plugin menu + setParentViewState({ + type: 'menu' + }); + } + }, [viewState, targetMarketplace, setParentViewState, marketplaces.length]); + useKeybinding('confirm:no', handleBack, { + context: 'Confirmation' + }); + + // Load marketplaces and count installed plugins + useEffect(() => { + async function loadMarketplaceData() { + try { + const config = await loadKnownMarketplacesConfig(); + + // Load marketplaces with graceful degradation + const { + marketplaces: marketplaces_0, + failures + } = await loadMarketplacesWithGracefulDegradation(config); + const marketplaceInfos: MarketplaceInfo[] = []; + for (const { + name, + config: marketplaceConfig, + data: marketplace + } of marketplaces_0) { + if (marketplace) { + // Count how many plugins from this marketplace are installed + const installedFromThisMarketplace = count(marketplace.plugins, plugin => isPluginInstalled(createPluginId(plugin.name, name))); + marketplaceInfos.push({ + name, + totalPlugins: marketplace.plugins.length, + installedCount: installedFromThisMarketplace, + source: getMarketplaceSourceDisplay(marketplaceConfig.source) + }); + } + } + + // Sort so claude-plugin-directory is always first + marketplaceInfos.sort((a, b) => { + if (a.name === 'claude-plugin-directory') return -1; + if (b.name === 'claude-plugin-directory') return 1; + return 0; + }); + setMarketplaces(marketplaceInfos); + + // Handle marketplace loading errors/warnings + const successCount = count(marketplaces_0, m => m.data !== null); + const errorResult = formatMarketplaceLoadingErrors(failures, successCount); + if (errorResult) { + if (errorResult.type === 'warning') { + setWarning(errorResult.message + '. Showing available marketplaces.'); + } else { + throw new Error(errorResult.message); + } + } + + // Skip marketplace selection if there's only one marketplace + if (marketplaceInfos.length === 1 && !targetMarketplace && !targetPlugin) { + const singleMarketplace = marketplaceInfos[0]; + if (singleMarketplace) { + setSelectedMarketplace(singleMarketplace.name); + setViewState('plugin-list'); + } + } + + // Handle targetMarketplace and targetPlugin after marketplaces are loaded + if (targetPlugin) { + // Search for the plugin across all marketplaces + let foundPlugin: InstallablePlugin | null = null; + let foundMarketplace: string | null = null; + for (const [name_0] of Object.entries(config)) { + const marketplace_0 = await getMarketplace(name_0); + if (marketplace_0) { + const plugin_0 = marketplace_0.plugins.find(p => p.name === targetPlugin); + if (plugin_0) { + const pluginId = createPluginId(plugin_0.name, name_0); + foundPlugin = { + entry: plugin_0, + marketplaceName: name_0, + pluginId, + // isPluginGloballyInstalled: only block when user/managed scope + // exists (nothing to add). Project/local-scope installs don't + // block — user may want to promote to user scope (gh-29997). + isInstalled: isPluginGloballyInstalled(pluginId) + }; + foundMarketplace = name_0; + break; + } + } + } + if (foundPlugin && foundMarketplace) { + // Block only on global (user/managed) install — project/local scope + // means the user might still want to add a user-scope entry so the + // plugin is available in other projects (gh-29997, gh-29240, gh-29392). + // The plugin-details view offers all three scope options; the backend + // (installPluginOp → addInstalledPlugin) already supports multiple + // scope entries per plugin. + const pluginId_0 = foundPlugin.pluginId; + const globallyInstalled = isPluginGloballyInstalled(pluginId_0); + if (globallyInstalled) { + setError(`Plugin '${pluginId_0}' is already installed globally. Use '/plugin' to manage existing plugins.`); + } else { + // Navigate to the plugin details view + setSelectedMarketplace(foundMarketplace); + setSelectedPlugin(foundPlugin); + setViewState('plugin-details'); + } + } else { + setError(`Plugin "${targetPlugin}" not found in any marketplace`); + } + } else if (targetMarketplace) { + // Navigate directly to the specified marketplace + const marketplaceExists = marketplaceInfos.some(m_0 => m_0.name === targetMarketplace); + if (marketplaceExists) { + setSelectedMarketplace(targetMarketplace); + setViewState('plugin-list'); + } else { + setError(`Marketplace "${targetMarketplace}" not found`); + } + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load marketplaces'); + } finally { + setLoading(false); + } + } + void loadMarketplaceData(); + }, [setError, targetMarketplace, targetPlugin]); + + // Load plugins when a marketplace is selected + useEffect(() => { + if (!selectedMarketplace) return; + let cancelled = false; + async function loadPluginsForMarketplace(marketplaceName: string) { + setLoading(true); + try { + const marketplace_1 = await getMarketplace(marketplaceName); + if (cancelled) return; + if (!marketplace_1) { + throw new Error(`Failed to load marketplace: ${marketplaceName}`); + } + + // Filter out already installed plugins + const installablePlugins: InstallablePlugin[] = []; + for (const entry of marketplace_1.plugins) { + const pluginId_1 = createPluginId(entry.name, marketplaceName); + if (isPluginBlockedByPolicy(pluginId_1)) continue; + installablePlugins.push({ + entry, + marketplaceName: marketplaceName, + pluginId: pluginId_1, + // Only mark as "installed" when globally scoped (user/managed). + // Project/local installs don't block — user can add user scope + // via the plugin-details view (gh-29997). + isInstalled: isPluginGloballyInstalled(pluginId_1) + }); + } + + // Fetch install counts and sort by popularity + try { + const counts = await getInstallCounts(); + if (cancelled) return; + setInstallCounts(counts); + if (counts) { + // Sort by install count (descending), then alphabetically + installablePlugins.sort((a_1, b_1) => { + const countA = counts.get(a_1.pluginId) ?? 0; + const countB = counts.get(b_1.pluginId) ?? 0; + if (countA !== countB) return countB - countA; + return a_1.entry.name.localeCompare(b_1.entry.name); + }); + } else { + // No counts available - sort alphabetically + installablePlugins.sort((a_2, b_2) => a_2.entry.name.localeCompare(b_2.entry.name)); + } + } catch (error_0) { + if (cancelled) return; + // Log the error, then gracefully degrade to alphabetical sort + logForDebugging(`Failed to fetch install counts: ${errorMessage(error_0)}`); + installablePlugins.sort((a_0, b_0) => a_0.entry.name.localeCompare(b_0.entry.name)); + } + setAvailablePlugins(installablePlugins); + setSelectedIndex(0); + setSelectedForInstall(new Set()); + } catch (err_0) { + if (cancelled) return; + setError(err_0 instanceof Error ? err_0.message : 'Failed to load plugins'); + } finally { + setLoading(false); + } + } + void loadPluginsForMarketplace(selectedMarketplace); + return () => { + cancelled = true; + }; + }, [selectedMarketplace, setError]); + + // Install selected plugins + const installSelectedPlugins = async () => { + if (selectedForInstall.size === 0) return; + const pluginsToInstall = availablePlugins.filter(p_0 => selectedForInstall.has(p_0.pluginId)); + setInstallingPlugins(new Set(pluginsToInstall.map(p_1 => p_1.pluginId))); + let successCount_0 = 0; + let failureCount = 0; + const newFailedPlugins: Array<{ + name: string; + reason: string; + }> = []; + for (const plugin_1 of pluginsToInstall) { + const result = await installPluginFromMarketplace({ + pluginId: plugin_1.pluginId, + entry: plugin_1.entry, + marketplaceName: plugin_1.marketplaceName, + scope: 'user' + }); + if (result.success) { + successCount_0++; + } else { + failureCount++; + newFailedPlugins.push({ + name: plugin_1.entry.name, + reason: result.error + }); + } + } + setInstallingPlugins(new Set()); + setSelectedForInstall(new Set()); + clearAllCaches(); + + // Handle installation results + if (failureCount === 0) { + // All succeeded + const message = `✓ Installed ${successCount_0} ${plural(successCount_0, 'plugin')}. ` + `Run /reload-plugins to activate.`; + setResult(message); + } else if (successCount_0 === 0) { + // All failed - show error with reasons + setError(`Failed to install: ${formatFailureDetails(newFailedPlugins, true)}`); + } else { + // Mixed results - show partial success + const message_0 = `✓ Installed ${successCount_0} of ${successCount_0 + failureCount} plugins. ` + `Failed: ${formatFailureDetails(newFailedPlugins, false)}. ` + `Run /reload-plugins to activate successfully installed plugins.`; + setResult(message_0); + } + + // Handle completion callback and navigation + if (successCount_0 > 0) { + if (onInstallComplete) { + await onInstallComplete(); + } + } + setParentViewState({ + type: 'menu' + }); + }; + + // Install single plugin from details view + const handleSinglePluginInstall = async (plugin_2: InstallablePlugin, scope: 'user' | 'project' | 'local' = 'user') => { + setIsInstalling(true); + setInstallError(null); + const result_0 = await installPluginFromMarketplace({ + pluginId: plugin_2.pluginId, + entry: plugin_2.entry, + marketplaceName: plugin_2.marketplaceName, + scope + }); + if (result_0.success) { + const loaded = await findPluginOptionsTarget(plugin_2.pluginId); + if (loaded) { + setIsInstalling(false); + setViewState({ + type: 'plugin-options', + plugin: loaded, + pluginId: plugin_2.pluginId + }); + return; + } + setResult(result_0.message); + if (onInstallComplete) { + await onInstallComplete(); + } + setParentViewState({ + type: 'menu' + }); + } else { + setIsInstalling(false); + setInstallError(result_0.error); + } + }; + + // Handle error state + useEffect(() => { + if (error) { + setResult(error); + } + }, [error, setResult]); + + // Marketplace-list navigation + useKeybindings({ + 'select:previous': () => { + if (selectedIndex > 0) { + setSelectedIndex(selectedIndex - 1); + } + }, + 'select:next': () => { + if (selectedIndex < marketplaces.length - 1) { + setSelectedIndex(selectedIndex + 1); + } + }, + 'select:accept': () => { + const marketplace_2 = marketplaces[selectedIndex]; + if (marketplace_2) { + setSelectedMarketplace(marketplace_2.name); + setViewState('plugin-list'); + } + } + }, { + context: 'Select', + isActive: viewState === 'marketplace-list' + }); + + // Plugin-list navigation + useKeybindings({ + 'select:previous': () => { + if (selectedIndex > 0) { + pagination.handleSelectionChange(selectedIndex - 1, setSelectedIndex); + } + }, + 'select:next': () => { + if (selectedIndex < availablePlugins.length - 1) { + pagination.handleSelectionChange(selectedIndex + 1, setSelectedIndex); + } + }, + 'select:accept': () => { + if (selectedIndex === availablePlugins.length && selectedForInstall.size > 0) { + void installSelectedPlugins(); + } else if (selectedIndex < availablePlugins.length) { + const plugin_3 = availablePlugins[selectedIndex]; + if (plugin_3) { + if (plugin_3.isInstalled) { + setParentViewState({ + type: 'manage-plugins', + targetPlugin: plugin_3.entry.name, + targetMarketplace: plugin_3.marketplaceName + }); + } else { + setSelectedPlugin(plugin_3); + setViewState('plugin-details'); + setDetailsMenuIndex(0); + setInstallError(null); + } + } + } + } + }, { + context: 'Select', + isActive: viewState === 'plugin-list' + }); + useKeybindings({ + 'plugin:toggle': () => { + if (selectedIndex < availablePlugins.length) { + const plugin_4 = availablePlugins[selectedIndex]; + if (plugin_4 && !plugin_4.isInstalled) { + const newSelection = new Set(selectedForInstall); + if (newSelection.has(plugin_4.pluginId)) { + newSelection.delete(plugin_4.pluginId); + } else { + newSelection.add(plugin_4.pluginId); + } + setSelectedForInstall(newSelection); + } + } + }, + 'plugin:install': () => { + if (selectedForInstall.size > 0) { + void installSelectedPlugins(); + } + } + }, { + context: 'Plugin', + isActive: viewState === 'plugin-list' + }); + + // Plugin-details navigation + const detailsMenuOptions = React.useMemo(() => { + if (!selectedPlugin) return []; + const hasHomepage = selectedPlugin.entry.homepage; + const githubRepo = extractGitHubRepo(selectedPlugin); + return buildPluginDetailsMenuOptions(hasHomepage, githubRepo); + }, [selectedPlugin]); + useKeybindings({ + 'select:previous': () => { + if (detailsMenuIndex > 0) { + setDetailsMenuIndex(detailsMenuIndex - 1); + } + }, + 'select:next': () => { + if (detailsMenuIndex < detailsMenuOptions.length - 1) { + setDetailsMenuIndex(detailsMenuIndex + 1); + } + }, + 'select:accept': () => { + if (!selectedPlugin) return; + const action = detailsMenuOptions[detailsMenuIndex]?.action; + const hasHomepage_0 = selectedPlugin.entry.homepage; + const githubRepo_0 = extractGitHubRepo(selectedPlugin); + if (action === 'install-user') { + void handleSinglePluginInstall(selectedPlugin, 'user'); + } else if (action === 'install-project') { + void handleSinglePluginInstall(selectedPlugin, 'project'); + } else if (action === 'install-local') { + void handleSinglePluginInstall(selectedPlugin, 'local'); + } else if (action === 'homepage' && hasHomepage_0) { + void openBrowser(hasHomepage_0); + } else if (action === 'github' && githubRepo_0) { + void openBrowser(`https://github.com/${githubRepo_0}`); + } else if (action === 'back') { + setViewState('plugin-list'); + setSelectedPlugin(null); + } + } + }, { + context: 'Select', + isActive: viewState === 'plugin-details' && !!selectedPlugin + }); + if (typeof viewState === 'object' && viewState.type === 'plugin-options') { + const { + plugin: plugin_5, + pluginId: pluginId_2 + } = viewState; + function finish(msg: string): void { + setResult(msg); + if (onInstallComplete) { + void onInstallComplete(); + } + setParentViewState({ + type: 'menu' + }); + } + return { + switch (outcome) { + case 'configured': + finish(`✓ Installed and configured ${plugin_5.name}. Run /reload-plugins to apply.`); + break; + case 'skipped': + finish(`✓ Installed ${plugin_5.name}. Run /reload-plugins to apply.`); + break; + case 'error': + finish(`Installed but failed to save config: ${detail}`); + break; + } + }} />; + } + + // Loading state + if (loading) { + return Loading…; + } + + // Error state + if (error) { + return {error}; + } + + // Marketplace selection view + if (viewState === 'marketplace-list') { + if (marketplaces.length === 0) { + return + + Select marketplace + + No marketplaces configured. + + Add a marketplace first using {"'Add marketplace'"}. + + + + + + + ; + } + return + + Select marketplace + + + {/* Warning banner for marketplace load failures */} + {warning && + + {figures.warning} {warning} + + } + {marketplaces.map((marketplace_3, index) => + + + {selectedIndex === index ? figures.pointer : ' '}{' '} + {marketplace_3.name} + + + + + {marketplace_3.totalPlugins}{' '} + {plural(marketplace_3.totalPlugins, 'plugin')} available + {marketplace_3.installedCount > 0 && ` · ${marketplace_3.installedCount} already installed`} + {marketplace_3.source && ` · ${marketplace_3.source}`} + + + )} + + + + + + + + + + ; + } + + // Plugin details view + if (viewState === 'plugin-details' && selectedPlugin) { + const hasHomepage_1 = selectedPlugin.entry.homepage; + const githubRepo_1 = extractGitHubRepo(selectedPlugin); + const menuOptions = buildPluginDetailsMenuOptions(hasHomepage_1, githubRepo_1); + return + + Plugin Details + + + {/* Plugin metadata */} + + {selectedPlugin.entry.name} + {selectedPlugin.entry.version && Version: {selectedPlugin.entry.version}} + {selectedPlugin.entry.description && + {selectedPlugin.entry.description} + } + {selectedPlugin.entry.author && + + By:{' '} + {typeof selectedPlugin.entry.author === 'string' ? selectedPlugin.entry.author : selectedPlugin.entry.author.name} + + } + + + {/* What will be installed */} + + Will install: + {selectedPlugin.entry.commands && + · Commands:{' '} + {Array.isArray(selectedPlugin.entry.commands) ? selectedPlugin.entry.commands.join(', ') : Object.keys(selectedPlugin.entry.commands).join(', ')} + } + {selectedPlugin.entry.agents && + · Agents:{' '} + {Array.isArray(selectedPlugin.entry.agents) ? selectedPlugin.entry.agents.join(', ') : Object.keys(selectedPlugin.entry.agents).join(', ')} + } + {selectedPlugin.entry.hooks && + · Hooks: {Object.keys(selectedPlugin.entry.hooks).join(', ')} + } + {selectedPlugin.entry.mcpServers && + · MCP Servers:{' '} + {Array.isArray(selectedPlugin.entry.mcpServers) ? selectedPlugin.entry.mcpServers.join(', ') : typeof selectedPlugin.entry.mcpServers === 'object' ? Object.keys(selectedPlugin.entry.mcpServers).join(', ') : 'configured'} + } + {!selectedPlugin.entry.commands && !selectedPlugin.entry.agents && !selectedPlugin.entry.hooks && !selectedPlugin.entry.mcpServers && <> + {typeof selectedPlugin.entry.source === 'object' && 'source' in selectedPlugin.entry.source && (selectedPlugin.entry.source.source === 'github' || selectedPlugin.entry.source.source === 'url' || selectedPlugin.entry.source.source === 'npm' || selectedPlugin.entry.source.source === 'pip') ? + · Component summary not available for remote plugin + : + // TODO: Actually scan local plugin directories to show real components + // This would require accessing the filesystem to check for: + // - commands/ directory and list files + // - agents/ directory and list files + // - hooks/ directory and list files + // - .mcp.json or mcp-servers.json files + + · Components will be discovered at installation + } + } + + + + + {/* Error message */} + {installError && + Error: {installError} + } + + {/* Menu options */} + + {menuOptions.map((option, index_0) => + {detailsMenuIndex === index_0 && {'> '}} + {detailsMenuIndex !== index_0 && {' '}} + + {isInstalling && option.action === 'install' ? 'Installing…' : option.label} + + )} + + + + + + + + + + + ; + } + + // Plugin installation view + if (availablePlugins.length === 0) { + return + + Install plugins + + No new plugins available to install. + + All plugins from this marketplace are already installed. + + + + + + + ; + } + + // Get visible plugins from pagination + const visiblePlugins = pagination.getVisibleItems(availablePlugins); + return + + Install Plugins + + + {/* Scroll up indicator */} + {pagination.scrollPosition.canScrollUp && + {figures.arrowUp} more above + } + + {/* Plugin list */} + {visiblePlugins.map((plugin_6, visibleIndex) => { + const actualIndex = pagination.toActualIndex(visibleIndex); + const isSelected = selectedIndex === actualIndex; + const isSelectedForInstall = selectedForInstall.has(plugin_6.pluginId); + const isInstalling_0 = installingPlugins.has(plugin_6.pluginId); + const isLast = visibleIndex === visiblePlugins.length - 1; + return + + + {isSelected ? figures.pointer : ' '}{' '} + + + {plugin_6.isInstalled ? figures.tick : isInstalling_0 ? figures.ellipsis : isSelectedForInstall ? figures.radioOn : figures.radioOff}{' '} + {plugin_6.entry.name} + {plugin_6.entry.category && [{plugin_6.entry.category}]} + {plugin_6.entry.tags?.includes('community-managed') && [Community Managed]} + {plugin_6.isInstalled && (installed)} + {installCounts && selectedMarketplace === OFFICIAL_MARKETPLACE_NAME && + {' · '} + {formatInstallCount(installCounts.get(plugin_6.pluginId) ?? 0)}{' '} + installs + } + + + {plugin_6.entry.description && + + {truncateToWidth(plugin_6.entry.description, 60)} + + {plugin_6.entry.version && · v{plugin_6.entry.version}} + } + ; + })} + + {/* Scroll down indicator */} + {pagination.scrollPosition.canScrollDown && + {figures.arrowDown} more below + } + + {/* Error messages shown in the UI */} + {error && + + {figures.cross} {error} + + } + + 0} /> + ; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwiUmVhY3QiLCJ1c2VFZmZlY3QiLCJ1c2VTdGF0ZSIsIkNvbmZpZ3VyYWJsZVNob3J0Y3V0SGludCIsIkJ5bGluZSIsIkJveCIsIlRleHQiLCJ1c2VLZXliaW5kaW5nIiwidXNlS2V5YmluZGluZ3MiLCJMb2FkZWRQbHVnaW4iLCJjb3VudCIsIm9wZW5Ccm93c2VyIiwibG9nRm9yRGVidWdnaW5nIiwiZXJyb3JNZXNzYWdlIiwiY2xlYXJBbGxDYWNoZXMiLCJmb3JtYXRJbnN0YWxsQ291bnQiLCJnZXRJbnN0YWxsQ291bnRzIiwiaXNQbHVnaW5HbG9iYWxseUluc3RhbGxlZCIsImlzUGx1Z2luSW5zdGFsbGVkIiwiY3JlYXRlUGx1Z2luSWQiLCJmb3JtYXRGYWlsdXJlRGV0YWlscyIsImZvcm1hdE1hcmtldHBsYWNlTG9hZGluZ0Vycm9ycyIsImdldE1hcmtldHBsYWNlU291cmNlRGlzcGxheSIsImxvYWRNYXJrZXRwbGFjZXNXaXRoR3JhY2VmdWxEZWdyYWRhdGlvbiIsImdldE1hcmtldHBsYWNlIiwibG9hZEtub3duTWFya2V0cGxhY2VzQ29uZmlnIiwiT0ZGSUNJQUxfTUFSS0VUUExBQ0VfTkFNRSIsImluc3RhbGxQbHVnaW5Gcm9tTWFya2V0cGxhY2UiLCJpc1BsdWdpbkJsb2NrZWRCeVBvbGljeSIsInBsdXJhbCIsInRydW5jYXRlVG9XaWR0aCIsImZpbmRQbHVnaW5PcHRpb25zVGFyZ2V0IiwiUGx1Z2luT3B0aW9uc0Zsb3ciLCJQbHVnaW5UcnVzdFdhcm5pbmciLCJidWlsZFBsdWdpbkRldGFpbHNNZW51T3B0aW9ucyIsImV4dHJhY3RHaXRIdWJSZXBvIiwiSW5zdGFsbGFibGVQbHVnaW4iLCJQbHVnaW5TZWxlY3Rpb25LZXlIaW50IiwiVmlld1N0YXRlIiwiUGFyZW50Vmlld1N0YXRlIiwidXNlUGFnaW5hdGlvbiIsIlByb3BzIiwiZXJyb3IiLCJzZXRFcnJvciIsInJlc3VsdCIsInNldFJlc3VsdCIsInNldFZpZXdTdGF0ZSIsInN0YXRlIiwib25JbnN0YWxsQ29tcGxldGUiLCJQcm9taXNlIiwidGFyZ2V0TWFya2V0cGxhY2UiLCJ0YXJnZXRQbHVnaW4iLCJ0eXBlIiwicGx1Z2luIiwicGx1Z2luSWQiLCJNYXJrZXRwbGFjZUluZm8iLCJuYW1lIiwidG90YWxQbHVnaW5zIiwiaW5zdGFsbGVkQ291bnQiLCJzb3VyY2UiLCJCcm93c2VNYXJrZXRwbGFjZSIsIl9yZXN1bHQiLCJzZXRQYXJlbnRWaWV3U3RhdGUiLCJSZWFjdE5vZGUiLCJ2aWV3U3RhdGUiLCJzZWxlY3RlZE1hcmtldHBsYWNlIiwic2V0U2VsZWN0ZWRNYXJrZXRwbGFjZSIsInNlbGVjdGVkUGx1Z2luIiwic2V0U2VsZWN0ZWRQbHVnaW4iLCJtYXJrZXRwbGFjZXMiLCJzZXRNYXJrZXRwbGFjZXMiLCJhdmFpbGFibGVQbHVnaW5zIiwic2V0QXZhaWxhYmxlUGx1Z2lucyIsImxvYWRpbmciLCJzZXRMb2FkaW5nIiwiaW5zdGFsbENvdW50cyIsInNldEluc3RhbGxDb3VudHMiLCJNYXAiLCJzZWxlY3RlZEluZGV4Iiwic2V0U2VsZWN0ZWRJbmRleCIsInNlbGVjdGVkRm9ySW5zdGFsbCIsInNldFNlbGVjdGVkRm9ySW5zdGFsbCIsIlNldCIsImluc3RhbGxpbmdQbHVnaW5zIiwic2V0SW5zdGFsbGluZ1BsdWdpbnMiLCJwYWdpbmF0aW9uIiwidG90YWxJdGVtcyIsImxlbmd0aCIsImRldGFpbHNNZW51SW5kZXgiLCJzZXREZXRhaWxzTWVudUluZGV4IiwiaXNJbnN0YWxsaW5nIiwic2V0SXNJbnN0YWxsaW5nIiwiaW5zdGFsbEVycm9yIiwic2V0SW5zdGFsbEVycm9yIiwid2FybmluZyIsInNldFdhcm5pbmciLCJoYW5kbGVCYWNrIiwidXNlQ2FsbGJhY2siLCJjb250ZXh0IiwibG9hZE1hcmtldHBsYWNlRGF0YSIsImNvbmZpZyIsImZhaWx1cmVzIiwibWFya2V0cGxhY2VJbmZvcyIsIm1hcmtldHBsYWNlQ29uZmlnIiwiZGF0YSIsIm1hcmtldHBsYWNlIiwiaW5zdGFsbGVkRnJvbVRoaXNNYXJrZXRwbGFjZSIsInBsdWdpbnMiLCJwdXNoIiwic29ydCIsImEiLCJiIiwic3VjY2Vzc0NvdW50IiwibSIsImVycm9yUmVzdWx0IiwibWVzc2FnZSIsIkVycm9yIiwic2luZ2xlTWFya2V0cGxhY2UiLCJmb3VuZFBsdWdpbiIsImZvdW5kTWFya2V0cGxhY2UiLCJPYmplY3QiLCJlbnRyaWVzIiwiZmluZCIsInAiLCJlbnRyeSIsIm1hcmtldHBsYWNlTmFtZSIsImlzSW5zdGFsbGVkIiwiZ2xvYmFsbHlJbnN0YWxsZWQiLCJtYXJrZXRwbGFjZUV4aXN0cyIsInNvbWUiLCJlcnIiLCJjYW5jZWxsZWQiLCJsb2FkUGx1Z2luc0Zvck1hcmtldHBsYWNlIiwiaW5zdGFsbGFibGVQbHVnaW5zIiwiY291bnRzIiwiY291bnRBIiwiZ2V0IiwiY291bnRCIiwibG9jYWxlQ29tcGFyZSIsImluc3RhbGxTZWxlY3RlZFBsdWdpbnMiLCJzaXplIiwicGx1Z2luc1RvSW5zdGFsbCIsImZpbHRlciIsImhhcyIsIm1hcCIsImZhaWx1cmVDb3VudCIsIm5ld0ZhaWxlZFBsdWdpbnMiLCJBcnJheSIsInJlYXNvbiIsInNjb3BlIiwic3VjY2VzcyIsImhhbmRsZVNpbmdsZVBsdWdpbkluc3RhbGwiLCJsb2FkZWQiLCJzZWxlY3Q6cHJldmlvdXMiLCJzZWxlY3Q6bmV4dCIsInNlbGVjdDphY2NlcHQiLCJpc0FjdGl2ZSIsImhhbmRsZVNlbGVjdGlvbkNoYW5nZSIsInBsdWdpbjp0b2dnbGUiLCJuZXdTZWxlY3Rpb24iLCJkZWxldGUiLCJhZGQiLCJwbHVnaW46aW5zdGFsbCIsImRldGFpbHNNZW51T3B0aW9ucyIsInVzZU1lbW8iLCJoYXNIb21lcGFnZSIsImhvbWVwYWdlIiwiZ2l0aHViUmVwbyIsImFjdGlvbiIsImZpbmlzaCIsIm1zZyIsIm91dGNvbWUiLCJkZXRhaWwiLCJpbmRleCIsInVuZGVmaW5lZCIsInBvaW50ZXIiLCJtZW51T3B0aW9ucyIsInZlcnNpb24iLCJkZXNjcmlwdGlvbiIsImF1dGhvciIsImNvbW1hbmRzIiwiaXNBcnJheSIsImpvaW4iLCJrZXlzIiwiYWdlbnRzIiwiaG9va3MiLCJtY3BTZXJ2ZXJzIiwib3B0aW9uIiwibGFiZWwiLCJ2aXNpYmxlUGx1Z2lucyIsImdldFZpc2libGVJdGVtcyIsInNjcm9sbFBvc2l0aW9uIiwiY2FuU2Nyb2xsVXAiLCJhcnJvd1VwIiwidmlzaWJsZUluZGV4IiwiYWN0dWFsSW5kZXgiLCJ0b0FjdHVhbEluZGV4IiwiaXNTZWxlY3RlZCIsImlzU2VsZWN0ZWRGb3JJbnN0YWxsIiwiaXNMYXN0IiwidGljayIsImVsbGlwc2lzIiwicmFkaW9PbiIsInJhZGlvT2ZmIiwiY2F0ZWdvcnkiLCJ0YWdzIiwiaW5jbHVkZXMiLCJjYW5TY3JvbGxEb3duIiwiYXJyb3dEb3duIiwiY3Jvc3MiXSwic291cmNlcyI6WyJCcm93c2VNYXJrZXRwbGFjZS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGZpZ3VyZXMgZnJvbSAnZmlndXJlcydcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgdXNlRWZmZWN0LCB1c2VTdGF0ZSB9IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9Db25maWd1cmFibGVTaG9ydGN1dEhpbnQuanMnXG5pbXBvcnQgeyBCeWxpbmUgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL2Rlc2lnbi1zeXN0ZW0vQnlsaW5lLmpzJ1xuaW1wb3J0IHsgQm94LCBUZXh0IH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHtcbiAgdXNlS2V5YmluZGluZyxcbiAgdXNlS2V5YmluZGluZ3MsXG59IGZyb20gJy4uLy4uL2tleWJpbmRpbmdzL3VzZUtleWJpbmRpbmcuanMnXG5pbXBvcnQgdHlwZSB7IExvYWRlZFBsdWdpbiB9IGZyb20gJy4uLy4uL3R5cGVzL3BsdWdpbi5qcydcbmltcG9ydCB7IGNvdW50IH0gZnJvbSAnLi4vLi4vdXRpbHMvYXJyYXkuanMnXG5pbXBvcnQgeyBvcGVuQnJvd3NlciB9IGZyb20gJy4uLy4uL3V0aWxzL2Jyb3dzZXIuanMnXG5pbXBvcnQgeyBsb2dGb3JEZWJ1Z2dpbmcgfSBmcm9tICcuLi8uLi91dGlscy9kZWJ1Zy5qcydcbmltcG9ydCB7IGVycm9yTWVzc2FnZSB9IGZyb20gJy4uLy4uL3V0aWxzL2Vycm9ycy5qcydcbmltcG9ydCB7IGNsZWFyQWxsQ2FjaGVzIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9jYWNoZVV0aWxzLmpzJ1xuaW1wb3J0IHtcbiAgZm9ybWF0SW5zdGFsbENvdW50LFxuICBnZXRJbnN0YWxsQ291bnRzLFxufSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL2luc3RhbGxDb3VudHMuanMnXG5pbXBvcnQge1xuICBpc1BsdWdpbkdsb2JhbGx5SW5zdGFsbGVkLFxuICBpc1BsdWdpbkluc3RhbGxlZCxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9pbnN0YWxsZWRQbHVnaW5zTWFuYWdlci5qcydcbmltcG9ydCB7XG4gIGNyZWF0ZVBsdWdpbklkLFxuICBmb3JtYXRGYWlsdXJlRGV0YWlscyxcbiAgZm9ybWF0TWFya2V0cGxhY2VMb2FkaW5nRXJyb3JzLFxuICBnZXRNYXJrZXRwbGFjZVNvdXJjZURpc3BsYXksXG4gIGxvYWRNYXJrZXRwbGFjZXNXaXRoR3JhY2VmdWxEZWdyYWRhdGlvbixcbn0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9tYXJrZXRwbGFjZUhlbHBlcnMuanMnXG5pbXBvcnQge1xuICBnZXRNYXJrZXRwbGFjZSxcbiAgbG9hZEtub3duTWFya2V0cGxhY2VzQ29uZmlnLFxufSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL21hcmtldHBsYWNlTWFuYWdlci5qcydcbmltcG9ydCB7IE9GRklDSUFMX01BUktFVFBMQUNFX05BTUUgfSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL29mZmljaWFsTWFya2V0cGxhY2UuanMnXG5pbXBvcnQgeyBpbnN0YWxsUGx1Z2luRnJvbU1hcmtldHBsYWNlIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9wbHVnaW5JbnN0YWxsYXRpb25IZWxwZXJzLmpzJ1xuaW1wb3J0IHsgaXNQbHVnaW5CbG9ja2VkQnlQb2xpY3kgfSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL3BsdWdpblBvbGljeS5qcydcbmltcG9ydCB7IHBsdXJhbCB9IGZyb20gJy4uLy4uL3V0aWxzL3N0cmluZ1V0aWxzLmpzJ1xuaW1wb3J0IHsgdHJ1bmNhdGVUb1dpZHRoIH0gZnJvbSAnLi4vLi4vdXRpbHMvdHJ1bmNhdGUuanMnXG5pbXBvcnQge1xuICBmaW5kUGx1Z2luT3B0aW9uc1RhcmdldCxcbiAgUGx1Z2luT3B0aW9uc0Zsb3csXG59IGZyb20gJy4vUGx1Z2luT3B0aW9uc0Zsb3cuanMnXG5pbXBvcnQgeyBQbHVnaW5UcnVzdFdhcm5pbmcgfSBmcm9tICcuL1BsdWdpblRydXN0V2FybmluZy5qcydcbmltcG9ydCB7XG4gIGJ1aWxkUGx1Z2luRGV0YWlsc01lbnVPcHRpb25zLFxuICBleHRyYWN0R2l0SHViUmVwbyxcbiAgdHlwZSBJbnN0YWxsYWJsZVBsdWdpbixcbiAgUGx1Z2luU2VsZWN0aW9uS2V5SGludCxcbn0gZnJvbSAnLi9wbHVnaW5EZXRhaWxzSGVscGVycy5qcydcbmltcG9ydCB0eXBlIHsgVmlld1N0YXRlIGFzIFBhcmVudFZpZXdTdGF0ZSB9IGZyb20gJy4vdHlwZXMuanMnXG5pbXBvcnQgeyB1c2VQYWdpbmF0aW9uIH0gZnJvbSAnLi91c2VQYWdpbmF0aW9uLmpzJ1xuXG50eXBlIFByb3BzID0ge1xuICBlcnJvcjogc3RyaW5nIHwgbnVsbFxuICBzZXRFcnJvcjogKGVycm9yOiBzdHJpbmcgfCBudWxsKSA9PiB2b2lkXG4gIHJlc3VsdDogc3RyaW5nIHwgbnVsbFxuICBzZXRSZXN1bHQ6IChyZXN1bHQ6IHN0cmluZyB8IG51bGwpID0+IHZvaWRcbiAgc2V0Vmlld1N0YXRlOiAoc3RhdGU6IFBhcmVudFZpZXdTdGF0ZSkgPT4gdm9pZFxuICBvbkluc3RhbGxDb21wbGV0ZT86ICgpID0+IHZvaWQgfCBQcm9taXNlPHZvaWQ+XG4gIHRhcmdldE1hcmtldHBsYWNlPzogc3RyaW5nXG4gIHRhcmdldFBsdWdpbj86IHN0cmluZ1xufVxuXG50eXBlIFZpZXdTdGF0ZSA9XG4gIHwgJ21hcmtldHBsYWNlLWxpc3QnXG4gIHwgJ3BsdWdpbi1saXN0J1xuICB8ICdwbHVnaW4tZGV0YWlscydcbiAgfCB7IHR5cGU6ICdwbHVnaW4tb3B0aW9ucyc7IHBsdWdpbjogTG9hZGVkUGx1Z2luOyBwbHVnaW5JZDogc3RyaW5nIH1cblxudHlwZSBNYXJrZXRwbGFjZUluZm8gPSB7XG4gIG5hbWU6IHN0cmluZ1xuICB0b3RhbFBsdWdpbnM6IG51bWJlclxuICBpbnN0YWxsZWRDb3VudDogbnVtYmVyXG4gIHNvdXJjZT86IHN0cmluZ1xufVxuXG5leHBvcnQgZnVuY3Rpb24gQnJvd3NlTWFya2V0cGxhY2Uoe1xuICBlcnJvcixcbiAgc2V0RXJyb3IsXG4gIHJlc3VsdDogX3Jlc3VsdCxcbiAgc2V0UmVzdWx0LFxuICBzZXRWaWV3U3RhdGU6IHNldFBhcmVudFZpZXdTdGF0ZSxcbiAgb25JbnN0YWxsQ29tcGxldGUsXG4gIHRhcmdldE1hcmtldHBsYWNlLFxuICB0YXJnZXRQbHVnaW4sXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIC8vIFZpZXcgc3RhdGVcbiAgY29uc3QgW3ZpZXdTdGF0ZSwgc2V0Vmlld1N0YXRlXSA9IHVzZVN0YXRlPFZpZXdTdGF0ZT4oJ21hcmtldHBsYWNlLWxpc3QnKVxuICBjb25zdCBbc2VsZWN0ZWRNYXJrZXRwbGFjZSwgc2V0U2VsZWN0ZWRNYXJrZXRwbGFjZV0gPSB1c2VTdGF0ZTxzdHJpbmcgfCBudWxsPihcbiAgICBudWxsLFxuICApXG4gIGNvbnN0IFtzZWxlY3RlZFBsdWdpbiwgc2V0U2VsZWN0ZWRQbHVnaW5dID1cbiAgICB1c2VTdGF0ZTxJbnN0YWxsYWJsZVBsdWdpbiB8IG51bGw+KG51bGwpXG5cbiAgLy8gRGF0YSBzdGF0ZVxuICBjb25zdCBbbWFya2V0cGxhY2VzLCBzZXRNYXJrZXRwbGFjZXNdID0gdXNlU3RhdGU8TWFya2V0cGxhY2VJbmZvW10+KFtdKVxuICBjb25zdCBbYXZhaWxhYmxlUGx1Z2lucywgc2V0QXZhaWxhYmxlUGx1Z2luc10gPSB1c2VTdGF0ZTxJbnN0YWxsYWJsZVBsdWdpbltdPihcbiAgICBbXSxcbiAgKVxuICBjb25zdCBbbG9hZGluZywgc2V0TG9hZGluZ10gPSB1c2VTdGF0ZSh0cnVlKVxuICBjb25zdCBbaW5zdGFsbENvdW50cywgc2V0SW5zdGFsbENvdW50c10gPSB1c2VTdGF0ZTxNYXA8XG4gICAgc3RyaW5nLFxuICAgIG51bWJlclxuICA+IHwgbnVsbD4obnVsbClcblxuICAvLyBTZWxlY3Rpb24gc3RhdGVcbiAgY29uc3QgW3NlbGVjdGVkSW5kZXgsIHNldFNlbGVjdGVkSW5kZXhdID0gdXNlU3RhdGUoMClcbiAgY29uc3QgW3NlbGVjdGVkRm9ySW5zdGFsbCwgc2V0U2VsZWN0ZWRGb3JJbnN0YWxsXSA9IHVzZVN0YXRlPFNldDxzdHJpbmc+PihcbiAgICBuZXcgU2V0KCksXG4gIClcbiAgY29uc3QgW2luc3RhbGxpbmdQbHVnaW5zLCBzZXRJbnN0YWxsaW5nUGx1Z2luc10gPSB1c2VTdGF0ZTxTZXQ8c3RyaW5nPj4oXG4gICAgbmV3IFNldCgpLFxuICApXG5cbiAgLy8gUGFnaW5hdGlvbiBmb3IgcGx1Z2luIGxpc3QgKGNvbnRpbnVvdXMgc2Nyb2xsaW5nKVxuICBjb25zdCBwYWdpbmF0aW9uID0gdXNlUGFnaW5hdGlvbjxJbnN0YWxsYWJsZVBsdWdpbj4oe1xuICAgIHRvdGFsSXRlbXM6IGF2YWlsYWJsZVBsdWdpbnMubGVuZ3RoLFxuICAgIHNlbGVjdGVkSW5kZXgsXG4gIH0pXG5cbiAgLy8gRGV0YWlscyB2aWV3IHN0YXRlXG4gIGNvbnN0IFtkZXRhaWxzTWVudUluZGV4LCBzZXREZXRhaWxzTWVudUluZGV4XSA9IHVzZVN0YXRlKDApXG4gIGNvbnN0IFtpc0luc3RhbGxpbmcsIHNldElzSW5zdGFsbGluZ10gPSB1c2VTdGF0ZShmYWxzZSlcbiAgY29uc3QgW2luc3RhbGxFcnJvciwgc2V0SW5zdGFsbEVycm9yXSA9IHVzZVN0YXRlPHN0cmluZyB8IG51bGw+KG51bGwpXG5cbiAgLy8gV2FybmluZyBzdGF0ZSBmb3Igbm9uLWNyaXRpY2FsIGVycm9ycyAoZS5nLiwgc29tZSBtYXJrZXRwbGFjZXMgZmFpbGVkIHRvIGxvYWQpXG4gIGNvbnN0IFt3YXJuaW5nLCBzZXRXYXJuaW5nXSA9IHVzZVN0YXRlPHN0cmluZyB8IG51bGw+KG51bGwpXG5cbiAgLy8gSGFuZGxlIGVzY2FwZSB0byBnbyBiYWNrIC0gdmlld1N0YXRlLWRlcGVuZGVudCBuYXZpZ2F0aW9uXG4gIGNvbnN0IGhhbmRsZUJhY2sgPSBSZWFjdC51c2VDYWxsYmFjaygoKSA9PiB7XG4gICAgaWYgKHZpZXdTdGF0ZSA9PT0gJ3BsdWdpbi1saXN0Jykge1xuICAgICAgLy8gSWYgbmF2aWdhdGVkIGRpcmVjdGx5IHRvIGEgc3BlY2lmaWMgbWFya2V0cGxhY2UgdmlhIHRhcmdldE1hcmtldHBsYWNlLFxuICAgICAgLy8gZ28gYmFjayB0byBtYW5hZ2UtbWFya2V0cGxhY2VzIHNob3dpbmcgdGhhdCBtYXJrZXRwbGFjZSdzIGRldGFpbHNcbiAgICAgIGlmICh0YXJnZXRNYXJrZXRwbGFjZSkge1xuICAgICAgICBzZXRQYXJlbnRWaWV3U3RhdGUoe1xuICAgICAgICAgIHR5cGU6ICdtYW5hZ2UtbWFya2V0cGxhY2VzJyxcbiAgICAgICAgICB0YXJnZXRNYXJrZXRwbGFjZSxcbiAgICAgICAgfSlcbiAgICAgIH0gZWxzZSBpZiAobWFya2V0cGxhY2VzLmxlbmd0aCA9PT0gMSkge1xuICAgICAgICAvLyBJZiB0aGVyZSdzIG9ubHkgb25lIG1hcmtldHBsYWNlLCBza2lwIHRoZSBtYXJrZXRwbGFjZS1saXN0IHZpZXdcbiAgICAgICAgLy8gc2luY2Ugd2UgYXV0by1uYXZpZ2F0ZWQgcGFzdCBpdCBvbiBsb2FkXG4gICAgICAgIHNldFBhcmVudFZpZXdTdGF0ZSh7IHR5cGU6ICdtZW51JyB9KVxuICAgICAgfSBlbHNlIHtcbiAgICAgICAgc2V0Vmlld1N0YXRlKCdtYXJrZXRwbGFjZS1saXN0JylcbiAgICAgICAgc2V0U2VsZWN0ZWRNYXJrZXRwbGFjZShudWxsKVxuICAgICAgICBzZXRTZWxlY3RlZEZvckluc3RhbGwobmV3IFNldCgpKVxuICAgICAgfVxuICAgIH0gZWxzZSBpZiAodmlld1N0YXRlID09PSAncGx1Z2luLWRldGFpbHMnKSB7XG4gICAgICBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1saXN0JylcbiAgICAgIHNldFNlbGVjdGVkUGx1Z2luKG51bGwpXG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIEF0IHJvb3QgbGV2ZWwgKG1hcmtldHBsYWNlLWxpc3QpLCBleGl0IHRoZSBwbHVnaW4gbWVudVxuICAgICAgc2V0UGFyZW50Vmlld1N0YXRlKHsgdHlwZTogJ21lbnUnIH0pXG4gICAgfVxuICB9LCBbdmlld1N0YXRlLCB0YXJnZXRNYXJrZXRwbGFjZSwgc2V0UGFyZW50Vmlld1N0YXRlLCBtYXJrZXRwbGFjZXMubGVuZ3RoXSlcblxuICB1c2VLZXliaW5kaW5nKCdjb25maXJtOm5vJywgaGFuZGxlQmFjaywgeyBjb250ZXh0OiAnQ29uZmlybWF0aW9uJyB9KVxuXG4gIC8vIExvYWQgbWFya2V0cGxhY2VzIGFuZCBjb3VudCBpbnN0YWxsZWQgcGx1Z2luc1xuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIGFzeW5jIGZ1bmN0aW9uIGxvYWRNYXJrZXRwbGFjZURhdGEoKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBjb25maWcgPSBhd2FpdCBsb2FkS25vd25NYXJrZXRwbGFjZXNDb25maWcoKVxuXG4gICAgICAgIC8vIExvYWQgbWFya2V0cGxhY2VzIHdpdGggZ3JhY2VmdWwgZGVncmFkYXRpb25cbiAgICAgICAgY29uc3QgeyBtYXJrZXRwbGFjZXMsIGZhaWx1cmVzIH0gPVxuICAgICAgICAgIGF3YWl0IGxvYWRNYXJrZXRwbGFjZXNXaXRoR3JhY2VmdWxEZWdyYWRhdGlvbihjb25maWcpXG5cbiAgICAgICAgY29uc3QgbWFya2V0cGxhY2VJbmZvczogTWFya2V0cGxhY2VJbmZvW10gPSBbXVxuICAgICAgICBmb3IgKGNvbnN0IHtcbiAgICAgICAgICBuYW1lLFxuICAgICAgICAgIGNvbmZpZzogbWFya2V0cGxhY2VDb25maWcsXG4gICAgICAgICAgZGF0YTogbWFya2V0cGxhY2UsXG4gICAgICAgIH0gb2YgbWFya2V0cGxhY2VzKSB7XG4gICAgICAgICAgaWYgKG1hcmtldHBsYWNlKSB7XG4gICAgICAgICAgICAvLyBDb3VudCBob3cgbWFueSBwbHVnaW5zIGZyb20gdGhpcyBtYXJrZXRwbGFjZSBhcmUgaW5zdGFsbGVkXG4gICAgICAgICAgICBjb25zdCBpbnN0YWxsZWRGcm9tVGhpc01hcmtldHBsYWNlID0gY291bnQoXG4gICAgICAgICAgICAgIG1hcmtldHBsYWNlLnBsdWdpbnMsXG4gICAgICAgICAgICAgIHBsdWdpbiA9PiBpc1BsdWdpbkluc3RhbGxlZChjcmVhdGVQbHVnaW5JZChwbHVnaW4ubmFtZSwgbmFtZSkpLFxuICAgICAgICAgICAgKVxuXG4gICAgICAgICAgICBtYXJrZXRwbGFjZUluZm9zLnB1c2goe1xuICAgICAgICAgICAgICBuYW1lLFxuICAgICAgICAgICAgICB0b3RhbFBsdWdpbnM6IG1hcmtldHBsYWNlLnBsdWdpbnMubGVuZ3RoLFxuICAgICAgICAgICAgICBpbnN0YWxsZWRDb3VudDogaW5zdGFsbGVkRnJvbVRoaXNNYXJrZXRwbGFjZSxcbiAgICAgICAgICAgICAgc291cmNlOiBnZXRNYXJrZXRwbGFjZVNvdXJjZURpc3BsYXkobWFya2V0cGxhY2VDb25maWcuc291cmNlKSxcbiAgICAgICAgICAgIH0pXG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gU29ydCBzbyBjbGF1ZGUtcGx1Z2luLWRpcmVjdG9yeSBpcyBhbHdheXMgZmlyc3RcbiAgICAgICAgbWFya2V0cGxhY2VJbmZvcy5zb3J0KChhLCBiKSA9PiB7XG4gICAgICAgICAgaWYgKGEubmFtZSA9PT0gJ2NsYXVkZS1wbHVnaW4tZGlyZWN0b3J5JykgcmV0dXJuIC0xXG4gICAgICAgICAgaWYgKGIubmFtZSA9PT0gJ2NsYXVkZS1wbHVnaW4tZGlyZWN0b3J5JykgcmV0dXJuIDFcbiAgICAgICAgICByZXR1cm4gMFxuICAgICAgICB9KVxuXG4gICAgICAgIHNldE1hcmtldHBsYWNlcyhtYXJrZXRwbGFjZUluZm9zKVxuXG4gICAgICAgIC8vIEhhbmRsZSBtYXJrZXRwbGFjZSBsb2FkaW5nIGVycm9ycy93YXJuaW5nc1xuICAgICAgICBjb25zdCBzdWNjZXNzQ291bnQgPSBjb3VudChtYXJrZXRwbGFjZXMsIG0gPT4gbS5kYXRhICE9PSBudWxsKVxuICAgICAgICBjb25zdCBlcnJvclJlc3VsdCA9IGZvcm1hdE1hcmtldHBsYWNlTG9hZGluZ0Vycm9ycyhcbiAgICAgICAgICBmYWlsdXJlcyxcbiAgICAgICAgICBzdWNjZXNzQ291bnQsXG4gICAgICAgIClcbiAgICAgICAgaWYgKGVycm9yUmVzdWx0KSB7XG4gICAgICAgICAgaWYgKGVycm9yUmVzdWx0LnR5cGUgPT09ICd3YXJuaW5nJykge1xuICAgICAgICAgICAgc2V0V2FybmluZyhcbiAgICAgICAgICAgICAgZXJyb3JSZXN1bHQubWVzc2FnZSArICcuIFNob3dpbmcgYXZhaWxhYmxlIG1hcmtldHBsYWNlcy4nLFxuICAgICAgICAgICAgKVxuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoZXJyb3JSZXN1bHQubWVzc2FnZSlcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBTa2lwIG1hcmtldHBsYWNlIHNlbGVjdGlvbiBpZiB0aGVyZSdzIG9ubHkgb25lIG1hcmtldHBsYWNlXG4gICAgICAgIGlmIChcbiAgICAgICAgICBtYXJrZXRwbGFjZUluZm9zLmxlbmd0aCA9PT0gMSAmJlxuICAgICAgICAgICF0YXJnZXRNYXJrZXRwbGFjZSAmJlxuICAgICAgICAgICF0YXJnZXRQbHVnaW5cbiAgICAgICAgKSB7XG4gICAgICAgICAgY29uc3Qgc2luZ2xlTWFya2V0cGxhY2UgPSBtYXJrZXRwbGFjZUluZm9zWzBdXG4gICAgICAgICAgaWYgKHNpbmdsZU1hcmtldHBsYWNlKSB7XG4gICAgICAgICAgICBzZXRTZWxlY3RlZE1hcmtldHBsYWNlKHNpbmdsZU1hcmtldHBsYWNlLm5hbWUpXG4gICAgICAgICAgICBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1saXN0JylcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBIYW5kbGUgdGFyZ2V0TWFya2V0cGxhY2UgYW5kIHRhcmdldFBsdWdpbiBhZnRlciBtYXJrZXRwbGFjZXMgYXJlIGxvYWRlZFxuICAgICAgICBpZiAodGFyZ2V0UGx1Z2luKSB7XG4gICAgICAgICAgLy8gU2VhcmNoIGZvciB0aGUgcGx1Z2luIGFjcm9zcyBhbGwgbWFya2V0cGxhY2VzXG4gICAgICAgICAgbGV0IGZvdW5kUGx1Z2luOiBJbnN0YWxsYWJsZVBsdWdpbiB8IG51bGwgPSBudWxsXG4gICAgICAgICAgbGV0IGZvdW5kTWFya2V0cGxhY2U6IHN0cmluZyB8IG51bGwgPSBudWxsXG5cbiAgICAgICAgICBmb3IgKGNvbnN0IFtuYW1lXSBvZiBPYmplY3QuZW50cmllcyhjb25maWcpKSB7XG4gICAgICAgICAgICBjb25zdCBtYXJrZXRwbGFjZSA9IGF3YWl0IGdldE1hcmtldHBsYWNlKG5hbWUpXG4gICAgICAgICAgICBpZiAobWFya2V0cGxhY2UpIHtcbiAgICAgICAgICAgICAgY29uc3QgcGx1Z2luID0gbWFya2V0cGxhY2UucGx1Z2lucy5maW5kKFxuICAgICAgICAgICAgICAgIHAgPT4gcC5uYW1lID09PSB0YXJnZXRQbHVnaW4sXG4gICAgICAgICAgICAgIClcbiAgICAgICAgICAgICAgaWYgKHBsdWdpbikge1xuICAgICAgICAgICAgICAgIGNvbnN0IHBsdWdpbklkID0gY3JlYXRlUGx1Z2luSWQocGx1Z2luLm5hbWUsIG5hbWUpXG4gICAgICAgICAgICAgICAgZm91bmRQbHVnaW4gPSB7XG4gICAgICAgICAgICAgICAgICBlbnRyeTogcGx1Z2luLFxuICAgICAgICAgICAgICAgICAgbWFya2V0cGxhY2VOYW1lOiBuYW1lLFxuICAgICAgICAgICAgICAgICAgcGx1Z2luSWQsXG4gICAgICAgICAgICAgICAgICAvLyBpc1BsdWdpbkdsb2JhbGx5SW5zdGFsbGVkOiBvbmx5IGJsb2NrIHdoZW4gdXNlci9tYW5hZ2VkIHNjb3BlXG4gICAgICAgICAgICAgICAgICAvLyBleGlzdHMgKG5vdGhpbmcgdG8gYWRkKS4gUHJvamVjdC9sb2NhbC1zY29wZSBpbnN0YWxscyBkb24ndFxuICAgICAgICAgICAgICAgICAgLy8gYmxvY2sg4oCUIHVzZXIgbWF5IHdhbnQgdG8gcHJvbW90ZSB0byB1c2VyIHNjb3BlIChnaC0yOTk5NykuXG4gICAgICAgICAgICAgICAgICBpc0luc3RhbGxlZDogaXNQbHVnaW5HbG9iYWxseUluc3RhbGxlZChwbHVnaW5JZCksXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGZvdW5kTWFya2V0cGxhY2UgPSBuYW1lXG4gICAgICAgICAgICAgICAgYnJlYWtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cblxuICAgICAgICAgIGlmIChmb3VuZFBsdWdpbiAmJiBmb3VuZE1hcmtldHBsYWNlKSB7XG4gICAgICAgICAgICAvLyBCbG9jayBvbmx5IG9uIGdsb2JhbCAodXNlci9tYW5hZ2VkKSBpbnN0YWxsIOKAlCBwcm9qZWN0L2xvY2FsIHNjb3BlXG4gICAgICAgICAgICAvLyBtZWFucyB0aGUgdXNlciBtaWdodCBzdGlsbCB3YW50IHRvIGFkZCBhIHVzZXItc2NvcGUgZW50cnkgc28gdGhlXG4gICAgICAgICAgICAvLyBwbHVnaW4gaXMgYXZhaWxhYmxlIGluIG90aGVyIHByb2plY3RzIChnaC0yOTk5NywgZ2gtMjkyNDAsIGdoLTI5MzkyKS5cbiAgICAgICAgICAgIC8vIFRoZSBwbHVnaW4tZGV0YWlscyB2aWV3IG9mZmVycyBhbGwgdGhyZWUgc2NvcGUgb3B0aW9uczsgdGhlIGJhY2tlbmRcbiAgICAgICAgICAgIC8vIChpbnN0YWxsUGx1Z2luT3Ag4oaSIGFkZEluc3RhbGxlZFBsdWdpbikgYWxyZWFkeSBzdXBwb3J0cyBtdWx0aXBsZVxuICAgICAgICAgICAgLy8gc2NvcGUgZW50cmllcyBwZXIgcGx1Z2luLlxuICAgICAgICAgICAgY29uc3QgcGx1Z2luSWQgPSBmb3VuZFBsdWdpbi5wbHVnaW5JZFxuICAgICAgICAgICAgY29uc3QgZ2xvYmFsbHlJbnN0YWxsZWQgPSBpc1BsdWdpbkdsb2JhbGx5SW5zdGFsbGVkKHBsdWdpbklkKVxuXG4gICAgICAgICAgICBpZiAoZ2xvYmFsbHlJbnN0YWxsZWQpIHtcbiAgICAgICAgICAgICAgc2V0RXJyb3IoXG4gICAgICAgICAgICAgICAgYFBsdWdpbiAnJHtwbHVnaW5JZH0nIGlzIGFscmVhZHkgaW5zdGFsbGVkIGdsb2JhbGx5LiBVc2UgJy9wbHVnaW4nIHRvIG1hbmFnZSBleGlzdGluZyBwbHVnaW5zLmAsXG4gICAgICAgICAgICAgIClcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIC8vIE5hdmlnYXRlIHRvIHRoZSBwbHVnaW4gZGV0YWlscyB2aWV3XG4gICAgICAgICAgICAgIHNldFNlbGVjdGVkTWFya2V0cGxhY2UoZm91bmRNYXJrZXRwbGFjZSlcbiAgICAgICAgICAgICAgc2V0U2VsZWN0ZWRQbHVnaW4oZm91bmRQbHVnaW4pXG4gICAgICAgICAgICAgIHNldFZpZXdTdGF0ZSgncGx1Z2luLWRldGFpbHMnKVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzZXRFcnJvcihgUGx1Z2luIFwiJHt0YXJnZXRQbHVnaW59XCIgbm90IGZvdW5kIGluIGFueSBtYXJrZXRwbGFjZWApXG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2UgaWYgKHRhcmdldE1hcmtldHBsYWNlKSB7XG4gICAgICAgICAgLy8gTmF2aWdhdGUgZGlyZWN0bHkgdG8gdGhlIHNwZWNpZmllZCBtYXJrZXRwbGFjZVxuICAgICAgICAgIGNvbnN0IG1hcmtldHBsYWNlRXhpc3RzID0gbWFya2V0cGxhY2VJbmZvcy5zb21lKFxuICAgICAgICAgICAgbSA9PiBtLm5hbWUgPT09IHRhcmdldE1hcmtldHBsYWNlLFxuICAgICAgICAgIClcbiAgICAgICAgICBpZiAobWFya2V0cGxhY2VFeGlzdHMpIHtcbiAgICAgICAgICAgIHNldFNlbGVjdGVkTWFya2V0cGxhY2UodGFyZ2V0TWFya2V0cGxhY2UpXG4gICAgICAgICAgICBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1saXN0JylcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgc2V0RXJyb3IoYE1hcmtldHBsYWNlIFwiJHt0YXJnZXRNYXJrZXRwbGFjZX1cIiBub3QgZm91bmRgKVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIHNldEVycm9yKFxuICAgICAgICAgIGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiAnRmFpbGVkIHRvIGxvYWQgbWFya2V0cGxhY2VzJyxcbiAgICAgICAgKVxuICAgICAgfSBmaW5hbGx5IHtcbiAgICAgICAgc2V0TG9hZGluZyhmYWxzZSlcbiAgICAgIH1cbiAgICB9XG4gICAgdm9pZCBsb2FkTWFya2V0cGxhY2VEYXRhKClcbiAgfSwgW3NldEVycm9yLCB0YXJnZXRNYXJrZXRwbGFjZSwgdGFyZ2V0UGx1Z2luXSlcblxuICAvLyBMb2FkIHBsdWdpbnMgd2hlbiBhIG1hcmtldHBsYWNlIGlzIHNlbGVjdGVkXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgaWYgKCFzZWxlY3RlZE1hcmtldHBsYWNlKSByZXR1cm5cblxuICAgIGxldCBjYW5jZWxsZWQgPSBmYWxzZVxuXG4gICAgYXN5bmMgZnVuY3Rpb24gbG9hZFBsdWdpbnNGb3JNYXJrZXRwbGFjZShtYXJrZXRwbGFjZU5hbWU6IHN0cmluZykge1xuICAgICAgc2V0TG9hZGluZyh0cnVlKVxuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgbWFya2V0cGxhY2UgPSBhd2FpdCBnZXRNYXJrZXRwbGFjZShtYXJrZXRwbGFjZU5hbWUpXG4gICAgICAgIGlmIChjYW5jZWxsZWQpIHJldHVyblxuICAgICAgICBpZiAoIW1hcmtldHBsYWNlKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGBGYWlsZWQgdG8gbG9hZCBtYXJrZXRwbGFjZTogJHttYXJrZXRwbGFjZU5hbWV9YClcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIEZpbHRlciBvdXQgYWxyZWFkeSBpbnN0YWxsZWQgcGx1Z2luc1xuICAgICAgICBjb25zdCBpbnN0YWxsYWJsZVBsdWdpbnM6IEluc3RhbGxhYmxlUGx1Z2luW10gPSBbXVxuICAgICAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIG1hcmtldHBsYWNlLnBsdWdpbnMpIHtcbiAgICAgICAgICBjb25zdCBwbHVnaW5JZCA9IGNyZWF0ZVBsdWdpbklkKGVudHJ5Lm5hbWUsIG1hcmtldHBsYWNlTmFtZSlcbiAgICAgICAgICBpZiAoaXNQbHVnaW5CbG9ja2VkQnlQb2xpY3kocGx1Z2luSWQpKSBjb250aW51ZVxuICAgICAgICAgIGluc3RhbGxhYmxlUGx1Z2lucy5wdXNoKHtcbiAgICAgICAgICAgIGVudHJ5LFxuICAgICAgICAgICAgbWFya2V0cGxhY2VOYW1lOiBtYXJrZXRwbGFjZU5hbWUsXG4gICAgICAgICAgICBwbHVnaW5JZCxcbiAgICAgICAgICAgIC8vIE9ubHkgbWFyayBhcyBcImluc3RhbGxlZFwiIHdoZW4gZ2xvYmFsbHkgc2NvcGVkICh1c2VyL21hbmFnZWQpLlxuICAgICAgICAgICAgLy8gUHJvamVjdC9sb2NhbCBpbnN0YWxscyBkb24ndCBibG9jayDigJQgdXNlciBjYW4gYWRkIHVzZXIgc2NvcGVcbiAgICAgICAgICAgIC8vIHZpYSB0aGUgcGx1Z2luLWRldGFpbHMgdmlldyAoZ2gtMjk5OTcpLlxuICAgICAgICAgICAgaXNJbnN0YWxsZWQ6IGlzUGx1Z2luR2xvYmFsbHlJbnN0YWxsZWQocGx1Z2luSWQpLFxuICAgICAgICAgIH0pXG4gICAgICAgIH1cblxuICAgICAgICAvLyBGZXRjaCBpbnN0YWxsIGNvdW50cyBhbmQgc29ydCBieSBwb3B1bGFyaXR5XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgY29uc3QgY291bnRzID0gYXdhaXQgZ2V0SW5zdGFsbENvdW50cygpXG4gICAgICAgICAgaWYgKGNhbmNlbGxlZCkgcmV0dXJuXG4gICAgICAgICAgc2V0SW5zdGFsbENvdW50cyhjb3VudHMpXG5cbiAgICAgICAgICBpZiAoY291bnRzKSB7XG4gICAgICAgICAgICAvLyBTb3J0IGJ5IGluc3RhbGwgY291bnQgKGRlc2NlbmRpbmcpLCB0aGVuIGFscGhhYmV0aWNhbGx5XG4gICAgICAgICAgICBpbnN0YWxsYWJsZVBsdWdpbnMuc29ydCgoYSwgYikgPT4ge1xuICAgICAgICAgICAgICBjb25zdCBjb3VudEEgPSBjb3VudHMuZ2V0KGEucGx1Z2luSWQpID8/IDBcbiAgICAgICAgICAgICAgY29uc3QgY291bnRCID0gY291bnRzLmdldChiLnBsdWdpbklkKSA/PyAwXG4gICAgICAgICAgICAgIGlmIChjb3VudEEgIT09IGNvdW50QikgcmV0dXJuIGNvdW50QiAtIGNvdW50QVxuICAgICAgICAgICAgICByZXR1cm4gYS5lbnRyeS5uYW1lLmxvY2FsZUNvbXBhcmUoYi5lbnRyeS5uYW1lKVxuICAgICAgICAgICAgfSlcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gTm8gY291bnRzIGF2YWlsYWJsZSAtIHNvcnQgYWxwaGFiZXRpY2FsbHlcbiAgICAgICAgICAgIGluc3RhbGxhYmxlUGx1Z2lucy5zb3J0KChhLCBiKSA9PlxuICAgICAgICAgICAgICBhLmVudHJ5Lm5hbWUubG9jYWxlQ29tcGFyZShiLmVudHJ5Lm5hbWUpLFxuICAgICAgICAgICAgKVxuICAgICAgICAgIH1cbiAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICBpZiAoY2FuY2VsbGVkKSByZXR1cm5cbiAgICAgICAgICAvLyBMb2cgdGhlIGVycm9yLCB0aGVuIGdyYWNlZnVsbHkgZGVncmFkZSB0byBhbHBoYWJldGljYWwgc29ydFxuICAgICAgICAgIGxvZ0ZvckRlYnVnZ2luZyhcbiAgICAgICAgICAgIGBGYWlsZWQgdG8gZmV0Y2ggaW5zdGFsbCBjb3VudHM6ICR7ZXJyb3JNZXNzYWdlKGVycm9yKX1gLFxuICAgICAgICAgIClcbiAgICAgICAgICBpbnN0YWxsYWJsZVBsdWdpbnMuc29ydCgoYSwgYikgPT5cbiAgICAgICAgICAgIGEuZW50cnkubmFtZS5sb2NhbGVDb21wYXJlKGIuZW50cnkubmFtZSksXG4gICAgICAgICAgKVxuICAgICAgICB9XG5cbiAgICAgICAgc2V0QXZhaWxhYmxlUGx1Z2lucyhpbnN0YWxsYWJsZVBsdWdpbnMpXG4gICAgICAgIHNldFNlbGVjdGVkSW5kZXgoMClcbiAgICAgICAgc2V0U2VsZWN0ZWRGb3JJbnN0YWxsKG5ldyBTZXQoKSlcbiAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICBpZiAoY2FuY2VsbGVkKSByZXR1cm5cbiAgICAgICAgc2V0RXJyb3IoZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6ICdGYWlsZWQgdG8gbG9hZCBwbHVnaW5zJylcbiAgICAgIH0gZmluYWxseSB7XG4gICAgICAgIHNldExvYWRpbmcoZmFsc2UpXG4gICAgICB9XG4gICAgfVxuXG4gICAgdm9pZCBsb2FkUGx1Z2luc0Zvck1hcmtldHBsYWNlKHNlbGVjdGVkTWFya2V0cGxhY2UpXG4gICAgcmV0dXJuICgpID0+IHtcbiAgICAgIGNhbmNlbGxlZCA9IHRydWVcbiAgICB9XG4gIH0sIFtzZWxlY3RlZE1hcmtldHBsYWNlLCBzZXRFcnJvcl0pXG5cbiAgLy8gSW5zdGFsbCBzZWxlY3RlZCBwbHVnaW5zXG4gIGNvbnN0IGluc3RhbGxTZWxlY3RlZFBsdWdpbnMgPSBhc3luYyAoKSA9PiB7XG4gICAgaWYgKHNlbGVjdGVkRm9ySW5zdGFsbC5zaXplID09PSAwKSByZXR1cm5cblxuICAgIGNvbnN0IHBsdWdpbnNUb0luc3RhbGwgPSBhdmFpbGFibGVQbHVnaW5zLmZpbHRlcihwID0+XG4gICAgICBzZWxlY3RlZEZvckluc3RhbGwuaGFzKHAucGx1Z2luSWQpLFxuICAgIClcblxuICAgIHNldEluc3RhbGxpbmdQbHVnaW5zKG5ldyBTZXQocGx1Z2luc1RvSW5zdGFsbC5tYXAocCA9PiBwLnBsdWdpbklkKSkpXG5cbiAgICBsZXQgc3VjY2Vzc0NvdW50ID0gMFxuICAgIGxldCBmYWlsdXJlQ291bnQgPSAwXG4gICAgY29uc3QgbmV3RmFpbGVkUGx1Z2luczogQXJyYXk8eyBuYW1lOiBzdHJpbmc7IHJlYXNvbjogc3RyaW5nIH0+ID0gW11cblxuICAgIGZvciAoY29uc3QgcGx1Z2luIG9mIHBsdWdpbnNUb0luc3RhbGwpIHtcbiAgICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGluc3RhbGxQbHVnaW5Gcm9tTWFya2V0cGxhY2Uoe1xuICAgICAgICBwbHVnaW5JZDogcGx1Z2luLnBsdWdpbklkLFxuICAgICAgICBlbnRyeTogcGx1Z2luLmVudHJ5LFxuICAgICAgICBtYXJrZXRwbGFjZU5hbWU6IHBsdWdpbi5tYXJrZXRwbGFjZU5hbWUsXG4gICAgICAgIHNjb3BlOiAndXNlcicsXG4gICAgICB9KVxuXG4gICAgICBpZiAocmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgICAgc3VjY2Vzc0NvdW50KytcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGZhaWx1cmVDb3VudCsrXG4gICAgICAgIG5ld0ZhaWxlZFBsdWdpbnMucHVzaCh7XG4gICAgICAgICAgbmFtZTogcGx1Z2luLmVudHJ5Lm5hbWUsXG4gICAgICAgICAgcmVhc29uOiByZXN1bHQuZXJyb3IsXG4gICAgICAgIH0pXG4gICAgICB9XG4gICAgfVxuXG4gICAgc2V0SW5zdGFsbGluZ1BsdWdpbnMobmV3IFNldCgpKVxuICAgIHNldFNlbGVjdGVkRm9ySW5zdGFsbChuZXcgU2V0KCkpXG4gICAgY2xlYXJBbGxDYWNoZXMoKVxuXG4gICAgLy8gSGFuZGxlIGluc3RhbGxhdGlvbiByZXN1bHRzXG4gICAgaWYgKGZhaWx1cmVDb3VudCA9PT0gMCkge1xuICAgICAgLy8gQWxsIHN1Y2NlZWRlZFxuICAgICAgY29uc3QgbWVzc2FnZSA9XG4gICAgICAgIGDinJMgSW5zdGFsbGVkICR7c3VjY2Vzc0NvdW50fSAke3BsdXJhbChzdWNjZXNzQ291bnQsICdwbHVnaW4nKX0uIGAgK1xuICAgICAgICBgUnVuIC9yZWxvYWQtcGx1Z2lucyB0byBhY3RpdmF0ZS5gXG5cbiAgICAgIHNldFJlc3VsdChtZXNzYWdlKVxuICAgIH0gZWxzZSBpZiAoc3VjY2Vzc0NvdW50ID09PSAwKSB7XG4gICAgICAvLyBBbGwgZmFpbGVkIC0gc2hvdyBlcnJvciB3aXRoIHJlYXNvbnNcbiAgICAgIHNldEVycm9yKFxuICAgICAgICBgRmFpbGVkIHRvIGluc3RhbGw6ICR7Zm9ybWF0RmFpbHVyZURldGFpbHMobmV3RmFpbGVkUGx1Z2lucywgdHJ1ZSl9YCxcbiAgICAgIClcbiAgICB9IGVsc2Uge1xuICAgICAgLy8gTWl4ZWQgcmVzdWx0cyAtIHNob3cgcGFydGlhbCBzdWNjZXNzXG4gICAgICBjb25zdCBtZXNzYWdlID1cbiAgICAgICAgYOKckyBJbnN0YWxsZWQgJHtzdWNjZXNzQ291bnR9IG9mICR7c3VjY2Vzc0NvdW50ICsgZmFpbHVyZUNvdW50fSBwbHVnaW5zLiBgICtcbiAgICAgICAgYEZhaWxlZDogJHtmb3JtYXRGYWlsdXJlRGV0YWlscyhuZXdGYWlsZWRQbHVnaW5zLCBmYWxzZSl9LiBgICtcbiAgICAgICAgYFJ1biAvcmVsb2FkLXBsdWdpbnMgdG8gYWN0aXZhdGUgc3VjY2Vzc2Z1bGx5IGluc3RhbGxlZCBwbHVnaW5zLmBcblxuICAgICAgc2V0UmVzdWx0KG1lc3NhZ2UpXG4gICAgfVxuXG4gICAgLy8gSGFuZGxlIGNvbXBsZXRpb24gY2FsbGJhY2sgYW5kIG5hdmlnYXRpb25cbiAgICBpZiAoc3VjY2Vzc0NvdW50ID4gMCkge1xuICAgICAgaWYgKG9uSW5zdGFsbENvbXBsZXRlKSB7XG4gICAgICAgIGF3YWl0IG9uSW5zdGFsbENvbXBsZXRlKClcbiAgICAgIH1cbiAgICB9XG5cbiAgICBzZXRQYXJlbnRWaWV3U3RhdGUoeyB0eXBlOiAnbWVudScgfSlcbiAgfVxuXG4gIC8vIEluc3RhbGwgc2luZ2xlIHBsdWdpbiBmcm9tIGRldGFpbHMgdmlld1xuICBjb25zdCBoYW5kbGVTaW5nbGVQbHVnaW5JbnN0YWxsID0gYXN5bmMgKFxuICAgIHBsdWdpbjogSW5zdGFsbGFibGVQbHVnaW4sXG4gICAgc2NvcGU6ICd1c2VyJyB8ICdwcm9qZWN0JyB8ICdsb2NhbCcgPSAndXNlcicsXG4gICkgPT4ge1xuICAgIHNldElzSW5zdGFsbGluZyh0cnVlKVxuICAgIHNldEluc3RhbGxFcnJvcihudWxsKVxuXG4gICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgaW5zdGFsbFBsdWdpbkZyb21NYXJrZXRwbGFjZSh7XG4gICAgICBwbHVnaW5JZDogcGx1Z2luLnBsdWdpbklkLFxuICAgICAgZW50cnk6IHBsdWdpbi5lbnRyeSxcbiAgICAgIG1hcmtldHBsYWNlTmFtZTogcGx1Z2luLm1hcmtldHBsYWNlTmFtZSxcbiAgICAgIHNjb3BlLFxuICAgIH0pXG5cbiAgICBpZiAocmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgIGNvbnN0IGxvYWRlZCA9IGF3YWl0IGZpbmRQbHVnaW5PcHRpb25zVGFyZ2V0KHBsdWdpbi5wbHVnaW5JZClcbiAgICAgIGlmIChsb2FkZWQpIHtcbiAgICAgICAgc2V0SXNJbnN0YWxsaW5nKGZhbHNlKVxuICAgICAgICBzZXRWaWV3U3RhdGUoe1xuICAgICAgICAgIHR5cGU6ICdwbHVnaW4tb3B0aW9ucycsXG4gICAgICAgICAgcGx1Z2luOiBsb2FkZWQsXG4gICAgICAgICAgcGx1Z2luSWQ6IHBsdWdpbi5wbHVnaW5JZCxcbiAgICAgICAgfSlcbiAgICAgICAgcmV0dXJuXG4gICAgICB9XG4gICAgICBzZXRSZXN1bHQocmVzdWx0Lm1lc3NhZ2UpXG4gICAgICBpZiAob25JbnN0YWxsQ29tcGxldGUpIHtcbiAgICAgICAgYXdhaXQgb25JbnN0YWxsQ29tcGxldGUoKVxuICAgICAgfVxuICAgICAgc2V0UGFyZW50Vmlld1N0YXRlKHsgdHlwZTogJ21lbnUnIH0pXG4gICAgfSBlbHNlIHtcbiAgICAgIHNldElzSW5zdGFsbGluZyhmYWxzZSlcbiAgICAgIHNldEluc3RhbGxFcnJvcihyZXN1bHQuZXJyb3IpXG4gICAgfVxuICB9XG5cbiAgLy8gSGFuZGxlIGVycm9yIHN0YXRlXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgaWYgKGVycm9yKSB7XG4gICAgICBzZXRSZXN1bHQoZXJyb3IpXG4gICAgfVxuICB9LCBbZXJyb3IsIHNldFJlc3VsdF0pXG5cbiAgLy8gTWFya2V0cGxhY2UtbGlzdCBuYXZpZ2F0aW9uXG4gIHVzZUtleWJpbmRpbmdzKFxuICAgIHtcbiAgICAgICdzZWxlY3Q6cHJldmlvdXMnOiAoKSA9PiB7XG4gICAgICAgIGlmIChzZWxlY3RlZEluZGV4ID4gMCkge1xuICAgICAgICAgIHNldFNlbGVjdGVkSW5kZXgoc2VsZWN0ZWRJbmRleCAtIDEpXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICAnc2VsZWN0Om5leHQnOiAoKSA9PiB7XG4gICAgICAgIGlmIChzZWxlY3RlZEluZGV4IDwgbWFya2V0cGxhY2VzLmxlbmd0aCAtIDEpIHtcbiAgICAgICAgICBzZXRTZWxlY3RlZEluZGV4KHNlbGVjdGVkSW5kZXggKyAxKVxuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgJ3NlbGVjdDphY2NlcHQnOiAoKSA9PiB7XG4gICAgICAgIGNvbnN0IG1hcmtldHBsYWNlID0gbWFya2V0cGxhY2VzW3NlbGVjdGVkSW5kZXhdXG4gICAgICAgIGlmIChtYXJrZXRwbGFjZSkge1xuICAgICAgICAgIHNldFNlbGVjdGVkTWFya2V0cGxhY2UobWFya2V0cGxhY2UubmFtZSlcbiAgICAgICAgICBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1saXN0JylcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICB9LFxuICAgIHsgY29udGV4dDogJ1NlbGVjdCcsIGlzQWN0aXZlOiB2aWV3U3RhdGUgPT09ICdtYXJrZXRwbGFjZS1saXN0JyB9LFxuICApXG5cbiAgLy8gUGx1Z2luLWxpc3QgbmF2aWdhdGlvblxuICB1c2VLZXliaW5kaW5ncyhcbiAgICB7XG4gICAgICAnc2VsZWN0OnByZXZpb3VzJzogKCkgPT4ge1xuICAgICAgICBpZiAoc2VsZWN0ZWRJbmRleCA+IDApIHtcbiAgICAgICAgICBwYWdpbmF0aW9uLmhhbmRsZVNlbGVjdGlvbkNoYW5nZShzZWxlY3RlZEluZGV4IC0gMSwgc2V0U2VsZWN0ZWRJbmRleClcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgICdzZWxlY3Q6bmV4dCc6ICgpID0+IHtcbiAgICAgICAgaWYgKHNlbGVjdGVkSW5kZXggPCBhdmFpbGFibGVQbHVnaW5zLmxlbmd0aCAtIDEpIHtcbiAgICAgICAgICBwYWdpbmF0aW9uLmhhbmRsZVNlbGVjdGlvbkNoYW5nZShzZWxlY3RlZEluZGV4ICsgMSwgc2V0U2VsZWN0ZWRJbmRleClcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgICdzZWxlY3Q6YWNjZXB0JzogKCkgPT4ge1xuICAgICAgICBpZiAoXG4gICAgICAgICAgc2VsZWN0ZWRJbmRleCA9PT0gYXZhaWxhYmxlUGx1Z2lucy5sZW5ndGggJiZcbiAgICAgICAgICBzZWxlY3RlZEZvckluc3RhbGwuc2l6ZSA+IDBcbiAgICAgICAgKSB7XG4gICAgICAgICAgdm9pZCBpbnN0YWxsU2VsZWN0ZWRQbHVnaW5zKClcbiAgICAgICAgfSBlbHNlIGlmIChzZWxlY3RlZEluZGV4IDwgYXZhaWxhYmxlUGx1Z2lucy5sZW5ndGgpIHtcbiAgICAgICAgICBjb25zdCBwbHVnaW4gPSBhdmFpbGFibGVQbHVnaW5zW3NlbGVjdGVkSW5kZXhdXG4gICAgICAgICAgaWYgKHBsdWdpbikge1xuICAgICAgICAgICAgaWYgKHBsdWdpbi5pc0luc3RhbGxlZCkge1xuICAgICAgICAgICAgICBzZXRQYXJlbnRWaWV3U3RhdGUoe1xuICAgICAgICAgICAgICAgIHR5cGU6ICdtYW5hZ2UtcGx1Z2lucycsXG4gICAgICAgICAgICAgICAgdGFyZ2V0UGx1Z2luOiBwbHVnaW4uZW50cnkubmFtZSxcbiAgICAgICAgICAgICAgICB0YXJnZXRNYXJrZXRwbGFjZTogcGx1Z2luLm1hcmtldHBsYWNlTmFtZSxcbiAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIHNldFNlbGVjdGVkUGx1Z2luKHBsdWdpbilcbiAgICAgICAgICAgICAgc2V0Vmlld1N0YXRlKCdwbHVnaW4tZGV0YWlscycpXG4gICAgICAgICAgICAgIHNldERldGFpbHNNZW51SW5kZXgoMClcbiAgICAgICAgICAgICAgc2V0SW5zdGFsbEVycm9yKG51bGwpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9LFxuICAgIH0sXG4gICAgeyBjb250ZXh0OiAnU2VsZWN0JywgaXNBY3RpdmU6IHZpZXdTdGF0ZSA9PT0gJ3BsdWdpbi1saXN0JyB9LFxuICApXG5cbiAgdXNlS2V5YmluZGluZ3MoXG4gICAge1xuICAgICAgJ3BsdWdpbjp0b2dnbGUnOiAoKSA9PiB7XG4gICAgICAgIGlmIChzZWxlY3RlZEluZGV4IDwgYXZhaWxhYmxlUGx1Z2lucy5sZW5ndGgpIHtcbiAgICAgICAgICBjb25zdCBwbHVnaW4gPSBhdmFpbGFibGVQbHVnaW5zW3NlbGVjdGVkSW5kZXhdXG4gICAgICAgICAgaWYgKHBsdWdpbiAmJiAhcGx1Z2luLmlzSW5zdGFsbGVkKSB7XG4gICAgICAgICAgICBjb25zdCBuZXdTZWxlY3Rpb24gPSBuZXcgU2V0KHNlbGVjdGVkRm9ySW5zdGFsbClcbiAgICAgICAgICAgIGlmIChuZXdTZWxlY3Rpb24uaGFzKHBsdWdpbi5wbHVnaW5JZCkpIHtcbiAgICAgICAgICAgICAgbmV3U2VsZWN0aW9uLmRlbGV0ZShwbHVnaW4ucGx1Z2luSWQpXG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICBuZXdTZWxlY3Rpb24uYWRkKHBsdWdpbi5wbHVnaW5JZClcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHNldFNlbGVjdGVkRm9ySW5zdGFsbChuZXdTZWxlY3Rpb24pXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9LFxuICAgICAgJ3BsdWdpbjppbnN0YWxsJzogKCkgPT4ge1xuICAgICAgICBpZiAoc2VsZWN0ZWRGb3JJbnN0YWxsLnNpemUgPiAwKSB7XG4gICAgICAgICAgdm9pZCBpbnN0YWxsU2VsZWN0ZWRQbHVnaW5zKClcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICB9LFxuICAgIHsgY29udGV4dDogJ1BsdWdpbicsIGlzQWN0aXZlOiB2aWV3U3RhdGUgPT09ICdwbHVnaW4tbGlzdCcgfSxcbiAgKVxuXG4gIC8vIFBsdWdpbi1kZXRhaWxzIG5hdmlnYXRpb25cbiAgY29uc3QgZGV0YWlsc01lbnVPcHRpb25zID0gUmVhY3QudXNlTWVtbygoKSA9PiB7XG4gICAgaWYgKCFzZWxlY3RlZFBsdWdpbikgcmV0dXJuIFtdXG4gICAgY29uc3QgaGFzSG9tZXBhZ2UgPSBzZWxlY3RlZFBsdWdpbi5lbnRyeS5ob21lcGFnZVxuICAgIGNvbnN0IGdpdGh1YlJlcG8gPSBleHRyYWN0R2l0SHViUmVwbyhzZWxlY3RlZFBsdWdpbilcbiAgICByZXR1cm4gYnVpbGRQbHVnaW5EZXRhaWxzTWVudU9wdGlvbnMoaGFzSG9tZXBhZ2UsIGdpdGh1YlJlcG8pXG4gIH0sIFtzZWxlY3RlZFBsdWdpbl0pXG5cbiAgdXNlS2V5YmluZGluZ3MoXG4gICAge1xuICAgICAgJ3NlbGVjdDpwcmV2aW91cyc6ICgpID0+IHtcbiAgICAgICAgaWYgKGRldGFpbHNNZW51SW5kZXggPiAwKSB7XG4gICAgICAgICAgc2V0RGV0YWlsc01lbnVJbmRleChkZXRhaWxzTWVudUluZGV4IC0gMSlcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgICdzZWxlY3Q6bmV4dCc6ICgpID0+IHtcbiAgICAgICAgaWYgKGRldGFpbHNNZW51SW5kZXggPCBkZXRhaWxzTWVudU9wdGlvbnMubGVuZ3RoIC0gMSkge1xuICAgICAgICAgIHNldERldGFpbHNNZW51SW5kZXgoZGV0YWlsc01lbnVJbmRleCArIDEpXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICAnc2VsZWN0OmFjY2VwdCc6ICgpID0+IHtcbiAgICAgICAgaWYgKCFzZWxlY3RlZFBsdWdpbikgcmV0dXJuXG4gICAgICAgIGNvbnN0IGFjdGlvbiA9IGRldGFpbHNNZW51T3B0aW9uc1tkZXRhaWxzTWVudUluZGV4XT8uYWN0aW9uXG4gICAgICAgIGNvbnN0IGhhc0hvbWVwYWdlID0gc2VsZWN0ZWRQbHVnaW4uZW50cnkuaG9tZXBhZ2VcbiAgICAgICAgY29uc3QgZ2l0aHViUmVwbyA9IGV4dHJhY3RHaXRIdWJSZXBvKHNlbGVjdGVkUGx1Z2luKVxuICAgICAgICBpZiAoYWN0aW9uID09PSAnaW5zdGFsbC11c2VyJykge1xuICAgICAgICAgIHZvaWQgaGFuZGxlU2luZ2xlUGx1Z2luSW5zdGFsbChzZWxlY3RlZFBsdWdpbiwgJ3VzZXInKVxuICAgICAgICB9IGVsc2UgaWYgKGFjdGlvbiA9PT0gJ2luc3RhbGwtcHJvamVjdCcpIHtcbiAgICAgICAgICB2b2lkIGhhbmRsZVNpbmdsZVBsdWdpbkluc3RhbGwoc2VsZWN0ZWRQbHVnaW4sICdwcm9qZWN0JylcbiAgICAgICAgfSBlbHNlIGlmIChhY3Rpb24gPT09ICdpbnN0YWxsLWxvY2FsJykge1xuICAgICAgICAgIHZvaWQgaGFuZGxlU2luZ2xlUGx1Z2luSW5zdGFsbChzZWxlY3RlZFBsdWdpbiwgJ2xvY2FsJylcbiAgICAgICAgfSBlbHNlIGlmIChhY3Rpb24gPT09ICdob21lcGFnZScgJiYgaGFzSG9tZXBhZ2UpIHtcbiAgICAgICAgICB2b2lkIG9wZW5Ccm93c2VyKGhhc0hvbWVwYWdlKVxuICAgICAgICB9IGVsc2UgaWYgKGFjdGlvbiA9PT0gJ2dpdGh1YicgJiYgZ2l0aHViUmVwbykge1xuICAgICAgICAgIHZvaWQgb3BlbkJyb3dzZXIoYGh0dHBzOi8vZ2l0aHViLmNvbS8ke2dpdGh1YlJlcG99YClcbiAgICAgICAgfSBlbHNlIGlmIChhY3Rpb24gPT09ICdiYWNrJykge1xuICAgICAgICAgIHNldFZpZXdTdGF0ZSgncGx1Z2luLWxpc3QnKVxuICAgICAgICAgIHNldFNlbGVjdGVkUGx1Z2luKG51bGwpXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfSxcbiAgICB7XG4gICAgICBjb250ZXh0OiAnU2VsZWN0JyxcbiAgICAgIGlzQWN0aXZlOiB2aWV3U3RhdGUgPT09ICdwbHVnaW4tZGV0YWlscycgJiYgISFzZWxlY3RlZFBsdWdpbixcbiAgICB9LFxuICApXG5cbiAgaWYgKHR5cGVvZiB2aWV3U3RhdGUgPT09ICdvYmplY3QnICYmIHZpZXdTdGF0ZS50eXBlID09PSAncGx1Z2luLW9wdGlvbnMnKSB7XG4gICAgY29uc3QgeyBwbHVnaW4sIHBsdWdpbklkIH0gPSB2aWV3U3RhdGVcbiAgICBmdW5jdGlvbiBmaW5pc2gobXNnOiBzdHJpbmcpOiB2b2lkIHtcbiAgICAgIHNldFJlc3VsdChtc2cpXG4gICAgICBpZiAob25JbnN0YWxsQ29tcGxldGUpIHtcbiAgICAgICAgdm9pZCBvbkluc3RhbGxDb21wbGV0ZSgpXG4gICAgICB9XG4gICAgICBzZXRQYXJlbnRWaWV3U3RhdGUoeyB0eXBlOiAnbWVudScgfSlcbiAgICB9XG4gICAgcmV0dXJuIChcbiAgICAgIDxQbHVnaW5PcHRpb25zRmxvd1xuICAgICAgICBwbHVnaW49e3BsdWdpbn1cbiAgICAgICAgcGx1Z2luSWQ9e3BsdWdpbklkfVxuICAgICAgICBvbkRvbmU9eyhvdXRjb21lLCBkZXRhaWwpID0+IHtcbiAgICAgICAgICBzd2l0Y2ggKG91dGNvbWUpIHtcbiAgICAgICAgICAgIGNhc2UgJ2NvbmZpZ3VyZWQnOlxuICAgICAgICAgICAgICBmaW5pc2goXG4gICAgICAgICAgICAgICAgYOKckyBJbnN0YWxsZWQgYW5kIGNvbmZpZ3VyZWQgJHtwbHVnaW4ubmFtZX0uIFJ1biAvcmVsb2FkLXBsdWdpbnMgdG8gYXBwbHkuYCxcbiAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgICBicmVha1xuICAgICAgICAgICAgY2FzZSAnc2tpcHBlZCc6XG4gICAgICAgICAgICAgIGZpbmlzaChcbiAgICAgICAgICAgICAgICBg4pyTIEluc3RhbGxlZCAke3BsdWdpbi5uYW1lfS4gUnVuIC9yZWxvYWQtcGx1Z2lucyB0byBhcHBseS5gLFxuICAgICAgICAgICAgICApXG4gICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgICBjYXNlICdlcnJvcic6XG4gICAgICAgICAgICAgIGZpbmlzaChgSW5zdGFsbGVkIGJ1dCBmYWlsZWQgdG8gc2F2ZSBjb25maWc6ICR7ZGV0YWlsfWApXG4gICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgfVxuICAgICAgICB9fVxuICAgICAgLz5cbiAgICApXG4gIH1cblxuICAvLyBMb2FkaW5nIHN0YXRlXG4gIGlmIChsb2FkaW5nKSB7XG4gICAgcmV0dXJuIDxUZXh0PkxvYWRpbmfigKY8L1RleHQ+XG4gIH1cblxuICAvLyBFcnJvciBzdGF0ZVxuICBpZiAoZXJyb3IpIHtcbiAgICByZXR1cm4gPFRleHQgY29sb3I9XCJlcnJvclwiPntlcnJvcn08L1RleHQ+XG4gIH1cblxuICAvLyBNYXJrZXRwbGFjZSBzZWxlY3Rpb24gdmlld1xuICBpZiAodmlld1N0YXRlID09PSAnbWFya2V0cGxhY2UtbGlzdCcpIHtcbiAgICBpZiAobWFya2V0cGxhY2VzLmxlbmd0aCA9PT0gMCkge1xuICAgICAgcmV0dXJuIChcbiAgICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgICAgPEJveCBtYXJnaW5Cb3R0b209ezF9PlxuICAgICAgICAgICAgPFRleHQgYm9sZD5TZWxlY3QgbWFya2V0cGxhY2U8L1RleHQ+XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICAgPFRleHQ+Tm8gbWFya2V0cGxhY2VzIGNvbmZpZ3VyZWQuPC9UZXh0PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgICAgQWRkIGEgbWFya2V0cGxhY2UgZmlyc3QgdXNpbmcge1wiJ0FkZCBtYXJrZXRwbGFjZSdcIn0uXG4gICAgICAgICAgPC9UZXh0PlxuICAgICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfSBwYWRkaW5nTGVmdD17MX0+XG4gICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICAgIGFjdGlvbj1cImNvbmZpcm06bm9cIlxuICAgICAgICAgICAgICAgIGNvbnRleHQ9XCJDb25maXJtYXRpb25cIlxuICAgICAgICAgICAgICAgIGZhbGxiYWNrPVwiRXNjXCJcbiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cImdvIGJhY2tcIlxuICAgICAgICAgICAgICAvPlxuICAgICAgICAgICAgPC9UZXh0PlxuICAgICAgICAgIDwvQm94PlxuICAgICAgICA8L0JveD5cbiAgICAgIClcbiAgICB9XG5cbiAgICByZXR1cm4gKFxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgIDxCb3ggbWFyZ2luQm90dG9tPXsxfT5cbiAgICAgICAgICA8VGV4dCBib2xkPlNlbGVjdCBtYXJrZXRwbGFjZTwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG5cbiAgICAgICAgey8qIFdhcm5pbmcgYmFubmVyIGZvciBtYXJrZXRwbGFjZSBsb2FkIGZhaWx1cmVzICovfVxuICAgICAgICB7d2FybmluZyAmJiAoXG4gICAgICAgICAgPEJveCBtYXJnaW5Cb3R0b209ezF9IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgICAgIDxUZXh0IGNvbG9yPVwid2FybmluZ1wiPlxuICAgICAgICAgICAgICB7ZmlndXJlcy53YXJuaW5nfSB7d2FybmluZ31cbiAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICA8L0JveD5cbiAgICAgICAgKX1cbiAgICAgICAge21hcmtldHBsYWNlcy5tYXAoKG1hcmtldHBsYWNlLCBpbmRleCkgPT4gKFxuICAgICAgICAgIDxCb3hcbiAgICAgICAgICAgIGtleT17bWFya2V0cGxhY2UubmFtZX1cbiAgICAgICAgICAgIGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIlxuICAgICAgICAgICAgbWFyZ2luQm90dG9tPXtpbmRleCA8IG1hcmtldHBsYWNlcy5sZW5ndGggLSAxID8gMSA6IDB9XG4gICAgICAgICAgPlxuICAgICAgICAgICAgPEJveD5cbiAgICAgICAgICAgICAgPFRleHQgY29sb3I9e3NlbGVjdGVkSW5kZXggPT09IGluZGV4ID8gJ3N1Z2dlc3Rpb24nIDogdW5kZWZpbmVkfT5cbiAgICAgICAgICAgICAgICB7c2VsZWN0ZWRJbmRleCA9PT0gaW5kZXggPyBmaWd1cmVzLnBvaW50ZXIgOiAnICd9eycgJ31cbiAgICAgICAgICAgICAgICB7bWFya2V0cGxhY2UubmFtZX1cbiAgICAgICAgICAgICAgPC9UZXh0PlxuICAgICAgICAgICAgPC9Cb3g+XG4gICAgICAgICAgICA8Qm94IG1hcmdpbkxlZnQ9ezJ9PlxuICAgICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICAgICAgICB7bWFya2V0cGxhY2UudG90YWxQbHVnaW5zfXsnICd9XG4gICAgICAgICAgICAgICAge3BsdXJhbChtYXJrZXRwbGFjZS50b3RhbFBsdWdpbnMsICdwbHVnaW4nKX0gYXZhaWxhYmxlXG4gICAgICAgICAgICAgICAge21hcmtldHBsYWNlLmluc3RhbGxlZENvdW50ID4gMCAmJlxuICAgICAgICAgICAgICAgICAgYCDCtyAke21hcmtldHBsYWNlLmluc3RhbGxlZENvdW50fSBhbHJlYWR5IGluc3RhbGxlZGB9XG4gICAgICAgICAgICAgICAge21hcmtldHBsYWNlLnNvdXJjZSAmJiBgIMK3ICR7bWFya2V0cGxhY2Uuc291cmNlfWB9XG4gICAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICAgIDwvQm94PlxuICAgICAgICAgIDwvQm94PlxuICAgICAgICApKX1cblxuICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3IgaXRhbGljPlxuICAgICAgICAgICAgPEJ5bGluZT5cbiAgICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICAgIGFjdGlvbj1cInNlbGVjdDphY2NlcHRcIlxuICAgICAgICAgICAgICAgIGNvbnRleHQ9XCJTZWxlY3RcIlxuICAgICAgICAgICAgICAgIGZhbGxiYWNrPVwiRW50ZXJcIlxuICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwic2VsZWN0XCJcbiAgICAgICAgICAgICAgLz5cbiAgICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICAgIGFjdGlvbj1cImNvbmZpcm06bm9cIlxuICAgICAgICAgICAgICAgIGNvbnRleHQ9XCJDb25maXJtYXRpb25cIlxuICAgICAgICAgICAgICAgIGZhbGxiYWNrPVwiRXNjXCJcbiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cImdvIGJhY2tcIlxuICAgICAgICAgICAgICAvPlxuICAgICAgICAgICAgPC9CeWxpbmU+XG4gICAgICAgICAgPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgIDwvQm94PlxuICAgIClcbiAgfVxuXG4gIC8vIFBsdWdpbiBkZXRhaWxzIHZpZXdcbiAgaWYgKHZpZXdTdGF0ZSA9PT0gJ3BsdWdpbi1kZXRhaWxzJyAmJiBzZWxlY3RlZFBsdWdpbikge1xuICAgIGNvbnN0IGhhc0hvbWVwYWdlID0gc2VsZWN0ZWRQbHVnaW4uZW50cnkuaG9tZXBhZ2VcbiAgICBjb25zdCBnaXRodWJSZXBvID0gZXh0cmFjdEdpdEh1YlJlcG8oc2VsZWN0ZWRQbHVnaW4pXG5cbiAgICBjb25zdCBtZW51T3B0aW9ucyA9IGJ1aWxkUGx1Z2luRGV0YWlsc01lbnVPcHRpb25zKGhhc0hvbWVwYWdlLCBnaXRodWJSZXBvKVxuXG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8Qm94IG1hcmdpbkJvdHRvbT17MX0+XG4gICAgICAgICAgPFRleHQgYm9sZD5QbHVnaW4gRGV0YWlsczwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG5cbiAgICAgICAgey8qIFBsdWdpbiBtZXRhZGF0YSAqL31cbiAgICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCIgbWFyZ2luQm90dG9tPXsxfT5cbiAgICAgICAgICA8VGV4dCBib2xkPntzZWxlY3RlZFBsdWdpbi5lbnRyeS5uYW1lfTwvVGV4dD5cbiAgICAgICAgICB7c2VsZWN0ZWRQbHVnaW4uZW50cnkudmVyc2lvbiAmJiAoXG4gICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5WZXJzaW9uOiB7c2VsZWN0ZWRQbHVnaW4uZW50cnkudmVyc2lvbn08L1RleHQ+XG4gICAgICAgICAgKX1cbiAgICAgICAgICB7c2VsZWN0ZWRQbHVnaW4uZW50cnkuZGVzY3JpcHRpb24gJiYgKFxuICAgICAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgICAgICA8VGV4dD57c2VsZWN0ZWRQbHVnaW4uZW50cnkuZGVzY3JpcHRpb259PC9UZXh0PlxuICAgICAgICAgICAgPC9Cb3g+XG4gICAgICAgICAgKX1cbiAgICAgICAgICB7c2VsZWN0ZWRQbHVnaW4uZW50cnkuYXV0aG9yICYmIChcbiAgICAgICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgICAgICAgQnk6eycgJ31cbiAgICAgICAgICAgICAgICB7dHlwZW9mIHNlbGVjdGVkUGx1Z2luLmVudHJ5LmF1dGhvciA9PT0gJ3N0cmluZydcbiAgICAgICAgICAgICAgICAgID8gc2VsZWN0ZWRQbHVnaW4uZW50cnkuYXV0aG9yXG4gICAgICAgICAgICAgICAgICA6IHNlbGVjdGVkUGx1Z2luLmVudHJ5LmF1dGhvci5uYW1lfVxuICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICA8L0JveD5cbiAgICAgICAgICApfVxuICAgICAgICA8L0JveD5cblxuICAgICAgICB7LyogV2hhdCB3aWxsIGJlIGluc3RhbGxlZCAqL31cbiAgICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCIgbWFyZ2luQm90dG9tPXsxfT5cbiAgICAgICAgICA8VGV4dCBib2xkPldpbGwgaW5zdGFsbDo8L1RleHQ+XG4gICAgICAgICAge3NlbGVjdGVkUGx1Z2luLmVudHJ5LmNvbW1hbmRzICYmIChcbiAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgICAgICDCtyBDb21tYW5kczp7JyAnfVxuICAgICAgICAgICAgICB7QXJyYXkuaXNBcnJheShzZWxlY3RlZFBsdWdpbi5lbnRyeS5jb21tYW5kcylcbiAgICAgICAgICAgICAgICA/IHNlbGVjdGVkUGx1Z2luLmVudHJ5LmNvbW1hbmRzLmpvaW4oJywgJylcbiAgICAgICAgICAgICAgICA6IE9iamVjdC5rZXlzKHNlbGVjdGVkUGx1Z2luLmVudHJ5LmNvbW1hbmRzKS5qb2luKCcsICcpfVxuICAgICAgICAgICAgPC9UZXh0PlxuICAgICAgICAgICl9XG4gICAgICAgICAge3NlbGVjdGVkUGx1Z2luLmVudHJ5LmFnZW50cyAmJiAoXG4gICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICAgICAgwrcgQWdlbnRzOnsnICd9XG4gICAgICAgICAgICAgIHtBcnJheS5pc0FycmF5KHNlbGVjdGVkUGx1Z2luLmVudHJ5LmFnZW50cylcbiAgICAgICAgICAgICAgICA/IHNlbGVjdGVkUGx1Z2luLmVudHJ5LmFnZW50cy5qb2luKCcsICcpXG4gICAgICAgICAgICAgICAgOiBPYmplY3Qua2V5cyhzZWxlY3RlZFBsdWdpbi5lbnRyeS5hZ2VudHMpLmpvaW4oJywgJyl9XG4gICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgKX1cbiAgICAgICAgICB7c2VsZWN0ZWRQbHVnaW4uZW50cnkuaG9va3MgJiYgKFxuICAgICAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgICAgIMK3IEhvb2tzOiB7T2JqZWN0LmtleXMoc2VsZWN0ZWRQbHVnaW4uZW50cnkuaG9va3MpLmpvaW4oJywgJyl9XG4gICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgKX1cbiAgICAgICAgICB7c2VsZWN0ZWRQbHVnaW4uZW50cnkubWNwU2VydmVycyAmJiAoXG4gICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICAgICAgwrcgTUNQIFNlcnZlcnM6eycgJ31cbiAgICAgICAgICAgICAge0FycmF5LmlzQXJyYXkoc2VsZWN0ZWRQbHVnaW4uZW50cnkubWNwU2VydmVycylcbiAgICAgICAgICAgICAgICA/IHNlbGVjdGVkUGx1Z2luLmVudHJ5Lm1jcFNlcnZlcnMuam9pbignLCAnKVxuICAgICAgICAgICAgICAgIDogdHlwZW9mIHNlbGVjdGVkUGx1Z2luLmVudHJ5Lm1jcFNlcnZlcnMgPT09ICdvYmplY3QnXG4gICAgICAgICAgICAgICAgICA/IE9iamVjdC5rZXlzKHNlbGVjdGVkUGx1Z2luLmVudHJ5Lm1jcFNlcnZlcnMpLmpvaW4oJywgJylcbiAgICAgICAgICAgICAgICAgIDogJ2NvbmZpZ3VyZWQnfVxuICAgICAgICAgICAgPC9UZXh0PlxuICAgICAgICAgICl9XG4gICAgICAgICAgeyFzZWxlY3RlZFBsdWdpbi5lbnRyeS5jb21tYW5kcyAmJlxuICAgICAgICAgICAgIXNlbGVjdGVkUGx1Z2luLmVudHJ5LmFnZW50cyAmJlxuICAgICAgICAgICAgIXNlbGVjdGVkUGx1Z2luLmVudHJ5Lmhvb2tzICYmXG4gICAgICAgICAgICAhc2VsZWN0ZWRQbHVnaW4uZW50cnkubWNwU2VydmVycyAmJiAoXG4gICAgICAgICAgICAgIDw+XG4gICAgICAgICAgICAgICAge3R5cGVvZiBzZWxlY3RlZFBsdWdpbi5lbnRyeS5zb3VyY2UgPT09ICdvYmplY3QnICYmXG4gICAgICAgICAgICAgICAgJ3NvdXJjZScgaW4gc2VsZWN0ZWRQbHVnaW4uZW50cnkuc291cmNlICYmXG4gICAgICAgICAgICAgICAgKHNlbGVjdGVkUGx1Z2luLmVudHJ5LnNvdXJjZS5zb3VyY2UgPT09ICdnaXRodWInIHx8XG4gICAgICAgICAgICAgICAgICBzZWxlY3RlZFBsdWdpbi5lbnRyeS5zb3VyY2Uuc291cmNlID09PSAndXJsJyB8fFxuICAgICAgICAgICAgICAgICAgc2VsZWN0ZWRQbHVnaW4uZW50cnkuc291cmNlLnNvdXJjZSA9PT0gJ25wbScgfHxcbiAgICAgICAgICAgICAgICAgIHNlbGVjdGVkUGx1Z2luLmVudHJ5LnNvdXJjZS5zb3VyY2UgPT09ICdwaXAnKSA/IChcbiAgICAgICAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgICAgICAgICAgICDCtyBDb21wb25lbnQgc3VtbWFyeSBub3QgYXZhaWxhYmxlIGZvciByZW1vdGUgcGx1Z2luXG4gICAgICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgICAgKSA6IChcbiAgICAgICAgICAgICAgICAgIC8vIFRPRE86IEFjdHVhbGx5IHNjYW4gbG9jYWwgcGx1Z2luIGRpcmVjdG9yaWVzIHRvIHNob3cgcmVhbCBjb21wb25lbnRzXG4gICAgICAgICAgICAgICAgICAvLyBUaGlzIHdvdWxkIHJlcXVpcmUgYWNjZXNzaW5nIHRoZSBmaWxlc3lzdGVtIHRvIGNoZWNrIGZvcjpcbiAgICAgICAgICAgICAgICAgIC8vIC0gY29tbWFuZHMvIGRpcmVjdG9yeSBhbmQgbGlzdCBmaWxlc1xuICAgICAgICAgICAgICAgICAgLy8gLSBhZ2VudHMvIGRpcmVjdG9yeSBhbmQgbGlzdCBmaWxlc1xuICAgICAgICAgICAgICAgICAgLy8gLSBob29rcy8gZGlyZWN0b3J5IGFuZCBsaXN0IGZpbGVzXG4gICAgICAgICAgICAgICAgICAvLyAtIC5tY3AuanNvbiBvciBtY3Atc2VydmVycy5qc29uIGZpbGVzXG4gICAgICAgICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICAgICAgICAgICAgwrcgQ29tcG9uZW50cyB3aWxsIGJlIGRpc2NvdmVyZWQgYXQgaW5zdGFsbGF0aW9uXG4gICAgICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgICAgKX1cbiAgICAgICAgICAgICAgPC8+XG4gICAgICAgICAgICApfVxuICAgICAgICA8L0JveD5cblxuICAgICAgICA8UGx1Z2luVHJ1c3RXYXJuaW5nIC8+XG5cbiAgICAgICAgey8qIEVycm9yIG1lc3NhZ2UgKi99XG4gICAgICAgIHtpbnN0YWxsRXJyb3IgJiYgKFxuICAgICAgICAgIDxCb3ggbWFyZ2luQm90dG9tPXsxfT5cbiAgICAgICAgICAgIDxUZXh0IGNvbG9yPVwiZXJyb3JcIj5FcnJvcjoge2luc3RhbGxFcnJvcn08L1RleHQ+XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICl9XG5cbiAgICAgICAgey8qIE1lbnUgb3B0aW9ucyAqL31cbiAgICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgICAge21lbnVPcHRpb25zLm1hcCgob3B0aW9uLCBpbmRleCkgPT4gKFxuICAgICAgICAgICAgPEJveCBrZXk9e29wdGlvbi5hY3Rpb259PlxuICAgICAgICAgICAgICB7ZGV0YWlsc01lbnVJbmRleCA9PT0gaW5kZXggJiYgPFRleHQ+eyc+ICd9PC9UZXh0Pn1cbiAgICAgICAgICAgICAge2RldGFpbHNNZW51SW5kZXggIT09IGluZGV4ICYmIDxUZXh0PnsnICAnfTwvVGV4dD59XG4gICAgICAgICAgICAgIDxUZXh0IGJvbGQ9e2RldGFpbHNNZW51SW5kZXggPT09IGluZGV4fT5cbiAgICAgICAgICAgICAgICB7aXNJbnN0YWxsaW5nICYmIG9wdGlvbi5hY3Rpb24gPT09ICdpbnN0YWxsJ1xuICAgICAgICAgICAgICAgICAgPyAnSW5zdGFsbGluZ+KApidcbiAgICAgICAgICAgICAgICAgIDogb3B0aW9uLmxhYmVsfVxuICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICA8L0JveD5cbiAgICAgICAgICApKX1cbiAgICAgICAgPC9Cb3g+XG5cbiAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9IHBhZGRpbmdMZWZ0PXsxfT5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICAgIDxCeWxpbmU+XG4gICAgICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgICAgICBhY3Rpb249XCJzZWxlY3Q6YWNjZXB0XCJcbiAgICAgICAgICAgICAgICBjb250ZXh0PVwiU2VsZWN0XCJcbiAgICAgICAgICAgICAgICBmYWxsYmFjaz1cIkVudGVyXCJcbiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cInNlbGVjdFwiXG4gICAgICAgICAgICAgIC8+XG4gICAgICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgICAgICBhY3Rpb249XCJjb25maXJtOm5vXCJcbiAgICAgICAgICAgICAgICBjb250ZXh0PVwiQ29uZmlybWF0aW9uXCJcbiAgICAgICAgICAgICAgICBmYWxsYmFjaz1cIkVzY1wiXG4gICAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJiYWNrXCJcbiAgICAgICAgICAgICAgLz5cbiAgICAgICAgICAgIDwvQnlsaW5lPlxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICA8L0JveD5cbiAgICApXG4gIH1cblxuICAvLyBQbHVnaW4gaW5zdGFsbGF0aW9uIHZpZXdcbiAgaWYgKGF2YWlsYWJsZVBsdWdpbnMubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8Qm94IG1hcmdpbkJvdHRvbT17MX0+XG4gICAgICAgICAgPFRleHQgYm9sZD5JbnN0YWxsIHBsdWdpbnM8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgICA8VGV4dCBkaW1Db2xvcj5ObyBuZXcgcGx1Z2lucyBhdmFpbGFibGUgdG8gaW5zdGFsbC48L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgIEFsbCBwbHVnaW5zIGZyb20gdGhpcyBtYXJrZXRwbGFjZSBhcmUgYWxyZWFkeSBpbnN0YWxsZWQuXG4gICAgICAgIDwvVGV4dD5cbiAgICAgICAgPEJveCBtYXJnaW5MZWZ0PXszfT5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvciBpdGFsaWM+XG4gICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgIGFjdGlvbj1cImNvbmZpcm06bm9cIlxuICAgICAgICAgICAgICBjb250ZXh0PVwiQ29uZmlybWF0aW9uXCJcbiAgICAgICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cImdvIGJhY2tcIlxuICAgICAgICAgICAgLz5cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgLy8gR2V0IHZpc2libGUgcGx1Z2lucyBmcm9tIHBhZ2luYXRpb25cbiAgY29uc3QgdmlzaWJsZVBsdWdpbnMgPSBwYWdpbmF0aW9uLmdldFZpc2libGVJdGVtcyhhdmFpbGFibGVQbHVnaW5zKVxuXG4gIHJldHVybiAoXG4gICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICA8Qm94IG1hcmdpbkJvdHRvbT17MX0+XG4gICAgICAgIDxUZXh0IGJvbGQ+SW5zdGFsbCBQbHVnaW5zPC9UZXh0PlxuICAgICAgPC9Cb3g+XG5cbiAgICAgIHsvKiBTY3JvbGwgdXAgaW5kaWNhdG9yICovfVxuICAgICAge3BhZ2luYXRpb24uc2Nyb2xsUG9zaXRpb24uY2FuU2Nyb2xsVXAgJiYgKFxuICAgICAgICA8Qm94PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPiB7ZmlndXJlcy5hcnJvd1VwfSBtb3JlIGFib3ZlPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICl9XG5cbiAgICAgIHsvKiBQbHVnaW4gbGlzdCAqL31cbiAgICAgIHt2aXNpYmxlUGx1Z2lucy5tYXAoKHBsdWdpbiwgdmlzaWJsZUluZGV4KSA9PiB7XG4gICAgICAgIGNvbnN0IGFjdHVhbEluZGV4ID0gcGFnaW5hdGlvbi50b0FjdHVhbEluZGV4KHZpc2libGVJbmRleClcbiAgICAgICAgY29uc3QgaXNTZWxlY3RlZCA9IHNlbGVjdGVkSW5kZXggPT09IGFjdHVhbEluZGV4XG4gICAgICAgIGNvbnN0IGlzU2VsZWN0ZWRGb3JJbnN0YWxsID0gc2VsZWN0ZWRGb3JJbnN0YWxsLmhhcyhwbHVnaW4ucGx1Z2luSWQpXG4gICAgICAgIGNvbnN0IGlzSW5zdGFsbGluZyA9IGluc3RhbGxpbmdQbHVnaW5zLmhhcyhwbHVnaW4ucGx1Z2luSWQpXG4gICAgICAgIGNvbnN0IGlzTGFzdCA9IHZpc2libGVJbmRleCA9PT0gdmlzaWJsZVBsdWdpbnMubGVuZ3RoIC0gMVxuXG4gICAgICAgIHJldHVybiAoXG4gICAgICAgICAgPEJveFxuICAgICAgICAgICAga2V5PXtwbHVnaW4ucGx1Z2luSWR9XG4gICAgICAgICAgICBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCJcbiAgICAgICAgICAgIG1hcmdpbkJvdHRvbT17aXNMYXN0ICYmICFlcnJvciA/IDAgOiAxfVxuICAgICAgICAgID5cbiAgICAgICAgICAgIDxCb3g+XG4gICAgICAgICAgICAgIDxUZXh0IGNvbG9yPXtpc1NlbGVjdGVkID8gJ3N1Z2dlc3Rpb24nIDogdW5kZWZpbmVkfT5cbiAgICAgICAgICAgICAgICB7aXNTZWxlY3RlZCA/IGZpZ3VyZXMucG9pbnRlciA6ICcgJ317JyAnfVxuICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgIDxUZXh0IGNvbG9yPXtwbHVnaW4uaXNJbnN0YWxsZWQgPyAnc3VjY2VzcycgOiB1bmRlZmluZWR9PlxuICAgICAgICAgICAgICAgIHtwbHVnaW4uaXNJbnN0YWxsZWRcbiAgICAgICAgICAgICAgICAgID8gZmlndXJlcy50aWNrXG4gICAgICAgICAgICAgICAgICA6IGlzSW5zdGFsbGluZ1xuICAgICAgICAgICAgICAgICAgICA/IGZpZ3VyZXMuZWxsaXBzaXNcbiAgICAgICAgICAgICAgICAgICAgOiBpc1NlbGVjdGVkRm9ySW5zdGFsbFxuICAgICAgICAgICAgICAgICAgICAgID8gZmlndXJlcy5yYWRpb09uXG4gICAgICAgICAgICAgICAgICAgICAgOiBmaWd1cmVzLnJhZGlvT2ZmfXsnICd9XG4gICAgICAgICAgICAgICAge3BsdWdpbi5lbnRyeS5uYW1lfVxuICAgICAgICAgICAgICAgIHtwbHVnaW4uZW50cnkuY2F0ZWdvcnkgJiYgKFxuICAgICAgICAgICAgICAgICAgPFRleHQgZGltQ29sb3I+IFt7cGx1Z2luLmVudHJ5LmNhdGVnb3J5fV08L1RleHQ+XG4gICAgICAgICAgICAgICAgKX1cbiAgICAgICAgICAgICAgICB7cGx1Z2luLmVudHJ5LnRhZ3M/LmluY2x1ZGVzKCdjb21tdW5pdHktbWFuYWdlZCcpICYmIChcbiAgICAgICAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPiBbQ29tbXVuaXR5IE1hbmFnZWRdPC9UZXh0PlxuICAgICAgICAgICAgICAgICl9XG4gICAgICAgICAgICAgICAge3BsdWdpbi5pc0luc3RhbGxlZCAmJiA8VGV4dCBkaW1Db2xvcj4gKGluc3RhbGxlZCk8L1RleHQ+fVxuICAgICAgICAgICAgICAgIHtpbnN0YWxsQ291bnRzICYmXG4gICAgICAgICAgICAgICAgICBzZWxlY3RlZE1hcmtldHBsYWNlID09PSBPRkZJQ0lBTF9NQVJLRVRQTEFDRV9OQU1FICYmIChcbiAgICAgICAgICAgICAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgICAgICAgICAgICAgeycgwrcgJ31cbiAgICAgICAgICAgICAgICAgICAgICB7Zm9ybWF0SW5zdGFsbENvdW50KFxuICAgICAgICAgICAgICAgICAgICAgICAgaW5zdGFsbENvdW50cy5nZXQocGx1Z2luLnBsdWdpbklkKSA/PyAwLFxuICAgICAgICAgICAgICAgICAgICAgICl9eycgJ31cbiAgICAgICAgICAgICAgICAgICAgICBpbnN0YWxsc1xuICAgICAgICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgICAgICApfVxuICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICA8L0JveD5cbiAgICAgICAgICAgIHtwbHVnaW4uZW50cnkuZGVzY3JpcHRpb24gJiYgKFxuICAgICAgICAgICAgICA8Qm94IG1hcmdpbkxlZnQ9ezR9PlxuICAgICAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgICAgICAgICAge3RydW5jYXRlVG9XaWR0aChwbHVnaW4uZW50cnkuZGVzY3JpcHRpb24sIDYwKX1cbiAgICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgICAge3BsdWdpbi5lbnRyeS52ZXJzaW9uICYmIChcbiAgICAgICAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPiDCtyB2e3BsdWdpbi5lbnRyeS52ZXJzaW9ufTwvVGV4dD5cbiAgICAgICAgICAgICAgICApfVxuICAgICAgICAgICAgICA8L0JveD5cbiAgICAgICAgICAgICl9XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgIClcbiAgICAgIH0pfVxuXG4gICAgICB7LyogU2Nyb2xsIGRvd24gaW5kaWNhdG9yICovfVxuICAgICAge3BhZ2luYXRpb24uc2Nyb2xsUG9zaXRpb24uY2FuU2Nyb2xsRG93biAmJiAoXG4gICAgICAgIDxCb3g+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IHtmaWd1cmVzLmFycm93RG93bn0gbW9yZSBiZWxvdzwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICApfVxuXG4gICAgICB7LyogRXJyb3IgbWVzc2FnZXMgc2hvd24gaW4gdGhlIFVJICovfVxuICAgICAge2Vycm9yICYmIChcbiAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgIDxUZXh0IGNvbG9yPVwiZXJyb3JcIj5cbiAgICAgICAgICAgIHtmaWd1cmVzLmNyb3NzfSB7ZXJyb3J9XG4gICAgICAgICAgPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICl9XG5cbiAgICAgIDxQbHVnaW5TZWxlY3Rpb25LZXlIaW50IGhhc1NlbGVjdGlvbj17c2VsZWN0ZWRGb3JJbnN0YWxsLnNpemUgPiAwfSAvPlxuICAgIDwvQm94PlxuICApXG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLE9BQU9BLE9BQU8sTUFBTSxTQUFTO0FBQzdCLE9BQU8sS0FBS0MsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0MsU0FBUyxFQUFFQyxRQUFRLFFBQVEsT0FBTztBQUMzQyxTQUFTQyx3QkFBd0IsUUFBUSw4Q0FBOEM7QUFDdkYsU0FBU0MsTUFBTSxRQUFRLDBDQUEwQztBQUNqRSxTQUFTQyxHQUFHLEVBQUVDLElBQUksUUFBUSxjQUFjO0FBQ3hDLFNBQ0VDLGFBQWEsRUFDYkMsY0FBYyxRQUNULG9DQUFvQztBQUMzQyxjQUFjQyxZQUFZLFFBQVEsdUJBQXVCO0FBQ3pELFNBQVNDLEtBQUssUUFBUSxzQkFBc0I7QUFDNUMsU0FBU0MsV0FBVyxRQUFRLHdCQUF3QjtBQUNwRCxTQUFTQyxlQUFlLFFBQVEsc0JBQXNCO0FBQ3RELFNBQVNDLFlBQVksUUFBUSx1QkFBdUI7QUFDcEQsU0FBU0MsY0FBYyxRQUFRLG1DQUFtQztBQUNsRSxTQUNFQyxrQkFBa0IsRUFDbEJD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 \ No newline at end of file diff --git a/src/commands/plugin/DiscoverPlugins.tsx b/src/commands/plugin/DiscoverPlugins.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d62a8dbb54fa7bf6ee7a48b7324f25e70da491a9 --- /dev/null +++ b/src/commands/plugin/DiscoverPlugins.tsx @@ -0,0 +1,781 @@ +import { c as _c } from "react/compiler-runtime"; +import figures from 'figures'; +import * as React from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js'; +import { Byline } from '../../components/design-system/Byline.js'; +import { SearchBox } from '../../components/SearchBox.js'; +import { useSearchInput } from '../../hooks/useSearchInput.js'; +import { useTerminalSize } from '../../hooks/useTerminalSize.js'; +// eslint-disable-next-line custom-rules/prefer-use-keybindings -- useInput needed for raw search mode text input +import { Box, Text, useInput, useTerminalFocus } from '../../ink.js'; +import { useKeybinding, useKeybindings } from '../../keybindings/useKeybinding.js'; +import type { LoadedPlugin } from '../../types/plugin.js'; +import { count } from '../../utils/array.js'; +import { openBrowser } from '../../utils/browser.js'; +import { logForDebugging } from '../../utils/debug.js'; +import { errorMessage } from '../../utils/errors.js'; +import { clearAllCaches } from '../../utils/plugins/cacheUtils.js'; +import { formatInstallCount, getInstallCounts } from '../../utils/plugins/installCounts.js'; +import { isPluginGloballyInstalled } from '../../utils/plugins/installedPluginsManager.js'; +import { createPluginId, detectEmptyMarketplaceReason, type EmptyMarketplaceReason, formatFailureDetails, formatMarketplaceLoadingErrors, loadMarketplacesWithGracefulDegradation } from '../../utils/plugins/marketplaceHelpers.js'; +import { loadKnownMarketplacesConfig } from '../../utils/plugins/marketplaceManager.js'; +import { OFFICIAL_MARKETPLACE_NAME } from '../../utils/plugins/officialMarketplace.js'; +import { installPluginFromMarketplace } from '../../utils/plugins/pluginInstallationHelpers.js'; +import { isPluginBlockedByPolicy } from '../../utils/plugins/pluginPolicy.js'; +import { plural } from '../../utils/stringUtils.js'; +import { truncateToWidth } from '../../utils/truncate.js'; +import { findPluginOptionsTarget, PluginOptionsFlow } from './PluginOptionsFlow.js'; +import { PluginTrustWarning } from './PluginTrustWarning.js'; +import { buildPluginDetailsMenuOptions, extractGitHubRepo, type InstallablePlugin } from './pluginDetailsHelpers.js'; +import type { ViewState as ParentViewState } from './types.js'; +import { usePagination } from './usePagination.js'; +type Props = { + error: string | null; + setError: (error: string | null) => void; + result: string | null; + setResult: (result: string | null) => void; + setViewState: (state: ParentViewState) => void; + onInstallComplete?: () => void | Promise; + onSearchModeChange?: (isActive: boolean) => void; + targetPlugin?: string; +}; +type ViewState = 'plugin-list' | 'plugin-details' | { + type: 'plugin-options'; + plugin: LoadedPlugin; + pluginId: string; +}; +export function DiscoverPlugins({ + error, + setError, + result: _result, + setResult, + setViewState: setParentViewState, + onInstallComplete, + onSearchModeChange, + targetPlugin +}: Props): React.ReactNode { + // View state + const [viewState, setViewState] = useState('plugin-list'); + const [selectedPlugin, setSelectedPlugin] = useState(null); + + // Data state + const [availablePlugins, setAvailablePlugins] = useState([]); + const [loading, setLoading] = useState(true); + const [installCounts, setInstallCounts] = useState | null>(null); + + // Search state + const [isSearchMode, setIsSearchModeRaw] = useState(false); + const setIsSearchMode = useCallback((active: boolean) => { + setIsSearchModeRaw(active); + onSearchModeChange?.(active); + }, [onSearchModeChange]); + const { + query: searchQuery, + setQuery: setSearchQuery, + cursorOffset: searchCursorOffset + } = useSearchInput({ + isActive: viewState === 'plugin-list' && isSearchMode && !loading, + onExit: () => { + setIsSearchMode(false); + } + }); + const isTerminalFocused = useTerminalFocus(); + const { + columns: terminalWidth + } = useTerminalSize(); + + // Filter plugins based on search query + const filteredPlugins = useMemo(() => { + if (!searchQuery) return availablePlugins; + const lowerQuery = searchQuery.toLowerCase(); + return availablePlugins.filter(plugin => plugin.entry.name.toLowerCase().includes(lowerQuery) || plugin.entry.description?.toLowerCase().includes(lowerQuery) || plugin.marketplaceName.toLowerCase().includes(lowerQuery)); + }, [availablePlugins, searchQuery]); + + // Selection state + const [selectedIndex, setSelectedIndex] = useState(0); + const [selectedForInstall, setSelectedForInstall] = useState>(new Set()); + const [installingPlugins, setInstallingPlugins] = useState>(new Set()); + + // Pagination for plugin list (continuous scrolling) + const pagination = usePagination({ + totalItems: filteredPlugins.length, + selectedIndex + }); + + // Reset selection when search query changes + useEffect(() => { + setSelectedIndex(0); + }, [searchQuery]); + + // Details view state + const [detailsMenuIndex, setDetailsMenuIndex] = useState(0); + const [isInstalling, setIsInstalling] = useState(false); + const [installError, setInstallError] = useState(null); + + // Warning state for non-critical errors + const [warning, setWarning] = useState(null); + + // Empty state reason + const [emptyReason, setEmptyReason] = useState(null); + + // Load all plugins from all marketplaces + useEffect(() => { + async function loadAllPlugins() { + try { + const config = await loadKnownMarketplacesConfig(); + + // Load marketplaces with graceful degradation + const { + marketplaces, + failures + } = await loadMarketplacesWithGracefulDegradation(config); + + // Collect all plugins from all marketplaces + const allPlugins: InstallablePlugin[] = []; + for (const { + name, + data: marketplace + } of marketplaces) { + if (marketplace) { + for (const entry of marketplace.plugins) { + const pluginId = createPluginId(entry.name, name); + allPlugins.push({ + entry, + marketplaceName: name, + pluginId, + // Only block when globally installed (user/managed scope). + // Project/local-scope installs don't block — user may want to + // promote to user scope so it's available everywhere (gh-29997). + isInstalled: isPluginGloballyInstalled(pluginId) + }); + } + } + } + + // Filter out installed and policy-blocked plugins + const uninstalledPlugins = allPlugins.filter(p => !p.isInstalled && !isPluginBlockedByPolicy(p.pluginId)); + + // Fetch install counts and sort by popularity + try { + const counts = await getInstallCounts(); + setInstallCounts(counts); + if (counts) { + // Sort by install count (descending), then alphabetically + uninstalledPlugins.sort((a_0, b_0) => { + const countA = counts.get(a_0.pluginId) ?? 0; + const countB = counts.get(b_0.pluginId) ?? 0; + if (countA !== countB) return countB - countA; + return a_0.entry.name.localeCompare(b_0.entry.name); + }); + } else { + // No counts available - sort alphabetically + uninstalledPlugins.sort((a_1, b_1) => a_1.entry.name.localeCompare(b_1.entry.name)); + } + } catch (error_0) { + // Log the error, then gracefully degrade to alphabetical sort + logForDebugging(`Failed to fetch install counts: ${errorMessage(error_0)}`); + uninstalledPlugins.sort((a, b) => a.entry.name.localeCompare(b.entry.name)); + } + setAvailablePlugins(uninstalledPlugins); + + // Detect empty reason if no plugins available + const configuredCount = Object.keys(config).length; + if (uninstalledPlugins.length === 0) { + const reason = await detectEmptyMarketplaceReason({ + configuredMarketplaceCount: configuredCount, + failedMarketplaceCount: failures.length + }); + setEmptyReason(reason); + } + + // Handle marketplace loading errors/warnings + const successCount = count(marketplaces, m => m.data !== null); + const errorResult = formatMarketplaceLoadingErrors(failures, successCount); + if (errorResult) { + if (errorResult.type === 'warning') { + setWarning(errorResult.message + '. Showing available plugins.'); + } else { + throw new Error(errorResult.message); + } + } + + // Handle targetPlugin - navigate directly to plugin details + // Search in allPlugins (before filtering) to handle installed plugins gracefully + if (targetPlugin) { + const foundPlugin = allPlugins.find(p_0 => p_0.entry.name === targetPlugin); + if (foundPlugin) { + if (foundPlugin.isInstalled) { + setError(`Plugin '${foundPlugin.pluginId}' is already installed. Use '/plugin' to manage existing plugins.`); + } else { + setSelectedPlugin(foundPlugin); + setViewState('plugin-details'); + } + } else { + setError(`Plugin "${targetPlugin}" not found in any marketplace`); + } + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load plugins'); + } finally { + setLoading(false); + } + } + void loadAllPlugins(); + }, [setError, targetPlugin]); + + // Install selected plugins + const installSelectedPlugins = async () => { + if (selectedForInstall.size === 0) return; + const pluginsToInstall = availablePlugins.filter(p_1 => selectedForInstall.has(p_1.pluginId)); + setInstallingPlugins(new Set(pluginsToInstall.map(p_2 => p_2.pluginId))); + let successCount_0 = 0; + let failureCount = 0; + const newFailedPlugins: Array<{ + name: string; + reason: string; + }> = []; + for (const plugin_0 of pluginsToInstall) { + const result = await installPluginFromMarketplace({ + pluginId: plugin_0.pluginId, + entry: plugin_0.entry, + marketplaceName: plugin_0.marketplaceName, + scope: 'user' + }); + if (result.success) { + successCount_0++; + } else { + failureCount++; + newFailedPlugins.push({ + name: plugin_0.entry.name, + reason: result.error + }); + } + } + setInstallingPlugins(new Set()); + setSelectedForInstall(new Set()); + clearAllCaches(); + + // Handle installation results + if (failureCount === 0) { + const message = `✓ Installed ${successCount_0} ${plural(successCount_0, 'plugin')}. ` + `Run /reload-plugins to activate.`; + setResult(message); + } else if (successCount_0 === 0) { + setError(`Failed to install: ${formatFailureDetails(newFailedPlugins, true)}`); + } else { + const message_0 = `✓ Installed ${successCount_0} of ${successCount_0 + failureCount} plugins. ` + `Failed: ${formatFailureDetails(newFailedPlugins, false)}. ` + `Run /reload-plugins to activate successfully installed plugins.`; + setResult(message_0); + } + if (successCount_0 > 0) { + if (onInstallComplete) { + await onInstallComplete(); + } + } + setParentViewState({ + type: 'menu' + }); + }; + + // Install single plugin from details view + const handleSinglePluginInstall = async (plugin_1: InstallablePlugin, scope: 'user' | 'project' | 'local' = 'user') => { + setIsInstalling(true); + setInstallError(null); + const result_0 = await installPluginFromMarketplace({ + pluginId: plugin_1.pluginId, + entry: plugin_1.entry, + marketplaceName: plugin_1.marketplaceName, + scope + }); + if (result_0.success) { + const loaded = await findPluginOptionsTarget(plugin_1.pluginId); + if (loaded) { + setIsInstalling(false); + setViewState({ + type: 'plugin-options', + plugin: loaded, + pluginId: plugin_1.pluginId + }); + return; + } + setResult(result_0.message); + if (onInstallComplete) { + await onInstallComplete(); + } + setParentViewState({ + type: 'menu' + }); + } else { + setIsInstalling(false); + setInstallError(result_0.error); + } + }; + + // Handle error state + useEffect(() => { + if (error) { + setResult(error); + } + }, [error, setResult]); + + // Escape in plugin-details view - go back to plugin-list + useKeybinding('confirm:no', () => { + setViewState('plugin-list'); + setSelectedPlugin(null); + }, { + context: 'Confirmation', + isActive: viewState === 'plugin-details' + }); + + // Escape in plugin-list view (not search mode) - exit to parent menu + useKeybinding('confirm:no', () => { + setParentViewState({ + type: 'menu' + }); + }, { + context: 'Confirmation', + isActive: viewState === 'plugin-list' && !isSearchMode + }); + + // Handle entering search mode (non-escape keys) + useInput((input, _key) => { + const keyIsNotCtrlOrMeta = !_key.ctrl && !_key.meta; + if (!isSearchMode) { + // Enter search mode with '/' or any printable character + if (input === '/' && keyIsNotCtrlOrMeta) { + setIsSearchMode(true); + setSearchQuery(''); + } else if (keyIsNotCtrlOrMeta && input.length > 0 && !/^\s+$/.test(input) && + // Don't enter search mode for navigation keys + input !== 'j' && input !== 'k' && input !== 'i') { + setIsSearchMode(true); + setSearchQuery(input); + } + } + }, { + isActive: viewState === 'plugin-list' && !loading + }); + + // Plugin-list navigation (non-search mode) + useKeybindings({ + 'select:previous': () => { + if (selectedIndex === 0) { + setIsSearchMode(true); + } else { + pagination.handleSelectionChange(selectedIndex - 1, setSelectedIndex); + } + }, + 'select:next': () => { + if (selectedIndex < filteredPlugins.length - 1) { + pagination.handleSelectionChange(selectedIndex + 1, setSelectedIndex); + } + }, + 'select:accept': () => { + if (selectedIndex === filteredPlugins.length && selectedForInstall.size > 0) { + void installSelectedPlugins(); + } else if (selectedIndex < filteredPlugins.length) { + const plugin_2 = filteredPlugins[selectedIndex]; + if (plugin_2) { + if (plugin_2.isInstalled) { + setParentViewState({ + type: 'manage-plugins', + targetPlugin: plugin_2.entry.name, + targetMarketplace: plugin_2.marketplaceName + }); + } else { + setSelectedPlugin(plugin_2); + setViewState('plugin-details'); + setDetailsMenuIndex(0); + setInstallError(null); + } + } + } + } + }, { + context: 'Select', + isActive: viewState === 'plugin-list' && !isSearchMode + }); + useKeybindings({ + 'plugin:toggle': () => { + if (selectedIndex < filteredPlugins.length) { + const plugin_3 = filteredPlugins[selectedIndex]; + if (plugin_3 && !plugin_3.isInstalled) { + const newSelection = new Set(selectedForInstall); + if (newSelection.has(plugin_3.pluginId)) { + newSelection.delete(plugin_3.pluginId); + } else { + newSelection.add(plugin_3.pluginId); + } + setSelectedForInstall(newSelection); + } + } + }, + 'plugin:install': () => { + if (selectedForInstall.size > 0) { + void installSelectedPlugins(); + } + } + }, { + context: 'Plugin', + isActive: viewState === 'plugin-list' && !isSearchMode + }); + + // Plugin-details navigation + const detailsMenuOptions = React.useMemo(() => { + if (!selectedPlugin) return []; + const hasHomepage = selectedPlugin.entry.homepage; + const githubRepo = extractGitHubRepo(selectedPlugin); + return buildPluginDetailsMenuOptions(hasHomepage, githubRepo); + }, [selectedPlugin]); + useKeybindings({ + 'select:previous': () => { + if (detailsMenuIndex > 0) { + setDetailsMenuIndex(detailsMenuIndex - 1); + } + }, + 'select:next': () => { + if (detailsMenuIndex < detailsMenuOptions.length - 1) { + setDetailsMenuIndex(detailsMenuIndex + 1); + } + }, + 'select:accept': () => { + if (!selectedPlugin) return; + const action = detailsMenuOptions[detailsMenuIndex]?.action; + const hasHomepage_0 = selectedPlugin.entry.homepage; + const githubRepo_0 = extractGitHubRepo(selectedPlugin); + if (action === 'install-user') { + void handleSinglePluginInstall(selectedPlugin, 'user'); + } else if (action === 'install-project') { + void handleSinglePluginInstall(selectedPlugin, 'project'); + } else if (action === 'install-local') { + void handleSinglePluginInstall(selectedPlugin, 'local'); + } else if (action === 'homepage' && hasHomepage_0) { + void openBrowser(hasHomepage_0); + } else if (action === 'github' && githubRepo_0) { + void openBrowser(`https://github.com/${githubRepo_0}`); + } else if (action === 'back') { + setViewState('plugin-list'); + setSelectedPlugin(null); + } + } + }, { + context: 'Select', + isActive: viewState === 'plugin-details' && !!selectedPlugin + }); + if (typeof viewState === 'object' && viewState.type === 'plugin-options') { + const { + plugin: plugin_4, + pluginId: pluginId_0 + } = viewState; + function finish(msg: string): void { + setResult(msg); + if (onInstallComplete) { + void onInstallComplete(); + } + setParentViewState({ + type: 'menu' + }); + } + return { + switch (outcome) { + case 'configured': + finish(`✓ Installed and configured ${plugin_4.name}. Run /reload-plugins to apply.`); + break; + case 'skipped': + finish(`✓ Installed ${plugin_4.name}. Run /reload-plugins to apply.`); + break; + case 'error': + finish(`Installed but failed to save config: ${detail}`); + break; + } + }} />; + } + + // Loading state + if (loading) { + return Loading…; + } + + // Error state + if (error) { + return {error}; + } + + // Plugin details view + if (viewState === 'plugin-details' && selectedPlugin) { + const hasHomepage_1 = selectedPlugin.entry.homepage; + const githubRepo_1 = extractGitHubRepo(selectedPlugin); + const menuOptions = buildPluginDetailsMenuOptions(hasHomepage_1, githubRepo_1); + return + + Plugin details + + + + {selectedPlugin.entry.name} + from {selectedPlugin.marketplaceName} + {selectedPlugin.entry.version && Version: {selectedPlugin.entry.version}} + {selectedPlugin.entry.description && + {selectedPlugin.entry.description} + } + {selectedPlugin.entry.author && + + By:{' '} + {typeof selectedPlugin.entry.author === 'string' ? selectedPlugin.entry.author : selectedPlugin.entry.author.name} + + } + + + + + {installError && + Error: {installError} + } + + + {menuOptions.map((option, index) => + {detailsMenuIndex === index && {'> '}} + {detailsMenuIndex !== index && {' '}} + + {isInstalling && option.action.startsWith('install-') ? 'Installing…' : option.label} + + )} + + + + + + + + + + + ; + } + + // Empty state + if (availablePlugins.length === 0) { + return + + Discover plugins + + + + + Esc to go back + + + ; + } + + // Get visible plugins from pagination + const visiblePlugins = pagination.getVisibleItems(filteredPlugins); + return + + Discover plugins + {pagination.needsPagination && + {' '} + ({pagination.scrollPosition.current}/ + {pagination.scrollPosition.total}) + } + + + {/* Search box */} + + + + + {/* Warning banner */} + {warning && + + {figures.warning} {warning} + + } + + {/* No search results */} + {filteredPlugins.length === 0 && searchQuery && + No plugins match "{searchQuery}" + } + + {/* Scroll up indicator */} + {pagination.scrollPosition.canScrollUp && + {figures.arrowUp} more above + } + + {/* Plugin list - use startIndex in key to force re-render on scroll */} + {visiblePlugins.map((plugin_5, visibleIndex) => { + const actualIndex = pagination.toActualIndex(visibleIndex); + const isSelected = selectedIndex === actualIndex; + const isSelectedForInstall = selectedForInstall.has(plugin_5.pluginId); + const isInstallingThis = installingPlugins.has(plugin_5.pluginId); + const isLast = visibleIndex === visiblePlugins.length - 1; + return + + + {isSelected && !isSearchMode ? figures.pointer : ' '}{' '} + + + {isInstallingThis ? figures.ellipsis : isSelectedForInstall ? figures.radioOn : figures.radioOff}{' '} + {plugin_5.entry.name} + · {plugin_5.marketplaceName} + {plugin_5.entry.tags?.includes('community-managed') && [Community Managed]} + {installCounts && plugin_5.marketplaceName === OFFICIAL_MARKETPLACE_NAME && + {' · '} + {formatInstallCount(installCounts.get(plugin_5.pluginId) ?? 0)}{' '} + installs + } + + + {plugin_5.entry.description && + + {truncateToWidth(plugin_5.entry.description, 60)} + + } + ; + })} + + {/* Scroll down indicator */} + {pagination.scrollPosition.canScrollDown && + {figures.arrowDown} more below + } + + {/* Error messages */} + {error && + + {figures.cross} {error} + + } + + 0} canToggle={selectedIndex < filteredPlugins.length && !filteredPlugins[selectedIndex]?.isInstalled} /> + ; +} +function DiscoverPluginsKeyHint(t0) { + const $ = _c(10); + const { + hasSelection, + canToggle + } = t0; + let t1; + if ($[0] !== hasSelection) { + t1 = hasSelection && ; + $[0] = hasSelection; + $[1] = t1; + } else { + t1 = $[1]; + } + let t2; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t2 = type to search; + $[2] = t2; + } else { + t2 = $[2]; + } + let t3; + if ($[3] !== canToggle) { + t3 = canToggle && ; + $[3] = canToggle; + $[4] = t3; + } else { + t3 = $[4]; + } + let t4; + let t5; + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { + t4 = ; + t5 = ; + $[5] = t4; + $[6] = t5; + } else { + t4 = $[5]; + t5 = $[6]; + } + let t6; + if ($[7] !== t1 || $[8] !== t3) { + t6 = {t1}{t2}{t3}{t4}{t5}; + $[7] = t1; + $[8] = t3; + $[9] = t6; + } else { + t6 = $[9]; + } + return t6; +} + +/** + * Context-aware empty state message for the Discover screen + */ +function EmptyStateMessage(t0) { + const $ = _c(6); + const { + reason + } = t0; + switch (reason) { + case "git-not-installed": + { + let t1; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t1 = <>Git is required to install marketplaces.Please install git and restart Claude Code.; + $[0] = t1; + } else { + t1 = $[0]; + } + return t1; + } + case "all-blocked-by-policy": + { + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = <>Your organization policy does not allow any external marketplaces.Contact your administrator.; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; + } + case "policy-restricts-sources": + { + let t1; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t1 = <>Your organization restricts which marketplaces can be added.Switch to the Marketplaces tab to view allowed sources.; + $[2] = t1; + } else { + t1 = $[2]; + } + return t1; + } + case "all-marketplaces-failed": + { + let t1; + if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + t1 = <>Failed to load marketplace data.Check your network connection.; + $[3] = t1; + } else { + t1 = $[3]; + } + return t1; + } + case "all-plugins-installed": + { + let t1; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t1 = <>All available plugins are already installed.Check for new plugins later or add more marketplaces.; + $[4] = t1; + } else { + t1 = $[4]; + } + return t1; + } + case "no-marketplaces-configured": + default: + { + let t1; + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { + t1 = <>No plugins available.Add a marketplace first using the Marketplaces tab.; + $[5] = t1; + } else { + t1 = $[5]; + } + return t1; + } + } +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwiUmVhY3QiLCJ1c2VDYWxsYmFjayIsInVzZUVmZmVjdCIsInVzZU1lbW8iLCJ1c2VTdGF0ZSIsIkNvbmZpZ3VyYWJsZVNob3J0Y3V0SGludCIsIkJ5bGluZSIsIlNlYXJjaEJveCIsInVzZVNlYXJjaElucHV0IiwidXNlVGVybWluYWxTaXplIiwiQm94IiwiVGV4dCIsInVzZUlucHV0IiwidXNlVGVybWluYWxGb2N1cyIsInVzZUtleWJpbmRpbmciLCJ1c2VLZXliaW5kaW5ncyIsIkxvYWRlZFBsdWdpbiIsImNvdW50Iiwib3BlbkJyb3dzZXIiLCJsb2dGb3JEZWJ1Z2dpbmciLCJlcnJvck1lc3NhZ2UiLCJjbGVhckFsbENhY2hlcyIsImZvcm1hdEluc3RhbGxDb3VudCIsImdldEluc3RhbGxDb3VudHMiLCJpc1BsdWdpbkdsb2JhbGx5SW5zdGFsbGVkIiwiY3JlYXRlUGx1Z2luSWQiLCJkZXRlY3RFbXB0eU1hcmtldHBsYWNlUmVhc29uIiwiRW1wdHlNYXJrZXRwbGFjZVJlYXNvbiIsImZvcm1hdEZhaWx1cmVEZXRhaWxzIiwiZm9ybWF0TWFya2V0cGxhY2VMb2FkaW5nRXJyb3JzIiwibG9hZE1hcmtldHBsYWNlc1dpdGhHcmFjZWZ1bERlZ3JhZGF0aW9uIiwibG9hZEtub3duTWFya2V0cGxhY2VzQ29uZmlnIiwiT0ZGSUNJQUxfTUFSS0VUUExBQ0VfTkFNRSIsImluc3RhbGxQbHVnaW5Gcm9tTWFya2V0cGxhY2UiLCJpc1BsdWdpbkJsb2NrZWRCeVBvbGljeSIsInBsdXJhbCIsInRydW5jYXRlVG9XaWR0aCIsImZpbmRQbHVnaW5PcHRpb25zVGFyZ2V0IiwiUGx1Z2luT3B0aW9uc0Zsb3ciLCJQbHVnaW5UcnVzdFdhcm5pbmciLCJidWlsZFBsdWdpbkRldGFpbHNNZW51T3B0aW9ucyIsImV4dHJhY3RHaXRIdWJSZXBvIiwiSW5zdGFsbGFibGVQbHVnaW4iLCJWaWV3U3RhdGUiLCJQYXJlbnRWaWV3U3RhdGUiLCJ1c2VQYWdpbmF0aW9uIiwiUHJvcHMiLCJlcnJvciIsInNldEVycm9yIiwicmVzdWx0Iiwic2V0UmVzdWx0Iiwic2V0Vmlld1N0YXRlIiwic3RhdGUiLCJvbkluc3RhbGxDb21wbGV0ZSIsIlByb21pc2UiLCJvblNlYXJjaE1vZGVDaGFuZ2UiLCJpc0FjdGl2ZSIsInRhcmdldFBsdWdpbiIsInR5cGUiLCJwbHVnaW4iLCJwbHVnaW5JZCIsIkRpc2NvdmVyUGx1Z2lucyIsIl9yZXN1bHQiLCJzZXRQYXJlbnRWaWV3U3RhdGUiLCJSZWFjdE5vZGUiLCJ2aWV3U3RhdGUiLCJzZWxlY3RlZFBsdWdpbiIsInNldFNlbGVjdGVkUGx1Z2luIiwiYXZhaWxhYmxlUGx1Z2lucyIsInNldEF2YWlsYWJsZVBsdWdpbnMiLCJsb2FkaW5nIiwic2V0TG9hZGluZyIsImluc3RhbGxDb3VudHMiLCJzZXRJbnN0YWxsQ291bnRzIiwiTWFwIiwiaXNTZWFyY2hNb2RlIiwic2V0SXNTZWFyY2hNb2RlUmF3Iiwic2V0SXNTZWFyY2hNb2RlIiwiYWN0aXZlIiwicXVlcnkiLCJzZWFyY2hRdWVyeSIsInNldFF1ZXJ5Iiwic2V0U2VhcmNoUXVlcnkiLCJjdXJzb3JPZmZzZXQiLCJzZWFyY2hDdXJzb3JPZmZzZXQiLCJvbkV4aXQiLCJpc1Rlcm1pbmFsRm9jdXNlZCIsImNvbHVtbnMiLCJ0ZXJtaW5hbFdpZHRoIiwiZmlsdGVyZWRQbHVnaW5zIiwibG93ZXJRdWVyeSIsInRvTG93ZXJDYXNlIiwiZmlsdGVyIiwiZW50cnkiLCJuYW1lIiwiaW5jbHVkZXMiLCJkZXNjcmlwdGlvbiIsIm1hcmtldHBsYWNlTmFtZSIsInNlbGVjdGVkSW5kZXgiLCJzZXRTZWxlY3RlZEluZGV4Iiwic2VsZWN0ZWRGb3JJbnN0YWxsIiwic2V0U2VsZWN0ZWRGb3JJbnN0YWxsIiwiU2V0IiwiaW5zdGFsbGluZ1BsdWdpbnMiLCJzZXRJbnN0YWxsaW5nUGx1Z2lucyIsInBhZ2luYXRpb24iLCJ0b3RhbEl0ZW1zIiwibGVuZ3RoIiwiZGV0YWlsc01lbnVJbmRleCIsInNldERldGFpbHNNZW51SW5kZXgiLCJpc0luc3RhbGxpbmciLCJzZXRJc0luc3RhbGxpbmciLCJpbnN0YWxsRXJyb3IiLCJzZXRJbnN0YWxsRXJyb3IiLCJ3YXJuaW5nIiwic2V0V2FybmluZyIsImVtcHR5UmVhc29uIiwic2V0RW1wdHlSZWFzb24iLCJsb2FkQWxsUGx1Z2lucyIsImNvbmZpZyIsIm1hcmtldHBsYWNlcyIsImZhaWx1cmVzIiwiYWxsUGx1Z2lucyIsImRhdGEiLCJtYXJrZXRwbGFjZSIsInBsdWdpbnMiLCJwdXNoIiwiaXNJbnN0YWxsZWQiLCJ1bmluc3RhbGxlZFBsdWdpbnMiLCJwIiwiY291bnRzIiwic29ydCIsImEiLCJiIiwiY291bnRBIiwiZ2V0IiwiY291bnRCIiwibG9jYWxlQ29tcGFyZSIsImNvbmZpZ3VyZWRDb3VudCIsIk9iamVjdCIsImtleXMiLCJyZWFzb24iLCJjb25maWd1cmVkTWFya2V0cGxhY2VDb3VudCIsImZhaWxlZE1hcmtldHBsYWNlQ291bnQiLCJzdWNjZXNzQ291bnQiLCJtIiwiZXJyb3JSZXN1bHQiLCJtZXNzYWdlIiwiRXJyb3IiLCJmb3VuZFBsdWdpbiIsImZpbmQiLCJlcnIiLCJpbnN0YWxsU2VsZWN0ZWRQbHVnaW5zIiwic2l6ZSIsInBsdWdpbnNUb0luc3RhbGwiLCJoYXMiLCJtYXAiLCJmYWlsdXJlQ291bnQiLCJuZXdGYWlsZWRQbHVnaW5zIiwiQXJyYXkiLCJzY29wZSIsInN1Y2Nlc3MiLCJoYW5kbGVTaW5nbGVQbHVnaW5JbnN0YWxsIiwibG9hZGVkIiwiY29udGV4dCIsImlucHV0IiwiX2tleSIsImtleUlzTm90Q3RybE9yTWV0YSIsImN0cmwiLCJtZXRhIiwidGVzdCIsInNlbGVjdDpwcmV2aW91cyIsImhhbmRsZVNlbGVjdGlvbkNoYW5nZSIsInNlbGVjdDpuZXh0Iiwic2VsZWN0OmFjY2VwdCIsInRhcmdldE1hcmtldHBsYWNlIiwicGx1Z2luOnRvZ2dsZSIsIm5ld1NlbGVjdGlvbiIsImRlbGV0ZSIsImFkZCIsInBsdWdpbjppbnN0YWxsIiwiZGV0YWlsc01lbnVPcHRpb25zIiwiaGFzSG9tZXBhZ2UiLCJob21lcGFnZSIsImdpdGh1YlJlcG8iLCJhY3Rpb24iLCJmaW5pc2giLCJtc2ciLCJvdXRjb21lIiwiZGV0YWlsIiwibWVudU9wdGlvbnMiLCJ2ZXJzaW9uIiwiYXV0aG9yIiwib3B0aW9uIiwiaW5kZXgiLCJzdGFydHNXaXRoIiwibGFiZWwiLCJ2aXNpYmxlUGx1Z2lucyIsImdldFZpc2libGVJdGVtcyIsIm5lZWRzUGFnaW5hdGlvbiIsInNjcm9sbFBvc2l0aW9uIiwiY3VycmVudCIsInRvdGFsIiwiY2FuU2Nyb2xsVXAiLCJhcnJvd1VwIiwidmlzaWJsZUluZGV4IiwiYWN0dWFsSW5kZXgiLCJ0b0FjdHVhbEluZGV4IiwiaXNTZWxlY3RlZCIsImlzU2VsZWN0ZWRGb3JJbnN0YWxsIiwiaXNJbnN0YWxsaW5nVGhpcyIsImlzTGFzdCIsInN0YXJ0SW5kZXgiLCJ1bmRlZmluZWQiLCJwb2ludGVyIiwiZWxsaXBzaXMiLCJyYWRpb09uIiwicmFkaW9PZmYiLCJ0YWdzIiwiY2FuU2Nyb2xsRG93biIsImFycm93RG93biIsImNyb3NzIiwiRGlzY292ZXJQbHVnaW5zS2V5SGludCIsInQwIiwiJCIsIl9jIiwiaGFzU2VsZWN0aW9uIiwiY2FuVG9nZ2xlIiwidDEiLCJ0MiIsIlN5bWJvbCIsImZvciIsInQzIiwidDQiLCJ0NSIsInQ2IiwiRW1wdHlTdGF0ZU1lc3NhZ2UiXSwic291cmNlcyI6WyJEaXNjb3ZlclBsdWdpbnMudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBmaWd1cmVzIGZyb20gJ2ZpZ3VyZXMnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IHVzZUNhbGxiYWNrLCB1c2VFZmZlY3QsIHVzZU1lbW8sIHVzZVN0YXRlIH0gZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBDb25maWd1cmFibGVTaG9ydGN1dEhpbnQgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL0NvbmZpZ3VyYWJsZVNob3J0Y3V0SGludC5qcydcbmltcG9ydCB7IEJ5bGluZSB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvZGVzaWduLXN5c3RlbS9CeWxpbmUuanMnXG5pbXBvcnQgeyBTZWFyY2hCb3ggfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL1NlYXJjaEJveC5qcydcbmltcG9ydCB7IHVzZVNlYXJjaElucHV0IH0gZnJvbSAnLi4vLi4vaG9va3MvdXNlU2VhcmNoSW5wdXQuanMnXG5pbXBvcnQgeyB1c2VUZXJtaW5hbFNpemUgfSBmcm9tICcuLi8uLi9ob29rcy91c2VUZXJtaW5hbFNpemUuanMnXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgY3VzdG9tLXJ1bGVzL3ByZWZlci11c2Uta2V5YmluZGluZ3MgLS0gdXNlSW5wdXQgbmVlZGVkIGZvciByYXcgc2VhcmNoIG1vZGUgdGV4dCBpbnB1dFxuaW1wb3J0IHsgQm94LCBUZXh0LCB1c2VJbnB1dCwgdXNlVGVybWluYWxGb2N1cyB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB7XG4gIHVzZUtleWJpbmRpbmcsXG4gIHVzZUtleWJpbmRpbmdzLFxufSBmcm9tICcuLi8uLi9rZXliaW5kaW5ncy91c2VLZXliaW5kaW5nLmpzJ1xuaW1wb3J0IHR5cGUgeyBMb2FkZWRQbHVnaW4gfSBmcm9tICcuLi8uLi90eXBlcy9wbHVnaW4uanMnXG5pbXBvcnQgeyBjb3VudCB9IGZyb20gJy4uLy4uL3V0aWxzL2FycmF5LmpzJ1xuaW1wb3J0IHsgb3BlbkJyb3dzZXIgfSBmcm9tICcuLi8uLi91dGlscy9icm93c2VyLmpzJ1xuaW1wb3J0IHsgbG9nRm9yRGVidWdnaW5nIH0gZnJvbSAnLi4vLi4vdXRpbHMvZGVidWcuanMnXG5pbXBvcnQgeyBlcnJvck1lc3NhZ2UgfSBmcm9tICcuLi8uLi91dGlscy9lcnJvcnMuanMnXG5pbXBvcnQgeyBjbGVhckFsbENhY2hlcyB9IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvY2FjaGVVdGlscy5qcydcbmltcG9ydCB7XG4gIGZvcm1hdEluc3RhbGxDb3VudCxcbiAgZ2V0SW5zdGFsbENvdW50cyxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9pbnN0YWxsQ291bnRzLmpzJ1xuaW1wb3J0IHsgaXNQbHVnaW5HbG9iYWxseUluc3RhbGxlZCB9IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvaW5zdGFsbGVkUGx1Z2luc01hbmFnZXIuanMnXG5pbXBvcnQge1xuICBjcmVhdGVQbHVnaW5JZCxcbiAgZGV0ZWN0RW1wdHlNYXJrZXRwbGFjZVJlYXNvbixcbiAgdHlwZSBFbXB0eU1hcmtldHBsYWNlUmVhc29uLFxuICBmb3JtYXRGYWlsdXJlRGV0YWlscyxcbiAgZm9ybWF0TWFya2V0cGxhY2VMb2FkaW5nRXJyb3JzLFxuICBsb2FkTWFya2V0cGxhY2VzV2l0aEdyYWNlZnVsRGVncmFkYXRpb24sXG59IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvbWFya2V0cGxhY2VIZWxwZXJzLmpzJ1xuaW1wb3J0IHsgbG9hZEtub3duTWFya2V0cGxhY2VzQ29uZmlnIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9tYXJrZXRwbGFjZU1hbmFnZXIuanMnXG5pbXBvcnQgeyBPRkZJQ0lBTF9NQVJLRVRQTEFDRV9OQU1FIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9vZmZpY2lhbE1hcmtldHBsYWNlLmpzJ1xuaW1wb3J0IHsgaW5zdGFsbFBsdWdpbkZyb21NYXJrZXRwbGFjZSB9IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvcGx1Z2luSW5zdGFsbGF0aW9uSGVscGVycy5qcydcbmltcG9ydCB7IGlzUGx1Z2luQmxvY2tlZEJ5UG9saWN5IH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9wbHVnaW5Qb2xpY3kuanMnXG5pbXBvcnQgeyBwbHVyYWwgfSBmcm9tICcuLi8uLi91dGlscy9zdHJpbmdVdGlscy5qcydcbmltcG9ydCB7IHRydW5jYXRlVG9XaWR0aCB9IGZyb20gJy4uLy4uL3V0aWxzL3RydW5jYXRlLmpzJ1xuaW1wb3J0IHtcbiAgZmluZFBsdWdpbk9wdGlvbnNUYXJnZXQsXG4gIFBsdWdpbk9wdGlvbnNGbG93LFxufSBmcm9tICcuL1BsdWdpbk9wdGlvbnNGbG93LmpzJ1xuaW1wb3J0IHsgUGx1Z2luVHJ1c3RXYXJuaW5nIH0gZnJvbSAnLi9QbHVnaW5UcnVzdFdhcm5pbmcuanMnXG5pbXBvcnQge1xuICBidWlsZFBsdWdpbkRldGFpbHNNZW51T3B0aW9ucyxcbiAgZXh0cmFjdEdpdEh1YlJlcG8sXG4gIHR5cGUgSW5zdGFsbGFibGVQbHVnaW4sXG59IGZyb20gJy4vcGx1Z2luRGV0YWlsc0hlbHBlcnMuanMnXG5pbXBvcnQgdHlwZSB7IFZpZXdTdGF0ZSBhcyBQYXJlbnRWaWV3U3RhdGUgfSBmcm9tICcuL3R5cGVzLmpzJ1xuaW1wb3J0IHsgdXNlUGFnaW5hdGlvbiB9IGZyb20gJy4vdXNlUGFnaW5hdGlvbi5qcydcblxudHlwZSBQcm9wcyA9IHtcbiAgZXJyb3I6IHN0cmluZyB8IG51bGxcbiAgc2V0RXJyb3I6IChlcnJvcjogc3RyaW5nIHwgbnVsbCkgPT4gdm9pZFxuICByZXN1bHQ6IHN0cmluZyB8IG51bGxcbiAgc2V0UmVzdWx0OiAocmVzdWx0OiBzdHJpbmcgfCBudWxsKSA9PiB2b2lkXG4gIHNldFZpZXdTdGF0ZTogKHN0YXRlOiBQYXJlbnRWaWV3U3RhdGUpID0+IHZvaWRcbiAgb25JbnN0YWxsQ29tcGxldGU/OiAoKSA9PiB2b2lkIHwgUHJvbWlzZTx2b2lkPlxuICBvblNlYXJjaE1vZGVDaGFuZ2U/OiAoaXNBY3RpdmU6IGJvb2xlYW4pID0+IHZvaWRcbiAgdGFyZ2V0UGx1Z2luPzogc3RyaW5nXG59XG5cbnR5cGUgVmlld1N0YXRlID1cbiAgfCAncGx1Z2luLWxpc3QnXG4gIHwgJ3BsdWdpbi1kZXRhaWxzJ1xuICB8IHsgdHlwZTogJ3BsdWdpbi1vcHRpb25zJzsgcGx1Z2luOiBMb2FkZWRQbHVnaW47IHBsdWdpbklkOiBzdHJpbmcgfVxuXG5leHBvcnQgZnVuY3Rpb24gRGlzY292ZXJQbHVnaW5zKHtcbiAgZXJyb3IsXG4gIHNldEVycm9yLFxuICByZXN1bHQ6IF9yZXN1bHQsXG4gIHNldFJlc3VsdCxcbiAgc2V0Vmlld1N0YXRlOiBzZXRQYXJlbnRWaWV3U3RhdGUsXG4gIG9uSW5zdGFsbENvbXBsZXRlLFxuICBvblNlYXJjaE1vZGVDaGFuZ2UsXG4gIHRhcmdldFBsdWdpbixcbn06IFByb3BzKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgLy8gVmlldyBzdGF0ZVxuICBjb25zdCBbdmlld1N0YXRlLCBzZXRWaWV3U3RhdGVdID0gdXNlU3RhdGU8Vmlld1N0YXRlPigncGx1Z2luLWxpc3QnKVxuICBjb25zdCBbc2VsZWN0ZWRQbHVnaW4sIHNldFNlbGVjdGVkUGx1Z2luXSA9XG4gICAgdXNlU3RhdGU8SW5zdGFsbGFibGVQbHVnaW4gfCBudWxsPihudWxsKVxuXG4gIC8vIERhdGEgc3RhdGVcbiAgY29uc3QgW2F2YWlsYWJsZVBsdWdpbnMsIHNldEF2YWlsYWJsZVBsdWdpbnNdID0gdXNlU3RhdGU8SW5zdGFsbGFibGVQbHVnaW5bXT4oXG4gICAgW10sXG4gIClcbiAgY29uc3QgW2xvYWRpbmcsIHNldExvYWRpbmddID0gdXNlU3RhdGUodHJ1ZSlcbiAgY29uc3QgW2luc3RhbGxDb3VudHMsIHNldEluc3RhbGxDb3VudHNdID0gdXNlU3RhdGU8TWFwPFxuICAgIHN0cmluZyxcbiAgICBudW1iZXJcbiAgPiB8IG51bGw+KG51bGwpXG5cbiAgLy8gU2VhcmNoIHN0YXRlXG4gIGNvbnN0IFtpc1NlYXJjaE1vZGUsIHNldElzU2VhcmNoTW9kZVJhd10gPSB1c2VTdGF0ZShmYWxzZSlcbiAgY29uc3Qgc2V0SXNTZWFyY2hNb2RlID0gdXNlQ2FsbGJhY2soXG4gICAgKGFjdGl2ZTogYm9vbGVhbikgPT4ge1xuICAgICAgc2V0SXNTZWFyY2hNb2RlUmF3KGFjdGl2ZSlcbiAgICAgIG9uU2VhcmNoTW9kZUNoYW5nZT8uKGFjdGl2ZSlcbiAgICB9LFxuICAgIFtvblNlYXJjaE1vZGVDaGFuZ2VdLFxuICApXG4gIGNvbnN0IHtcbiAgICBxdWVyeTogc2VhcmNoUXVlcnksXG4gICAgc2V0UXVlcnk6IHNldFNlYXJjaFF1ZXJ5LFxuICAgIGN1cnNvck9mZnNldDogc2VhcmNoQ3Vyc29yT2Zmc2V0LFxuICB9ID0gdXNlU2VhcmNoSW5wdXQoe1xuICAgIGlzQWN0aXZlOiB2aWV3U3RhdGUgPT09ICdwbHVnaW4tbGlzdCcgJiYgaXNTZWFyY2hNb2RlICYmICFsb2FkaW5nLFxuICAgIG9uRXhpdDogKCkgPT4ge1xuICAgICAgc2V0SXNTZWFyY2hNb2RlKGZhbHNlKVxuICAgIH0sXG4gIH0pXG4gIGNvbnN0IGlzVGVybWluYWxGb2N1c2VkID0gdXNlVGVybWluYWxGb2N1cygpXG4gIGNvbnN0IHsgY29sdW1uczogdGVybWluYWxXaWR0aCB9ID0gdXNlVGVybWluYWxTaXplKClcblxuICAvLyBGaWx0ZXIgcGx1Z2lucyBiYXNlZCBvbiBzZWFyY2ggcXVlcnlcbiAgY29uc3QgZmlsdGVyZWRQbHVnaW5zID0gdXNlTWVtbygoKSA9PiB7XG4gICAgaWYgKCFzZWFyY2hRdWVyeSkgcmV0dXJuIGF2YWlsYWJsZVBsdWdpbnNcbiAgICBjb25zdCBsb3dlclF1ZXJ5ID0gc2VhcmNoUXVlcnkudG9Mb3dlckNhc2UoKVxuICAgIHJldHVybiBhdmFpbGFibGVQbHVnaW5zLmZpbHRlcihcbiAgICAgIHBsdWdpbiA9PlxuICAgICAgICBwbHVnaW4uZW50cnkubmFtZS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKGxvd2VyUXVlcnkpIHx8XG4gICAgICAgIHBsdWdpbi5lbnRyeS5kZXNjcmlwdGlvbj8udG9Mb3dlckNhc2UoKS5pbmNsdWRlcyhsb3dlclF1ZXJ5KSB8fFxuICAgICAgICBwbHVnaW4ubWFya2V0cGxhY2VOYW1lLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMobG93ZXJRdWVyeSksXG4gICAgKVxuICB9LCBbYXZhaWxhYmxlUGx1Z2lucywgc2VhcmNoUXVlcnldKVxuXG4gIC8vIFNlbGVjdGlvbiBzdGF0ZVxuICBjb25zdCBbc2VsZWN0ZWRJbmRleCwgc2V0U2VsZWN0ZWRJbmRleF0gPSB1c2VTdGF0ZSgwKVxuICBjb25zdCBbc2VsZWN0ZWRGb3JJbnN0YWxsLCBzZXRTZWxlY3RlZEZvckluc3RhbGxdID0gdXNlU3RhdGU8U2V0PHN0cmluZz4+KFxuICAgIG5ldyBTZXQoKSxcbiAgKVxuICBjb25zdCBbaW5zdGFsbGluZ1BsdWdpbnMsIHNldEluc3RhbGxpbmdQbHVnaW5zXSA9IHVzZVN0YXRlPFNldDxzdHJpbmc+PihcbiAgICBuZXcgU2V0KCksXG4gIClcblxuICAvLyBQYWdpbmF0aW9uIGZvciBwbHVnaW4gbGlzdCAoY29udGludW91cyBzY3JvbGxpbmcpXG4gIGNvbnN0IHBhZ2luYXRpb24gPSB1c2VQYWdpbmF0aW9uPEluc3RhbGxhYmxlUGx1Z2luPih7XG4gICAgdG90YWxJdGVtczogZmlsdGVyZWRQbHVnaW5zLmxlbmd0aCxcbiAgICBzZWxlY3RlZEluZGV4LFxuICB9KVxuXG4gIC8vIFJlc2V0IHNlbGVjdGlvbiB3aGVuIHNlYXJjaCBxdWVyeSBjaGFuZ2VzXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgc2V0U2VsZWN0ZWRJbmRleCgwKVxuICB9LCBbc2VhcmNoUXVlcnldKVxuXG4gIC8vIERldGFpbHMgdmlldyBzdGF0ZVxuICBjb25zdCBbZGV0YWlsc01lbnVJbmRleCwgc2V0RGV0YWlsc01lbnVJbmRleF0gPSB1c2VTdGF0ZSgwKVxuICBjb25zdCBbaXNJbnN0YWxsaW5nLCBzZXRJc0luc3RhbGxpbmddID0gdXNlU3RhdGUoZmFsc2UpXG4gIGNvbnN0IFtpbnN0YWxsRXJyb3IsIHNldEluc3RhbGxFcnJvcl0gPSB1c2VTdGF0ZTxzdHJpbmcgfCBudWxsPihudWxsKVxuXG4gIC8vIFdhcm5pbmcgc3RhdGUgZm9yIG5vbi1jcml0aWNhbCBlcnJvcnNcbiAgY29uc3QgW3dhcm5pbmcsIHNldFdhcm5pbmddID0gdXNlU3RhdGU8c3RyaW5nIHwgbnVsbD4obnVsbClcblxuICAvLyBFbXB0eSBzdGF0ZSByZWFzb25cbiAgY29uc3QgW2VtcHR5UmVhc29uLCBzZXRFbXB0eVJlYXNvbl0gPSB1c2VTdGF0ZTxFbXB0eU1hcmtldHBsYWNlUmVhc29uIHwgbnVsbD4oXG4gICAgbnVsbCxcbiAgKVxuXG4gIC8vIExvYWQgYWxsIHBsdWdpbnMgZnJvbSBhbGwgbWFya2V0cGxhY2VzXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgYXN5bmMgZnVuY3Rpb24gbG9hZEFsbFBsdWdpbnMoKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBjb25maWcgPSBhd2FpdCBsb2FkS25vd25NYXJrZXRwbGFjZXNDb25maWcoKVxuXG4gICAgICAgIC8vIExvYWQgbWFya2V0cGxhY2VzIHdpdGggZ3JhY2VmdWwgZGVncmFkYXRpb25cbiAgICAgICAgY29uc3QgeyBtYXJrZXRwbGFjZXMsIGZhaWx1cmVzIH0gPVxuICAgICAgICAgIGF3YWl0IGxvYWRNYXJrZXRwbGFjZXNXaXRoR3JhY2VmdWxEZWdyYWRhdGlvbihjb25maWcpXG5cbiAgICAgICAgLy8gQ29sbGVjdCBhbGwgcGx1Z2lucyBmcm9tIGFsbCBtYXJrZXRwbGFjZXNcbiAgICAgICAgY29uc3QgYWxsUGx1Z2luczogSW5zdGFsbGFibGVQbHVnaW5bXSA9IFtdXG5cbiAgICAgICAgZm9yIChjb25zdCB7IG5hbWUsIGRhdGE6IG1hcmtldHBsYWNlIH0gb2YgbWFya2V0cGxhY2VzKSB7XG4gICAgICAgICAgaWYgKG1hcmtldHBsYWNlKSB7XG4gICAgICAgICAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIG1hcmtldHBsYWNlLnBsdWdpbnMpIHtcbiAgICAgICAgICAgICAgY29uc3QgcGx1Z2luSWQgPSBjcmVhdGVQbHVnaW5JZChlbnRyeS5uYW1lLCBuYW1lKVxuICAgICAgICAgICAgICBhbGxQbHVnaW5zLnB1c2goe1xuICAgICAgICAgICAgICAgIGVudHJ5LFxuICAgICAgICAgICAgICAgIG1hcmtldHBsYWNlTmFtZTogbmFtZSxcbiAgICAgICAgICAgICAgICBwbHVnaW5JZCxcbiAgICAgICAgICAgICAgICAvLyBPbmx5IGJsb2NrIHdoZW4gZ2xvYmFsbHkgaW5zdGFsbGVkICh1c2VyL21hbmFnZWQgc2NvcGUpLlxuICAgICAgICAgICAgICAgIC8vIFByb2plY3QvbG9jYWwtc2NvcGUgaW5zdGFsbHMgZG9uJ3QgYmxvY2sg4oCUIHVzZXIgbWF5IHdhbnQgdG9cbiAgICAgICAgICAgICAgICAvLyBwcm9tb3RlIHRvIHVzZXIgc2NvcGUgc28gaXQncyBhdmFpbGFibGUgZXZlcnl3aGVyZSAoZ2gtMjk5OTcpLlxuICAgICAgICAgICAgICAgIGlzSW5zdGFsbGVkOiBpc1BsdWdpbkdsb2JhbGx5SW5zdGFsbGVkKHBsdWdpbklkKSxcbiAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBGaWx0ZXIgb3V0IGluc3RhbGxlZCBhbmQgcG9saWN5LWJsb2NrZWQgcGx1Z2luc1xuICAgICAgICBjb25zdCB1bmluc3RhbGxlZFBsdWdpbnMgPSBhbGxQbHVnaW5zLmZpbHRlcihcbiAgICAgICAgICBwID0+ICFwLmlzSW5zdGFsbGVkICYmICFpc1BsdWdpbkJsb2NrZWRCeVBvbGljeShwLnBsdWdpbklkKSxcbiAgICAgICAgKVxuXG4gICAgICAgIC8vIEZldGNoIGluc3RhbGwgY291bnRzIGFuZCBzb3J0IGJ5IHBvcHVsYXJpdHlcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBjb25zdCBjb3VudHMgPSBhd2FpdCBnZXRJbnN0YWxsQ291bnRzKClcbiAgICAgICAgICBzZXRJbnN0YWxsQ291bnRzKGNvdW50cylcblxuICAgICAgICAgIGlmIChjb3VudHMpIHtcbiAgICAgICAgICAgIC8vIFNvcnQgYnkgaW5zdGFsbCBjb3VudCAoZGVzY2VuZGluZyksIHRoZW4gYWxwaGFiZXRpY2FsbHlcbiAgICAgICAgICAgIHVuaW5zdGFsbGVkUGx1Z2lucy5zb3J0KChhLCBiKSA9PiB7XG4gICAgICAgICAgICAgIGNvbnN0IGNvdW50QSA9IGNvdW50cy5nZXQoYS5wbHVnaW5JZCkgPz8gMFxuICAgICAgICAgICAgICBjb25zdCBjb3VudEIgPSBjb3VudHMuZ2V0KGIucGx1Z2luSWQpID8/IDBcbiAgICAgICAgICAgICAgaWYgKGNvdW50QSAhPT0gY291bnRCKSByZXR1cm4gY291bnRCIC0gY291bnRBXG4gICAgICAgICAgICAgIHJldHVybiBhLmVudHJ5Lm5hbWUubG9jYWxlQ29tcGFyZShiLmVudHJ5Lm5hbWUpXG4gICAgICAgICAgICB9KVxuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAvLyBObyBjb3VudHMgYXZhaWxhYmxlIC0gc29ydCBhbHBoYWJldGljYWxseVxuICAgICAgICAgICAgdW5pbnN0YWxsZWRQbHVnaW5zLnNvcnQoKGEsIGIpID0+XG4gICAgICAgICAgICAgIGEuZW50cnkubmFtZS5sb2NhbGVDb21wYXJlKGIuZW50cnkubmFtZSksXG4gICAgICAgICAgICApXG4gICAgICAgICAgfVxuICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgIC8vIExvZyB0aGUgZXJyb3IsIHRoZW4gZ3JhY2VmdWxseSBkZWdyYWRlIHRvIGFscGhhYmV0aWNhbCBzb3J0XG4gICAgICAgICAgbG9nRm9yRGVidWdnaW5nKFxuICAgICAgICAgICAgYEZhaWxlZCB0byBmZXRjaCBpbnN0YWxsIGNvdW50czogJHtlcnJvck1lc3NhZ2UoZXJyb3IpfWAsXG4gICAgICAgICAgKVxuICAgICAgICAgIHVuaW5zdGFsbGVkUGx1Z2lucy5zb3J0KChhLCBiKSA9PlxuICAgICAgICAgICAgYS5lbnRyeS5uYW1lLmxvY2FsZUNvbXBhcmUoYi5lbnRyeS5uYW1lKSxcbiAgICAgICAgICApXG4gICAgICAgIH1cblxuICAgICAgICBzZXRBdmFpbGFibGVQbHVnaW5zKHVuaW5zdGFsbGVkUGx1Z2lucylcblxuICAgICAgICAvLyBEZXRlY3QgZW1wdHkgcmVhc29uIGlmIG5vIHBsdWdpbnMgYXZhaWxhYmxlXG4gICAgICAgIGNvbnN0IGNvbmZpZ3VyZWRDb3VudCA9IE9iamVjdC5rZXlzKGNvbmZpZykubGVuZ3RoXG4gICAgICAgIGlmICh1bmluc3RhbGxlZFBsdWdpbnMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgY29uc3QgcmVhc29uID0gYXdhaXQgZGV0ZWN0RW1wdHlNYXJrZXRwbGFjZVJlYXNvbih7XG4gICAgICAgICAgICBjb25maWd1cmVkTWFya2V0cGxhY2VDb3VudDogY29uZmlndXJlZENvdW50LFxuICAgICAgICAgICAgZmFpbGVkTWFya2V0cGxhY2VDb3VudDogZmFpbHVyZXMubGVuZ3RoLFxuICAgICAgICAgIH0pXG4gICAgICAgICAgc2V0RW1wdHlSZWFzb24ocmVhc29uKVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gSGFuZGxlIG1hcmtldHBsYWNlIGxvYWRpbmcgZXJyb3JzL3dhcm5pbmdzXG4gICAgICAgIGNvbnN0IHN1Y2Nlc3NDb3VudCA9IGNvdW50KG1hcmtldHBsYWNlcywgbSA9PiBtLmRhdGEgIT09IG51bGwpXG4gICAgICAgIGNvbnN0IGVycm9yUmVzdWx0ID0gZm9ybWF0TWFya2V0cGxhY2VMb2FkaW5nRXJyb3JzKFxuICAgICAgICAgIGZhaWx1cmVzLFxuICAgICAgICAgIHN1Y2Nlc3NDb3VudCxcbiAgICAgICAgKVxuICAgICAgICBpZiAoZXJyb3JSZXN1bHQpIHtcbiAgICAgICAgICBpZiAoZXJyb3JSZXN1bHQudHlwZSA9PT0gJ3dhcm5pbmcnKSB7XG4gICAgICAgICAgICBzZXRXYXJuaW5nKGVycm9yUmVzdWx0Lm1lc3NhZ2UgKyAnLiBTaG93aW5nIGF2YWlsYWJsZSBwbHVnaW5zLicpXG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihlcnJvclJlc3VsdC5tZXNzYWdlKVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIEhhbmRsZSB0YXJnZXRQbHVnaW4gLSBuYXZpZ2F0ZSBkaXJlY3RseSB0byBwbHVnaW4gZGV0YWlsc1xuICAgICAgICAvLyBTZWFyY2ggaW4gYWxsUGx1Z2lucyAoYmVmb3JlIGZpbHRlcmluZykgdG8gaGFuZGxlIGluc3RhbGxlZCBwbHVnaW5zIGdyYWNlZnVsbHlcbiAgICAgICAgaWYgKHRhcmdldFBsdWdpbikge1xuICAgICAgICAgIGNvbnN0IGZvdW5kUGx1Z2luID0gYWxsUGx1Z2lucy5maW5kKFxuICAgICAgICAgICAgcCA9PiBwLmVudHJ5Lm5hbWUgPT09IHRhcmdldFBsdWdpbixcbiAgICAgICAgICApXG5cbiAgICAgICAgICBpZiAoZm91bmRQbHVnaW4pIHtcbiAgICAgICAgICAgIGlmIChmb3VuZFBsdWdpbi5pc0luc3RhbGxlZCkge1xuICAgICAgICAgICAgICBzZXRFcnJvcihcbiAgICAgICAgICAgICAgICBgUGx1Z2luICcke2ZvdW5kUGx1Z2luLnBsdWdpbklkfScgaXMgYWxyZWFkeSBpbnN0YWxsZWQuIFVzZSAnL3BsdWdpbicgdG8gbWFuYWdlIGV4aXN0aW5nIHBsdWdpbnMuYCxcbiAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgc2V0U2VsZWN0ZWRQbHVnaW4oZm91bmRQbHVnaW4pXG4gICAgICAgICAgICAgIHNldFZpZXdTdGF0ZSgncGx1Z2luLWRldGFpbHMnKVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzZXRFcnJvcihgUGx1Z2luIFwiJHt0YXJnZXRQbHVnaW59XCIgbm90IGZvdW5kIGluIGFueSBtYXJrZXRwbGFjZWApXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgc2V0RXJyb3IoZXJyIGluc3RhbmNlb2YgRXJyb3IgPyBlcnIubWVzc2FnZSA6ICdGYWlsZWQgdG8gbG9hZCBwbHVnaW5zJylcbiAgICAgIH0gZmluYWxseSB7XG4gICAgICAgIHNldExvYWRpbmcoZmFsc2UpXG4gICAgICB9XG4gICAgfVxuICAgIHZvaWQgbG9hZEFsbFBsdWdpbnMoKVxuICB9LCBbc2V0RXJyb3IsIHRhcmdldFBsdWdpbl0pXG5cbiAgLy8gSW5zdGFsbCBzZWxlY3RlZCBwbHVnaW5zXG4gIGNvbnN0IGluc3RhbGxTZWxlY3RlZFBsdWdpbnMgPSBhc3luYyAoKSA9PiB7XG4gICAgaWYgKHNlbGVjdGVkRm9ySW5zdGFsbC5zaXplID09PSAwKSByZXR1cm5cblxuICAgIGNvbnN0IHBsdWdpbnNUb0luc3RhbGwgPSBhdmFpbGFibGVQbHVnaW5zLmZpbHRlcihwID0+XG4gICAgICBzZWxlY3RlZEZvckluc3RhbGwuaGFzKHAucGx1Z2luSWQpLFxuICAgIClcblxuICAgIHNldEluc3RhbGxpbmdQbHVnaW5zKG5ldyBTZXQocGx1Z2luc1RvSW5zdGFsbC5tYXAocCA9PiBwLnBsdWdpbklkKSkpXG5cbiAgICBsZXQgc3VjY2Vzc0NvdW50ID0gMFxuICAgIGxldCBmYWlsdXJlQ291bnQgPSAwXG4gICAgY29uc3QgbmV3RmFpbGVkUGx1Z2luczogQXJyYXk8eyBuYW1lOiBzdHJpbmc7IHJlYXNvbjogc3RyaW5nIH0+ID0gW11cblxuICAgIGZvciAoY29uc3QgcGx1Z2luIG9mIHBsdWdpbnNUb0luc3RhbGwpIHtcbiAgICAgIGNvbnN0IHJlc3VsdCA9IGF3YWl0IGluc3RhbGxQbHVnaW5Gcm9tTWFya2V0cGxhY2Uoe1xuICAgICAgICBwbHVnaW5JZDogcGx1Z2luLnBsdWdpbklkLFxuICAgICAgICBlbnRyeTogcGx1Z2luLmVudHJ5LFxuICAgICAgICBtYXJrZXRwbGFjZU5hbWU6IHBsdWdpbi5tYXJrZXRwbGFjZU5hbWUsXG4gICAgICAgIHNjb3BlOiAndXNlcicsXG4gICAgICB9KVxuXG4gICAgICBpZiAocmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgICAgc3VjY2Vzc0NvdW50KytcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGZhaWx1cmVDb3VudCsrXG4gICAgICAgIG5ld0ZhaWxlZFBsdWdpbnMucHVzaCh7XG4gICAgICAgICAgbmFtZTogcGx1Z2luLmVudHJ5Lm5hbWUsXG4gICAgICAgICAgcmVhc29uOiByZXN1bHQuZXJyb3IsXG4gICAgICAgIH0pXG4gICAgICB9XG4gICAgfVxuXG4gICAgc2V0SW5zdGFsbGluZ1BsdWdpbnMobmV3IFNldCgpKVxuICAgIHNldFNlbGVjdGVkRm9ySW5zdGFsbChuZXcgU2V0KCkpXG4gICAgY2xlYXJBbGxDYWNoZXMoKVxuXG4gICAgLy8gSGFuZGxlIGluc3RhbGxhdGlvbiByZXN1bHRzXG4gICAgaWYgKGZhaWx1cmVDb3VudCA9PT0gMCkge1xuICAgICAgY29uc3QgbWVzc2FnZSA9XG4gICAgICAgIGDinJMgSW5zdGFsbGVkICR7c3VjY2Vzc0NvdW50fSAke3BsdXJhbChzdWNjZXNzQ291bnQsICdwbHVnaW4nKX0uIGAgK1xuICAgICAgICBgUnVuIC9yZWxvYWQtcGx1Z2lucyB0byBhY3RpdmF0ZS5gXG4gICAgICBzZXRSZXN1bHQobWVzc2FnZSlcbiAgICB9IGVsc2UgaWYgKHN1Y2Nlc3NDb3VudCA9PT0gMCkge1xuICAgICAgc2V0RXJyb3IoXG4gICAgICAgIGBGYWlsZWQgdG8gaW5zdGFsbDogJHtmb3JtYXRGYWlsdXJlRGV0YWlscyhuZXdGYWlsZWRQbHVnaW5zLCB0cnVlKX1gLFxuICAgICAgKVxuICAgIH0gZWxzZSB7XG4gICAgICBjb25zdCBtZXNzYWdlID1cbiAgICAgICAgYOKckyBJbnN0YWxsZWQgJHtzdWNjZXNzQ291bnR9IG9mICR7c3VjY2Vzc0NvdW50ICsgZmFpbHVyZUNvdW50fSBwbHVnaW5zLiBgICtcbiAgICAgICAgYEZhaWxlZDogJHtmb3JtYXRGYWlsdXJlRGV0YWlscyhuZXdGYWlsZWRQbHVnaW5zLCBmYWxzZSl9LiBgICtcbiAgICAgICAgYFJ1biAvcmVsb2FkLXBsdWdpbnMgdG8gYWN0aXZhdGUgc3VjY2Vzc2Z1bGx5IGluc3RhbGxlZCBwbHVnaW5zLmBcbiAgICAgIHNldFJlc3VsdChtZXNzYWdlKVxuICAgIH1cblxuICAgIGlmIChzdWNjZXNzQ291bnQgPiAwKSB7XG4gICAgICBpZiAob25JbnN0YWxsQ29tcGxldGUpIHtcbiAgICAgICAgYXdhaXQgb25JbnN0YWxsQ29tcGxldGUoKVxuICAgICAgfVxuICAgIH1cblxuICAgIHNldFBhcmVudFZpZXdTdGF0ZSh7IHR5cGU6ICdtZW51JyB9KVxuICB9XG5cbiAgLy8gSW5zdGFsbCBzaW5nbGUgcGx1Z2luIGZyb20gZGV0YWlscyB2aWV3XG4gIGNvbnN0IGhhbmRsZVNpbmdsZVBsdWdpbkluc3RhbGwgPSBhc3luYyAoXG4gICAgcGx1Z2luOiBJbnN0YWxsYWJsZVBsdWdpbixcbiAgICBzY29wZTogJ3VzZXInIHwgJ3Byb2plY3QnIHwgJ2xvY2FsJyA9ICd1c2VyJyxcbiAgKSA9PiB7XG4gICAgc2V0SXNJbnN0YWxsaW5nKHRydWUpXG4gICAgc2V0SW5zdGFsbEVycm9yKG51bGwpXG5cbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBpbnN0YWxsUGx1Z2luRnJvbU1hcmtldHBsYWNlKHtcbiAgICAgIHBsdWdpbklkOiBwbHVnaW4ucGx1Z2luSWQsXG4gICAgICBlbnRyeTogcGx1Z2luLmVudHJ5LFxuICAgICAgbWFya2V0cGxhY2VOYW1lOiBwbHVnaW4ubWFya2V0cGxhY2VOYW1lLFxuICAgICAgc2NvcGUsXG4gICAgfSlcblxuICAgIGlmIChyZXN1bHQuc3VjY2Vzcykge1xuICAgICAgY29uc3QgbG9hZGVkID0gYXdhaXQgZmluZFBsdWdpbk9wdGlvbnNUYXJnZXQocGx1Z2luLnBsdWdpbklkKVxuICAgICAgaWYgKGxvYWRlZCkge1xuICAgICAgICBzZXRJc0luc3RhbGxpbmcoZmFsc2UpXG4gICAgICAgIHNldFZpZXdTdGF0ZSh7XG4gICAgICAgICAgdHlwZTogJ3BsdWdpbi1vcHRpb25zJyxcbiAgICAgICAgICBwbHVnaW46IGxvYWRlZCxcbiAgICAgICAgICBwbHVnaW5JZDogcGx1Z2luLnBsdWdpbklkLFxuICAgICAgICB9KVxuICAgICAgICByZXR1cm5cbiAgICAgIH1cbiAgICAgIHNldFJlc3VsdChyZXN1bHQubWVzc2FnZSlcbiAgICAgIGlmIChvbkluc3RhbGxDb21wbGV0ZSkge1xuICAgICAgICBhd2FpdCBvbkluc3RhbGxDb21wbGV0ZSgpXG4gICAgICB9XG4gICAgICBzZXRQYXJlbnRWaWV3U3RhdGUoeyB0eXBlOiAnbWVudScgfSlcbiAgICB9IGVsc2Uge1xuICAgICAgc2V0SXNJbnN0YWxsaW5nKGZhbHNlKVxuICAgICAgc2V0SW5zdGFsbEVycm9yKHJlc3VsdC5lcnJvcilcbiAgICB9XG4gIH1cblxuICAvLyBIYW5kbGUgZXJyb3Igc3RhdGVcbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICBpZiAoZXJyb3IpIHtcbiAgICAgIHNldFJlc3VsdChlcnJvcilcbiAgICB9XG4gIH0sIFtlcnJvciwgc2V0UmVzdWx0XSlcblxuICAvLyBFc2NhcGUgaW4gcGx1Z2luLWRldGFpbHMgdmlldyAtIGdvIGJhY2sgdG8gcGx1Z2luLWxpc3RcbiAgdXNlS2V5YmluZGluZyhcbiAgICAnY29uZmlybTpubycsXG4gICAgKCkgPT4ge1xuICAgICAgc2V0Vmlld1N0YXRlKCdwbHVnaW4tbGlzdCcpXG4gICAgICBzZXRTZWxlY3RlZFBsdWdpbihudWxsKVxuICAgIH0sXG4gICAge1xuICAgICAgY29udGV4dDogJ0NvbmZpcm1hdGlvbicsXG4gICAgICBpc0FjdGl2ZTogdmlld1N0YXRlID09PSAncGx1Z2luLWRldGFpbHMnLFxuICAgIH0sXG4gIClcblxuICAvLyBFc2NhcGUgaW4gcGx1Z2luLWxpc3QgdmlldyAobm90IHNlYXJjaCBtb2RlKSAtIGV4aXQgdG8gcGFyZW50IG1lbnVcbiAgdXNlS2V5YmluZGluZyhcbiAgICAnY29uZmlybTpubycsXG4gICAgKCkgPT4ge1xuICAgICAgc2V0UGFyZW50Vmlld1N0YXRlKHsgdHlwZTogJ21lbnUnIH0pXG4gICAgfSxcbiAgICB7XG4gICAgICBjb250ZXh0OiAnQ29uZmlybWF0aW9uJyxcbiAgICAgIGlzQWN0aXZlOiB2aWV3U3RhdGUgPT09ICdwbHVnaW4tbGlzdCcgJiYgIWlzU2VhcmNoTW9kZSxcbiAgICB9LFxuICApXG5cbiAgLy8gSGFuZGxlIGVudGVyaW5nIHNlYXJjaCBtb2RlIChub24tZXNjYXBlIGtleXMpXG4gIHVzZUlucHV0KFxuICAgIChpbnB1dCwgX2tleSkgPT4ge1xuICAgICAgY29uc3Qga2V5SXNOb3RDdHJsT3JNZXRhID0gIV9rZXkuY3RybCAmJiAhX2tleS5tZXRhXG4gICAgICBpZiAoIWlzU2VhcmNoTW9kZSkge1xuICAgICAgICAvLyBFbnRlciBzZWFyY2ggbW9kZSB3aXRoICcvJyBvciBhbnkgcHJpbnRhYmxlIGNoYXJhY3RlclxuICAgICAgICBpZiAoaW5wdXQgPT09ICcvJyAmJiBrZXlJc05vdEN0cmxPck1ldGEpIHtcbiAgICAgICAgICBzZXRJc1NlYXJjaE1vZGUodHJ1ZSlcbiAgICAgICAgICBzZXRTZWFyY2hRdWVyeSgnJylcbiAgICAgICAgfSBlbHNlIGlmIChcbiAgICAgICAgICBrZXlJc05vdEN0cmxPck1ldGEgJiZcbiAgICAgICAgICBpbnB1dC5sZW5ndGggPiAwICYmXG4gICAgICAgICAgIS9eXFxzKyQvLnRlc3QoaW5wdXQpICYmXG4gICAgICAgICAgLy8gRG9uJ3QgZW50ZXIgc2VhcmNoIG1vZGUgZm9yIG5hdmlnYXRpb24ga2V5c1xuICAgICAgICAgIGlucHV0ICE9PSAnaicgJiZcbiAgICAgICAgICBpbnB1dCAhPT0gJ2snICYmXG4gICAgICAgICAgaW5wdXQgIT09ICdpJ1xuICAgICAgICApIHtcbiAgICAgICAgICBzZXRJc1NlYXJjaE1vZGUodHJ1ZSlcbiAgICAgICAgICBzZXRTZWFyY2hRdWVyeShpbnB1dClcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH0sXG4gICAgeyBpc0FjdGl2ZTogdmlld1N0YXRlID09PSAncGx1Z2luLWxpc3QnICYmICFsb2FkaW5nIH0sXG4gIClcblxuICAvLyBQbHVnaW4tbGlzdCBuYXZpZ2F0aW9uIChub24tc2VhcmNoIG1vZGUpXG4gIHVzZUtleWJpbmRpbmdzKFxuICAgIHtcbiAgICAgICdzZWxlY3Q6cHJldmlvdXMnOiAoKSA9PiB7XG4gICAgICAgIGlmIChzZWxlY3RlZEluZGV4ID09PSAwKSB7XG4gICAgICAgICAgc2V0SXNTZWFyY2hNb2RlKHRydWUpXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcGFnaW5hdGlvbi5oYW5kbGVTZWxlY3Rpb25DaGFuZ2Uoc2VsZWN0ZWRJbmRleCAtIDEsIHNldFNlbGVjdGVkSW5kZXgpXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICAnc2VsZWN0Om5leHQnOiAoKSA9PiB7XG4gICAgICAgIGlmIChzZWxlY3RlZEluZGV4IDwgZmlsdGVyZWRQbHVnaW5zLmxlbmd0aCAtIDEpIHtcbiAgICAgICAgICBwYWdpbmF0aW9uLmhhbmRsZVNlbGVjdGlvbkNoYW5nZShzZWxlY3RlZEluZGV4ICsgMSwgc2V0U2VsZWN0ZWRJbmRleClcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgICdzZWxlY3Q6YWNjZXB0JzogKCkgPT4ge1xuICAgICAgICBpZiAoXG4gICAgICAgICAgc2VsZWN0ZWRJbmRleCA9PT0gZmlsdGVyZWRQbHVnaW5zLmxlbmd0aCAmJlxuICAgICAgICAgIHNlbGVjdGVkRm9ySW5zdGFsbC5zaXplID4gMFxuICAgICAgICApIHtcbiAgICAgICAgICB2b2lkIGluc3RhbGxTZWxlY3RlZFBsdWdpbnMoKVxuICAgICAgICB9IGVsc2UgaWYgKHNlbGVjdGVkSW5kZXggPCBmaWx0ZXJlZFBsdWdpbnMubGVuZ3RoKSB7XG4gICAgICAgICAgY29uc3QgcGx1Z2luID0gZmlsdGVyZWRQbHVnaW5zW3NlbGVjdGVkSW5kZXhdXG4gICAgICAgICAgaWYgKHBsdWdpbikge1xuICAgICAgICAgICAgaWYgKHBsdWdpbi5pc0luc3RhbGxlZCkge1xuICAgICAgICAgICAgICBzZXRQYXJlbnRWaWV3U3RhdGUoe1xuICAgICAgICAgICAgICAgIHR5cGU6ICdtYW5hZ2UtcGx1Z2lucycsXG4gICAgICAgICAgICAgICAgdGFyZ2V0UGx1Z2luOiBwbHVnaW4uZW50cnkubmFtZSxcbiAgICAgICAgICAgICAgICB0YXJnZXRNYXJrZXRwbGFjZTogcGx1Z2luLm1hcmtldHBsYWNlTmFtZSxcbiAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIHNldFNlbGVjdGVkUGx1Z2luKHBsdWdpbilcbiAgICAgICAgICAgICAgc2V0Vmlld1N0YXRlKCdwbHVnaW4tZGV0YWlscycpXG4gICAgICAgICAgICAgIHNldERldGFpbHNNZW51SW5kZXgoMClcbiAgICAgICAgICAgICAgc2V0SW5zdGFsbEVycm9yKG51bGwpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9LFxuICAgIH0sXG4gICAge1xuICAgICAgY29udGV4dDogJ1NlbGVjdCcsXG4gICAgICBpc0FjdGl2ZTogdmlld1N0YXRlID09PSAncGx1Z2luLWxpc3QnICYmICFpc1NlYXJjaE1vZGUsXG4gICAgfSxcbiAgKVxuXG4gIHVzZUtleWJpbmRpbmdzKFxuICAgIHtcbiAgICAgICdwbHVnaW46dG9nZ2xlJzogKCkgPT4ge1xuICAgICAgICBpZiAoc2VsZWN0ZWRJbmRleCA8IGZpbHRlcmVkUGx1Z2lucy5sZW5ndGgpIHtcbiAgICAgICAgICBjb25zdCBwbHVnaW4gPSBmaWx0ZXJlZFBsdWdpbnNbc2VsZWN0ZWRJbmRleF1cbiAgICAgICAgICBpZiAocGx1Z2luICYmICFwbHVnaW4uaXNJbnN0YWxsZWQpIHtcbiAgICAgICAgICAgIGNvbnN0IG5ld1NlbGVjdGlvbiA9IG5ldyBTZXQoc2VsZWN0ZWRGb3JJbnN0YWxsKVxuICAgICAgICAgICAgaWYgKG5ld1NlbGVjdGlvbi5oYXMocGx1Z2luLnBsdWdpbklkKSkge1xuICAgICAgICAgICAgICBuZXdTZWxlY3Rpb24uZGVsZXRlKHBsdWdpbi5wbHVnaW5JZClcbiAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgIG5ld1NlbGVjdGlvbi5hZGQocGx1Z2luLnBsdWdpbklkKVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgc2V0U2VsZWN0ZWRGb3JJbnN0YWxsKG5ld1NlbGVjdGlvbilcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICAncGx1Z2luOmluc3RhbGwnOiAoKSA9PiB7XG4gICAgICAgIGlmIChzZWxlY3RlZEZvckluc3RhbGwuc2l6ZSA+IDApIHtcbiAgICAgICAgICB2b2lkIGluc3RhbGxTZWxlY3RlZFBsdWdpbnMoKVxuICAgICAgICB9XG4gICAgICB9LFxuICAgIH0sXG4gICAge1xuICAgICAgY29udGV4dDogJ1BsdWdpbicsXG4gICAgICBpc0FjdGl2ZTogdmlld1N0YXRlID09PSAncGx1Z2luLWxpc3QnICYmICFpc1NlYXJjaE1vZGUsXG4gICAgfSxcbiAgKVxuXG4gIC8vIFBsdWdpbi1kZXRhaWxzIG5hdmlnYXRpb25cbiAgY29uc3QgZGV0YWlsc01lbnVPcHRpb25zID0gUmVhY3QudXNlTWVtbygoKSA9PiB7XG4gICAgaWYgKCFzZWxlY3RlZFBsdWdpbikgcmV0dXJuIFtdXG4gICAgY29uc3QgaGFzSG9tZXBhZ2UgPSBzZWxlY3RlZFBsdWdpbi5lbnRyeS5ob21lcGFnZVxuICAgIGNvbnN0IGdpdGh1YlJlcG8gPSBleHRyYWN0R2l0SHViUmVwbyhzZWxlY3RlZFBsdWdpbilcbiAgICByZXR1cm4gYnVpbGRQbHVnaW5EZXRhaWxzTWVudU9wdGlvbnMoaGFzSG9tZXBhZ2UsIGdpdGh1YlJlcG8pXG4gIH0sIFtzZWxlY3RlZFBsdWdpbl0pXG5cbiAgdXNlS2V5YmluZGluZ3MoXG4gICAge1xuICAgICAgJ3NlbGVjdDpwcmV2aW91cyc6ICgpID0+IHtcbiAgICAgICAgaWYgKGRldGFpbHNNZW51SW5kZXggPiAwKSB7XG4gICAgICAgICAgc2V0RGV0YWlsc01lbnVJbmRleChkZXRhaWxzTWVudUluZGV4IC0gMSlcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgICdzZWxlY3Q6bmV4dCc6ICgpID0+IHtcbiAgICAgICAgaWYgKGRldGFpbHNNZW51SW5kZXggPCBkZXRhaWxzTWVudU9wdGlvbnMubGVuZ3RoIC0gMSkge1xuICAgICAgICAgIHNldERldGFpbHNNZW51SW5kZXgoZGV0YWlsc01lbnVJbmRleCArIDEpXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICAnc2VsZWN0OmFjY2VwdCc6ICgpID0+IHtcbiAgICAgICAgaWYgKCFzZWxlY3RlZFBsdWdpbikgcmV0dXJuXG4gICAgICAgIGNvbnN0IGFjdGlvbiA9IGRldGFpbHNNZW51T3B0aW9uc1tkZXRhaWxzTWVudUluZGV4XT8uYWN0aW9uXG4gICAgICAgIGNvbnN0IGhhc0hvbWVwYWdlID0gc2VsZWN0ZWRQbHVnaW4uZW50cnkuaG9tZXBhZ2VcbiAgICAgICAgY29uc3QgZ2l0aHViUmVwbyA9IGV4dHJhY3RHaXRIdWJSZXBvKHNlbGVjdGVkUGx1Z2luKVxuICAgICAgICBpZiAoYWN0aW9uID09PSAnaW5zdGFsbC11c2VyJykge1xuICAgICAgICAgIHZvaWQgaGFuZGxlU2luZ2xlUGx1Z2luSW5zdGFsbChzZWxlY3RlZFBsdWdpbiwgJ3VzZXInKVxuICAgICAgICB9IGVsc2UgaWYgKGFjdGlvbiA9PT0gJ2luc3RhbGwtcHJvamVjdCcpIHtcbiAgICAgICAgICB2b2lkIGhhbmRsZVNpbmdsZVBsdWdpbkluc3RhbGwoc2VsZWN0ZWRQbHVnaW4sICdwcm9qZWN0JylcbiAgICAgICAgfSBlbHNlIGlmIChhY3Rpb24gPT09ICdpbnN0YWxsLWxvY2FsJykge1xuICAgICAgICAgIHZvaWQgaGFuZGxlU2luZ2xlUGx1Z2luSW5zdGFsbChzZWxlY3RlZFBsdWdpbiwgJ2xvY2FsJylcbiAgICAgICAgfSBlbHNlIGlmIChhY3Rpb24gPT09ICdob21lcGFnZScgJiYgaGFzSG9tZXBhZ2UpIHtcbiAgICAgICAgICB2b2lkIG9wZW5Ccm93c2VyKGhhc0hvbWVwYWdlKVxuICAgICAgICB9IGVsc2UgaWYgKGFjdGlvbiA9PT0gJ2dpdGh1YicgJiYgZ2l0aHViUmVwbykge1xuICAgICAgICAgIHZvaWQgb3BlbkJyb3dzZXIoYGh0dHBzOi8vZ2l0aHViLmNvbS8ke2dpdGh1YlJlcG99YClcbiAgICAgICAgfSBlbHNlIGlmIChhY3Rpb24gPT09ICdiYWNrJykge1xuICAgICAgICAgIHNldFZpZXdTdGF0ZSgncGx1Z2luLWxpc3QnKVxuICAgICAgICAgIHNldFNlbGVjdGVkUGx1Z2luKG51bGwpXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfSxcbiAgICB7XG4gICAgICBjb250ZXh0OiAnU2VsZWN0JyxcbiAgICAgIGlzQWN0aXZlOiB2aWV3U3RhdGUgPT09ICdwbHVnaW4tZGV0YWlscycgJiYgISFzZWxlY3RlZFBsdWdpbixcbiAgICB9LFxuICApXG5cbiAgaWYgKHR5cGVvZiB2aWV3U3RhdGUgPT09ICdvYmplY3QnICYmIHZpZXdTdGF0ZS50eXBlID09PSAncGx1Z2luLW9wdGlvbnMnKSB7XG4gICAgY29uc3QgeyBwbHVnaW4sIHBsdWdpbklkIH0gPSB2aWV3U3RhdGVcbiAgICBmdW5jdGlvbiBmaW5pc2gobXNnOiBzdHJpbmcpOiB2b2lkIHtcbiAgICAgIHNldFJlc3VsdChtc2cpXG4gICAgICBpZiAob25JbnN0YWxsQ29tcGxldGUpIHtcbiAgICAgICAgdm9pZCBvbkluc3RhbGxDb21wbGV0ZSgpXG4gICAgICB9XG4gICAgICBzZXRQYXJlbnRWaWV3U3RhdGUoeyB0eXBlOiAnbWVudScgfSlcbiAgICB9XG4gICAgcmV0dXJuIChcbiAgICAgIDxQbHVnaW5PcHRpb25zRmxvd1xuICAgICAgICBwbHVnaW49e3BsdWdpbn1cbiAgICAgICAgcGx1Z2luSWQ9e3BsdWdpbklkfVxuICAgICAgICBvbkRvbmU9eyhvdXRjb21lLCBkZXRhaWwpID0+IHtcbiAgICAgICAgICBzd2l0Y2ggKG91dGNvbWUpIHtcbiAgICAgICAgICAgIGNhc2UgJ2NvbmZpZ3VyZWQnOlxuICAgICAgICAgICAgICBmaW5pc2goXG4gICAgICAgICAgICAgICAgYOKckyBJbnN0YWxsZWQgYW5kIGNvbmZpZ3VyZWQgJHtwbHVnaW4ubmFtZX0uIFJ1biAvcmVsb2FkLXBsdWdpbnMgdG8gYXBwbHkuYCxcbiAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgICBicmVha1xuICAgICAgICAgICAgY2FzZSAnc2tpcHBlZCc6XG4gICAgICAgICAgICAgIGZpbmlzaChcbiAgICAgICAgICAgICAgICBg4pyTIEluc3RhbGxlZCAke3BsdWdpbi5uYW1lfS4gUnVuIC9yZWxvYWQtcGx1Z2lucyB0byBhcHBseS5gLFxuICAgICAgICAgICAgICApXG4gICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgICBjYXNlICdlcnJvcic6XG4gICAgICAgICAgICAgIGZpbmlzaChgSW5zdGFsbGVkIGJ1dCBmYWlsZWQgdG8gc2F2ZSBjb25maWc6ICR7ZGV0YWlsfWApXG4gICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgfVxuICAgICAgICB9fVxuICAgICAgLz5cbiAgICApXG4gIH1cblxuICAvLyBMb2FkaW5nIHN0YXRlXG4gIGlmIChsb2FkaW5nKSB7XG4gICAgcmV0dXJuIDxUZXh0PkxvYWRpbmfigKY8L1RleHQ+XG4gIH1cblxuICAvLyBFcnJvciBzdGF0ZVxuICBpZiAoZXJyb3IpIHtcbiAgICByZXR1cm4gPFRleHQgY29sb3I9XCJlcnJvclwiPntlcnJvcn08L1RleHQ+XG4gIH1cblxuICAvLyBQbHVnaW4gZGV0YWlscyB2aWV3XG4gIGlmICh2aWV3U3RhdGUgPT09ICdwbHVnaW4tZGV0YWlscycgJiYgc2VsZWN0ZWRQbHVnaW4pIHtcbiAgICBjb25zdCBoYXNIb21lcGFnZSA9IHNlbGVjdGVkUGx1Z2luLmVudHJ5LmhvbWVwYWdlXG4gICAgY29uc3QgZ2l0aHViUmVwbyA9IGV4dHJhY3RHaXRIdWJSZXBvKHNlbGVjdGVkUGx1Z2luKVxuXG4gICAgY29uc3QgbWVudU9wdGlvbnMgPSBidWlsZFBsdWdpbkRldGFpbHNNZW51T3B0aW9ucyhoYXNIb21lcGFnZSwgZ2l0aHViUmVwbylcblxuICAgIHJldHVybiAoXG4gICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgPEJveCBtYXJnaW5Cb3R0b209ezF9PlxuICAgICAgICAgIDxUZXh0IGJvbGQ+UGx1Z2luIGRldGFpbHM8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuXG4gICAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiIG1hcmdpbkJvdHRvbT17MX0+XG4gICAgICAgICAgPFRleHQgYm9sZD57c2VsZWN0ZWRQbHVnaW4uZW50cnkubmFtZX08L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+ZnJvbSB7c2VsZWN0ZWRQbHVnaW4ubWFya2V0cGxhY2VOYW1lfTwvVGV4dD5cbiAgICAgICAgICB7c2VsZWN0ZWRQbHVnaW4uZW50cnkudmVyc2lvbiAmJiAoXG4gICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5WZXJzaW9uOiB7c2VsZWN0ZWRQbHVnaW4uZW50cnkudmVyc2lvbn08L1RleHQ+XG4gICAgICAgICAgKX1cbiAgICAgICAgICB7c2VsZWN0ZWRQbHVnaW4uZW50cnkuZGVzY3JpcHRpb24gJiYgKFxuICAgICAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgICAgICA8VGV4dD57c2VsZWN0ZWRQbHVnaW4uZW50cnkuZGVzY3JpcHRpb259PC9UZXh0PlxuICAgICAgICAgICAgPC9Cb3g+XG4gICAgICAgICAgKX1cbiAgICAgICAgICB7c2VsZWN0ZWRQbHVnaW4uZW50cnkuYXV0aG9yICYmIChcbiAgICAgICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgICAgICAgQnk6eycgJ31cbiAgICAgICAgICAgICAgICB7dHlwZW9mIHNlbGVjdGVkUGx1Z2luLmVudHJ5LmF1dGhvciA9PT0gJ3N0cmluZydcbiAgICAgICAgICAgICAgICAgID8gc2VsZWN0ZWRQbHVnaW4uZW50cnkuYXV0aG9yXG4gICAgICAgICAgICAgICAgICA6IHNlbGVjdGVkUGx1Z2luLmVudHJ5LmF1dGhvci5uYW1lfVxuICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICA8L0JveD5cbiAgICAgICAgICApfVxuICAgICAgICA8L0JveD5cblxuICAgICAgICA8UGx1Z2luVHJ1c3RXYXJuaW5nIC8+XG5cbiAgICAgICAge2luc3RhbGxFcnJvciAmJiAoXG4gICAgICAgICAgPEJveCBtYXJnaW5Cb3R0b209ezF9PlxuICAgICAgICAgICAgPFRleHQgY29sb3I9XCJlcnJvclwiPkVycm9yOiB7aW5zdGFsbEVycm9yfTwvVGV4dD5cbiAgICAgICAgICA8L0JveD5cbiAgICAgICAgKX1cblxuICAgICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgICB7bWVudU9wdGlvbnMubWFwKChvcHRpb24sIGluZGV4KSA9PiAoXG4gICAgICAgICAgICA8Qm94IGtleT17b3B0aW9uLmFjdGlvbn0+XG4gICAgICAgICAgICAgIHtkZXRhaWxzTWVudUluZGV4ID09PSBpbmRleCAmJiA8VGV4dD57Jz4gJ308L1RleHQ+fVxuICAgICAgICAgICAgICB7ZGV0YWlsc01lbnVJbmRleCAhPT0gaW5kZXggJiYgPFRleHQ+eycgICd9PC9UZXh0Pn1cbiAgICAgICAgICAgICAgPFRleHQgYm9sZD17ZGV0YWlsc01lbnVJbmRleCA9PT0gaW5kZXh9PlxuICAgICAgICAgICAgICAgIHtpc0luc3RhbGxpbmcgJiYgb3B0aW9uLmFjdGlvbi5zdGFydHNXaXRoKCdpbnN0YWxsLScpXG4gICAgICAgICAgICAgICAgICA/ICdJbnN0YWxsaW5n4oCmJ1xuICAgICAgICAgICAgICAgICAgOiBvcHRpb24ubGFiZWx9XG4gICAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICAgIDwvQm94PlxuICAgICAgICAgICkpfVxuICAgICAgICA8L0JveD5cblxuICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgICA8QnlsaW5lPlxuICAgICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgICAgYWN0aW9uPVwic2VsZWN0OmFjY2VwdFwiXG4gICAgICAgICAgICAgICAgY29udGV4dD1cIlNlbGVjdFwiXG4gICAgICAgICAgICAgICAgZmFsbGJhY2s9XCJFbnRlclwiXG4gICAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJzZWxlY3RcIlxuICAgICAgICAgICAgICAvPlxuICAgICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTpub1wiXG4gICAgICAgICAgICAgICAgY29udGV4dD1cIkNvbmZpcm1hdGlvblwiXG4gICAgICAgICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwiYmFja1wiXG4gICAgICAgICAgICAgIC8+XG4gICAgICAgICAgICA8L0J5bGluZT5cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgLy8gRW1wdHkgc3RhdGVcbiAgaWYgKGF2YWlsYWJsZVBsdWdpbnMubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8Qm94IG1hcmdpbkJvdHRvbT17MX0+XG4gICAgICAgICAgPFRleHQgYm9sZD5EaXNjb3ZlciBwbHVnaW5zPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICAgPEVtcHR5U3RhdGVNZXNzYWdlIHJlYXNvbj17ZW1wdHlSZWFzb259IC8+XG4gICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvciBpdGFsaWM+XG4gICAgICAgICAgICBFc2MgdG8gZ28gYmFja1xuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICA8L0JveD5cbiAgICApXG4gIH1cblxuICAvLyBHZXQgdmlzaWJsZSBwbHVnaW5zIGZyb20gcGFnaW5hdGlvblxuICBjb25zdCB2aXNpYmxlUGx1Z2lucyA9IHBhZ2luYXRpb24uZ2V0VmlzaWJsZUl0ZW1zKGZpbHRlcmVkUGx1Z2lucylcblxuICByZXR1cm4gKFxuICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgPEJveD5cbiAgICAgICAgPFRleHQgYm9sZD5EaXNjb3ZlciBwbHVnaW5zPC9UZXh0PlxuICAgICAgICB7cGFnaW5hdGlvbi5uZWVkc1BhZ2luYXRpb24gJiYgKFxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgICAgeycgJ31cbiAgICAgICAgICAgICh7cGFnaW5hdGlvbi5zY3JvbGxQb3NpdGlvbi5jdXJyZW50fS9cbiAgICAgICAgICAgIHtwYWdpbmF0aW9uLnNjcm9sbFBvc2l0aW9uLnRvdGFsfSlcbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICl9XG4gICAgICA8L0JveD5cblxuICAgICAgey8qIFNlYXJjaCBib3ggKi99XG4gICAgICA8Qm94IG1hcmdpbkJvdHRvbT17MX0+XG4gICAgICAgIDxTZWFyY2hCb3hcbiAgICAgICAgICBxdWVyeT17c2VhcmNoUXVlcnl9XG4gICAgICAgICAgaXNGb2N1c2VkPXtpc1NlYXJjaE1vZGV9XG4gICAgICAgICAgaXNUZXJtaW5hbEZvY3VzZWQ9e2lzVGVybWluYWxGb2N1c2VkfVxuICAgICAgICAgIHdpZHRoPXt0ZXJtaW5hbFdpZHRoIC0gNH1cbiAgICAgICAgICBjdXJzb3JPZmZzZXQ9e3NlYXJjaEN1cnNvck9mZnNldH1cbiAgICAgICAgLz5cbiAgICAgIDwvQm94PlxuXG4gICAgICB7LyogV2FybmluZyBiYW5uZXIgKi99XG4gICAgICB7d2FybmluZyAmJiAoXG4gICAgICAgIDxCb3ggbWFyZ2luQm90dG9tPXsxfT5cbiAgICAgICAgICA8VGV4dCBjb2xvcj1cIndhcm5pbmdcIj5cbiAgICAgICAgICAgIHtmaWd1cmVzLndhcm5pbmd9IHt3YXJuaW5nfVxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICApfVxuXG4gICAgICB7LyogTm8gc2VhcmNoIHJlc3VsdHMgKi99XG4gICAgICB7ZmlsdGVyZWRQbHVnaW5zLmxlbmd0aCA9PT0gMCAmJiBzZWFyY2hRdWVyeSAmJiAoXG4gICAgICAgIDxCb3ggbWFyZ2luQm90dG9tPXsxfT5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5ObyBwbHVnaW5zIG1hdGNoICZxdW90O3tzZWFyY2hRdWVyeX0mcXVvdDs8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgKX1cblxuICAgICAgey8qIFNjcm9sbCB1cCBpbmRpY2F0b3IgKi99XG4gICAgICB7cGFnaW5hdGlvbi5zY3JvbGxQb3NpdGlvbi5jYW5TY3JvbGxVcCAmJiAoXG4gICAgICAgIDxCb3g+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IHtmaWd1cmVzLmFycm93VXB9IG1vcmUgYWJvdmU8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgKX1cblxuICAgICAgey8qIFBsdWdpbiBsaXN0IC0gdXNlIHN0YXJ0SW5kZXggaW4ga2V5IHRvIGZvcmNlIHJlLXJlbmRlciBvbiBzY3JvbGwgKi99XG4gICAgICB7dmlzaWJsZVBsdWdpbnMubWFwKChwbHVnaW4sIHZpc2libGVJbmRleCkgPT4ge1xuICAgICAgICBjb25zdCBhY3R1YWxJbmRleCA9IHBhZ2luYXRpb24udG9BY3R1YWxJbmRleCh2aXNpYmxlSW5kZXgpXG4gICAgICAgIGNvbnN0IGlzU2VsZWN0ZWQgPSBzZWxlY3RlZEluZGV4ID09PSBhY3R1YWxJbmRleFxuICAgICAgICBjb25zdCBpc1NlbGVjdGVkRm9ySW5zdGFsbCA9IHNlbGVjdGVkRm9ySW5zdGFsbC5oYXMocGx1Z2luLnBsdWdpbklkKVxuICAgICAgICBjb25zdCBpc0luc3RhbGxpbmdUaGlzID0gaW5zdGFsbGluZ1BsdWdpbnMuaGFzKHBsdWdpbi5wbHVnaW5JZClcbiAgICAgICAgY29uc3QgaXNMYXN0ID0gdmlzaWJsZUluZGV4ID09PSB2aXNpYmxlUGx1Z2lucy5sZW5ndGggLSAxXG5cbiAgICAgICAgcmV0dXJuIChcbiAgICAgICAgICA8Qm94XG4gICAgICAgICAgICBrZXk9e2Ake3BhZ2luYXRpb24uc3RhcnRJbmRleH0tJHtwbHVnaW4ucGx1Z2luSWR9YH1cbiAgICAgICAgICAgIGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIlxuICAgICAgICAgICAgbWFyZ2luQm90dG9tPXtpc0xhc3QgJiYgIWVycm9yID8gMCA6IDF9XG4gICAgICAgICAgPlxuICAgICAgICAgICAgPEJveD5cbiAgICAgICAgICAgICAgPFRleHRcbiAgICAgICAgICAgICAgICBjb2xvcj17aXNTZWxlY3RlZCAmJiAhaXNTZWFyY2hNb2RlID8gJ3N1Z2dlc3Rpb24nIDogdW5kZWZpbmVkfVxuICAgICAgICAgICAgICA+XG4gICAgICAgICAgICAgICAge2lzU2VsZWN0ZWQgJiYgIWlzU2VhcmNoTW9kZSA/IGZpZ3VyZXMucG9pbnRlciA6ICcgJ317JyAnfVxuICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgIDxUZXh0PlxuICAgICAgICAgICAgICAgIHtpc0luc3RhbGxpbmdUaGlzXG4gICAgICAgICAgICAgICAgICA/IGZpZ3VyZXMuZWxsaXBzaXNcbiAgICAgICAgICAgICAgICAgIDogaXNTZWxlY3RlZEZvckluc3RhbGxcbiAgICAgICAgICAgICAgICAgICAgPyBmaWd1cmVzLnJhZGlvT25cbiAgICAgICAgICAgICAgICAgICAgOiBmaWd1cmVzLnJhZGlvT2ZmfXsnICd9XG4gICAgICAgICAgICAgICAge3BsdWdpbi5lbnRyeS5uYW1lfVxuICAgICAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPiDCtyB7cGx1Z2luLm1hcmtldHBsYWNlTmFtZX08L1RleHQ+XG4gICAgICAgICAgICAgICAge3BsdWdpbi5lbnRyeS50YWdzPy5pbmNsdWRlcygnY29tbXVuaXR5LW1hbmFnZWQnKSAmJiAoXG4gICAgICAgICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj4gW0NvbW11bml0eSBNYW5hZ2VkXTwvVGV4dD5cbiAgICAgICAgICAgICAgICApfVxuICAgICAgICAgICAgICAgIHtpbnN0YWxsQ291bnRzICYmXG4gICAgICAgICAgICAgICAgICBwbHVnaW4ubWFya2V0cGxhY2VOYW1lID09PSBPRkZJQ0lBTF9NQVJLRVRQTEFDRV9OQU1FICYmIChcbiAgICAgICAgICAgICAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgICAgICAgICAgICAgeycgwrcgJ31cbiAgICAgICAgICAgICAgICAgICAgICB7Zm9ybWF0SW5zdGFsbENvdW50KFxuICAgICAgICAgICAgICAgICAgICAgICAgaW5zdGFsbENvdW50cy5nZXQocGx1Z2luLnBsdWdpbklkKSA/PyAwLFxuICAgICAgICAgICAgICAgICAgICAgICl9eycgJ31cbiAgICAgICAgICAgICAgICAgICAgICBpbnN0YWxsc1xuICAgICAgICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgICAgICApfVxuICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICA8L0JveD5cbiAgICAgICAgICAgIHtwbHVnaW4uZW50cnkuZGVzY3JpcHRpb24gJiYgKFxuICAgICAgICAgICAgICA8Qm94IG1hcmdpbkxlZnQ9ezR9PlxuICAgICAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgICAgICAgICAge3RydW5jYXRlVG9XaWR0aChwbHVnaW4uZW50cnkuZGVzY3JpcHRpb24sIDYwKX1cbiAgICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgIDwvQm94PlxuICAgICAgICAgICAgKX1cbiAgICAgICAgICA8L0JveD5cbiAgICAgICAgKVxuICAgICAgfSl9XG5cbiAgICAgIHsvKiBTY3JvbGwgZG93biBpbmRpY2F0b3IgKi99XG4gICAgICB7cGFnaW5hdGlvbi5zY3JvbGxQb3NpdGlvbi5jYW5TY3JvbGxEb3duICYmIChcbiAgICAgICAgPEJveD5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj4ge2ZpZ3VyZXMuYXJyb3dEb3dufSBtb3JlIGJlbG93PC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICl9XG5cbiAgICAgIHsvKiBFcnJvciBtZXNzYWdlcyAqL31cbiAgICAgIHtlcnJvciAmJiAoXG4gICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICA8VGV4dCBjb2xvcj1cImVycm9yXCI+XG4gICAgICAgICAgICB7ZmlndXJlcy5jcm9zc30ge2Vycm9yfVxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICApfVxuXG4gICAgICA8RGlzY292ZXJQbHVnaW5zS2V5SGludFxuICAgICAgICBoYXNTZWxlY3Rpb249e3NlbGVjdGVkRm9ySW5zdGFsbC5zaXplID4gMH1cbiAgICAgICAgY2FuVG9nZ2xlPXtcbiAgICAgICAgICBzZWxlY3RlZEluZGV4IDwgZmlsdGVyZWRQbHVnaW5zLmxlbmd0aCAmJlxuICAgICAgICAgICFmaWx0ZXJlZFBsdWdpbnNbc2VsZWN0ZWRJbmRleF0/LmlzSW5zdGFsbGVkXG4gICAgICAgIH1cbiAgICAgIC8+XG4gICAgPC9Cb3g+XG4gIClcbn1cblxuZnVuY3Rpb24gRGlzY292ZXJQbHVnaW5zS2V5SGludCh7XG4gIGhhc1NlbGVjdGlvbixcbiAgY2FuVG9nZ2xlLFxufToge1xuICBoYXNTZWxlY3Rpb246IGJvb2xlYW5cbiAgY2FuVG9nZ2xlOiBib29sZWFuXG59KTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgcmV0dXJuIChcbiAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICA8VGV4dCBkaW1Db2xvciBpdGFsaWM+XG4gICAgICAgIDxCeWxpbmU+XG4gICAgICAgICAge2hhc1NlbGVjdGlvbiAmJiAoXG4gICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgIGFjdGlvbj1cInBsdWdpbjppbnN0YWxsXCJcbiAgICAgICAgICAgICAgY29udGV4dD1cIlBsdWdpblwiXG4gICAgICAgICAgICAgIGZhbGxiYWNrPVwiaVwiXG4gICAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwiaW5zdGFsbFwiXG4gICAgICAgICAgICAgIGJvbGRcbiAgICAgICAgICAgIC8+XG4gICAgICAgICAgKX1cbiAgICAgICAgICA8VGV4dD50eXBlIHRvIHNlYXJjaDwvVGV4dD5cbiAgICAgICAgICB7Y2FuVG9nZ2xlICYmIChcbiAgICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgICAgYWN0aW9uPVwicGx1Z2luOnRvZ2dsZVwiXG4gICAgICAgICAgICAgIGNvbnRleHQ9XCJQbHVnaW5cIlxuICAgICAgICAgICAgICBmYWxsYmFjaz1cIlNwYWNlXCJcbiAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJ0b2dnbGVcIlxuICAgICAgICAgICAgLz5cbiAgICAgICAgICApfVxuICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgIGFjdGlvbj1cInNlbGVjdDphY2NlcHRcIlxuICAgICAgICAgICAgY29udGV4dD1cIlNlbGVjdFwiXG4gICAgICAgICAgICBmYWxsYmFjaz1cIkVudGVyXCJcbiAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwiZGV0YWlsc1wiXG4gICAgICAgICAgLz5cbiAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICBhY3Rpb249XCJjb25maXJtOm5vXCJcbiAgICAgICAgICAgIGNvbnRleHQ9XCJDb25maXJtYXRpb25cIlxuICAgICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgICAgZGVzY3JpcHRpb249XCJiYWNrXCJcbiAgICAgICAgICAvPlxuICAgICAgICA8L0J5bGluZT5cbiAgICAgIDwvVGV4dD5cbiAgICA8L0JveD5cbiAgKVxufVxuXG4vKipcbiAqIENvbnRleHQtYXdhcmUgZW1wdHkgc3RhdGUgbWVzc2FnZSBmb3IgdGhlIERpc2NvdmVyIHNjcmVlblxuICovXG5mdW5jdGlvbiBFbXB0eVN0YXRlTWVzc2FnZSh7XG4gIHJlYXNvbixcbn06IHtcbiAgcmVhc29uOiBFbXB0eU1hcmtldHBsYWNlUmVhc29uIHwgbnVsbFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIHN3aXRjaCAocmVhc29uKSB7XG4gICAgY2FzZSAnZ2l0LW5vdC1pbnN0YWxsZWQnOlxuICAgICAgcmV0dXJuIChcbiAgICAgICAgPD5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5HaXQgaXMgcmVxdWlyZWQgdG8gaW5zdGFsbCBtYXJrZXRwbGFjZXMuPC9UZXh0PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPlBsZWFzZSBpbnN0YWxsIGdpdCBhbmQgcmVzdGFydCBDbGF1ZGUgQ29kZS48L1RleHQ+XG4gICAgICAgIDwvPlxuICAgICAgKVxuICAgIGNhc2UgJ2FsbC1ibG9ja2VkLWJ5LXBvbGljeSc6XG4gICAgICByZXR1cm4gKFxuICAgICAgICA8PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgICAgWW91ciBvcmdhbml6YXRpb24gcG9saWN5IGRvZXMgbm90IGFsbG93IGFueSBleHRlcm5hbCBtYXJrZXRwbGFjZXMuXG4gICAgICAgICAgPC9UZXh0PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPkNvbnRhY3QgeW91ciBhZG1pbmlzdHJhdG9yLjwvVGV4dD5cbiAgICAgICAgPC8+XG4gICAgICApXG4gICAgY2FzZSAncG9saWN5LXJlc3RyaWN0cy1zb3VyY2VzJzpcbiAgICAgIHJldHVybiAoXG4gICAgICAgIDw+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgICBZb3VyIG9yZ2FuaXphdGlvbiByZXN0cmljdHMgd2hpY2ggbWFya2V0cGxhY2VzIGNhbiBiZSBhZGRlZC5cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgICBTd2l0Y2ggdG8gdGhlIE1hcmtldHBsYWNlcyB0YWIgdG8gdmlldyBhbGxvd2VkIHNvdXJjZXMuXG4gICAgICAgICAgPC9UZXh0PlxuICAgICAgICA8Lz5cbiAgICAgIClcbiAgICBjYXNlICdhbGwtbWFya2V0cGxhY2VzLWZhaWxlZCc6XG4gICAgICByZXR1cm4gKFxuICAgICAgICA8PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPkZhaWxlZCB0byBsb2FkIG1hcmtldHBsYWNlIGRhdGEuPC9UZXh0PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPkNoZWNrIHlvdXIgbmV0d29yayBjb25uZWN0aW9uLjwvVGV4dD5cbiAgICAgICAgPC8+XG4gICAgICApXG4gICAgY2FzZSAnYWxsLXBsdWdpbnMtaW5zdGFsbGVkJzpcbiAgICAgIHJldHVybiAoXG4gICAgICAgIDw+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+QWxsIGF2YWlsYWJsZSBwbHVnaW5zIGFyZSBhbHJlYWR5IGluc3RhbGxlZC48L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgICBDaGVjayBmb3IgbmV3IHBsdWdpbnMgbGF0ZXIgb3IgYWRkIG1vcmUgbWFya2V0cGxhY2VzLlxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgPC8+XG4gICAgICApXG4gICAgY2FzZSAnbm8tbWFya2V0cGxhY2VzLWNvbmZpZ3VyZWQnOlxuICAgIGRlZmF1bHQ6XG4gICAgICByZXR1cm4gKFxuICAgICAgICA8PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPk5vIHBsdWdpbnMgYXZhaWxhYmxlLjwvVGV4dD5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICAgIEFkZCBhIG1hcmtldHBsYWNlIGZpcnN0IHVzaW5nIHRoZSBNYXJrZXRwbGFjZXMgdGFiLlxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgPC8+XG4gICAgICApXG4gIH1cbn1cbiJdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU9BLE9BQU8sTUFBTSxTQUFTO0FBQzdCLE9BQU8sS0FB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 \ No newline at end of file diff --git a/src/commands/plugin/ManageMarketplaces.tsx b/src/commands/plugin/ManageMarketplaces.tsx new file mode 100644 index 0000000000000000000000000000000000000000..0e5d3ccf1b5d042c6850bc812ab0718ec5d5e63d --- /dev/null +++ b/src/commands/plugin/ManageMarketplaces.tsx @@ -0,0 +1,838 @@ +import { c as _c } from "react/compiler-runtime"; +import figures from 'figures'; +import * as React from 'react'; +import { useEffect, useRef, useState } from 'react'; +import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from 'src/services/analytics/index.js'; +import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js'; +import { Byline } from '../../components/design-system/Byline.js'; +import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js'; +// eslint-disable-next-line custom-rules/prefer-use-keybindings -- useInput needed for marketplace-specific u/r shortcuts and y/n confirmation not in keybinding schema +import { Box, Text, useInput } from '../../ink.js'; +import { useKeybinding, useKeybindings } from '../../keybindings/useKeybinding.js'; +import type { LoadedPlugin } from '../../types/plugin.js'; +import { count } from '../../utils/array.js'; +import { shouldSkipPluginAutoupdate } from '../../utils/config.js'; +import { errorMessage } from '../../utils/errors.js'; +import { clearAllCaches } from '../../utils/plugins/cacheUtils.js'; +import { createPluginId, formatMarketplaceLoadingErrors, getMarketplaceSourceDisplay, loadMarketplacesWithGracefulDegradation } from '../../utils/plugins/marketplaceHelpers.js'; +import { loadKnownMarketplacesConfig, refreshMarketplace, removeMarketplaceSource, setMarketplaceAutoUpdate } from '../../utils/plugins/marketplaceManager.js'; +import { updatePluginsForMarketplaces } from '../../utils/plugins/pluginAutoupdate.js'; +import { loadAllPlugins } from '../../utils/plugins/pluginLoader.js'; +import { isMarketplaceAutoUpdate } from '../../utils/plugins/schemas.js'; +import { getSettingsForSource, updateSettingsForSource } from '../../utils/settings/settings.js'; +import { plural } from '../../utils/stringUtils.js'; +import type { ViewState } from './types.js'; +type Props = { + setViewState: (state: ViewState) => void; + error?: string | null; + setError?: (error: string | null) => void; + setResult: (result: string | null) => void; + exitState: { + pending: boolean; + keyName: 'Ctrl-C' | 'Ctrl-D' | null; + }; + onManageComplete?: () => void | Promise; + targetMarketplace?: string; + action?: 'update' | 'remove'; +}; +type MarketplaceState = { + name: string; + source: string; + lastUpdated?: string; + pluginCount?: number; + installedPlugins?: LoadedPlugin[]; + pendingUpdate?: boolean; + pendingRemove?: boolean; + autoUpdate?: boolean; +}; +type InternalViewState = 'list' | 'details' | 'confirm-remove'; +export function ManageMarketplaces({ + setViewState, + error, + setError, + setResult, + exitState, + onManageComplete, + targetMarketplace, + action +}: Props): React.ReactNode { + const [marketplaceStates, setMarketplaceStates] = useState([]); + const [loading, setLoading] = useState(true); + const [selectedIndex, setSelectedIndex] = useState(0); + const [isProcessing, setIsProcessing] = useState(false); + const [processError, setProcessError] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); + const [progressMessage, setProgressMessage] = useState(null); + const [internalView, setInternalView] = useState('list'); + const [selectedMarketplace, setSelectedMarketplace] = useState(null); + const [detailsMenuIndex, setDetailsMenuIndex] = useState(0); + const hasAttemptedAutoAction = useRef(false); + + // Load marketplaces and their installed plugins + useEffect(() => { + async function loadMarketplaces() { + try { + const config = await loadKnownMarketplacesConfig(); + const { + enabled, + disabled + } = await loadAllPlugins(); + const allPlugins = [...enabled, ...disabled]; + + // Load marketplaces with graceful degradation + const { + marketplaces, + failures + } = await loadMarketplacesWithGracefulDegradation(config); + const states: MarketplaceState[] = []; + for (const { + name, + config: entry, + data: marketplace + } of marketplaces) { + // Get all plugins installed from this marketplace + const installedFromMarketplace = allPlugins.filter(plugin => plugin.source.endsWith(`@${name}`)); + states.push({ + name, + source: getMarketplaceSourceDisplay(entry.source), + lastUpdated: entry.lastUpdated, + pluginCount: marketplace?.plugins.length, + installedPlugins: installedFromMarketplace, + pendingUpdate: false, + pendingRemove: false, + autoUpdate: isMarketplaceAutoUpdate(name, entry) + }); + } + + // Sort: claude-plugin-directory first, then alphabetically + states.sort((a, b) => { + if (a.name === 'claude-plugin-directory') return -1; + if (b.name === 'claude-plugin-directory') return 1; + return a.name.localeCompare(b.name); + }); + setMarketplaceStates(states); + + // Handle marketplace loading errors/warnings + const successCount = count(marketplaces, m => m.data !== null); + const errorResult = formatMarketplaceLoadingErrors(failures, successCount); + if (errorResult) { + if (errorResult.type === 'warning') { + setProcessError(errorResult.message); + } else { + throw new Error(errorResult.message); + } + } + + // Auto-execute if target and action provided + if (targetMarketplace && !hasAttemptedAutoAction.current && !error) { + hasAttemptedAutoAction.current = true; + const targetIndex = states.findIndex(s => s.name === targetMarketplace); + if (targetIndex >= 0) { + const targetState = states[targetIndex]; + if (action) { + // Mark the action as pending and execute + setSelectedIndex(targetIndex + 1); // +1 because "Add Marketplace" is at index 0 + const newStates = [...states]; + if (action === 'update') { + newStates[targetIndex]!.pendingUpdate = true; + } else if (action === 'remove') { + newStates[targetIndex]!.pendingRemove = true; + } + setMarketplaceStates(newStates); + // Apply the change immediately + setTimeout(applyChanges, 100, newStates); + } else if (targetState) { + // No action - just show the details view for this marketplace + setSelectedIndex(targetIndex + 1); // +1 because "Add Marketplace" is at index 0 + setSelectedMarketplace(targetState); + setInternalView('details'); + } + } else if (setError) { + setError(`Marketplace not found: ${targetMarketplace}`); + } + } + } catch (err) { + if (setError) { + setError(err instanceof Error ? err.message : 'Failed to load marketplaces'); + } + setProcessError(err instanceof Error ? err.message : 'Failed to load marketplaces'); + } finally { + setLoading(false); + } + } + void loadMarketplaces(); + // eslint-disable-next-line react-hooks/exhaustive-deps + // biome-ignore lint/correctness/useExhaustiveDependencies: intentional + }, [targetMarketplace, action, error]); + + // Check if there are any pending changes + const hasPendingChanges = () => { + return marketplaceStates.some(state => state.pendingUpdate || state.pendingRemove); + }; + + // Get count of pending operations + const getPendingCounts = () => { + const updateCount = count(marketplaceStates, s => s.pendingUpdate); + const removeCount = count(marketplaceStates, s => s.pendingRemove); + return { + updateCount, + removeCount + }; + }; + + // Apply all pending changes + const applyChanges = async (states?: MarketplaceState[]) => { + const statesToProcess = states || marketplaceStates; + const wasInDetailsView = internalView === 'details'; + setIsProcessing(true); + setProcessError(null); + setSuccessMessage(null); + setProgressMessage(null); + try { + const settings = getSettingsForSource('userSettings'); + let updatedCount = 0; + let removedCount = 0; + const refreshedMarketplaces = new Set(); + for (const state of statesToProcess) { + // Handle remove + if (state.pendingRemove) { + // First uninstall all plugins from this marketplace + if (state.installedPlugins && state.installedPlugins.length > 0) { + const newEnabledPlugins = { + ...settings?.enabledPlugins + }; + for (const plugin of state.installedPlugins) { + const pluginId = createPluginId(plugin.name, state.name); + // Mark as disabled/uninstalled + newEnabledPlugins[pluginId] = false; + } + updateSettingsForSource('userSettings', { + enabledPlugins: newEnabledPlugins + }); + } + + // Then remove the marketplace + await removeMarketplaceSource(state.name); + removedCount++; + logEvent('tengu_marketplace_removed', { + marketplace_name: state.name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + plugins_uninstalled: state.installedPlugins?.length || 0 + }); + continue; + } + + // Handle update + if (state.pendingUpdate) { + // Refresh individual marketplace for efficiency with progress reporting + await refreshMarketplace(state.name, (message: string) => { + setProgressMessage(message); + }); + updatedCount++; + refreshedMarketplaces.add(state.name.toLowerCase()); + logEvent('tengu_marketplace_updated', { + marketplace_name: state.name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS + }); + } + } + + // After marketplace clones are refreshed, bump installed plugins from + // those marketplaces to the new version. Without this, the loader's + // cache-on-miss (copyPluginToVersionedCache) creates the new version + // dir on the next loadAllPlugins() call, but installed_plugins.json + // stays on the old version — so cleanupOrphanedPluginVersionsInBackground + // stamps the NEW dir with .orphaned_at on the next startup. See #29512. + // updatePluginOp (called inside the helper) is what actually writes + // installed_plugins.json via updateInstallationPathOnDisk. + let updatedPluginCount = 0; + if (refreshedMarketplaces.size > 0) { + const updatedPluginIds = await updatePluginsForMarketplaces(refreshedMarketplaces); + updatedPluginCount = updatedPluginIds.length; + } + + // Clear caches after changes + clearAllCaches(); + + // Call completion callback + if (onManageComplete) { + await onManageComplete(); + } + + // Reload marketplace data to show updated timestamps + const config = await loadKnownMarketplacesConfig(); + const { + enabled, + disabled + } = await loadAllPlugins(); + const allPlugins = [...enabled, ...disabled]; + const { + marketplaces + } = await loadMarketplacesWithGracefulDegradation(config); + const newStates: MarketplaceState[] = []; + for (const { + name, + config: entry, + data: marketplace + } of marketplaces) { + const installedFromMarketplace = allPlugins.filter(plugin => plugin.source.endsWith(`@${name}`)); + newStates.push({ + name, + source: getMarketplaceSourceDisplay(entry.source), + lastUpdated: entry.lastUpdated, + pluginCount: marketplace?.plugins.length, + installedPlugins: installedFromMarketplace, + pendingUpdate: false, + pendingRemove: false, + autoUpdate: isMarketplaceAutoUpdate(name, entry) + }); + } + + // Sort: claude-plugin-directory first, then alphabetically + newStates.sort((a, b) => { + if (a.name === 'claude-plugin-directory') return -1; + if (b.name === 'claude-plugin-directory') return 1; + return a.name.localeCompare(b.name); + }); + setMarketplaceStates(newStates); + + // Update selected marketplace reference with fresh data + if (wasInDetailsView && selectedMarketplace) { + const updatedMarketplace = newStates.find(s => s.name === selectedMarketplace.name); + if (updatedMarketplace) { + setSelectedMarketplace(updatedMarketplace); + } + } + + // Build success message + const actions: string[] = []; + if (updatedCount > 0) { + const pluginPart = updatedPluginCount > 0 ? ` (${updatedPluginCount} ${plural(updatedPluginCount, 'plugin')} bumped)` : ''; + actions.push(`Updated ${updatedCount} ${plural(updatedCount, 'marketplace')}${pluginPart}`); + } + if (removedCount > 0) { + actions.push(`Removed ${removedCount} ${plural(removedCount, 'marketplace')}`); + } + if (actions.length > 0) { + const successMsg = `${figures.tick} ${actions.join(', ')}`; + // If we were in details view, stay there and show success + if (wasInDetailsView) { + setSuccessMessage(successMsg); + } else { + // Otherwise show result and exit to menu + setResult(successMsg); + setTimeout(setViewState, 2000, { + type: 'menu' as const + }); + } + } else if (!wasInDetailsView) { + setViewState({ + type: 'menu' + }); + } + } catch (err) { + const errorMsg = errorMessage(err); + setProcessError(errorMsg); + if (setError) { + setError(errorMsg); + } + } finally { + setIsProcessing(false); + setProgressMessage(null); + } + }; + + // Handle confirming marketplace removal + const confirmRemove = async () => { + if (!selectedMarketplace) return; + + // Mark for removal and apply + const newStates = marketplaceStates.map(state => state.name === selectedMarketplace.name ? { + ...state, + pendingRemove: true + } : state); + setMarketplaceStates(newStates); + await applyChanges(newStates); + }; + + // Build menu options for details view + const buildDetailsMenuOptions = (marketplace: MarketplaceState | null): Array<{ + label: string; + secondaryLabel?: string; + value: string; + }> => { + if (!marketplace) return []; + const options: Array<{ + label: string; + secondaryLabel?: string; + value: string; + }> = [{ + label: `Browse plugins (${marketplace.pluginCount ?? 0})`, + value: 'browse' + }, { + label: 'Update marketplace', + secondaryLabel: marketplace.lastUpdated ? `(last updated ${new Date(marketplace.lastUpdated).toLocaleDateString()})` : undefined, + value: 'update' + }]; + + // Only show auto-update toggle if auto-updater is not globally disabled + if (!shouldSkipPluginAutoupdate()) { + options.push({ + label: marketplace.autoUpdate ? 'Disable auto-update' : 'Enable auto-update', + value: 'toggle-auto-update' + }); + } + options.push({ + label: 'Remove marketplace', + value: 'remove' + }); + return options; + }; + + // Handle toggling auto-update for a marketplace + const handleToggleAutoUpdate = async (marketplace: MarketplaceState) => { + const newAutoUpdate = !marketplace.autoUpdate; + try { + await setMarketplaceAutoUpdate(marketplace.name, newAutoUpdate); + + // Update local state + setMarketplaceStates(prev => prev.map(state => state.name === marketplace.name ? { + ...state, + autoUpdate: newAutoUpdate + } : state)); + + // Update selected marketplace reference + setSelectedMarketplace(prev => prev ? { + ...prev, + autoUpdate: newAutoUpdate + } : prev); + } catch (err) { + setProcessError(err instanceof Error ? err.message : 'Failed to update setting'); + } + }; + + // Escape in details or confirm-remove view - go back to list + useKeybinding('confirm:no', () => { + setInternalView('list'); + setDetailsMenuIndex(0); + }, { + context: 'Confirmation', + isActive: !isProcessing && (internalView === 'details' || internalView === 'confirm-remove') + }); + + // Escape in list view with pending changes - clear pending changes + useKeybinding('confirm:no', () => { + setMarketplaceStates(prev => prev.map(state => ({ + ...state, + pendingUpdate: false, + pendingRemove: false + }))); + setSelectedIndex(0); + }, { + context: 'Confirmation', + isActive: !isProcessing && internalView === 'list' && hasPendingChanges() + }); + + // Escape in list view without pending changes - exit to parent menu + useKeybinding('confirm:no', () => { + setViewState({ + type: 'menu' + }); + }, { + context: 'Confirmation', + isActive: !isProcessing && internalView === 'list' && !hasPendingChanges() + }); + + // List view — navigation (up/down/enter via configurable keybindings) + useKeybindings({ + 'select:previous': () => setSelectedIndex(prev => Math.max(0, prev - 1)), + 'select:next': () => { + const totalItems = marketplaceStates.length + 1; + setSelectedIndex(prev => Math.min(totalItems - 1, prev + 1)); + }, + 'select:accept': () => { + const marketplaceIndex = selectedIndex - 1; + if (selectedIndex === 0) { + setViewState({ + type: 'add-marketplace' + }); + } else if (hasPendingChanges()) { + void applyChanges(); + } else { + const marketplace = marketplaceStates[marketplaceIndex]; + if (marketplace) { + setSelectedMarketplace(marketplace); + setInternalView('details'); + setDetailsMenuIndex(0); + } + } + } + }, { + context: 'Select', + isActive: !isProcessing && internalView === 'list' + }); + + // List view — marketplace-specific actions (u/r shortcuts) + useInput(input => { + const marketplaceIndex = selectedIndex - 1; + if ((input === 'u' || input === 'U') && marketplaceIndex >= 0) { + setMarketplaceStates(prev => prev.map((state, idx) => idx === marketplaceIndex ? { + ...state, + pendingUpdate: !state.pendingUpdate, + pendingRemove: state.pendingUpdate ? state.pendingRemove : false + } : state)); + } else if ((input === 'r' || input === 'R') && marketplaceIndex >= 0) { + const marketplace = marketplaceStates[marketplaceIndex]; + if (marketplace) { + setSelectedMarketplace(marketplace); + setInternalView('confirm-remove'); + } + } + }, { + isActive: !isProcessing && internalView === 'list' + }); + + // Details view — navigation + useKeybindings({ + 'select:previous': () => setDetailsMenuIndex(prev => Math.max(0, prev - 1)), + 'select:next': () => { + const menuOptions = buildDetailsMenuOptions(selectedMarketplace); + setDetailsMenuIndex(prev => Math.min(menuOptions.length - 1, prev + 1)); + }, + 'select:accept': () => { + if (!selectedMarketplace) return; + const menuOptions = buildDetailsMenuOptions(selectedMarketplace); + const selectedOption = menuOptions[detailsMenuIndex]; + if (selectedOption?.value === 'browse') { + setViewState({ + type: 'browse-marketplace', + targetMarketplace: selectedMarketplace.name + }); + } else if (selectedOption?.value === 'update') { + const newStates = marketplaceStates.map(state => state.name === selectedMarketplace.name ? { + ...state, + pendingUpdate: true + } : state); + setMarketplaceStates(newStates); + void applyChanges(newStates); + } else if (selectedOption?.value === 'toggle-auto-update') { + void handleToggleAutoUpdate(selectedMarketplace); + } else if (selectedOption?.value === 'remove') { + setInternalView('confirm-remove'); + } + } + }, { + context: 'Select', + isActive: !isProcessing && internalView === 'details' + }); + + // Confirm-remove view — y/n input + useInput(input => { + if (input === 'y' || input === 'Y') { + void confirmRemove(); + } else if (input === 'n' || input === 'N') { + setInternalView('list'); + setSelectedMarketplace(null); + } + }, { + isActive: !isProcessing && internalView === 'confirm-remove' + }); + if (loading) { + return Loading marketplaces…; + } + if (marketplaceStates.length === 0) { + return + + Manage marketplaces + + + {/* Add Marketplace option */} + + {figures.pointer} + + + Add Marketplace + + + + + + {exitState.pending ? <>Press {exitState.keyName} again to go back : + + + } + + + ; + } + + // Show confirmation dialog + if (internalView === 'confirm-remove' && selectedMarketplace) { + const pluginCount = selectedMarketplace.installedPlugins?.length || 0; + return + + Remove marketplace {selectedMarketplace.name}? + + + {pluginCount > 0 && + + This will also uninstall {pluginCount}{' '} + {plural(pluginCount, 'plugin')} from this marketplace: + + } + {selectedMarketplace.installedPlugins && selectedMarketplace.installedPlugins.length > 0 && + {selectedMarketplace.installedPlugins.map(plugin => + • {plugin.name} + )} + } + + + Press y to confirm or n to + cancel + + + + ; + } + + // Show marketplace details + if (internalView === 'details' && selectedMarketplace) { + // Check if this marketplace is currently being processed + // Check pendingUpdate first so we show updating state immediately when user presses Enter + const isUpdating = selectedMarketplace.pendingUpdate || isProcessing; + const menuOptions = buildDetailsMenuOptions(selectedMarketplace); + return + {selectedMarketplace.name} + {selectedMarketplace.source} + + + {selectedMarketplace.pluginCount || 0} available{' '} + {plural(selectedMarketplace.pluginCount || 0, 'plugin')} + + + + {/* Installed plugins section */} + {selectedMarketplace.installedPlugins && selectedMarketplace.installedPlugins.length > 0 && + + Installed plugins ({selectedMarketplace.installedPlugins.length} + ): + + + {selectedMarketplace.installedPlugins.map(plugin => + {figures.bullet} + + {plugin.name} + {plugin.manifest.description} + + )} + + } + + {/* Processing indicator */} + {isUpdating && + Updating marketplace… + {progressMessage && {progressMessage}} + } + + {/* Success message */} + {!isUpdating && successMessage && + {successMessage} + } + + {/* Error message */} + {!isUpdating && processError && + {processError} + } + + {/* Menu options */} + {!isUpdating && + {menuOptions.map((option, idx) => { + if (!option) return null; + const isSelected = idx === detailsMenuIndex; + return + + {isSelected ? figures.pointer : ' '} {option.label} + + {option.secondaryLabel && {option.secondaryLabel}} + ; + })} + } + + {/* Show explanatory text at the bottom when auto-update is enabled */} + {!isUpdating && !shouldSkipPluginAutoupdate() && selectedMarketplace.autoUpdate && + + Auto-update enabled. Claude Code will automatically update this + marketplace and its installed plugins. + + } + + + + {isUpdating ? <>Please wait… : + + + } + + + ; + } + + // Show marketplace list + const { + updateCount, + removeCount + } = getPendingCounts(); + return + + Manage marketplaces + + + {/* Add Marketplace option */} + + + {selectedIndex === 0 ? figures.pointer : ' '} + + + + Add Marketplace + + + + {/* Marketplace list */} + + {marketplaceStates.map((state, idx) => { + const isSelected = idx + 1 === selectedIndex; // +1 because Add Marketplace is at index 0 + + // Build status indicators + const indicators: string[] = []; + if (state.pendingUpdate) indicators.push('UPDATE'); + if (state.pendingRemove) indicators.push('REMOVE'); + return + + {isSelected ? figures.pointer : ' '}{' '} + {state.pendingRemove ? figures.cross : figures.bullet} + + + + + {state.name === 'claude-plugins-official' && } + {state.name} + {state.name === 'claude-plugins-official' && } + + {indicators.length > 0 && [{indicators.join(', ')}]} + + {state.source} + + {state.pluginCount !== undefined && <>{state.pluginCount} available} + {state.installedPlugins && state.installedPlugins.length > 0 && <> • {state.installedPlugins.length} installed} + {state.lastUpdated && <> + {' '} + • Updated{' '} + {new Date(state.lastUpdated).toLocaleDateString()} + } + + + ; + })} + + + {/* Pending changes summary */} + {hasPendingChanges() && + + Pending changes:{' '} + Enter to apply + + {updateCount > 0 && + • Update {updateCount} {plural(updateCount, 'marketplace')} + } + {removeCount > 0 && + • Remove {removeCount} {plural(removeCount, 'marketplace')} + } + } + + {/* Processing indicator */} + {isProcessing && + Processing changes… + } + + {/* Error display */} + {processError && + {processError} + } + + + ; +} +type ManageMarketplacesKeyHintsProps = { + exitState: Props['exitState']; + hasPendingActions: boolean; +}; +function ManageMarketplacesKeyHints(t0) { + const $ = _c(18); + const { + exitState, + hasPendingActions + } = t0; + if (exitState.pending) { + let t1; + if ($[0] !== exitState.keyName) { + t1 = Press {exitState.keyName} again to go back; + $[0] = exitState.keyName; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; + } + let t1; + if ($[2] !== hasPendingActions) { + t1 = hasPendingActions && ; + $[2] = hasPendingActions; + $[3] = t1; + } else { + t1 = $[3]; + } + let t2; + if ($[4] !== hasPendingActions) { + t2 = !hasPendingActions && ; + $[4] = hasPendingActions; + $[5] = t2; + } else { + t2 = $[5]; + } + let t3; + if ($[6] !== hasPendingActions) { + t3 = !hasPendingActions && ; + $[6] = hasPendingActions; + $[7] = t3; + } else { + t3 = $[7]; + } + let t4; + if ($[8] !== hasPendingActions) { + t4 = !hasPendingActions && ; + $[8] = hasPendingActions; + $[9] = t4; + } else { + t4 = $[9]; + } + const t5 = hasPendingActions ? "cancel" : "go back"; + let t6; + if ($[10] !== t5) { + t6 = ; + $[10] = t5; + $[11] = t6; + } else { + t6 = $[11]; + } + let t7; + if ($[12] !== t1 || $[13] !== t2 || $[14] !== t3 || $[15] !== t4 || $[16] !== t6) { + t7 = {t1}{t2}{t3}{t4}{t6}; + $[12] = t1; + $[13] = t2; + $[14] = t3; + $[15] = t4; + $[16] = t6; + $[17] = t7; + } else { + t7 = $[17]; + } + return t7; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwiUmVhY3QiLCJ1c2VFZmZlY3QiLCJ1c2VSZWYiLCJ1c2VTdGF0ZSIsIkFuYWx5dGljc01ldGFkYXRhX0lfVkVSSUZJRURfVEhJU19JU19OT1RfQ09ERV9PUl9GSUxFUEFUSFMiLCJsb2dFdmVudCIsIkNvbmZpZ3VyYWJsZVNob3J0Y3V0SGludCIsIkJ5bGluZSIsIktleWJvYXJkU2hvcnRjdXRIaW50IiwiQm94IiwiVGV4dCIsInVzZUlucHV0IiwidXNlS2V5YmluZGluZyIsInVzZUtleWJpbmRpbmdzIiwiTG9hZGVkUGx1Z2luIiwiY291bnQiLCJzaG91bGRTa2lwUGx1Z2luQXV0b3VwZGF0ZSIsImVycm9yTWVzc2FnZSIsImNsZWFyQWxsQ2FjaGVzIiwiY3JlYXRlUGx1Z2luSWQiLCJmb3JtYXRNYXJrZXRwbGFjZUxvYWRpbmdFcnJvcnMiLCJnZXRNYXJrZXRwbGFjZVNvdXJjZURpc3BsYXkiLCJsb2FkTWFya2V0cGxhY2VzV2l0aEdyYWNlZnVsRGVncmFkYXRpb24iLCJsb2FkS25vd25NYXJrZXRwbGFjZXNDb25maWciLCJyZWZyZXNoTWFya2V0cGxhY2UiLCJyZW1vdmVNYXJrZXRwbGFjZVNvdXJjZSIsInNldE1hcmtldHBsYWNlQXV0b1VwZGF0ZSIsInVwZGF0ZVBsdWdpbnNGb3JNYXJrZXRwbGFjZXMiLCJsb2FkQWxsUGx1Z2lucyIsImlzTWFya2V0cGxhY2VBdXRvVXBkYXRlIiwiZ2V0U2V0dGluZ3NGb3JTb3VyY2UiLCJ1cGRhdGVTZXR0aW5nc0ZvclNvdXJjZSIsInBsdXJhbCIsIlZpZXdTdGF0ZSIsIlByb3BzIiwic2V0Vmlld1N0YXRlIiwic3RhdGUiLCJlcnJvciIsInNldEVycm9yIiwic2V0UmVzdWx0IiwicmVzdWx0IiwiZXhpdFN0YXRlIiwicGVuZGluZyIsImtleU5hbWUiLCJvbk1hbmFnZUNvbXBsZXRlIiwiUHJvbWlzZSIsInRhcmdldE1hcmtldHBsYWNlIiwiYWN0aW9uIiwiTWFya2V0cGxhY2VTdGF0ZSIsIm5hbWUiLCJzb3VyY2UiLCJsYXN0VXBkYXRlZCIsInBsdWdpbkNvdW50IiwiaW5zdGFsbGVkUGx1Z2lucyIsInBlbmRpbmdVcGRhdGUiLCJwZW5kaW5nUmVtb3ZlIiwiYXV0b1VwZGF0ZSIsIkludGVybmFsVmlld1N0YXRlIiwiTWFuYWdlTWFya2V0cGxhY2VzIiwiUmVhY3ROb2RlIiwibWFya2V0cGxhY2VTdGF0ZXMiLCJzZXRNYXJrZXRwbGFjZVN0YXRlcyIsImxvYWRpbmciLCJzZXRMb2FkaW5nIiwic2VsZWN0ZWRJbmRleCIsInNldFNlbGVjdGVkSW5kZXgiLCJpc1Byb2Nlc3NpbmciLCJzZXRJc1Byb2Nlc3NpbmciLCJwcm9jZXNzRXJyb3IiLCJzZXRQcm9jZXNzRXJyb3IiLCJzdWNjZXNzTWVzc2FnZSIsInNldFN1Y2Nlc3NNZXNzYWdlIiwicHJvZ3Jlc3NNZXNzYWdlIiwic2V0UHJvZ3Jlc3NNZXNzYWdlIiwiaW50ZXJuYWxWaWV3Iiwic2V0SW50ZXJuYWxWaWV3Iiwic2VsZWN0ZWRNYXJrZXRwbGFjZSIsInNldFNlbGVjdGVkTWFya2V0cGxhY2UiLCJkZXRhaWxzTWVudUluZGV4Iiwic2V0RGV0YWlsc01lbnVJbmRleCIsImhhc0F0dGVtcHRlZEF1dG9BY3Rpb24iLCJsb2FkTWFya2V0cGxhY2VzIiwiY29uZmlnIiwiZW5hYmxlZCIsImRpc2FibGVkIiwiYWxsUGx1Z2lucyIsIm1hcmtldHBsYWNlcyIsImZhaWx1cmVzIiwic3RhdGVzIiwiZW50cnkiLCJkYXRhIiwibWFya2V0cGxhY2UiLCJpbnN0YWxsZWRGcm9tTWFya2V0cGxhY2UiLCJmaWx0ZXIiLCJwbHVnaW4iLCJlbmRzV2l0aCIsInB1c2giLCJwbHVnaW5zIiwibGVuZ3RoIiwic29ydCIsImEiLCJiIiwibG9jYWxlQ29tcGFyZSIsInN1Y2Nlc3NDb3VudCIsIm0iLCJlcnJvclJlc3VsdCIsInR5cGUiLCJtZXNzYWdlIiwiRXJyb3IiLCJjdXJyZW50IiwidGFyZ2V0SW5kZXgiLCJmaW5kSW5kZXgiLCJzIiwidGFyZ2V0U3RhdGUiLCJuZXdTdGF0ZXMiLCJzZXRUaW1lb3V0IiwiYXBwbHlDaGFuZ2VzIiwiZXJyIiwiaGFzUGVuZGluZ0NoYW5nZXMiLCJzb21lIiwiZ2V0UGVuZGluZ0NvdW50cyIsInVwZGF0ZUNvdW50IiwicmVtb3ZlQ291bnQiLCJzdGF0ZXNUb1Byb2Nlc3MiLCJ3YXNJbkRldGFpbHNWaWV3Iiwic2V0dGluZ3MiLCJ1cGRhdGVkQ291bnQiLCJyZW1vdmVkQ291bnQiLCJyZWZyZXNoZWRNYXJrZXRwbGFjZXMiLCJTZXQiLCJuZXdFbmFibGVkUGx1Z2lucyIsImVuYWJsZWRQbHVnaW5zIiwicGx1Z2luSWQiLCJtYXJrZXRwbGFjZV9uYW1lIiwicGx1Z2luc191bmluc3RhbGxlZCIsImFkZCIsInRvTG93ZXJDYXNlIiwidXBkYXRlZFBsdWdpbkNvdW50Iiwic2l6ZSIsInVwZGF0ZWRQbHVnaW5JZHMiLCJ1cGRhdGVkTWFya2V0cGxhY2UiLCJmaW5kIiwiYWN0aW9ucyIsInBsdWdpblBhcnQiLCJzdWNjZXNzTXNnIiwidGljayIsImpvaW4iLCJjb25zdCIsImVycm9yTXNnIiwiY29uZmlybVJlbW92ZSIsIm1hcCIsImJ1aWxkRGV0YWlsc01lbnVPcHRpb25zIiwiQXJyYXkiLCJsYWJlbCIsInNlY29uZGFyeUxhYmVsIiwidmFsdWUiLCJvcHRpb25zIiwiRGF0ZSIsInRvTG9jYWxlRGF0ZVN0cmluZyIsInVuZGVmaW5lZCIsImhhbmRsZVRvZ2dsZUF1dG9VcGRhdGUiLCJuZXdBdXRvVXBkYXRlIiwicHJldiIsImNvbnRleHQiLCJpc0FjdGl2ZSIsInNlbGVjdDpwcmV2aW91cyIsIk1hdGgiLCJtYXgiLCJzZWxlY3Q6bmV4dCIsInRvdGFsSXRlbXMiLCJtaW4iLCJzZWxlY3Q6YWNjZXB0IiwibWFya2V0cGxhY2VJbmRleCIsImlucHV0IiwiaWR4IiwibWVudU9wdGlvbnMiLCJzZWxlY3RlZE9wdGlvbiIsInBvaW50ZXIiLCJpc1VwZGF0aW5nIiwiYnVsbGV0IiwibWFuaWZlc3QiLCJkZXNjcmlwdGlvbiIsIm9wdGlvbiIsImlzU2VsZWN0ZWQiLCJpbmRpY2F0b3JzIiwiY3Jvc3MiLCJNYW5hZ2VNYXJrZXRwbGFjZXNLZXlIaW50c1Byb3BzIiwiaGFzUGVuZGluZ0FjdGlvbnMiLCJNYW5hZ2VNYXJrZXRwbGFjZXNLZXlIaW50cyIsInQwIiwiJCIsIl9jIiwidDEiLCJ0MiIsInQzIiwidDQiLCJ0NSIsInQ2IiwidDciXSwic291cmNlcyI6WyJNYW5hZ2VNYXJrZXRwbGFjZXMudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBmaWd1cmVzIGZyb20gJ2ZpZ3VyZXMnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IHVzZUVmZmVjdCwgdXNlUmVmLCB1c2VTdGF0ZSB9IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHtcbiAgdHlwZSBBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTLFxuICBsb2dFdmVudCxcbn0gZnJvbSAnc3JjL3NlcnZpY2VzL2FuYWx5dGljcy9pbmRleC5qcydcbmltcG9ydCB7IENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludCB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50LmpzJ1xuaW1wb3J0IHsgQnlsaW5lIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL0J5bGluZS5qcydcbmltcG9ydCB7IEtleWJvYXJkU2hvcnRjdXRIaW50IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL0tleWJvYXJkU2hvcnRjdXRIaW50LmpzJ1xuLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGN1c3RvbS1ydWxlcy9wcmVmZXItdXNlLWtleWJpbmRpbmdzIC0tIHVzZUlucHV0IG5lZWRlZCBmb3IgbWFya2V0cGxhY2Utc3BlY2lmaWMgdS9yIHNob3J0Y3V0cyBhbmQgeS9uIGNvbmZpcm1hdGlvbiBub3QgaW4ga2V5YmluZGluZyBzY2hlbWFcbmltcG9ydCB7IEJveCwgVGV4dCwgdXNlSW5wdXQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQge1xuICB1c2VLZXliaW5kaW5nLFxuICB1c2VLZXliaW5kaW5ncyxcbn0gZnJvbSAnLi4vLi4va2V5YmluZGluZ3MvdXNlS2V5YmluZGluZy5qcydcbmltcG9ydCB0eXBlIHsgTG9hZGVkUGx1Z2luIH0gZnJvbSAnLi4vLi4vdHlwZXMvcGx1Z2luLmpzJ1xuaW1wb3J0IHsgY291bnQgfSBmcm9tICcuLi8uLi91dGlscy9hcnJheS5qcydcbmltcG9ydCB7IHNob3VsZFNraXBQbHVnaW5BdXRvdXBkYXRlIH0gZnJvbSAnLi4vLi4vdXRpbHMvY29uZmlnLmpzJ1xuaW1wb3J0IHsgZXJyb3JNZXNzYWdlIH0gZnJvbSAnLi4vLi4vdXRpbHMvZXJyb3JzLmpzJ1xuaW1wb3J0IHsgY2xlYXJBbGxDYWNoZXMgfSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL2NhY2hlVXRpbHMuanMnXG5pbXBvcnQge1xuICBjcmVhdGVQbHVnaW5JZCxcbiAgZm9ybWF0TWFya2V0cGxhY2VMb2FkaW5nRXJyb3JzLFxuICBnZXRNYXJrZXRwbGFjZVNvdXJjZURpc3BsYXksXG4gIGxvYWRNYXJrZXRwbGFjZXNXaXRoR3JhY2VmdWxEZWdyYWRhdGlvbixcbn0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9tYXJrZXRwbGFjZUhlbHBlcnMuanMnXG5pbXBvcnQge1xuICBsb2FkS25vd25NYXJrZXRwbGFjZXNDb25maWcsXG4gIHJlZnJlc2hNYXJrZXRwbGFjZSxcbiAgcmVtb3ZlTWFya2V0cGxhY2VTb3VyY2UsXG4gIHNldE1hcmtldHBsYWNlQXV0b1VwZGF0ZSxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9tYXJrZXRwbGFjZU1hbmFnZXIuanMnXG5pbXBvcnQgeyB1cGRhdGVQbHVnaW5zRm9yTWFya2V0cGxhY2VzIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9wbHVnaW5BdXRvdXBkYXRlLmpzJ1xuaW1wb3J0IHsgbG9hZEFsbFBsdWdpbnMgfSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL3BsdWdpbkxvYWRlci5qcydcbmltcG9ydCB7IGlzTWFya2V0cGxhY2VBdXRvVXBkYXRlIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9zY2hlbWFzLmpzJ1xuaW1wb3J0IHtcbiAgZ2V0U2V0dGluZ3NGb3JTb3VyY2UsXG4gIHVwZGF0ZVNldHRpbmdzRm9yU291cmNlLFxufSBmcm9tICcuLi8uLi91dGlscy9zZXR0aW5ncy9zZXR0aW5ncy5qcydcbmltcG9ydCB7IHBsdXJhbCB9IGZyb20gJy4uLy4uL3V0aWxzL3N0cmluZ1V0aWxzLmpzJ1xuaW1wb3J0IHR5cGUgeyBWaWV3U3RhdGUgfSBmcm9tICcuL3R5cGVzLmpzJ1xuXG50eXBlIFByb3BzID0ge1xuICBzZXRWaWV3U3RhdGU6IChzdGF0ZTogVmlld1N0YXRlKSA9PiB2b2lkXG4gIGVycm9yPzogc3RyaW5nIHwgbnVsbFxuICBzZXRFcnJvcj86IChlcnJvcjogc3RyaW5nIHwgbnVsbCkgPT4gdm9pZFxuICBzZXRSZXN1bHQ6IChyZXN1bHQ6IHN0cmluZyB8IG51bGwpID0+IHZvaWRcbiAgZXhpdFN0YXRlOiB7XG4gICAgcGVuZGluZzogYm9vbGVhblxuICAgIGtleU5hbWU6ICdDdHJsLUMnIHwgJ0N0cmwtRCcgfCBudWxsXG4gIH1cbiAgb25NYW5hZ2VDb21wbGV0ZT86ICgpID0+IHZvaWQgfCBQcm9taXNlPHZvaWQ+XG4gIHRhcmdldE1hcmtldHBsYWNlPzogc3RyaW5nXG4gIGFjdGlvbj86ICd1cGRhdGUnIHwgJ3JlbW92ZSdcbn1cblxudHlwZSBNYXJrZXRwbGFjZVN0YXRlID0ge1xuICBuYW1lOiBzdHJpbmdcbiAgc291cmNlOiBzdHJpbmdcbiAgbGFzdFVwZGF0ZWQ/OiBzdHJpbmdcbiAgcGx1Z2luQ291bnQ/OiBudW1iZXJcbiAgaW5zdGFsbGVkUGx1Z2lucz86IExvYWRlZFBsdWdpbltdXG4gIHBlbmRpbmdVcGRhdGU/OiBib29sZWFuXG4gIHBlbmRpbmdSZW1vdmU/OiBib29sZWFuXG4gIGF1dG9VcGRhdGU/OiBib29sZWFuXG59XG5cbnR5cGUgSW50ZXJuYWxWaWV3U3RhdGUgPSAnbGlzdCcgfCAnZGV0YWlscycgfCAnY29uZmlybS1yZW1vdmUnXG5cbmV4cG9ydCBmdW5jdGlvbiBNYW5hZ2VNYXJrZXRwbGFjZXMoe1xuICBzZXRWaWV3U3RhdGUsXG4gIGVycm9yLFxuICBzZXRFcnJvcixcbiAgc2V0UmVzdWx0LFxuICBleGl0U3RhdGUsXG4gIG9uTWFuYWdlQ29tcGxldGUsXG4gIHRhcmdldE1hcmtldHBsYWNlLFxuICBhY3Rpb24sXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IFttYXJrZXRwbGFjZVN0YXRlcywgc2V0TWFya2V0cGxhY2VTdGF0ZXNdID0gdXNlU3RhdGU8XG4gICAgTWFya2V0cGxhY2VTdGF0ZVtdXG4gID4oW10pXG4gIGNvbnN0IFtsb2FkaW5nLCBzZXRMb2FkaW5nXSA9IHVzZVN0YXRlKHRydWUpXG4gIGNvbnN0IFtzZWxlY3RlZEluZGV4LCBzZXRTZWxlY3RlZEluZGV4XSA9IHVzZVN0YXRlKDApXG4gIGNvbnN0IFtpc1Byb2Nlc3NpbmcsIHNldElzUHJvY2Vzc2luZ10gPSB1c2VTdGF0ZShmYWxzZSlcbiAgY29uc3QgW3Byb2Nlc3NFcnJvciwgc2V0UHJvY2Vzc0Vycm9yXSA9IHVzZVN0YXRlPHN0cmluZyB8IG51bGw+KG51bGwpXG4gIGNvbnN0IFtzdWNjZXNzTWVzc2FnZSwgc2V0U3VjY2Vzc01lc3NhZ2VdID0gdXNlU3RhdGU8c3RyaW5nIHwgbnVsbD4obnVsbClcbiAgY29uc3QgW3Byb2dyZXNzTWVzc2FnZSwgc2V0UHJvZ3Jlc3NNZXNzYWdlXSA9IHVzZVN0YXRlPHN0cmluZyB8IG51bGw+KG51bGwpXG4gIGNvbnN0IFtpbnRlcm5hbFZpZXcsIHNldEludGVybmFsVmlld10gPSB1c2VTdGF0ZTxJbnRlcm5hbFZpZXdTdGF0ZT4oJ2xpc3QnKVxuICBjb25zdCBbc2VsZWN0ZWRNYXJrZXRwbGFjZSwgc2V0U2VsZWN0ZWRNYXJrZXRwbGFjZV0gPVxuICAgIHVzZVN0YXRlPE1hcmtldHBsYWNlU3RhdGUgfCBudWxsPihudWxsKVxuICBjb25zdCBbZGV0YWlsc01lbnVJbmRleCwgc2V0RGV0YWlsc01lbnVJbmRleF0gPSB1c2VTdGF0ZSgwKVxuICBjb25zdCBoYXNBdHRlbXB0ZWRBdXRvQWN0aW9uID0gdXNlUmVmKGZhbHNlKVxuXG4gIC8vIExvYWQgbWFya2V0cGxhY2VzIGFuZCB0aGVpciBpbnN0YWxsZWQgcGx1Z2luc1xuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIGFzeW5jIGZ1bmN0aW9uIGxvYWRNYXJrZXRwbGFjZXMoKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBjb25maWcgPSBhd2FpdCBsb2FkS25vd25NYXJrZXRwbGFjZXNDb25maWcoKVxuICAgICAgICBjb25zdCB7IGVuYWJsZWQsIGRpc2FibGVkIH0gPSBhd2FpdCBsb2FkQWxsUGx1Z2lucygpXG4gICAgICAgIGNvbnN0IGFsbFBsdWdpbnMgPSBbLi4uZW5hYmxlZCwgLi4uZGlzYWJsZWRdXG5cbiAgICAgICAgLy8gTG9hZCBtYXJrZXRwbGFjZXMgd2l0aCBncmFjZWZ1bCBkZWdyYWRhdGlvblxuICAgICAgICBjb25zdCB7IG1hcmtldHBsYWNlcywgZmFpbHVyZXMgfSA9XG4gICAgICAgICAgYXdhaXQgbG9hZE1hcmtldHBsYWNlc1dpdGhHcmFjZWZ1bERlZ3JhZGF0aW9uKGNvbmZpZylcblxuICAgICAgICBjb25zdCBzdGF0ZXM6IE1hcmtldHBsYWNlU3RhdGVbXSA9IFtdXG4gICAgICAgIGZvciAoY29uc3QgeyBuYW1lLCBjb25maWc6IGVudHJ5LCBkYXRhOiBtYXJrZXRwbGFjZSB9IG9mIG1hcmtldHBsYWNlcykge1xuICAgICAgICAgIC8vIEdldCBhbGwgcGx1Z2lucyBpbnN0YWxsZWQgZnJvbSB0aGlzIG1hcmtldHBsYWNlXG4gICAgICAgICAgY29uc3QgaW5zdGFsbGVkRnJvbU1hcmtldHBsYWNlID0gYWxsUGx1Z2lucy5maWx0ZXIocGx1Z2luID0+XG4gICAgICAgICAgICBwbHVnaW4uc291cmNlLmVuZHNXaXRoKGBAJHtuYW1lfWApLFxuICAgICAgICAgIClcblxuICAgICAgICAgIHN0YXRlcy5wdXNoKHtcbiAgICAgICAgICAgIG5hbWUsXG4gICAgICAgICAgICBzb3VyY2U6IGdldE1hcmtldHBsYWNlU291cmNlRGlzcGxheShlbnRyeS5zb3VyY2UpLFxuICAgICAgICAgICAgbGFzdFVwZGF0ZWQ6IGVudHJ5Lmxhc3RVcGRhdGVkLFxuICAgICAgICAgICAgcGx1Z2luQ291bnQ6IG1hcmtldHBsYWNlPy5wbHVnaW5zLmxlbmd0aCxcbiAgICAgICAgICAgIGluc3RhbGxlZFBsdWdpbnM6IGluc3RhbGxlZEZyb21NYXJrZXRwbGFjZSxcbiAgICAgICAgICAgIHBlbmRpbmdVcGRhdGU6IGZhbHNlLFxuICAgICAgICAgICAgcGVuZGluZ1JlbW92ZTogZmFsc2UsXG4gICAgICAgICAgICBhdXRvVXBkYXRlOiBpc01hcmtldHBsYWNlQXV0b1VwZGF0ZShuYW1lLCBlbnRyeSksXG4gICAgICAgICAgfSlcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFNvcnQ6IGNsYXVkZS1wbHVnaW4tZGlyZWN0b3J5IGZpcnN0LCB0aGVuIGFscGhhYmV0aWNhbGx5XG4gICAgICAgIHN0YXRlcy5zb3J0KChhLCBiKSA9PiB7XG4gICAgICAgICAgaWYgKGEubmFtZSA9PT0gJ2NsYXVkZS1wbHVnaW4tZGlyZWN0b3J5JykgcmV0dXJuIC0xXG4gICAgICAgICAgaWYgKGIubmFtZSA9PT0gJ2NsYXVkZS1wbHVnaW4tZGlyZWN0b3J5JykgcmV0dXJuIDFcbiAgICAgICAgICByZXR1cm4gYS5uYW1lLmxvY2FsZUNvbXBhcmUoYi5uYW1lKVxuICAgICAgICB9KVxuICAgICAgICBzZXRNYXJrZXRwbGFjZVN0YXRlcyhzdGF0ZXMpXG5cbiAgICAgICAgLy8gSGFuZGxlIG1hcmtldHBsYWNlIGxvYWRpbmcgZXJyb3JzL3dhcm5pbmdzXG4gICAgICAgIGNvbnN0IHN1Y2Nlc3NDb3VudCA9IGNvdW50KG1hcmtldHBsYWNlcywgbSA9PiBtLmRhdGEgIT09IG51bGwpXG4gICAgICAgIGNvbnN0IGVycm9yUmVzdWx0ID0gZm9ybWF0TWFya2V0cGxhY2VMb2FkaW5nRXJyb3JzKFxuICAgICAgICAgIGZhaWx1cmVzLFxuICAgICAgICAgIHN1Y2Nlc3NDb3VudCxcbiAgICAgICAgKVxuICAgICAgICBpZiAoZXJyb3JSZXN1bHQpIHtcbiAgICAgICAgICBpZiAoZXJyb3JSZXN1bHQudHlwZSA9PT0gJ3dhcm5pbmcnKSB7XG4gICAgICAgICAgICBzZXRQcm9jZXNzRXJyb3IoZXJyb3JSZXN1bHQubWVzc2FnZSlcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKGVycm9yUmVzdWx0Lm1lc3NhZ2UpXG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gQXV0by1leGVjdXRlIGlmIHRhcmdldCBhbmQgYWN0aW9uIHByb3ZpZGVkXG4gICAgICAgIGlmICh0YXJnZXRNYXJrZXRwbGFjZSAmJiAhaGFzQXR0ZW1wdGVkQXV0b0FjdGlvbi5jdXJyZW50ICYmICFlcnJvcikge1xuICAgICAgICAgIGhhc0F0dGVtcHRlZEF1dG9BY3Rpb24uY3VycmVudCA9IHRydWVcbiAgICAgICAgICBjb25zdCB0YXJnZXRJbmRleCA9IHN0YXRlcy5maW5kSW5kZXgoXG4gICAgICAgICAgICBzID0+IHMubmFtZSA9PT0gdGFyZ2V0TWFya2V0cGxhY2UsXG4gICAgICAgICAgKVxuICAgICAgICAgIGlmICh0YXJnZXRJbmRleCA+PSAwKSB7XG4gICAgICAgICAgICBjb25zdCB0YXJnZXRTdGF0ZSA9IHN0YXRlc1t0YXJnZXRJbmRleF1cbiAgICAgICAgICAgIGlmIChhY3Rpb24pIHtcbiAgICAgICAgICAgICAgLy8gTWFyayB0aGUgYWN0aW9uIGFzIHBlbmRpbmcgYW5kIGV4ZWN1dGVcbiAgICAgICAgICAgICAgc2V0U2VsZWN0ZWRJbmRleCh0YXJnZXRJbmRleCArIDEpIC8vICsxIGJlY2F1c2UgXCJBZGQgTWFya2V0cGxhY2VcIiBpcyBhdCBpbmRleCAwXG4gICAgICAgICAgICAgIGNvbnN0IG5ld1N0YXRlcyA9IFsuLi5zdGF0ZXNdXG4gICAgICAgICAgICAgIGlmIChhY3Rpb24gPT09ICd1cGRhdGUnKSB7XG4gICAgICAgICAgICAgICAgbmV3U3RhdGVzW3RhcmdldEluZGV4XSEucGVuZGluZ1VwZGF0ZSA9IHRydWVcbiAgICAgICAgICAgICAgfSBlbHNlIGlmIChhY3Rpb24gPT09ICdyZW1vdmUnKSB7XG4gICAgICAgICAgICAgICAgbmV3U3RhdGVzW3RhcmdldEluZGV4XSEucGVuZGluZ1JlbW92ZSA9IHRydWVcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBzZXRNYXJrZXRwbGFjZVN0YXRlcyhuZXdTdGF0ZXMpXG4gICAgICAgICAgICAgIC8vIEFwcGx5IHRoZSBjaGFuZ2UgaW1tZWRpYXRlbHlcbiAgICAgICAgICAgICAgc2V0VGltZW91dChhcHBseUNoYW5nZXMsIDEwMCwgbmV3U3RhdGVzKVxuICAgICAgICAgICAgfSBlbHNlIGlmICh0YXJnZXRTdGF0ZSkge1xuICAgICAgICAgICAgICAvLyBObyBhY3Rpb24gLSBqdXN0IHNob3cgdGhlIGRldGFpbHMgdmlldyBmb3IgdGhpcyBtYXJrZXRwbGFjZVxuICAgICAgICAgICAgICBzZXRTZWxlY3RlZEluZGV4KHRhcmdldEluZGV4ICsgMSkgLy8gKzEgYmVjYXVzZSBcIkFkZCBNYXJrZXRwbGFjZVwiIGlzIGF0IGluZGV4IDBcbiAgICAgICAgICAgICAgc2V0U2VsZWN0ZWRNYXJrZXRwbGFjZSh0YXJnZXRTdGF0ZSlcbiAgICAgICAgICAgICAgc2V0SW50ZXJuYWxWaWV3KCdkZXRhaWxzJylcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9IGVsc2UgaWYgKHNldEVycm9yKSB7XG4gICAgICAgICAgICBzZXRFcnJvcihgTWFya2V0cGxhY2Ugbm90IGZvdW5kOiAke3RhcmdldE1hcmtldHBsYWNlfWApXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgaWYgKHNldEVycm9yKSB7XG4gICAgICAgICAgc2V0RXJyb3IoXG4gICAgICAgICAgICBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogJ0ZhaWxlZCB0byBsb2FkIG1hcmtldHBsYWNlcycsXG4gICAgICAgICAgKVxuICAgICAgICB9XG4gICAgICAgIHNldFByb2Nlc3NFcnJvcihcbiAgICAgICAgICBlcnIgaW5zdGFuY2VvZiBFcnJvciA/IGVyci5tZXNzYWdlIDogJ0ZhaWxlZCB0byBsb2FkIG1hcmtldHBsYWNlcycsXG4gICAgICAgIClcbiAgICAgIH0gZmluYWxseSB7XG4gICAgICAgIHNldExvYWRpbmcoZmFsc2UpXG4gICAgICB9XG4gICAgfVxuICAgIHZvaWQgbG9hZE1hcmtldHBsYWNlcygpXG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIHJlYWN0LWhvb2tzL2V4aGF1c3RpdmUtZGVwc1xuICAgIC8vIGJpb21lLWlnbm9yZSBsaW50L2NvcnJlY3RuZXNzL3VzZUV4aGF1c3RpdmVEZXBlbmRlbmNpZXM6IGludGVudGlvbmFsXG4gIH0sIFt0YXJnZXRNYXJrZXRwbGFjZSwgYWN0aW9uLCBlcnJvcl0pXG5cbiAgLy8gQ2hlY2sgaWYgdGhlcmUgYXJlIGFueSBwZW5kaW5nIGNoYW5nZXNcbiAgY29uc3QgaGFzUGVuZGluZ0NoYW5nZXMgPSAoKSA9PiB7XG4gICAgcmV0dXJuIG1hcmtldHBsYWNlU3RhdGVzLnNvbWUoXG4gICAgICBzdGF0ZSA9PiBzdGF0ZS5wZW5kaW5nVXBkYXRlIHx8IHN0YXRlLnBlbmRpbmdSZW1vdmUsXG4gICAgKVxuICB9XG5cbiAgLy8gR2V0IGNvdW50IG9mIHBlbmRpbmcgb3BlcmF0aW9uc1xuICBjb25zdCBnZXRQZW5kaW5nQ291bnRzID0gKCkgPT4ge1xuICAgIGNvbnN0IHVwZGF0ZUNvdW50ID0gY291bnQobWFya2V0cGxhY2VTdGF0ZXMsIHMgPT4gcy5wZW5kaW5nVXBkYXRlKVxuICAgIGNvbnN0IHJlbW92ZUNvdW50ID0gY291bnQobWFya2V0cGxhY2VTdGF0ZXMsIHMgPT4gcy5wZW5kaW5nUmVtb3ZlKVxuICAgIHJldHVybiB7IHVwZGF0ZUNvdW50LCByZW1vdmVDb3VudCB9XG4gIH1cblxuICAvLyBBcHBseSBhbGwgcGVuZGluZyBjaGFuZ2VzXG4gIGNvbnN0IGFwcGx5Q2hhbmdlcyA9IGFzeW5jIChzdGF0ZXM/OiBNYXJrZXRwbGFjZVN0YXRlW10pID0+IHtcbiAgICBjb25zdCBzdGF0ZXNUb1Byb2Nlc3MgPSBzdGF0ZXMgfHwgbWFya2V0cGxhY2VTdGF0ZXNcbiAgICBjb25zdCB3YXNJbkRldGFpbHNWaWV3ID0gaW50ZXJuYWxWaWV3ID09PSAnZGV0YWlscydcbiAgICBzZXRJc1Byb2Nlc3NpbmcodHJ1ZSlcbiAgICBzZXRQcm9jZXNzRXJyb3IobnVsbClcbiAgICBzZXRTdWNjZXNzTWVzc2FnZShudWxsKVxuICAgIHNldFByb2dyZXNzTWVzc2FnZShudWxsKVxuXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHNldHRpbmdzID0gZ2V0U2V0dGluZ3NGb3JTb3VyY2UoJ3VzZXJTZXR0aW5ncycpXG4gICAgICBsZXQgdXBkYXRlZENvdW50ID0gMFxuICAgICAgbGV0IHJlbW92ZWRDb3VudCA9IDBcbiAgICAgIGNvbnN0IHJlZnJlc2hlZE1hcmtldHBsYWNlcyA9IG5ldyBTZXQ8c3RyaW5nPigpXG5cbiAgICAgIGZvciAoY29uc3Qgc3RhdGUgb2Ygc3RhdGVzVG9Qcm9jZXNzKSB7XG4gICAgICAgIC8vIEhhbmRsZSByZW1vdmVcbiAgICAgICAgaWYgKHN0YXRlLnBlbmRpbmdSZW1vdmUpIHtcbiAgICAgICAgICAvLyBGaXJzdCB1bmluc3RhbGwgYWxsIHBsdWdpbnMgZnJvbSB0aGlzIG1hcmtldHBsYWNlXG4gICAgICAgICAgaWYgKHN0YXRlLmluc3RhbGxlZFBsdWdpbnMgJiYgc3RhdGUuaW5zdGFsbGVkUGx1Z2lucy5sZW5ndGggPiAwKSB7XG4gICAgICAgICAgICBjb25zdCBuZXdFbmFibGVkUGx1Z2lucyA9IHsgLi4uc2V0dGluZ3M/LmVuYWJsZWRQbHVnaW5zIH1cbiAgICAgICAgICAgIGZvciAoY29uc3QgcGx1Z2luIG9mIHN0YXRlLmluc3RhbGxlZFBsdWdpbnMpIHtcbiAgICAgICAgICAgICAgY29uc3QgcGx1Z2luSWQgPSBjcmVhdGVQbHVnaW5JZChwbHVnaW4ubmFtZSwgc3RhdGUubmFtZSlcbiAgICAgICAgICAgICAgLy8gTWFyayBhcyBkaXNhYmxlZC91bmluc3RhbGxlZFxuICAgICAgICAgICAgICBuZXdFbmFibGVkUGx1Z2luc1twbHVnaW5JZF0gPSBmYWxzZVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdXBkYXRlU2V0dGluZ3NGb3JTb3VyY2UoJ3VzZXJTZXR0aW5ncycsIHtcbiAgICAgICAgICAgICAgZW5hYmxlZFBsdWdpbnM6IG5ld0VuYWJsZWRQbHVnaW5zLFxuICAgICAgICAgICAgfSlcbiAgICAgICAgICB9XG5cbiAgICAgICAgICAvLyBUaGVuIHJlbW92ZSB0aGUgbWFya2V0cGxhY2VcbiAgICAgICAgICBhd2FpdCByZW1vdmVNYXJrZXRwbGFjZVNvdXJjZShzdGF0ZS5uYW1lKVxuICAgICAgICAgIHJlbW92ZWRDb3VudCsrXG5cbiAgICAgICAgICBsb2dFdmVudCgndGVuZ3VfbWFya2V0cGxhY2VfcmVtb3ZlZCcsIHtcbiAgICAgICAgICAgIG1hcmtldHBsYWNlX25hbWU6XG4gICAgICAgICAgICAgIHN0YXRlLm5hbWUgYXMgQW5hbHl0aWNzTWV0YWRhdGFfSV9WRVJJRklFRF9USElTX0lTX05PVF9DT0RFX09SX0ZJTEVQQVRIUyxcbiAgICAgICAgICAgIHBsdWdpbnNfdW5pbnN0YWxsZWQ6IHN0YXRlLmluc3RhbGxlZFBsdWdpbnM/Lmxlbmd0aCB8fCAwLFxuICAgICAgICAgIH0pXG4gICAgICAgICAgY29udGludWVcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIEhhbmRsZSB1cGRhdGVcbiAgICAgICAgaWYgKHN0YXRlLnBlbmRpbmdVcGRhdGUpIHtcbiAgICAgICAgICAvLyBSZWZyZXNoIGluZGl2aWR1YWwgbWFya2V0cGxhY2UgZm9yIGVmZmljaWVuY3kgd2l0aCBwcm9ncmVzcyByZXBvcnRpbmdcbiAgICAgICAgICBhd2FpdCByZWZyZXNoTWFya2V0cGxhY2Uoc3RhdGUubmFtZSwgKG1lc3NhZ2U6IHN0cmluZykgPT4ge1xuICAgICAgICAgICAgc2V0UHJvZ3Jlc3NNZXNzYWdlKG1lc3NhZ2UpXG4gICAgICAgICAgfSlcbiAgICAgICAgICB1cGRhdGVkQ291bnQrK1xuICAgICAgICAgIHJlZnJlc2hlZE1hcmtldHBsYWNlcy5hZGQoc3RhdGUubmFtZS50b0xvd2VyQ2FzZSgpKVxuXG4gICAgICAgICAgbG9nRXZlbnQoJ3Rlbmd1X21hcmtldHBsYWNlX3VwZGF0ZWQnLCB7XG4gICAgICAgICAgICBtYXJrZXRwbGFjZV9uYW1lOlxuICAgICAgICAgICAgICBzdGF0ZS5uYW1lIGFzIEFuYWx5dGljc01ldGFkYXRhX0lfVkVSSUZJRURfVEhJU19JU19OT1RfQ09ERV9PUl9GSUxFUEFUSFMsXG4gICAgICAgICAgfSlcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyBBZnRlciBtYXJrZXRwbGFjZSBjbG9uZXMgYXJlIHJlZnJlc2hlZCwgYnVtcCBpbnN0YWxsZWQgcGx1Z2lucyBmcm9tXG4gICAgICAvLyB0aG9zZSBtYXJrZXRwbGFjZXMgdG8gdGhlIG5ldyB2ZXJzaW9uLiBXaXRob3V0IHRoaXMsIHRoZSBsb2FkZXInc1xuICAgICAgLy8gY2FjaGUtb24tbWlzcyAoY29weVBsdWdpblRvVmVyc2lvbmVkQ2FjaGUpIGNyZWF0ZXMgdGhlIG5ldyB2ZXJzaW9uXG4gICAgICAvLyBkaXIgb24gdGhlIG5leHQgbG9hZEFsbFBsdWdpbnMoKSBjYWxsLCBidXQgaW5zdGFsbGVkX3BsdWdpbnMuanNvblxuICAgICAgLy8gc3RheXMgb24gdGhlIG9sZCB2ZXJzaW9uIOKAlCBzbyBjbGVhbnVwT3JwaGFuZWRQbHVnaW5WZXJzaW9uc0luQmFja2dyb3VuZFxuICAgICAgLy8gc3RhbXBzIHRoZSBORVcgZGlyIHdpdGggLm9ycGhhbmVkX2F0IG9uIHRoZSBuZXh0IHN0YXJ0dXAuIFNlZSAjMjk1MTIuXG4gICAgICAvLyB1cGRhdGVQbHVnaW5PcCAoY2FsbGVkIGluc2lkZSB0aGUgaGVscGVyKSBpcyB3aGF0IGFjdHVhbGx5IHdyaXRlc1xuICAgICAgLy8gaW5zdGFsbGVkX3BsdWdpbnMuanNvbiB2aWEgdXBkYXRlSW5zdGFsbGF0aW9uUGF0aE9uRGlzay5cbiAgICAgIGxldCB1cGRhdGVkUGx1Z2luQ291bnQgPSAwXG4gICAgICBpZiAocmVmcmVzaGVkTWFya2V0cGxhY2VzLnNpemUgPiAwKSB7XG4gICAgICAgIGNvbnN0IHVwZGF0ZWRQbHVnaW5JZHMgPSBhd2FpdCB1cGRhdGVQbHVnaW5zRm9yTWFya2V0cGxhY2VzKFxuICAgICAgICAgIHJlZnJlc2hlZE1hcmtldHBsYWNlcyxcbiAgICAgICAgKVxuICAgICAgICB1cGRhdGVkUGx1Z2luQ291bnQgPSB1cGRhdGVkUGx1Z2luSWRzLmxlbmd0aFxuICAgICAgfVxuXG4gICAgICAvLyBDbGVhciBjYWNoZXMgYWZ0ZXIgY2hhbmdlc1xuICAgICAgY2xlYXJBbGxDYWNoZXMoKVxuXG4gICAgICAvLyBDYWxsIGNvbXBsZXRpb24gY2FsbGJhY2tcbiAgICAgIGlmIChvbk1hbmFnZUNvbXBsZXRlKSB7XG4gICAgICAgIGF3YWl0IG9uTWFuYWdlQ29tcGxldGUoKVxuICAgICAgfVxuXG4gICAgICAvLyBSZWxvYWQgbWFya2V0cGxhY2UgZGF0YSB0byBzaG93IHVwZGF0ZWQgdGltZXN0YW1wc1xuICAgICAgY29uc3QgY29uZmlnID0gYXdhaXQgbG9hZEtub3duTWFya2V0cGxhY2VzQ29uZmlnKClcbiAgICAgIGNvbnN0IHsgZW5hYmxlZCwgZGlzYWJsZWQgfSA9IGF3YWl0IGxvYWRBbGxQbHVnaW5zKClcbiAgICAgIGNvbnN0IGFsbFBsdWdpbnMgPSBbLi4uZW5hYmxlZCwgLi4uZGlzYWJsZWRdXG5cbiAgICAgIGNvbnN0IHsgbWFya2V0cGxhY2VzIH0gPVxuICAgICAgICBhd2FpdCBsb2FkTWFya2V0cGxhY2VzV2l0aEdyYWNlZnVsRGVncmFkYXRpb24oY29uZmlnKVxuXG4gICAgICBjb25zdCBuZXdTdGF0ZXM6IE1hcmtldHBsYWNlU3RhdGVbXSA9IFtdXG4gICAgICBmb3IgKGNvbnN0IHsgbmFtZSwgY29uZmlnOiBlbnRyeSwgZGF0YTogbWFya2V0cGxhY2UgfSBvZiBtYXJrZXRwbGFjZXMpIHtcbiAgICAgICAgY29uc3QgaW5zdGFsbGVkRnJvbU1hcmtldHBsYWNlID0gYWxsUGx1Z2lucy5maWx0ZXIocGx1Z2luID0+XG4gICAgICAgICAgcGx1Z2luLnNvdXJjZS5lbmRzV2l0aChgQCR7bmFtZX1gKSxcbiAgICAgICAgKVxuXG4gICAgICAgIG5ld1N0YXRlcy5wdXNoKHtcbiAgICAgICAgICBuYW1lLFxuICAgICAgICAgIHNvdXJjZTogZ2V0TWFya2V0cGxhY2VTb3VyY2VEaXNwbGF5KGVudHJ5LnNvdXJjZSksXG4gICAgICAgICAgbGFzdFVwZGF0ZWQ6IGVudHJ5Lmxhc3RVcGRhdGVkLFxuICAgICAgICAgIHBsdWdpbkNvdW50OiBtYXJrZXRwbGFjZT8ucGx1Z2lucy5sZW5ndGgsXG4gICAgICAgICAgaW5zdGFsbGVkUGx1Z2luczogaW5zdGFsbGVkRnJvbU1hcmtldHBsYWNlLFxuICAgICAgICAgIHBlbmRpbmdVcGRhdGU6IGZhbHNlLFxuICAgICAgICAgIHBlbmRpbmdSZW1vdmU6IGZhbHNlLFxuICAgICAgICAgIGF1dG9VcGRhdGU6IGlzTWFya2V0cGxhY2VBdXRvVXBkYXRlKG5hbWUsIGVudHJ5KSxcbiAgICAgICAgfSlcbiAgICAgIH1cblxuICAgICAgLy8gU29ydDogY2xhdWRlLXBsdWdpbi1kaXJlY3RvcnkgZmlyc3QsIHRoZW4gYWxwaGFiZXRpY2FsbHlcbiAgICAgIG5ld1N0YXRlcy5zb3J0KChhLCBiKSA9PiB7XG4gICAgICAgIGlmIChhLm5hbWUgPT09ICdjbGF1ZGUtcGx1Z2luLWRpcmVjdG9yeScpIHJldHVybiAtMVxuICAgICAgICBpZiAoYi5uYW1lID09PSAnY2xhdWRlLXBsdWdpbi1kaXJlY3RvcnknKSByZXR1cm4gMVxuICAgICAgICByZXR1cm4gYS5uYW1lLmxvY2FsZUNvbXBhcmUoYi5uYW1lKVxuICAgICAgfSlcbiAgICAgIHNldE1hcmtldHBsYWNlU3RhdGVzKG5ld1N0YXRlcylcblxuICAgICAgLy8gVXBkYXRlIHNlbGVjdGVkIG1hcmtldHBsYWNlIHJlZmVyZW5jZSB3aXRoIGZyZXNoIGRhdGFcbiAgICAgIGlmICh3YXNJbkRldGFpbHNWaWV3ICYmIHNlbGVjdGVkTWFya2V0cGxhY2UpIHtcbiAgICAgICAgY29uc3QgdXBkYXRlZE1hcmtldHBsYWNlID0gbmV3U3RhdGVzLmZpbmQoXG4gICAgICAgICAgcyA9PiBzLm5hbWUgPT09IHNlbGVjdGVkTWFya2V0cGxhY2UubmFtZSxcbiAgICAgICAgKVxuICAgICAgICBpZiAodXBkYXRlZE1hcmtldHBsYWNlKSB7XG4gICAgICAgICAgc2V0U2VsZWN0ZWRNYXJrZXRwbGFjZSh1cGRhdGVkTWFya2V0cGxhY2UpXG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgLy8gQnVpbGQgc3VjY2VzcyBtZXNzYWdlXG4gICAgICBjb25zdCBhY3Rpb25zOiBzdHJpbmdbXSA9IFtdXG4gICAgICBpZiAodXBkYXRlZENvdW50ID4gMCkge1xuICAgICAgICBjb25zdCBwbHVnaW5QYXJ0ID1cbiAgICAgICAgICB1cGRhdGVkUGx1Z2luQ291bnQgPiAwXG4gICAgICAgICAgICA/IGAgKCR7dXBkYXRlZFBsdWdpbkNvdW50fSAke3BsdXJhbCh1cGRhdGVkUGx1Z2luQ291bnQsICdwbHVnaW4nKX0gYnVtcGVkKWBcbiAgICAgICAgICAgIDogJydcbiAgICAgICAgYWN0aW9ucy5wdXNoKFxuICAgICAgICAgIGBVcGRhdGVkICR7dXBkYXRlZENvdW50fSAke3BsdXJhbCh1cGRhdGVkQ291bnQsICdtYXJrZXRwbGFjZScpfSR7cGx1Z2luUGFydH1gLFxuICAgICAgICApXG4gICAgICB9XG4gICAgICBpZiAocmVtb3ZlZENvdW50ID4gMCkge1xuICAgICAgICBhY3Rpb25zLnB1c2goXG4gICAgICAgICAgYFJlbW92ZWQgJHtyZW1vdmVkQ291bnR9ICR7cGx1cmFsKHJlbW92ZWRDb3VudCwgJ21hcmtldHBsYWNlJyl9YCxcbiAgICAgICAgKVxuICAgICAgfVxuXG4gICAgICBpZiAoYWN0aW9ucy5sZW5ndGggPiAwKSB7XG4gICAgICAgIGNvbnN0IHN1Y2Nlc3NNc2cgPSBgJHtmaWd1cmVzLnRpY2t9ICR7YWN0aW9ucy5qb2luKCcsICcpfWBcbiAgICAgICAgLy8gSWYgd2Ugd2VyZSBpbiBkZXRhaWxzIHZpZXcsIHN0YXkgdGhlcmUgYW5kIHNob3cgc3VjY2Vzc1xuICAgICAgICBpZiAod2FzSW5EZXRhaWxzVmlldykge1xuICAgICAgICAgIHNldFN1Y2Nlc3NNZXNzYWdlKHN1Y2Nlc3NNc2cpXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgLy8gT3RoZXJ3aXNlIHNob3cgcmVzdWx0IGFuZCBleGl0IHRvIG1lbnVcbiAgICAgICAgICBzZXRSZXN1bHQoc3VjY2Vzc01zZylcbiAgICAgICAgICBzZXRUaW1lb3V0KHNldFZpZXdTdGF0ZSwgMjAwMCwgeyB0eXBlOiAnbWVudScgYXMgY29uc3QgfSlcbiAgICAgICAgfVxuICAgICAgfSBlbHNlIGlmICghd2FzSW5EZXRhaWxzVmlldykge1xuICAgICAgICBzZXRWaWV3U3RhdGUoeyB0eXBlOiAnbWVudScgfSlcbiAgICAgIH1cbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIGNvbnN0IGVycm9yTXNnID0gZXJyb3JNZXNzYWdlKGVycilcbiAgICAgIHNldFByb2Nlc3NFcnJvcihlcnJvck1zZylcbiAgICAgIGlmIChzZXRFcnJvcikge1xuICAgICAgICBzZXRFcnJvcihlcnJvck1zZylcbiAgICAgIH1cbiAgICB9IGZpbmFsbHkge1xuICAgICAgc2V0SXNQcm9jZXNzaW5nKGZhbHNlKVxuICAgICAgc2V0UHJvZ3Jlc3NNZXNzYWdlKG51bGwpXG4gICAgfVxuICB9XG5cbiAgLy8gSGFuZGxlIGNvbmZpcm1pbmcgbWFya2V0cGxhY2UgcmVtb3ZhbFxuICBjb25zdCBjb25maXJtUmVtb3ZlID0gYXN5bmMgKCkgPT4ge1xuICAgIGlmICghc2VsZWN0ZWRNYXJrZXRwbGFjZSkgcmV0dXJuXG5cbiAgICAvLyBNYXJrIGZvciByZW1vdmFsIGFuZCBhcHBseVxuICAgIGNvbnN0IG5ld1N0YXRlcyA9IG1hcmtldHBsYWNlU3RhdGVzLm1hcChzdGF0ZSA9PlxuICAgICAgc3RhdGUubmFtZSA9PT0gc2VsZWN0ZWRNYXJrZXRwbGFjZS5uYW1lXG4gICAgICAgID8geyAuLi5zdGF0ZSwgcGVuZGluZ1JlbW92ZTogdHJ1ZSB9XG4gICAgICAgIDogc3RhdGUsXG4gICAgKVxuICAgIHNldE1hcmtldHBsYWNlU3RhdGVzKG5ld1N0YXRlcylcbiAgICBhd2FpdCBhcHBseUNoYW5nZXMobmV3U3RhdGVzKVxuICB9XG5cbiAgLy8gQnVpbGQgbWVudSBvcHRpb25zIGZvciBkZXRhaWxzIHZpZXdcbiAgY29uc3QgYnVpbGREZXRhaWxzTWVudU9wdGlvbnMgPSAoXG4gICAgbWFya2V0cGxhY2U6IE1hcmtldHBsYWNlU3RhdGUgfCBudWxsLFxuICApOiBBcnJheTx7IGxhYmVsOiBzdHJpbmc7IHNlY29uZGFyeUxhYmVsPzogc3RyaW5nOyB2YWx1ZTogc3RyaW5nIH0+ID0+IHtcbiAgICBpZiAoIW1hcmtldHBsYWNlKSByZXR1cm4gW11cblxuICAgIGNvbnN0IG9wdGlvbnM6IEFycmF5PHtcbiAgICAgIGxhYmVsOiBzdHJpbmdcbiAgICAgIHNlY29uZGFyeUxhYmVsPzogc3RyaW5nXG4gICAgICB2YWx1ZTogc3RyaW5nXG4gICAgfT4gPSBbXG4gICAgICB7XG4gICAgICAgIGxhYmVsOiBgQnJvd3NlIHBsdWdpbnMgKCR7bWFya2V0cGxhY2UucGx1Z2luQ291bnQgPz8gMH0pYCxcbiAgICAgICAgdmFsdWU6ICdicm93c2UnLFxuICAgICAgfSxcbiAgICAgIHtcbiAgICAgICAgbGFiZWw6ICdVcGRhdGUgbWFya2V0cGxhY2UnLFxuICAgICAgICBzZWNvbmRhcnlMYWJlbDogbWFya2V0cGxhY2UubGFzdFVwZGF0ZWRcbiAgICAgICAgICA/IGAobGFzdCB1cGRhdGVkICR7bmV3IERhdGUobWFya2V0cGxhY2UubGFzdFVwZGF0ZWQpLnRvTG9jYWxlRGF0ZVN0cmluZygpfSlgXG4gICAgICAgICAgOiB1bmRlZmluZWQsXG4gICAgICAgIHZhbHVlOiAndXBkYXRlJyxcbiAgICAgIH0sXG4gICAgXVxuXG4gICAgLy8gT25seSBzaG93IGF1dG8tdXBkYXRlIHRvZ2dsZSBpZiBhdXRvLXVwZGF0ZXIgaXMgbm90IGdsb2JhbGx5IGRpc2FibGVkXG4gICAgaWYgKCFzaG91bGRTa2lwUGx1Z2luQXV0b3VwZGF0ZSgpKSB7XG4gICAgICBvcHRpb25zLnB1c2goe1xuICAgICAgICBsYWJlbDogbWFya2V0cGxhY2UuYXV0b1VwZGF0ZVxuICAgICAgICAgID8gJ0Rpc2FibGUgYXV0by11cGRhdGUnXG4gICAgICAgICAgOiAnRW5hYmxlIGF1dG8tdXBkYXRlJyxcbiAgICAgICAgdmFsdWU6ICd0b2dnbGUtYXV0by11cGRhdGUnLFxuICAgICAgfSlcbiAgICB9XG5cbiAgICBvcHRpb25zLnB1c2goeyBsYWJlbDogJ1JlbW92ZSBtYXJrZXRwbGFjZScsIHZhbHVlOiAncmVtb3ZlJyB9KVxuXG4gICAgcmV0dXJuIG9wdGlvbnNcbiAgfVxuXG4gIC8vIEhhbmRsZSB0b2dnbGluZyBhdXRvLXVwZGF0ZSBmb3IgYSBtYXJrZXRwbGFjZVxuICBjb25zdCBoYW5kbGVUb2dnbGVBdXRvVXBkYXRlID0gYXN5bmMgKG1hcmtldHBsYWNlOiBNYXJrZXRwbGFjZVN0YXRlKSA9PiB7XG4gICAgY29uc3QgbmV3QXV0b1VwZGF0ZSA9ICFtYXJrZXRwbGFjZS5hdXRvVXBkYXRlXG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IHNldE1hcmtldHBsYWNlQXV0b1VwZGF0ZShtYXJrZXRwbGFjZS5uYW1lLCBuZXdBdXRvVXBkYXRlKVxuXG4gICAgICAvLyBVcGRhdGUgbG9jYWwgc3RhdGVcbiAgICAgIHNldE1hcmtldHBsYWNlU3RhdGVzKHByZXYgPT5cbiAgICAgICAgcHJldi5tYXAoc3RhdGUgPT5cbiAgICAgICAgICBzdGF0ZS5uYW1lID09PSBtYXJrZXRwbGFjZS5uYW1lXG4gICAgICAgICAgICA/IHsgLi4uc3RhdGUsIGF1dG9VcGRhdGU6IG5ld0F1dG9VcGRhdGUgfVxuICAgICAgICAgICAgOiBzdGF0ZSxcbiAgICAgICAgKSxcbiAgICAgIClcblxuICAgICAgLy8gVXBkYXRlIHNlbGVjdGVkIG1hcmtldHBsYWNlIHJlZmVyZW5jZVxuICAgICAgc2V0U2VsZWN0ZWRNYXJrZXRwbGFjZShwcmV2ID0+XG4gICAgICAgIHByZXYgPyB7IC4uLnByZXYsIGF1dG9VcGRhdGU6IG5ld0F1dG9VcGRhdGUgfSA6IHByZXYsXG4gICAgICApXG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBzZXRQcm9jZXNzRXJyb3IoXG4gICAgICAgIGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiAnRmFpbGVkIHRvIHVwZGF0ZSBzZXR0aW5nJyxcbiAgICAgIClcbiAgICB9XG4gIH1cblxuICAvLyBFc2NhcGUgaW4gZGV0YWlscyBvciBjb25maXJtLXJlbW92ZSB2aWV3IC0gZ28gYmFjayB0byBsaXN0XG4gIHVzZUtleWJpbmRpbmcoXG4gICAgJ2NvbmZpcm06bm8nLFxuICAgICgpID0+IHtcbiAgICAgIHNldEludGVybmFsVmlldygnbGlzdCcpXG4gICAgICBzZXREZXRhaWxzTWVudUluZGV4KDApXG4gICAgfSxcbiAgICB7XG4gICAgICBjb250ZXh0OiAnQ29uZmlybWF0aW9uJyxcbiAgICAgIGlzQWN0aXZlOlxuICAgICAgICAhaXNQcm9jZXNzaW5nICYmXG4gICAgICAgIChpbnRlcm5hbFZpZXcgPT09ICdkZXRhaWxzJyB8fCBpbnRlcm5hbFZpZXcgPT09ICdjb25maXJtLXJlbW92ZScpLFxuICAgIH0sXG4gIClcblxuICAvLyBFc2NhcGUgaW4gbGlzdCB2aWV3IHdpdGggcGVuZGluZyBjaGFuZ2VzIC0gY2xlYXIgcGVuZGluZyBjaGFuZ2VzXG4gIHVzZUtleWJpbmRpbmcoXG4gICAgJ2NvbmZpcm06bm8nLFxuICAgICgpID0+IHtcbiAgICAgIHNldE1hcmtldHBsYWNlU3RhdGVzKHByZXYgPT5cbiAgICAgICAgcHJldi5tYXAoc3RhdGUgPT4gKHtcbiAgICAgICAgICAuLi5zdGF0ZSxcbiAgICAgICAgICBwZW5kaW5nVXBkYXRlOiBmYWxzZSxcbiAgICAgICAgICBwZW5kaW5nUmVtb3ZlOiBmYWxzZSxcbiAgICAgICAgfSkpLFxuICAgICAgKVxuICAgICAgc2V0U2VsZWN0ZWRJbmRleCgwKVxuICAgIH0sXG4gICAge1xuICAgICAgY29udGV4dDogJ0NvbmZpcm1hdGlvbicsXG4gICAgICBpc0FjdGl2ZTogIWlzUHJvY2Vzc2luZyAmJiBpbnRlcm5hbFZpZXcgPT09ICdsaXN0JyAmJiBoYXNQZW5kaW5nQ2hhbmdlcygpLFxuICAgIH0sXG4gIClcblxuICAvLyBFc2NhcGUgaW4gbGlzdCB2aWV3IHdpdGhvdXQgcGVuZGluZyBjaGFuZ2VzIC0gZXhpdCB0byBwYXJlbnQgbWVudVxuICB1c2VLZXliaW5kaW5nKFxuICAgICdjb25maXJtOm5vJyxcbiAgICAoKSA9PiB7XG4gICAgICBzZXRWaWV3U3RhdGUoeyB0eXBlOiAnbWVudScgfSlcbiAgICB9LFxuICAgIHtcbiAgICAgIGNvbnRleHQ6ICdDb25maXJtYXRpb24nLFxuICAgICAgaXNBY3RpdmU6XG4gICAgICAgICFpc1Byb2Nlc3NpbmcgJiYgaW50ZXJuYWxWaWV3ID09PSAnbGlzdCcgJiYgIWhhc1BlbmRpbmdDaGFuZ2VzKCksXG4gICAgfSxcbiAgKVxuXG4gIC8vIExpc3QgdmlldyDigJQgbmF2aWdhdGlvbiAodXAvZG93bi9lbnRlciB2aWEgY29uZmlndXJhYmxlIGtleWJpbmRpbmdzKVxuICB1c2VLZXliaW5kaW5ncyhcbiAgICB7XG4gICAgICAnc2VsZWN0OnByZXZpb3VzJzogKCkgPT4gc2V0U2VsZWN0ZWRJbmRleChwcmV2ID0+IE1hdGgubWF4KDAsIHByZXYgLSAxKSksXG4gICAgICAnc2VsZWN0Om5leHQnOiAoKSA9PiB7XG4gICAgICAgIGNvbnN0IHRvdGFsSXRlbXMgPSBtYXJrZXRwbGFjZVN0YXRlcy5sZW5ndGggKyAxXG4gICAgICAgIHNldFNlbGVjdGVkSW5kZXgocHJldiA9PiBNYXRoLm1pbih0b3RhbEl0ZW1zIC0gMSwgcHJldiArIDEpKVxuICAgICAgfSxcbiAgICAgICdzZWxlY3Q6YWNjZXB0JzogKCkgPT4ge1xuICAgICAgICBjb25zdCBtYXJrZXRwbGFjZUluZGV4ID0gc2VsZWN0ZWRJbmRleCAtIDFcbiAgICAgICAgaWYgKHNlbGVjdGVkSW5kZXggPT09IDApIHtcbiAgICAgICAgICBzZXRWaWV3U3RhdGUoeyB0eXBlOiAnYWRkLW1hcmtldHBsYWNlJyB9KVxuICAgICAgICB9IGVsc2UgaWYgKGhhc1BlbmRpbmdDaGFuZ2VzKCkpIHtcbiAgICAgICAgICB2b2lkIGFwcGx5Q2hhbmdlcygpXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgY29uc3QgbWFya2V0cGxhY2UgPSBtYXJrZXRwbGFjZVN0YXRlc1ttYXJrZXRwbGFjZUluZGV4XVxuICAgICAgICAgIGlmIChtYXJrZXRwbGFjZSkge1xuICAgICAgICAgICAgc2V0U2VsZWN0ZWRNYXJrZXRwbGFjZShtYXJrZXRwbGFjZSlcbiAgICAgICAgICAgIHNldEludGVybmFsVmlldygnZGV0YWlscycpXG4gICAgICAgICAgICBzZXREZXRhaWxzTWVudUluZGV4KDApXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9LFxuICAgIH0sXG4gICAgeyBjb250ZXh0OiAnU2VsZWN0JywgaXNBY3RpdmU6ICFpc1Byb2Nlc3NpbmcgJiYgaW50ZXJuYWxWaWV3ID09PSAnbGlzdCcgfSxcbiAgKVxuXG4gIC8vIExpc3QgdmlldyDigJQgbWFya2V0cGxhY2Utc3BlY2lmaWMgYWN0aW9ucyAodS9yIHNob3J0Y3V0cylcbiAgdXNlSW5wdXQoXG4gICAgaW5wdXQgPT4ge1xuICAgICAgY29uc3QgbWFya2V0cGxhY2VJbmRleCA9IHNlbGVjdGVkSW5kZXggLSAxXG4gICAgICBpZiAoKGlucHV0ID09PSAndScgfHwgaW5wdXQgPT09ICdVJykgJiYgbWFya2V0cGxhY2VJbmRleCA+PSAwKSB7XG4gICAgICAgIHNldE1hcmtldHBsYWNlU3RhdGVzKHByZXYgPT5cbiAgICAgICAgICBwcmV2Lm1hcCgoc3RhdGUsIGlkeCkgPT5cbiAgICAgICAgICAgIGlkeCA9PT0gbWFya2V0cGxhY2VJbmRleFxuICAgICAgICAgICAgICA/IHtcbiAgICAgICAgICAgICAgICAgIC4uLnN0YXRlLFxuICAgICAgICAgICAgICAgICAgcGVuZGluZ1VwZGF0ZTogIXN0YXRlLnBlbmRpbmdVcGRhdGUsXG4gICAgICAgICAgICAgICAgICBwZW5kaW5nUmVtb3ZlOiBzdGF0ZS5wZW5kaW5nVXBkYXRlXG4gICAgICAgICAgICAgICAgICAgID8gc3RhdGUucGVuZGluZ1JlbW92ZVxuICAgICAgICAgICAgICAgICAgICA6IGZhbHNlLFxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgOiBzdGF0ZSxcbiAgICAgICAgICApLFxuICAgICAgICApXG4gICAgICB9IGVsc2UgaWYgKChpbnB1dCA9PT0gJ3InIHx8IGlucHV0ID09PSAnUicpICYmIG1hcmtldHBsYWNlSW5kZXggPj0gMCkge1xuICAgICAgICBjb25zdCBtYXJrZXRwbGFjZSA9IG1hcmtldHBsYWNlU3RhdGVzW21hcmtldHBsYWNlSW5kZXhdXG4gICAgICAgIGlmIChtYXJrZXRwbGFjZSkge1xuICAgICAgICAgIHNldFNlbGVjdGVkTWFya2V0cGxhY2UobWFya2V0cGxhY2UpXG4gICAgICAgICAgc2V0SW50ZXJuYWxWaWV3KCdjb25maXJtLXJlbW92ZScpXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9LFxuICAgIHsgaXNBY3RpdmU6ICFpc1Byb2Nlc3NpbmcgJiYgaW50ZXJuYWxWaWV3ID09PSAnbGlzdCcgfSxcbiAgKVxuXG4gIC8vIERldGFpbHMgdmlldyDigJQgbmF2aWdhdGlvblxuICB1c2VLZXliaW5kaW5ncyhcbiAgICB7XG4gICAgICAnc2VsZWN0OnByZXZpb3VzJzogKCkgPT5cbiAgICAgICAgc2V0RGV0YWlsc01lbnVJbmRleChwcmV2ID0+IE1hdGgubWF4KDAsIHByZXYgLSAxKSksXG4gICAgICAnc2VsZWN0Om5leHQnOiAoKSA9PiB7XG4gICAgICAgIGNvbnN0IG1lbnVPcHRpb25zID0gYnVpbGREZXRhaWxzTWVudU9wdGlvbnMoc2VsZWN0ZWRNYXJrZXRwbGFjZSlcbiAgICAgICAgc2V0RGV0YWlsc01lbnVJbmRleChwcmV2ID0+IE1hdGgubWluKG1lbnVPcHRpb25zLmxlbmd0aCAtIDEsIHByZXYgKyAxKSlcbiAgICAgIH0sXG4gICAgICAnc2VsZWN0OmFjY2VwdCc6ICgpID0+IHtcbiAgICAgICAgaWYgKCFzZWxlY3RlZE1hcmtldHBsYWNlKSByZXR1cm5cbiAgICAgICAgY29uc3QgbWVudU9wdGlvbnMgPSBidWlsZERldGFpbHNNZW51T3B0aW9ucyhzZWxlY3RlZE1hcmtldHBsYWNlKVxuICAgICAgICBjb25zdCBzZWxlY3RlZE9wdGlvbiA9IG1lbnVPcHRpb25zW2RldGFpbHNNZW51SW5kZXhdXG4gICAgICAgIGlmIChzZWxlY3RlZE9wdGlvbj8udmFsdWUgPT09ICdicm93c2UnKSB7XG4gICAgICAgICAgc2V0Vmlld1N0YXRlKHtcbiAgICAgICAgICAgIHR5cGU6ICdicm93c2UtbWFya2V0cGxhY2UnLFxuICAgICAgICAgICAgdGFyZ2V0TWFya2V0cGxhY2U6IHNlbGVjdGVkTWFya2V0cGxhY2UubmFtZSxcbiAgICAgICAgICB9KVxuICAgICAgICB9IGVsc2UgaWYgKHNlbGVjdGVkT3B0aW9uPy52YWx1ZSA9PT0gJ3VwZGF0ZScpIHtcbiAgICAgICAgICBjb25zdCBuZXdTdGF0ZXMgPSBtYXJrZXRwbGFjZVN0YXRlcy5tYXAoc3RhdGUgPT5cbiAgICAgICAgICAgIHN0YXRlLm5hbWUgPT09IHNlbGVjdGVkTWFya2V0cGxhY2UubmFtZVxuICAgICAgICAgICAgICA/IHsgLi4uc3RhdGUsIHBlbmRpbmdVcGRhdGU6IHRydWUgfVxuICAgICAgICAgICAgICA6IHN0YXRlLFxuICAgICAgICAgIClcbiAgICAgICAgICBzZXRNYXJrZXRwbGFjZVN0YXRlcyhuZXdTdGF0ZXMpXG4gICAgICAgICAgdm9pZCBhcHBseUNoYW5nZXMobmV3U3RhdGVzKVxuICAgICAgICB9IGVsc2UgaWYgKHNlbGVjdGVkT3B0aW9uPy52YWx1ZSA9PT0gJ3RvZ2dsZS1hdXRvLXVwZGF0ZScpIHtcbiAgICAgICAgICB2b2lkIGhhbmRsZVRvZ2dsZUF1dG9VcGRhdGUoc2VsZWN0ZWRNYXJrZXRwbGFjZSlcbiAgICAgICAgfSBlbHNlIGlmIChzZWxlY3RlZE9wdGlvbj8udmFsdWUgPT09ICdyZW1vdmUnKSB7XG4gICAgICAgICAgc2V0SW50ZXJuYWxWaWV3KCdjb25maXJtLXJlbW92ZScpXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfSxcbiAgICB7XG4gICAgICBjb250ZXh0OiAnU2VsZWN0JyxcbiAgICAgIGlzQWN0aXZlOiAhaXNQcm9jZXNzaW5nICYmIGludGVybmFsVmlldyA9PT0gJ2RldGFpbHMnLFxuICAgIH0sXG4gIClcblxuICAvLyBDb25maXJtLXJlbW92ZSB2aWV3IOKAlCB5L24gaW5wdXRcbiAgdXNlSW5wdXQoXG4gICAgaW5wdXQgPT4ge1xuICAgICAgaWYgKGlucHV0ID09PSAneScgfHwgaW5wdXQgPT09ICdZJykge1xuICAgICAgICB2b2lkIGNvbmZpcm1SZW1vdmUoKVxuICAgICAgfSBlbHNlIGlmIChpbnB1dCA9PT0gJ24nIHx8IGlucHV0ID09PSAnTicpIHtcbiAgICAgICAgc2V0SW50ZXJuYWxWaWV3KCdsaXN0JylcbiAgICAgICAgc2V0U2VsZWN0ZWRNYXJrZXRwbGFjZShudWxsKVxuICAgICAgfVxuICAgIH0sXG4gICAgeyBpc0FjdGl2ZTogIWlzUHJvY2Vzc2luZyAmJiBpbnRlcm5hbFZpZXcgPT09ICdjb25maXJtLXJlbW92ZScgfSxcbiAgKVxuXG4gIGlmIChsb2FkaW5nKSB7XG4gICAgcmV0dXJuIDxUZXh0PkxvYWRpbmcgbWFya2V0cGxhY2Vz4oCmPC9UZXh0PlxuICB9XG5cbiAgaWYgKG1hcmtldHBsYWNlU3RhdGVzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiAoXG4gICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgPEJveCBtYXJnaW5Cb3R0b209ezF9PlxuICAgICAgICAgIDxUZXh0IGJvbGQ+TWFuYWdlIG1hcmtldHBsYWNlczwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG5cbiAgICAgICAgey8qIEFkZCBNYXJrZXRwbGFjZSBvcHRpb24gKi99XG4gICAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cInJvd1wiIGdhcD17MX0+XG4gICAgICAgICAgPFRleHQgY29sb3I9XCJzdWdnZXN0aW9uXCI+e2ZpZ3VyZXMucG9pbnRlcn0gKzwvVGV4dD5cbiAgICAgICAgICA8VGV4dCBib2xkIGNvbG9yPVwic3VnZ2VzdGlvblwiPlxuICAgICAgICAgICAgQWRkIE1hcmtldHBsYWNlXG4gICAgICAgICAgPC9UZXh0PlxuICAgICAgICA8L0JveD5cblxuICAgICAgICA8Qm94IG1hcmdpbkxlZnQ9ezN9PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yIGl0YWxpYz5cbiAgICAgICAgICAgIHtleGl0U3RhdGUucGVuZGluZyA/IChcbiAgICAgICAgICAgICAgPD5QcmVzcyB7ZXhpdFN0YXRlLmtleU5hbWV9IGFnYWluIHRvIGdvIGJhY2s8Lz5cbiAgICAgICAgICAgICkgOiAoXG4gICAgICAgICAgICAgIDxCeWxpbmU+XG4gICAgICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICAgICAgYWN0aW9uPVwic2VsZWN0OmFjY2VwdFwiXG4gICAgICAgICAgICAgICAgICBjb250ZXh0PVwiU2VsZWN0XCJcbiAgICAgICAgICAgICAgICAgIGZhbGxiYWNrPVwiRW50ZXJcIlxuICAgICAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJzZWxlY3RcIlxuICAgICAgICAgICAgICAgIC8+XG4gICAgICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTpub1wiXG4gICAgICAgICAgICAgICAgICBjb250ZXh0PVwiQ29uZmlybWF0aW9uXCJcbiAgICAgICAgICAgICAgICAgIGZhbGxiYWNrPVwiRXNjXCJcbiAgICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwiZ28gYmFja1wiXG4gICAgICAgICAgICAgICAgLz5cbiAgICAgICAgICAgICAgPC9CeWxpbmU+XG4gICAgICAgICAgICApfVxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICA8L0JveD5cbiAgICApXG4gIH1cblxuICAvLyBTaG93IGNvbmZpcm1hdGlvbiBkaWFsb2dcbiAgaWYgKGludGVybmFsVmlldyA9PT0gJ2NvbmZpcm0tcmVtb3ZlJyAmJiBzZWxlY3RlZE1hcmtldHBsYWNlKSB7XG4gICAgY29uc3QgcGx1Z2luQ291bnQgPSBzZWxlY3RlZE1hcmtldHBsYWNlLmluc3RhbGxlZFBsdWdpbnM/Lmxlbmd0aCB8fCAwXG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8VGV4dCBib2xkIGNvbG9yPVwid2FybmluZ1wiPlxuICAgICAgICAgIFJlbW92ZSBtYXJrZXRwbGFjZSA8VGV4dCBpdGFsaWM+e3NlbGVjdGVkTWFya2V0cGxhY2UubmFtZX08L1RleHQ+P1xuICAgICAgICA8L1RleHQ+XG4gICAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICAgIHtwbHVnaW5Db3VudCA+IDAgJiYgKFxuICAgICAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgICAgICA8VGV4dCBjb2xvcj1cIndhcm5pbmdcIj5cbiAgICAgICAgICAgICAgICBUaGlzIHdpbGwgYWxzbyB1bmluc3RhbGwge3BsdWdpbkNvdW50fXsnICd9XG4gICAgICAgICAgICAgICAge3BsdXJhbChwbHVnaW5Db3VudCwgJ3BsdWdpbicpfSBmcm9tIHRoaXMgbWFya2V0cGxhY2U6XG4gICAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICAgIDwvQm94PlxuICAgICAgICAgICl9XG4gICAgICAgICAge3NlbGVjdGVkTWFya2V0cGxhY2UuaW5zdGFsbGVkUGx1Z2lucyAmJlxuICAgICAgICAgICAgc2VsZWN0ZWRNYXJrZXRwbGFjZS5pbnN0YWxsZWRQbHVnaW5zLmxlbmd0aCA+IDAgJiYgKFxuICAgICAgICAgICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIiBtYXJnaW5Ub3A9ezF9IG1hcmdpbkxlZnQ9ezJ9PlxuICAgICAgICAgICAgICAgIHtzZWxlY3RlZE1hcmtldHBsYWNlLmluc3RhbGxlZFBsdWdpbnMubWFwKHBsdWdpbiA9PiAoXG4gICAgICAgICAgICAgICAgICA8VGV4dCBrZXk9e3BsdWdpbi5uYW1lfSBkaW1Db2xvcj5cbiAgICAgICAgICAgICAgICAgICAg4oCiIHtwbHVnaW4ubmFtZX1cbiAgICAgICAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICAgICAgICApKX1cbiAgICAgICAgICAgICAgPC9Cb3g+XG4gICAgICAgICAgICApfVxuICAgICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICAgIDxUZXh0PlxuICAgICAgICAgICAgICBQcmVzcyA8VGV4dCBib2xkPnk8L1RleHQ+IHRvIGNvbmZpcm0gb3IgPFRleHQgYm9sZD5uPC9UZXh0PiB0b1xuICAgICAgICAgICAgICBjYW5jZWxcbiAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICA8L0JveD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICA8L0JveD5cbiAgICApXG4gIH1cblxuICAvLyBTaG93IG1hcmtldHBsYWNlIGRldGFpbHNcbiAgaWYgKGludGVybmFsVmlldyA9PT0gJ2RldGFpbHMnICYmIHNlbGVjdGVkTWFya2V0cGxhY2UpIHtcbiAgICAvLyBDaGVjayBpZiB0aGlzIG1hcmtldHBsYWNlIGlzIGN1cnJlbnRseSBiZWluZyBwcm9jZXNzZWRcbiAgICAvLyBDaGVjayBwZW5kaW5nVXBkYXRlIGZpcnN0IHNvIHdlIHNob3cgdXBkYXRpbmcgc3RhdGUgaW1tZWRpYXRlbHkgd2hlbiB1c2VyIHByZXNzZXMgRW50ZXJcbiAgICBjb25zdCBpc1VwZGF0aW5nID0gc2VsZWN0ZWRNYXJrZXRwbGFjZS5wZW5kaW5nVXBkYXRlIHx8IGlzUHJvY2Vzc2luZ1xuXG4gICAgY29uc3QgbWVudU9wdGlvbnMgPSBidWlsZERldGFpbHNNZW51T3B0aW9ucyhzZWxlY3RlZE1hcmtldHBsYWNlKVxuXG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8VGV4dCBib2xkPntzZWxlY3RlZE1hcmtldHBsYWNlLm5hbWV9PC9UZXh0PlxuICAgICAgICA8VGV4dCBkaW1Db2xvcj57c2VsZWN0ZWRNYXJrZXRwbGFjZS5zb3VyY2V9PC9UZXh0PlxuICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgPFRleHQ+XG4gICAgICAgICAgICB7c2VsZWN0ZWRNYXJrZXRwbGFjZS5wbHVnaW5Db3VudCB8fCAwfSBhdmFpbGFibGV7JyAnfVxuICAgICAgICAgICAge3BsdXJhbChzZWxlY3RlZE1hcmtldHBsYWNlLnBsdWdpbkNvdW50IHx8IDAsICdwbHVnaW4nKX1cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuXG4gICAgICAgIHsvKiBJbnN0YWxsZWQgcGx1Z2lucyBzZWN0aW9uICovfVxuICAgICAgICB7c2VsZWN0ZWRNYXJrZXRwbGFjZS5pbnN0YWxsZWRQbHVnaW5zICYmXG4gICAgICAgICAgc2VsZWN0ZWRNYXJrZXRwbGFjZS5pbnN0YWxsZWRQbHVnaW5zLmxlbmd0aCA+IDAgJiYgKFxuICAgICAgICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCIgbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICAgICAgPFRleHQgYm9sZD5cbiAgICAgICAgICAgICAgICBJbnN0YWxsZWQgcGx1Z2lucyAoe3NlbGVjdGVkTWFya2V0cGxhY2UuaW5zdGFsbGVkUGx1Z2lucy5sZW5ndGh9XG4gICAgICAgICAgICAgICAgKTpcbiAgICAgICAgICAgICAgPC9UZXh0PlxuICAgICAgICAgICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIiBtYXJnaW5MZWZ0PXsxfT5cbiAgICAgICAgICAgICAgICB7c2VsZWN0ZWRNYXJrZXRwbGFjZS5pbnN0YWxsZWRQbHVnaW5zLm1hcChwbHVnaW4gPT4gKFxuICAgICAgICAgICAgICAgICAgPEJveCBrZXk9e3BsdWdpbi5uYW1lfSBmbGV4RGlyZWN0aW9uPVwicm93XCIgZ2FwPXsxfT5cbiAgICAgICAgICAgICAgICAgICAgPFRleHQ+e2ZpZ3VyZXMuYnVsbGV0fTwvVGV4dD5cbiAgICAgICAgICAgICAgICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgICAgICAgICAgICAgICAgPFRleHQ+e3BsdWdpbi5uYW1lfTwvVGV4dD5cbiAgICAgICAgICAgICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj57cGx1Z2luLm1hbmlmZXN0LmRlc2NyaXB0aW9ufTwvVGV4dD5cbiAgICAgICAgICAgICAgICAgICAgPC9Cb3g+XG4gICAgICAgICAgICAgICAgICA8L0JveD5cbiAgICAgICAgICAgICAgICApKX1cbiAgICAgICAgICAgICAgPC9Cb3g+XG4gICAgICAgICAgICA8L0JveD5cbiAgICAgICAgICApfVxuXG4gICAgICAgIHsvKiBQcm9jZXNzaW5nIGluZGljYXRvciAqL31cbiAgICAgICAge2lzVXBkYXRpbmcgJiYgKFxuICAgICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfSBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgICAgICA8VGV4dCBjb2xvcj1cImNsYXVkZVwiPlVwZGF0aW5nIG1hcmtldHBsYWNl4oCmPC9UZXh0PlxuICAgICAgICAgICAge3Byb2dyZXNzTWVzc2FnZSAmJiA8VGV4dCBkaW1Db2xvcj57cHJvZ3Jlc3NNZXNzYWdlfTwvVGV4dD59XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICl9XG5cbiAgICAgICAgey8qIFN1Y2Nlc3MgbWVzc2FnZSAqL31cbiAgICAgICAgeyFpc1VwZGF0aW5nICYmIHN1Y2Nlc3NNZXNzYWdlICYmIChcbiAgICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgICA8VGV4dCBjb2xvcj1cImNsYXVkZVwiPntzdWNjZXNzTWVzc2FnZX08L1RleHQ+XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICl9XG5cbiAgICAgICAgey8qIEVycm9yIG1lc3NhZ2UgKi99XG4gICAgICAgIHshaXNVcGRhdGluZyAmJiBwcm9jZXNzRXJyb3IgJiYgKFxuICAgICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICAgIDxUZXh0IGNvbG9yPVwiZXJyb3JcIj57cHJvY2Vzc0Vycm9yfTwvVGV4dD5cbiAgICAgICAgICA8L0JveD5cbiAgICAgICAgKX1cblxuICAgICAgICB7LyogTWVudSBvcHRpb25zICovfVxuICAgICAgICB7IWlzVXBkYXRpbmcgJiYgKFxuICAgICAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiIG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgICB7bWVudU9wdGlvbnMubWFwKChvcHRpb24sIGlkeCkgPT4ge1xuICAgICAgICAgICAgICBpZiAoIW9wdGlvbikgcmV0dXJuIG51bGxcbiAgICAgICAgICAgICAgY29uc3QgaXNTZWxlY3RlZCA9IGlkeCA9PT0gZGV0YWlsc01lbnVJbmRleFxuICAgICAgICAgICAgICByZXR1cm4gKFxuICAgICAgICAgICAgICAgIDxCb3gga2V5PXtvcHRpb24udmFsdWV9PlxuICAgICAgICAgICAgICAgICAgPFRleHQgY29sb3I9e2lzU2VsZWN0ZWQgPyAnc3VnZ2VzdGlvbicgOiB1bmRlZmluZWR9PlxuICAgICAgICAgICAgICAgICAgICB7aXNTZWxlY3RlZCA/IGZpZ3VyZXMucG9pbnRlciA6ICcgJ30ge29wdGlvbi5sYWJlbH1cbiAgICAgICAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICAgICAgICAgIHtvcHRpb24uc2Vjb25kYXJ5TGFiZWwgJiYgKFxuICAgICAgICAgICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj4ge29wdGlvbi5zZWNvbmRhcnlMYWJlbH08L1RleHQ+XG4gICAgICAgICAgICAgICAgICApfVxuICAgICAgICAgICAgICAgIDwvQm94PlxuICAgICAgICAgICAgICApXG4gICAgICAgICAgICB9KX1cbiAgICAgICAgICA8L0JveD5cbiAgICAgICAgKX1cblxuICAgICAgICB7LyogU2hvdyBleHBsYW5hdG9yeSB0ZXh0IGF0IHRoZSBib3R0b20gd2hlbiBhdXRvLXVwZGF0ZSBpcyBlbmFibGVkICovfVxuICAgICAgICB7IWlzVXBkYXRpbmcgJiZcbiAgICAgICAgICAhc2hvdWxkU2tpcFBsdWdpbkF1dG91cGRhdGUoKSAmJlxuICAgICAgICAgIHNlbGVjdGVkTWFya2V0cGxhY2UuYXV0b1VwZGF0ZSAmJiAoXG4gICAgICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgICAgICAgIEF1dG8tdXBkYXRlIGVuYWJsZWQuIENsYXVkZSBDb2RlIHdpbGwgYXV0b21hdGljYWxseSB1cGRhdGUgdGhpc1xuICAgICAgICAgICAgICAgIG1hcmtldHBsYWNlIGFuZCBpdHMgaW5zdGFsbGVkIHBsdWdpbnMuXG4gICAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICAgIDwvQm94PlxuICAgICAgICAgICl9XG5cbiAgICAgICAgPEJveCBtYXJnaW5MZWZ0PXszfT5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvciBpdGFsaWM+XG4gICAgICAgICAgICB7aXNVcGRhdGluZyA/IChcbiAgICAgICAgICAgICAgPD5QbGVhc2Ugd2FpdOKApjwvPlxuICAgICAgICAgICAgKSA6IChcbiAgICAgICAgICAgICAgPEJ5bGluZT5cbiAgICAgICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgICAgICBhY3Rpb249XCJzZWxlY3Q6YWNjZXB0XCJcbiAgICAgICAgICAgICAgICAgIGNvbnRleHQ9XCJTZWxlY3RcIlxuICAgICAgICAgICAgICAgICAgZmFsbGJhY2s9XCJFbnRlclwiXG4gICAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cInNlbGVjdFwiXG4gICAgICAgICAgICAgICAgLz5cbiAgICAgICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgICAgICBhY3Rpb249XCJjb25maXJtOm5vXCJcbiAgICAgICAgICAgICAgICAgIGNvbnRleHQ9XCJDb25maXJtYXRpb25cIlxuICAgICAgICAgICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJnbyBiYWNrXCJcbiAgICAgICAgICAgICAgICAvPlxuICAgICAgICAgICAgICA8L0J5bGluZT5cbiAgICAgICAgICAgICl9XG4gICAgICAgICAgPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgIDwvQm94PlxuICAgIClcbiAgfVxuXG4gIC8vIFNob3cgbWFya2V0cGxhY2UgbGlzdFxuICBjb25zdCB7IHVwZGF0ZUNvdW50LCByZW1vdmVDb3VudCB9ID0gZ2V0UGVuZGluZ0NvdW50cygpXG5cbiAgcmV0dXJuIChcbiAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgIDxCb3ggbWFyZ2luQm90dG9tPXsxfT5cbiAgICAgICAgPFRleHQgYm9sZD5NYW5hZ2UgbWFya2V0cGxhY2VzPC9UZXh0PlxuICAgICAgPC9Cb3g+XG5cbiAgICAgIHsvKiBBZGQgTWFya2V0cGxhY2Ugb3B0aW9uICovfVxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwicm93XCIgZ2FwPXsxfSBtYXJnaW5Cb3R0b209ezF9PlxuICAgICAgICA8VGV4dCBjb2xvcj17c2VsZWN0ZWRJbmRleCA9PT0gMCA/ICdzdWdnZXN0aW9uJyA6IHVuZGVmaW5lZH0+XG4gICAgICAgICAge3NlbGVjdGVkSW5kZXggPT09IDAgPyBmaWd1cmVzLnBvaW50ZXIgOiAnICd9ICtcbiAgICAgICAgPC9UZXh0PlxuICAgICAgICA8VGV4dCBib2xkIGNvbG9yPXtzZWxlY3RlZEluZGV4ID09PSAwID8gJ3N1Z2dlc3Rpb24nIDogdW5kZWZpbmVkfT5cbiAgICAgICAgICBBZGQgTWFya2V0cGxhY2VcbiAgICAgICAgPC9UZXh0PlxuICAgICAgPC9Cb3g+XG5cbiAgICAgIHsvKiBNYXJrZXRwbGFjZSBsaXN0ICovfVxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgIHttYXJrZXRwbGFjZVN0YXRlcy5tYXAoKHN0YXRlLCBpZHgpID0+IHtcbiAgICAgICAgICBjb25zdCBpc1NlbGVjdGVkID0gaWR4ICsgMSA9PT0gc2VsZWN0ZWRJbmRleCAvLyArMSBiZWNhdXNlIEFkZCBNYXJrZXRwbGFjZSBpcyBhdCBpbmRleCAwXG5cbiAgICAgICAgICAvLyBCdWlsZCBzdGF0dXMgaW5kaWNhdG9yc1xuICAgICAgICAgIGNvbnN0IGluZGljYXRvcnM6IHN0cmluZ1tdID0gW11cbiAgICAgICAgICBpZiAoc3RhdGUucGVuZGluZ1VwZGF0ZSkgaW5kaWNhdG9ycy5wdXNoKCdVUERBVEUnKVxuICAgICAgICAgIGlmIChzdGF0ZS5wZW5kaW5nUmVtb3ZlKSBpbmRpY2F0b3JzLnB1c2goJ1JFTU9WRScpXG5cbiAgICAgICAgICByZXR1cm4gKFxuICAgICAgICAgICAgPEJveCBrZXk9e3N0YXRlLm5hbWV9IGZsZXhEaXJlY3Rpb249XCJyb3dcIiBnYXA9ezF9IG1hcmdpbkJvdHRvbT17MX0+XG4gICAgICAgICAgICAgIDxUZXh0IGNvbG9yPXtpc1NlbGVjdGVkID8gJ3N1Z2dlc3Rpb24nIDogdW5kZWZpbmVkfT5cbiAgICAgICAgICAgICAgICB7aXNTZWxlY3RlZCA/IGZpZ3VyZXMucG9pbnRlciA6ICcgJ317JyAnfVxuICAgICAgICAgICAgICAgIHtzdGF0ZS5wZW5kaW5nUmVtb3ZlID8gZmlndXJlcy5jcm9zcyA6IGZpZ3VyZXMuYnVsbGV0fVxuICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiIGZsZXhHcm93PXsxfT5cbiAgICAgICAgICAgICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJyb3dcIiBnYXA9ezF9PlxuICAgICAgICAgICAgICAgICAgPFRleHRcbiAgICAgICAgICAgICAgICAgICAgYm9sZFxuICAgICAgICAgICAgICAgICAgICBzdHJpa2V0aHJvdWdoPXtzdGF0ZS5wZW5kaW5nUmVtb3ZlfVxuICAgICAgICAgICAgICAgICAgICBkaW1Db2xvcj17c3RhdGUucGVuZGluZ1JlbW92ZX1cbiAgICAgICAgICAgICAgICAgID5cbiAgICAgICAgICAgICAgICAgICAge3N0YXRlLm5hbWUgPT09ICdjbGF1ZGUtcGx1Z2lucy1vZmZpY2lhbCcgJiYgKFxuICAgICAgICAgICAgICAgICAgICAgIDxUZXh0IGNvbG9yPVwiY2xhdWRlXCI+4py7IDwvVGV4dD5cbiAgICAgICAgICAgICAgICAgICAgKX1cbiAgICAgICAgICAgICAgICAgICAge3N0YXRlLm5hbWV9XG4gICAgICAgICAgICAgICAgICAgIHtzdGF0ZS5uYW1lID09PSAnY2xhdWRlLXBsdWdpbnMtb2ZmaWNpYWwnICYmIChcbiAgICAgICAgICAgICAgICAgICAgICA8VGV4dCBjb2xvcj1cImNsYXVkZVwiPiDinLs8L1RleHQ+XG4gICAgICAgICAgICAgICAgICAgICl9XG4gICAgICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgICAgICB7aW5kaWNhdG9ycy5sZW5ndGggPiAwICYmIChcbiAgICAgICAgICAgICAgICAgICAgPFRleHQgY29sb3I9XCJ3YXJuaW5nXCI+W3tpbmRpY2F0b3JzLmpvaW4oJywgJyl9XTwvVGV4dD5cbiAgICAgICAgICAgICAgICAgICl9XG4gICAgICAgICAgICAgICAgPC9Cb3g+XG4gICAgICAgICAgICAgICAgPFRleHQgZGltQ29sb3I+e3N0YXRlLnNvdXJjZX08L1RleHQ+XG4gICAgICAgICAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgICAgICAgICB7c3RhdGUucGx1Z2luQ291bnQgIT09IHVuZGVmaW5lZCAmJiAoXG4gICAgICAgICAgICAgICAgICAgIDw+e3N0YXRlLnBsdWdpbkNvdW50fSBhdmFpbGFibGU8Lz5cbiAgICAgICAgICAgICAgICAgICl9XG4gICAgICAgICAgICAgICAgICB7c3RhdGUuaW5zdGFsbGVkUGx1Z2lucyAmJlxuICAgICAgICAgICAgICAgICAgICBzdGF0ZS5pbnN0YWxsZWRQbHVnaW5zLmxlbmd0aCA+IDAgJiYgKFxuICAgICAgICAgICAgICAgICAgICAgIDw+IOKAoiB7c3RhdGUuaW5zdGFsbGVkUGx1Z2lucy5sZW5ndGh9IGluc3RhbGxlZDwvPlxuICAgICAgICAgICAgICAgICAgICApfVxuICAgICAgICAgICAgICAgICAge3N0YXRlLmxhc3RVcGRhdGVkICYmIChcbiAgICAgICAgICAgICAgICAgICAgPD5cbiAgICAgICAgICAgICAgICAgICAgICB7JyAnfVxuICAgICAgICAgICAgICAgICAgICAgIOKAoiBVcGRhdGVkeycgJ31cbiAgICAgICAgICAgICAgICAgICAgICB7bmV3IERhdGUoc3RhdGUubGFzdFVwZGF0ZWQpLnRvTG9jYWxlRGF0ZVN0cmluZygpfVxuICAgICAgICAgICAgICAgICAgICA8Lz5cbiAgICAgICAgICAgICAgICAgICl9XG4gICAgICAgICAgICAgICAgPC9UZXh0PlxuICAgICAgICAgICAgICA8L0JveD5cbiAgICAgICAgICAgIDwvQm94PlxuICAgICAgICAgIClcbiAgICAgICAgfSl9XG4gICAgICA8L0JveD5cblxuICAgICAgey8qIFBlbmRpbmcgY2hhbmdlcyBzdW1tYXJ5ICovfVxuICAgICAge2hhc1BlbmRpbmdDaGFuZ2VzKCkgJiYgKFxuICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0gZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICAgIDxUZXh0PlxuICAgICAgICAgICAgPFRleHQgYm9sZD5QZW5kaW5nIGNoYW5nZXM6PC9UZXh0PnsnICd9XG4gICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5FbnRlciB0byBhcHBseTwvVGV4dD5cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAge3VwZGF0ZUNvdW50ID4gMCAmJiAoXG4gICAgICAgICAgICA8VGV4dD5cbiAgICAgICAgICAgICAg4oCiIFVwZGF0ZSB7dXBkYXRlQ291bnR9IHtwbHVyYWwodXBkYXRlQ291bnQsICdtYXJrZXRwbGFjZScpfVxuICAgICAgICAgICAgPC9UZXh0PlxuICAgICAgICAgICl9XG4gICAgICAgICAge3JlbW92ZUNvdW50ID4gMCAmJiAoXG4gICAgICAgICAgICA8VGV4dCBjb2xvcj1cIndhcm5pbmdcIj5cbiAgICAgICAgICAgICAg4oCiIFJlbW92ZSB7cmVtb3ZlQ291bnR9IHtwbHVyYWwocmVtb3ZlQ291bnQsICdtYXJrZXRwbGFjZScpfVxuICAgICAgICAgICAgPC9UZXh0PlxuICAgICAgICAgICl9XG4gICAgICAgIDwvQm94PlxuICAgICAgKX1cblxuICAgICAgey8qIFByb2Nlc3NpbmcgaW5kaWNhdG9yICovfVxuICAgICAge2lzUHJvY2Vzc2luZyAmJiAoXG4gICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICA8VGV4dCBjb2xvcj1cImNsYXVkZVwiPlByb2Nlc3NpbmcgY2hhbmdlc+KApjwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICApfVxuXG4gICAgICB7LyogRXJyb3IgZGlzcGxheSAqL31cbiAgICAgIHtwcm9jZXNzRXJyb3IgJiYgKFxuICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgPFRleHQgY29sb3I9XCJlcnJvclwiPntwcm9jZXNzRXJyb3J9PC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICl9XG5cbiAgICAgIDxNYW5hZ2VNYXJrZXRwbGFjZXNLZXlIaW50c1xuICAgICAgICBleGl0U3RhdGU9e2V4aXRTdGF0ZX1cbiAgICAgICAgaGFzUGVuZGluZ0FjdGlvbnM9e2hhc1BlbmRpbmdDaGFuZ2VzKCl9XG4gICAgICAvPlxuICAgIDwvQm94PlxuICApXG59XG5cbnR5cGUgTWFuYWdlTWFya2V0cGxhY2VzS2V5SGludHNQcm9wcyA9IHtcbiAgZXhpdFN0YXRlOiBQcm9wc1snZXhpdFN0YXRlJ11cbiAgaGFzUGVuZGluZ0FjdGlvbnM6IGJvb2xlYW5cbn1cblxuZnVuY3Rpb24gTWFuYWdlTWFya2V0cGxhY2VzS2V5SGludHMoe1xuICBleGl0U3RhdGUsXG4gIGhhc1BlbmRpbmdBY3Rpb25zLFxufTogTWFuYWdlTWFya2V0cGxhY2VzS2V5SGludHNQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGlmIChleGl0U3RhdGUucGVuZGluZykge1xuICAgIHJldHVybiAoXG4gICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yIGl0YWxpYz5cbiAgICAgICAgICBQcmVzcyB7ZXhpdFN0YXRlLmtleU5hbWV9IGFnYWluIHRvIGdvIGJhY2tcbiAgICAgICAgPC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgcmV0dXJuIChcbiAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICA8VGV4dCBkaW1Db2xvciBpdGFsaWM+XG4gICAgICAgIDxCeWxpbmU+XG4gICAgICAgICAge2hhc1BlbmRpbmdBY3Rpb25zICYmIChcbiAgICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgICAgYWN0aW9uPVwic2VsZWN0OmFjY2VwdFwiXG4gICAgICAgICAgICAgIGNvbnRleHQ9XCJTZWxlY3RcIlxuICAgICAgICAgICAgICBmYWxsYmFjaz1cIkVudGVyXCJcbiAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJhcHBseSBjaGFuZ2VzXCJcbiAgICAgICAgICAgIC8+XG4gICAgICAgICAgKX1cbiAgICAgICAgICB7IWhhc1BlbmRpbmdBY3Rpb25zICYmIChcbiAgICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgICAgYWN0aW9uPVwic2VsZWN0OmFjY2VwdFwiXG4gICAgICAgICAgICAgIGNvbnRleHQ9XCJTZWxlY3RcIlxuICAgICAgICAgICAgICBmYWxsYmFjaz1cIkVudGVyXCJcbiAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJzZWxlY3RcIlxuICAgICAgICAgICAgLz5cbiAgICAgICAgICApfVxuICAgICAgICAgIHshaGFzUGVuZGluZ0FjdGlvbnMgJiYgKFxuICAgICAgICAgICAgPEtleWJvYXJkU2hvcnRjdXRIaW50IHNob3J0Y3V0PVwidVwiIGFjdGlvbj1cInVwZGF0ZVwiIC8+XG4gICAgICAgICAgKX1cbiAgICAgICAgICB7IWhhc1BlbmRpbmdBY3Rpb25zICYmIChcbiAgICAgICAgICAgIDxLZXlib2FyZFNob3J0Y3V0SGludCBzaG9ydGN1dD1cInJcIiBhY3Rpb249XCJyZW1vdmVcIiAvPlxuICAgICAgICAgICl9XG4gICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTpub1wiXG4gICAgICAgICAgICBjb250ZXh0PVwiQ29uZmlybWF0aW9uXCJcbiAgICAgICAgICAgIGZhbGxiYWNrPVwiRXNjXCJcbiAgICAgICAgICAgIGRlc2NyaXB0aW9uPXtoYXNQZW5kaW5nQWN0aW9ucyA/ICdjYW5jZWwnIDogJ2dvIGJhY2snfVxuICAgICAgICAgIC8+XG4gICAgICAgIDwvQnlsaW5lPlxuICAgICAgPC9UZXh0PlxuICAgIDwvQm94PlxuICApXG59XG4iXSwibWFwcGluZ3MiOiI7QUFBQSxPQUFPQSxPQUFPLE1BQU0sU0FBUztBQUM3QixPQUFPLEtBQUtDLEtBQUssTUFBTSxPQUFPO0FBQzlCLFNBQVNDLFNBQVMsRUFBRUMsTUFBTSxFQUFFQyxRQUFRLFFBQVEsT0FBTztBQUNuRCxTQUNFLEtBQUtDLDBEQUEwRCxFQUMvREMsUUFBUSxRQUNILGlDQUFpQztBQUN4QyxTQUFTQyx3QkFBd0IsUUFBUSw4Q0FBOEM7QUFDdkYsU0FBU0MsTUFBTSxRQUFRLDBDQUEwQztBQUNqRSxTQUFTQyxvQkFBb0IsUUFBUSx3REFBd0Q7QUFDN0Y7QUFDQSxTQUFTQyxHQUFHLEVBQUVDLElBQUksRUFBRUMsUUFBUSxRQUFRLGNBQWM7QUFDbEQsU0FDRUMsYUFBYSxFQUNiQyxjQUFjLFFBQ1Qsb0NBQW9DO0FBQzNDLGNBQWNDLFlBQVksUUFBUSx1QkFBdUI7QUFDekQsU0FBU0MsS0FBSyxRQUFRLHNCQUFzQjtBQUM1QyxTQUFTQywwQkFBMEIsUUFBUSx1QkFBdUI7QUFDbEUsU0FBU0MsWUFBWSxRQUFRLHVCQUF1QjtBQUNwRCxTQUFTQyxjQUFjLFFBQVEsbUNBQW1DO0FBQ2xFLFNBQ0VDLGNBQWMsRUFDZEMsOEJBQThCLEVBQzlCQywyQkFBMkIsRUFDM0JDLHVDQUF1QyxRQUNsQywyQ0FBMkM7QUFDbEQsU0FDRUMsMkJBQTJCLEVBQzNCQyxrQkFBa0IsRUFDbEJDLHVCQUF1QixFQUN2QkMsd0JBQXdCLFFBQ25CLDJDQUEyQztBQUNsRCxTQUFTQyw0QkFBNEIsUUFBUSx5Q0FBeUM7QUFDdEYsU0FBU0MsY0FBYyxRQUFRLHFDQUFxQztBQUNwRSxTQUFTQyx1QkFBdUIsUUFBUSxnQ0FBZ0M7QUFDeEUs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 \ No newline at end of file diff --git a/src/commands/plugin/ManagePlugins.tsx b/src/commands/plugin/ManagePlugins.tsx new file mode 100644 index 0000000000000000000000000000000000000000..64d9a342888ed45f7ae114142e0fa8bdc71e6139 --- /dev/null +++ b/src/commands/plugin/ManagePlugins.tsx @@ -0,0 +1,2215 @@ +import figures from 'figures'; +import type { Dirent } from 'fs'; +import * as fs from 'fs/promises'; +import * as path from 'path'; +import * as React from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js'; +import { Byline } from '../../components/design-system/Byline.js'; +import { MCPRemoteServerMenu } from '../../components/mcp/MCPRemoteServerMenu.js'; +import { MCPStdioServerMenu } from '../../components/mcp/MCPStdioServerMenu.js'; +import { MCPToolDetailView } from '../../components/mcp/MCPToolDetailView.js'; +import { MCPToolListView } from '../../components/mcp/MCPToolListView.js'; +import type { ClaudeAIServerInfo, HTTPServerInfo, SSEServerInfo, StdioServerInfo } from '../../components/mcp/types.js'; +import { SearchBox } from '../../components/SearchBox.js'; +import { useSearchInput } from '../../hooks/useSearchInput.js'; +import { useTerminalSize } from '../../hooks/useTerminalSize.js'; +// eslint-disable-next-line custom-rules/prefer-use-keybindings -- useInput needed for raw search mode text input +import { Box, Text, useInput, useTerminalFocus } from '../../ink.js'; +import { useKeybinding, useKeybindings } from '../../keybindings/useKeybinding.js'; +import { getBuiltinPluginDefinition } from '../../plugins/builtinPlugins.js'; +import { useMcpToggleEnabled } from '../../services/mcp/MCPConnectionManager.js'; +import type { MCPServerConnection, McpClaudeAIProxyServerConfig, McpHTTPServerConfig, McpSSEServerConfig, McpStdioServerConfig } from '../../services/mcp/types.js'; +import { filterToolsByServer } from '../../services/mcp/utils.js'; +import { disablePluginOp, enablePluginOp, getPluginInstallationFromV2, isInstallableScope, isPluginEnabledAtProjectScope, uninstallPluginOp, updatePluginOp } from '../../services/plugins/pluginOperations.js'; +import { useAppState } from '../../state/AppState.js'; +import type { Tool } from '../../Tool.js'; +import type { LoadedPlugin, PluginError } from '../../types/plugin.js'; +import { count } from '../../utils/array.js'; +import { openBrowser } from '../../utils/browser.js'; +import { logForDebugging } from '../../utils/debug.js'; +import { errorMessage, toError } from '../../utils/errors.js'; +import { logError } from '../../utils/log.js'; +import { clearAllCaches } from '../../utils/plugins/cacheUtils.js'; +import { loadInstalledPluginsV2 } from '../../utils/plugins/installedPluginsManager.js'; +import { getMarketplace } from '../../utils/plugins/marketplaceManager.js'; +import { isMcpbSource, loadMcpbFile, type McpbNeedsConfigResult, type UserConfigValues } from '../../utils/plugins/mcpbHandler.js'; +import { getPluginDataDirSize, pluginDataDirPath } from '../../utils/plugins/pluginDirectories.js'; +import { getFlaggedPlugins, markFlaggedPluginsSeen, removeFlaggedPlugin } from '../../utils/plugins/pluginFlagging.js'; +import { type PersistablePluginScope, parsePluginIdentifier } from '../../utils/plugins/pluginIdentifier.js'; +import { loadAllPlugins } from '../../utils/plugins/pluginLoader.js'; +import { loadPluginOptions, type PluginOptionSchema, savePluginOptions } from '../../utils/plugins/pluginOptionsStorage.js'; +import { isPluginBlockedByPolicy } from '../../utils/plugins/pluginPolicy.js'; +import { getPluginEditableScopes } from '../../utils/plugins/pluginStartupCheck.js'; +import { getSettings_DEPRECATED, getSettingsForSource, updateSettingsForSource } from '../../utils/settings/settings.js'; +import { jsonParse } from '../../utils/slowOperations.js'; +import { plural } from '../../utils/stringUtils.js'; +import { formatErrorMessage, getErrorGuidance } from './PluginErrors.js'; +import { PluginOptionsDialog } from './PluginOptionsDialog.js'; +import { PluginOptionsFlow } from './PluginOptionsFlow.js'; +import type { ViewState as ParentViewState } from './types.js'; +import { UnifiedInstalledCell } from './UnifiedInstalledCell.js'; +import type { UnifiedInstalledItem } from './unifiedTypes.js'; +import { usePagination } from './usePagination.js'; +type Props = { + setViewState: (state: ParentViewState) => void; + setResult: (result: string | null) => void; + onManageComplete?: () => void | Promise; + onSearchModeChange?: (isActive: boolean) => void; + targetPlugin?: string; + targetMarketplace?: string; + action?: 'enable' | 'disable' | 'uninstall'; +}; +type FlaggedPluginInfo = { + id: string; + name: string; + marketplace: string; + reason: string; + text: string; + flaggedAt: string; +}; +type FailedPluginInfo = { + id: string; + name: string; + marketplace: string; + errors: PluginError[]; + scope: PersistablePluginScope; +}; +type ViewState = 'plugin-list' | 'plugin-details' | 'configuring' | { + type: 'plugin-options'; +} | { + type: 'configuring-options'; + schema: PluginOptionSchema; +} | 'confirm-project-uninstall' | { + type: 'confirm-data-cleanup'; + size: { + bytes: number; + human: string; + }; +} | { + type: 'flagged-detail'; + plugin: FlaggedPluginInfo; +} | { + type: 'failed-plugin-details'; + plugin: FailedPluginInfo; +} | { + type: 'mcp-detail'; + client: MCPServerConnection; +} | { + type: 'mcp-tools'; + client: MCPServerConnection; +} | { + type: 'mcp-tool-detail'; + client: MCPServerConnection; + tool: Tool; +}; +type MarketplaceInfo = { + name: string; + installedPlugins: LoadedPlugin[]; + enabledCount?: number; + disabledCount?: number; +}; +type PluginState = { + plugin: LoadedPlugin; + marketplace: string; + scope?: 'user' | 'project' | 'local' | 'managed' | 'builtin'; + pendingEnable?: boolean; // Toggle enable/disable + pendingUpdate?: boolean; // Marked for update +}; + +/** + * Get list of base file names (without .md extension) from a directory + * @param dirPath The directory path to list files from + * @returns Array of base file names without .md extension + * @example + * // Given directory contains: agent-sdk-verifier-py.md, agent-sdk-verifier-ts.md, README.txt + * await getBaseFileNames('/path/to/agents') + * // Returns: ['agent-sdk-verifier-py', 'agent-sdk-verifier-ts'] + */ +async function getBaseFileNames(dirPath: string): Promise { + try { + const entries = await fs.readdir(dirPath, { + withFileTypes: true + }); + return entries.filter((entry: Dirent) => entry.isFile() && entry.name.endsWith('.md')).map((entry: Dirent) => { + // Remove .md extension specifically + const baseName = path.basename(entry.name, '.md'); + return baseName; + }); + } catch (error) { + const errorMsg = errorMessage(error); + logForDebugging(`Failed to read plugin components from ${dirPath}: ${errorMsg}`, { + level: 'error' + }); + logError(toError(error)); + // Return empty array to allow graceful degradation - plugin details can still be shown + return []; + } +} + +/** + * Get list of skill directory names from a skills directory + * Skills are directories containing a SKILL.md file + * @param dirPath The skills directory path to scan + * @returns Array of skill directory names that contain SKILL.md + * @example + * // Given directory contains: my-skill/SKILL.md, another-skill/SKILL.md, README.txt + * await getSkillDirNames('/path/to/skills') + * // Returns: ['my-skill', 'another-skill'] + */ +async function getSkillDirNames(dirPath: string): Promise { + try { + const entries = await fs.readdir(dirPath, { + withFileTypes: true + }); + const skillNames: string[] = []; + for (const entry of entries) { + // Check if it's a directory or symlink (symlinks may point to skill directories) + if (entry.isDirectory() || entry.isSymbolicLink()) { + // Check if this directory contains a SKILL.md file + const skillFilePath = path.join(dirPath, entry.name, 'SKILL.md'); + try { + const st = await fs.stat(skillFilePath); + if (st.isFile()) { + skillNames.push(entry.name); + } + } catch { + // No SKILL.md file in this directory, skip it + } + } + } + return skillNames; + } catch (error) { + const errorMsg = errorMessage(error); + logForDebugging(`Failed to read skill directories from ${dirPath}: ${errorMsg}`, { + level: 'error' + }); + logError(toError(error)); + // Return empty array to allow graceful degradation - plugin details can still be shown + return []; + } +} + +// Component to display installed plugin components +function PluginComponentsDisplay({ + plugin, + marketplace +}: { + plugin: LoadedPlugin; + marketplace: string; +}): React.ReactNode { + const [components, setComponents] = useState<{ + commands?: string | string[] | Record | null; + agents?: string | string[] | Record | null; + skills?: string | string[] | Record | null; + hooks?: unknown; + mcpServers?: unknown; + } | null>(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + useEffect(() => { + async function loadComponents() { + try { + // Built-in plugins don't have a marketplace entry — read from the + // registered definition directly. + if (marketplace === 'builtin') { + const builtinDef = getBuiltinPluginDefinition(plugin.name); + if (builtinDef) { + const skillNames = builtinDef.skills?.map(s => s.name) ?? []; + const hookEvents = builtinDef.hooks ? Object.keys(builtinDef.hooks) : []; + const mcpServerNames = builtinDef.mcpServers ? Object.keys(builtinDef.mcpServers) : []; + setComponents({ + commands: null, + agents: null, + skills: skillNames.length > 0 ? skillNames : null, + hooks: hookEvents.length > 0 ? hookEvents : null, + mcpServers: mcpServerNames.length > 0 ? mcpServerNames : null + }); + } else { + setError(`Built-in plugin ${plugin.name} not found`); + } + setLoading(false); + return; + } + const marketplaceData = await getMarketplace(marketplace); + // Find the plugin entry in the array + const pluginEntry = marketplaceData.plugins.find(p => p.name === plugin.name); + if (pluginEntry) { + // Combine commands from both sources + const commandPathList = []; + if (plugin.commandsPath) { + commandPathList.push(plugin.commandsPath); + } + if (plugin.commandsPaths) { + commandPathList.push(...plugin.commandsPaths); + } + + // Get base file names from all command paths + const commandList: string[] = []; + for (const commandPath of commandPathList) { + if (typeof commandPath === 'string') { + // commandPath is already a full path + const baseNames = await getBaseFileNames(commandPath); + commandList.push(...baseNames); + } + } + + // Combine agents from both sources + const agentPathList = []; + if (plugin.agentsPath) { + agentPathList.push(plugin.agentsPath); + } + if (plugin.agentsPaths) { + agentPathList.push(...plugin.agentsPaths); + } + + // Get base file names from all agent paths + const agentList: string[] = []; + for (const agentPath of agentPathList) { + if (typeof agentPath === 'string') { + // agentPath is already a full path + const baseNames_0 = await getBaseFileNames(agentPath); + agentList.push(...baseNames_0); + } + } + + // Combine skills from both sources + const skillPathList = []; + if (plugin.skillsPath) { + skillPathList.push(plugin.skillsPath); + } + if (plugin.skillsPaths) { + skillPathList.push(...plugin.skillsPaths); + } + + // Get skill directory names from all skill paths + // Skills are directories containing SKILL.md files + const skillList: string[] = []; + for (const skillPath of skillPathList) { + if (typeof skillPath === 'string') { + // skillPath is already a full path to a skills directory + const skillDirNames = await getSkillDirNames(skillPath); + skillList.push(...skillDirNames); + } + } + + // Combine hooks from both sources + const hooksList = []; + if (plugin.hooksConfig) { + hooksList.push(Object.keys(plugin.hooksConfig)); + } + if (pluginEntry.hooks) { + hooksList.push(pluginEntry.hooks); + } + + // Combine MCP servers from both sources + const mcpServersList = []; + if (plugin.mcpServers) { + mcpServersList.push(Object.keys(plugin.mcpServers)); + } + if (pluginEntry.mcpServers) { + mcpServersList.push(pluginEntry.mcpServers); + } + setComponents({ + commands: commandList.length > 0 ? commandList : null, + agents: agentList.length > 0 ? agentList : null, + skills: skillList.length > 0 ? skillList : null, + hooks: hooksList.length > 0 ? hooksList : null, + mcpServers: mcpServersList.length > 0 ? mcpServersList : null + }); + } else { + setError(`Plugin ${plugin.name} not found in marketplace`); + } + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to load components'); + } finally { + setLoading(false); + } + } + void loadComponents(); + }, [plugin.name, plugin.commandsPath, plugin.commandsPaths, plugin.agentsPath, plugin.agentsPaths, plugin.skillsPath, plugin.skillsPaths, plugin.hooksConfig, plugin.mcpServers, marketplace]); + if (loading) { + return null; // Don't show loading state for cleaner UI + } + if (error) { + return + Components: + Error: {error} + ; + } + if (!components) { + return null; // No components info available + } + const hasComponents = components.commands || components.agents || components.skills || components.hooks || components.mcpServers; + if (!hasComponents) { + return null; // No components defined + } + return + Installed components: + {components.commands ? + • Commands:{' '} + {typeof components.commands === 'string' ? components.commands : Array.isArray(components.commands) ? components.commands.join(', ') : Object.keys(components.commands).join(', ')} + : null} + {components.agents ? + • Agents:{' '} + {typeof components.agents === 'string' ? components.agents : Array.isArray(components.agents) ? components.agents.join(', ') : Object.keys(components.agents).join(', ')} + : null} + {components.skills ? + • Skills:{' '} + {typeof components.skills === 'string' ? components.skills : Array.isArray(components.skills) ? components.skills.join(', ') : Object.keys(components.skills).join(', ')} + : null} + {components.hooks ? + • Hooks:{' '} + {typeof components.hooks === 'string' ? components.hooks : Array.isArray(components.hooks) ? components.hooks.map(String).join(', ') : typeof components.hooks === 'object' && components.hooks !== null ? Object.keys(components.hooks).join(', ') : String(components.hooks)} + : null} + {components.mcpServers ? + • MCP Servers:{' '} + {typeof components.mcpServers === 'string' ? components.mcpServers : Array.isArray(components.mcpServers) ? components.mcpServers.map(String).join(', ') : typeof components.mcpServers === 'object' && components.mcpServers !== null ? Object.keys(components.mcpServers).join(', ') : String(components.mcpServers)} + : null} + ; +} + +/** + * Check if a plugin is from a local source and cannot be remotely updated + * @returns Error message if local, null if remote/updatable + */ +async function checkIfLocalPlugin(pluginName: string, marketplaceName: string): Promise { + const marketplace = await getMarketplace(marketplaceName); + const entry = marketplace?.plugins.find(p => p.name === pluginName); + if (entry && typeof entry.source === 'string') { + return `Local plugins cannot be updated remotely. To update, modify the source at: ${entry.source}`; + } + return null; +} + +/** + * Filter out plugins that are force-disabled by org policy (policySettings). + * These are blocked by the organization and cannot be re-enabled by the user. + * Checks policySettings directly rather than installation scope, since managed + * settings don't create installation records with scope 'managed'. + */ +export function filterManagedDisabledPlugins(plugins: LoadedPlugin[]): LoadedPlugin[] { + return plugins.filter(plugin => { + const marketplace = plugin.source.split('@')[1] || 'local'; + return !isPluginBlockedByPolicy(`${plugin.name}@${marketplace}`); + }); +} +export function ManagePlugins({ + setViewState: setParentViewState, + setResult, + onManageComplete, + onSearchModeChange, + targetPlugin, + targetMarketplace, + action +}: Props): React.ReactNode { + // App state for MCP access + const mcpClients = useAppState(s => s.mcp.clients); + const mcpTools = useAppState(s_0 => s_0.mcp.tools); + const pluginErrors = useAppState(s_1 => s_1.plugins.errors); + const flaggedPlugins = getFlaggedPlugins(); + + // Search state + const [isSearchMode, setIsSearchModeRaw] = useState(false); + const setIsSearchMode = useCallback((active: boolean) => { + setIsSearchModeRaw(active); + onSearchModeChange?.(active); + }, [onSearchModeChange]); + const isTerminalFocused = useTerminalFocus(); + const { + columns: terminalWidth + } = useTerminalSize(); + + // View state + const [viewState, setViewState] = useState('plugin-list'); + const { + query: searchQuery, + setQuery: setSearchQuery, + cursorOffset: searchCursorOffset + } = useSearchInput({ + isActive: viewState === 'plugin-list' && isSearchMode, + onExit: () => { + setIsSearchMode(false); + } + }); + const [selectedPlugin, setSelectedPlugin] = useState(null); + + // Data state + const [marketplaces, setMarketplaces] = useState([]); + const [pluginStates, setPluginStates] = useState([]); + const [loading, setLoading] = useState(true); + const [pendingToggles, setPendingToggles] = useState>(new Map()); + + // Guard to prevent auto-navigation from re-triggering after the user + // navigates away (targetPlugin is never cleared by the parent). + const hasAutoNavigated = useRef(false); + // Auto-action (enable/disable/uninstall) to fire after auto-navigation lands. + // Ref, not state: it's consumed by a one-shot effect that already re-runs on + // viewState/selectedPlugin, so a render-triggering state var would be redundant. + const pendingAutoActionRef = useRef<'enable' | 'disable' | 'uninstall' | undefined>(undefined); + + // MCP toggle hook + const toggleMcpServer = useMcpToggleEnabled(); + + // Handle escape to go back - viewState-dependent navigation + const handleBack = React.useCallback(() => { + if (viewState === 'plugin-details') { + setViewState('plugin-list'); + setSelectedPlugin(null); + setProcessError(null); + } else if (typeof viewState === 'object' && viewState.type === 'failed-plugin-details') { + setViewState('plugin-list'); + setProcessError(null); + } else if (viewState === 'configuring') { + setViewState('plugin-details'); + setConfigNeeded(null); + } else if (typeof viewState === 'object' && (viewState.type === 'plugin-options' || viewState.type === 'configuring-options')) { + // Cancel mid-sequence — plugin is already enabled, just bail to list. + // User can configure later via the Configure options menu if they want. + setViewState('plugin-list'); + setSelectedPlugin(null); + setResult('Plugin enabled. Configuration skipped — run /reload-plugins to apply.'); + if (onManageComplete) { + void onManageComplete(); + } + } else if (typeof viewState === 'object' && viewState.type === 'flagged-detail') { + setViewState('plugin-list'); + setProcessError(null); + } else if (typeof viewState === 'object' && viewState.type === 'mcp-detail') { + setViewState('plugin-list'); + setProcessError(null); + } else if (typeof viewState === 'object' && viewState.type === 'mcp-tools') { + setViewState({ + type: 'mcp-detail', + client: viewState.client + }); + } else if (typeof viewState === 'object' && viewState.type === 'mcp-tool-detail') { + setViewState({ + type: 'mcp-tools', + client: viewState.client + }); + } else { + if (pendingToggles.size > 0) { + setResult('Run /reload-plugins to apply plugin changes.'); + return; + } + setParentViewState({ + type: 'menu' + }); + } + }, [viewState, setParentViewState, pendingToggles, setResult]); + + // Escape when not in search mode - go back. + // Excludes confirm-project-uninstall (has its own confirm:no handler in + // Confirmation context — letting this fire would create competing handlers) + // and confirm-data-cleanup (uses raw useInput where n and escape are + // DIFFERENT actions: keep-data vs cancel). + useKeybinding('confirm:no', handleBack, { + context: 'Confirmation', + isActive: (viewState !== 'plugin-list' || !isSearchMode) && viewState !== 'confirm-project-uninstall' && !(typeof viewState === 'object' && viewState.type === 'confirm-data-cleanup') + }); + + // Helper to get MCP status + const getMcpStatus = (client: MCPServerConnection): 'connected' | 'disabled' | 'pending' | 'needs-auth' | 'failed' => { + if (client.type === 'connected') return 'connected'; + if (client.type === 'disabled') return 'disabled'; + if (client.type === 'pending') return 'pending'; + if (client.type === 'needs-auth') return 'needs-auth'; + return 'failed'; + }; + + // Derive unified items from plugins and MCP servers + const unifiedItems = useMemo(() => { + const mergedSettings = getSettings_DEPRECATED(); + + // Build map of plugin name -> child MCPs + // Plugin MCPs have names like "plugin:pluginName:serverName" + const pluginMcpMap = new Map>(); + for (const client_0 of mcpClients) { + if (client_0.name.startsWith('plugin:')) { + const parts = client_0.name.split(':'); + if (parts.length >= 3) { + const pluginName = parts[1]!; + const serverName = parts.slice(2).join(':'); + const existing = pluginMcpMap.get(pluginName) || []; + existing.push({ + displayName: serverName, + client: client_0 + }); + pluginMcpMap.set(pluginName, existing); + } + } + } + + // Build plugin items (unsorted for now) + type PluginWithChildren = { + item: UnifiedInstalledItem & { + type: 'plugin'; + }; + originalScope: 'user' | 'project' | 'local' | 'managed' | 'builtin'; + childMcps: Array<{ + displayName: string; + client: MCPServerConnection; + }>; + }; + const pluginsWithChildren: PluginWithChildren[] = []; + for (const state of pluginStates) { + const pluginId = `${state.plugin.name}@${state.marketplace}`; + const isEnabled = mergedSettings?.enabledPlugins?.[pluginId] !== false; + const errors = pluginErrors.filter(e => 'plugin' in e && e.plugin === state.plugin.name || e.source === pluginId || e.source.startsWith(`${state.plugin.name}@`)); + + // Built-in plugins use 'builtin' scope; others look up from V2 data. + const originalScope = state.plugin.isBuiltin ? 'builtin' : state.scope || 'user'; + pluginsWithChildren.push({ + item: { + type: 'plugin', + id: pluginId, + name: state.plugin.name, + description: state.plugin.manifest.description, + marketplace: state.marketplace, + scope: originalScope, + isEnabled, + errorCount: errors.length, + errors, + plugin: state.plugin, + pendingEnable: state.pendingEnable, + pendingUpdate: state.pendingUpdate, + pendingToggle: pendingToggles.get(pluginId) + }, + originalScope, + childMcps: pluginMcpMap.get(state.plugin.name) || [] + }); + } + + // Find orphan errors (errors for plugins that failed to load entirely) + const matchedPluginIds = new Set(pluginsWithChildren.map(({ + item + }) => item.id)); + const matchedPluginNames = new Set(pluginsWithChildren.map(({ + item: item_0 + }) => item_0.name)); + const orphanErrorsBySource = new Map(); + for (const error of pluginErrors) { + if (matchedPluginIds.has(error.source) || 'plugin' in error && typeof error.plugin === 'string' && matchedPluginNames.has(error.plugin)) { + continue; + } + const existing_0 = orphanErrorsBySource.get(error.source) || []; + existing_0.push(error); + orphanErrorsBySource.set(error.source, existing_0); + } + const pluginScopes = getPluginEditableScopes(); + const failedPluginItems: UnifiedInstalledItem[] = []; + for (const [pluginId_0, errors_0] of orphanErrorsBySource) { + // Skip plugins that are already shown in the flagged section + if (pluginId_0 in flaggedPlugins) continue; + const parsed = parsePluginIdentifier(pluginId_0); + const pluginName_0 = parsed.name || pluginId_0; + const marketplace = parsed.marketplace || 'unknown'; + const rawScope = pluginScopes.get(pluginId_0); + // 'flag' is session-only (from --plugin-dir / flagSettings) and undefined + // means the plugin isn't in any settings source. Default both to 'user' + // since UnifiedInstalledItem doesn't have a 'flag' scope variant. + const scope = rawScope === 'flag' || rawScope === undefined ? 'user' : rawScope; + failedPluginItems.push({ + type: 'failed-plugin', + id: pluginId_0, + name: pluginName_0, + marketplace, + scope, + errorCount: errors_0.length, + errors: errors_0 + }); + } + + // Build standalone MCP items + const standaloneMcps: UnifiedInstalledItem[] = []; + for (const client_1 of mcpClients) { + if (client_1.name === 'ide') continue; + if (client_1.name.startsWith('plugin:')) continue; + standaloneMcps.push({ + type: 'mcp', + id: `mcp:${client_1.name}`, + name: client_1.name, + description: undefined, + scope: client_1.config.scope, + status: getMcpStatus(client_1), + client: client_1 + }); + } + + // Define scope order for display + const scopeOrder: Record = { + flagged: -1, + project: 0, + local: 1, + user: 2, + enterprise: 3, + managed: 4, + dynamic: 5, + builtin: 6 + }; + + // Build final list by merging plugins (with their child MCPs) and standalone MCPs + // Group by scope to avoid duplicate scope headers + const unified: UnifiedInstalledItem[] = []; + + // Create a map of scope -> items for proper merging + const itemsByScope = new Map(); + + // Add plugins with their child MCPs + for (const { + item: item_1, + originalScope: originalScope_0, + childMcps + } of pluginsWithChildren) { + const scope_0 = item_1.scope; + if (!itemsByScope.has(scope_0)) { + itemsByScope.set(scope_0, []); + } + itemsByScope.get(scope_0)!.push(item_1); + // Add child MCPs right after the plugin, indented (use original scope, not 'flagged'). + // Built-in plugins map to 'user' for display since MCP ConfigScope doesn't include 'builtin'. + for (const { + displayName, + client: client_2 + } of childMcps) { + const displayScope = originalScope_0 === 'builtin' ? 'user' : originalScope_0; + if (!itemsByScope.has(displayScope)) { + itemsByScope.set(displayScope, []); + } + itemsByScope.get(displayScope)!.push({ + type: 'mcp', + id: `mcp:${client_2.name}`, + name: displayName, + description: undefined, + scope: displayScope, + status: getMcpStatus(client_2), + client: client_2, + indented: true + }); + } + } + + // Add standalone MCPs to their respective scope groups + for (const mcp of standaloneMcps) { + const scope_1 = mcp.scope; + if (!itemsByScope.has(scope_1)) { + itemsByScope.set(scope_1, []); + } + itemsByScope.get(scope_1)!.push(mcp); + } + + // Add failed plugins to their respective scope groups + for (const failedPlugin of failedPluginItems) { + const scope_2 = failedPlugin.scope; + if (!itemsByScope.has(scope_2)) { + itemsByScope.set(scope_2, []); + } + itemsByScope.get(scope_2)!.push(failedPlugin); + } + + // Add flagged (delisted) plugins from user settings. + // Reason/text are looked up from the cached security messages file. + for (const [pluginId_1, entry] of Object.entries(flaggedPlugins)) { + const parsed_0 = parsePluginIdentifier(pluginId_1); + const pluginName_1 = parsed_0.name || pluginId_1; + const marketplace_0 = parsed_0.marketplace || 'unknown'; + if (!itemsByScope.has('flagged')) { + itemsByScope.set('flagged', []); + } + itemsByScope.get('flagged')!.push({ + type: 'flagged-plugin', + id: pluginId_1, + name: pluginName_1, + marketplace: marketplace_0, + scope: 'flagged', + reason: 'delisted', + text: 'Removed from marketplace', + flaggedAt: entry.flaggedAt + }); + } + + // Sort scopes and build final list + const sortedScopes = [...itemsByScope.keys()].sort((a, b) => (scopeOrder[a] ?? 99) - (scopeOrder[b] ?? 99)); + for (const scope_3 of sortedScopes) { + const items = itemsByScope.get(scope_3)!; + + // Separate items into plugin groups (with their child MCPs) and standalone MCPs + // This preserves parent-child relationships that would be broken by naive sorting + const pluginGroups: UnifiedInstalledItem[][] = []; + const standaloneMcpsInScope: UnifiedInstalledItem[] = []; + let i = 0; + while (i < items.length) { + const item_2 = items[i]!; + if (item_2.type === 'plugin' || item_2.type === 'failed-plugin' || item_2.type === 'flagged-plugin') { + // Collect the plugin and its child MCPs as a group + const group: UnifiedInstalledItem[] = [item_2]; + i++; + // Look ahead for indented child MCPs + let nextItem = items[i]; + while (nextItem?.type === 'mcp' && nextItem.indented) { + group.push(nextItem); + i++; + nextItem = items[i]; + } + pluginGroups.push(group); + } else if (item_2.type === 'mcp' && !item_2.indented) { + // Standalone MCP (not a child of a plugin) + standaloneMcpsInScope.push(item_2); + i++; + } else { + // Skip orphaned indented MCPs (shouldn't happen) + i++; + } + } + + // Sort plugin groups by the plugin name (first item in each group) + pluginGroups.sort((a_0, b_0) => a_0[0]!.name.localeCompare(b_0[0]!.name)); + + // Sort standalone MCPs by name + standaloneMcpsInScope.sort((a_1, b_1) => a_1.name.localeCompare(b_1.name)); + + // Build final list: plugins (with their children) first, then standalone MCPs + for (const group_0 of pluginGroups) { + unified.push(...group_0); + } + unified.push(...standaloneMcpsInScope); + } + return unified; + }, [pluginStates, mcpClients, pluginErrors, pendingToggles, flaggedPlugins]); + + // Mark flagged plugins as seen when the Installed view renders them. + // After 48 hours from seenAt, they auto-clear on next load. + const flaggedIds = useMemo(() => unifiedItems.filter(item_3 => item_3.type === 'flagged-plugin').map(item_4 => item_4.id), [unifiedItems]); + useEffect(() => { + if (flaggedIds.length > 0) { + void markFlaggedPluginsSeen(flaggedIds); + } + }, [flaggedIds]); + + // Filter items based on search query (matches name or description) + const filteredItems = useMemo(() => { + if (!searchQuery) return unifiedItems; + const lowerQuery = searchQuery.toLowerCase(); + return unifiedItems.filter(item_5 => item_5.name.toLowerCase().includes(lowerQuery) || 'description' in item_5 && item_5.description?.toLowerCase().includes(lowerQuery)); + }, [unifiedItems, searchQuery]); + + // Selection state + const [selectedIndex, setSelectedIndex] = useState(0); + + // Pagination for unified list (continuous scrolling) + const pagination = usePagination({ + totalItems: filteredItems.length, + selectedIndex, + maxVisible: 8 + }); + + // Details view state + const [detailsMenuIndex, setDetailsMenuIndex] = useState(0); + const [isProcessing, setIsProcessing] = useState(false); + const [processError, setProcessError] = useState(null); + + // Configuration state + const [configNeeded, setConfigNeeded] = useState(null); + const [_isLoadingConfig, setIsLoadingConfig] = useState(false); + const [selectedPluginHasMcpb, setSelectedPluginHasMcpb] = useState(false); + + // Detect if selected plugin has MCPB + // Reads raw marketplace.json to work with old cached marketplaces + useEffect(() => { + if (!selectedPlugin) { + setSelectedPluginHasMcpb(false); + return; + } + async function detectMcpb() { + // Check plugin manifest first + const mcpServersSpec = selectedPlugin!.plugin.manifest.mcpServers; + let hasMcpb = false; + if (mcpServersSpec) { + hasMcpb = typeof mcpServersSpec === 'string' && isMcpbSource(mcpServersSpec) || Array.isArray(mcpServersSpec) && mcpServersSpec.some(s_2 => typeof s_2 === 'string' && isMcpbSource(s_2)); + } + + // If not in manifest, read raw marketplace.json directly (bypassing schema validation) + // This works even with old cached marketplaces from before MCPB support + if (!hasMcpb) { + try { + const marketplaceDir = path.join(selectedPlugin!.plugin.path, '..'); + const marketplaceJsonPath = path.join(marketplaceDir, '.claude-plugin', 'marketplace.json'); + const content = await fs.readFile(marketplaceJsonPath, 'utf-8'); + const marketplace_1 = jsonParse(content); + const entry_0 = marketplace_1.plugins?.find((p: { + name: string; + }) => p.name === selectedPlugin!.plugin.name); + if (entry_0?.mcpServers) { + const spec = entry_0.mcpServers; + hasMcpb = typeof spec === 'string' && isMcpbSource(spec) || Array.isArray(spec) && spec.some((s_3: unknown) => typeof s_3 === 'string' && isMcpbSource(s_3)); + } + } catch (err) { + logForDebugging(`Failed to read raw marketplace.json: ${err}`); + } + } + setSelectedPluginHasMcpb(hasMcpb); + } + void detectMcpb(); + }, [selectedPlugin]); + + // Load installed plugins grouped by marketplace + useEffect(() => { + async function loadInstalledPlugins() { + setLoading(true); + try { + const { + enabled, + disabled + } = await loadAllPlugins(); + const mergedSettings = getSettings_DEPRECATED(); // Use merged settings to respect all layers + + const allPlugins = filterManagedDisabledPlugins([...enabled, ...disabled]); + + // Group plugins by marketplace + const pluginsByMarketplace: Record = {}; + for (const plugin of allPlugins) { + const marketplace = plugin.source.split('@')[1] || 'local'; + if (!pluginsByMarketplace[marketplace]) { + pluginsByMarketplace[marketplace] = []; + } + pluginsByMarketplace[marketplace]!.push(plugin); + } + + // Create marketplace info array with enabled/disabled counts + const marketplaceInfos: MarketplaceInfo[] = []; + for (const [name, plugins] of Object.entries(pluginsByMarketplace)) { + const enabledCount = count(plugins, p => { + const pluginId = `${p.name}@${name}`; + return mergedSettings?.enabledPlugins?.[pluginId] !== false; + }); + const disabledCount = plugins.length - enabledCount; + marketplaceInfos.push({ + name, + installedPlugins: plugins, + enabledCount, + disabledCount + }); + } + + // Sort marketplaces: claude-plugin-directory first, then alphabetically + marketplaceInfos.sort((a, b) => { + if (a.name === 'claude-plugin-directory') return -1; + if (b.name === 'claude-plugin-directory') return 1; + return a.name.localeCompare(b.name); + }); + setMarketplaces(marketplaceInfos); + + // Build flat list of all plugin states + const allStates: PluginState[] = []; + for (const marketplace of marketplaceInfos) { + for (const plugin of marketplace.installedPlugins) { + const pluginId = `${plugin.name}@${marketplace.name}`; + // Built-in plugins don't have V2 install entries — skip the lookup. + const scope = plugin.isBuiltin ? 'builtin' : getPluginInstallationFromV2(pluginId).scope; + allStates.push({ + plugin, + marketplace: marketplace.name, + scope, + pendingEnable: undefined, + pendingUpdate: false + }); + } + } + setPluginStates(allStates); + setSelectedIndex(0); + } finally { + setLoading(false); + } + } + void loadInstalledPlugins(); + }, []); + + // Auto-navigate to target plugin if specified (once only) + useEffect(() => { + if (hasAutoNavigated.current) return; + if (targetPlugin && marketplaces.length > 0 && !loading) { + // targetPlugin may be `name` or `name@marketplace` (parseArgs passes the + // raw arg through). Parse it so p.name matching works either way. + const { + name: targetName, + marketplace: targetMktFromId + } = parsePluginIdentifier(targetPlugin); + const effectiveTargetMarketplace = targetMarketplace ?? targetMktFromId; + + // Use targetMarketplace if provided, otherwise search all + const marketplacesToSearch = effectiveTargetMarketplace ? marketplaces.filter(m => m.name === effectiveTargetMarketplace) : marketplaces; + + // First check successfully loaded plugins + for (const marketplace_2 of marketplacesToSearch) { + const plugin = marketplace_2.installedPlugins.find(p_0 => p_0.name === targetName); + if (plugin) { + // Get scope from V2 data for proper operation handling + const pluginId_2 = `${plugin.name}@${marketplace_2.name}`; + const { + scope: scope_4 + } = getPluginInstallationFromV2(pluginId_2); + const pluginState: PluginState = { + plugin, + marketplace: marketplace_2.name, + scope: scope_4, + pendingEnable: undefined, + pendingUpdate: false + }; + setSelectedPlugin(pluginState); + setViewState('plugin-details'); + pendingAutoActionRef.current = action; + hasAutoNavigated.current = true; + return; + } + } + + // Fall back to failed plugins (those with errors but not loaded) + const failedItem = unifiedItems.find(item_6 => item_6.type === 'failed-plugin' && item_6.name === targetName); + if (failedItem && failedItem.type === 'failed-plugin') { + setViewState({ + type: 'failed-plugin-details', + plugin: { + id: failedItem.id, + name: failedItem.name, + marketplace: failedItem.marketplace, + errors: failedItem.errors, + scope: failedItem.scope + } + }); + hasAutoNavigated.current = true; + } + + // No match in loaded OR failed plugins — close the dialog with a + // message rather than silently landing on the plugin list. Only do + // this when an action was requested (e.g. /plugin uninstall X); + // plain navigation (/plugin manage) should still just show the list. + if (!hasAutoNavigated.current && action) { + hasAutoNavigated.current = true; + setResult(`Plugin "${targetPlugin}" is not installed in this project`); + } + } + }, [targetPlugin, targetMarketplace, marketplaces, loading, unifiedItems, action, setResult]); + + // Handle single plugin operations from details view + const handleSingleOperation = async (operation: 'enable' | 'disable' | 'update' | 'uninstall') => { + if (!selectedPlugin) return; + const pluginScope = selectedPlugin.scope || 'user'; + const isBuiltin = pluginScope === 'builtin'; + + // Built-in plugins can only be enabled/disabled, not updated/uninstalled. + if (isBuiltin && (operation === 'update' || operation === 'uninstall')) { + setProcessError('Built-in plugins cannot be updated or uninstalled.'); + return; + } + + // Managed scope plugins can only be updated, not enabled/disabled/uninstalled + if (!isBuiltin && !isInstallableScope(pluginScope) && operation !== 'update') { + setProcessError('This plugin is managed by your organization. Contact your admin to disable it.'); + return; + } + setIsProcessing(true); + setProcessError(null); + try { + const pluginId_3 = `${selectedPlugin.plugin.name}@${selectedPlugin.marketplace}`; + let reverseDependents: string[] | undefined; + + // enable/disable omit scope — pluginScope is the install scope from + // installed_plugins.json (where files are cached), which can diverge + // from the settings scope (where enablement lives). Passing it trips + // the cross-scope guard. Auto-detect finds the right scope. #38084 + switch (operation) { + case 'enable': + { + const enableResult = await enablePluginOp(pluginId_3); + if (!enableResult.success) { + throw new Error(enableResult.message); + } + break; + } + case 'disable': + { + const disableResult = await disablePluginOp(pluginId_3); + if (!disableResult.success) { + throw new Error(disableResult.message); + } + reverseDependents = disableResult.reverseDependents; + break; + } + case 'uninstall': + { + if (isBuiltin) break; // guarded above; narrows pluginScope + if (!isInstallableScope(pluginScope)) break; + // If the plugin is enabled in .claude/settings.json (shared with the + // team), divert to a confirmation dialog that offers to disable in + // settings.local.json instead. Check the settings file directly — + // `pluginScope` (from installed_plugins.json) can be 'user' even when + // the plugin is ALSO project-enabled, and uninstalling the user-scope + // install would leave the project enablement active. + if (isPluginEnabledAtProjectScope(pluginId_3)) { + setIsProcessing(false); + setViewState('confirm-project-uninstall'); + return; + } + // If the plugin has persistent data (${CLAUDE_PLUGIN_DATA}) AND this + // is the last scope, prompt before deleting it. For multi-scope + // installs, the op's isLastScope check won't delete regardless of + // the user's y/n — showing the dialog would mislead ("y" → nothing + // happens). Length check mirrors pluginOperations.ts:513. + const installs = loadInstalledPluginsV2().plugins[pluginId_3]; + const isLastScope = !installs || installs.length <= 1; + const dataSize = isLastScope ? await getPluginDataDirSize(pluginId_3) : null; + if (dataSize) { + setIsProcessing(false); + setViewState({ + type: 'confirm-data-cleanup', + size: dataSize + }); + return; + } + const result_0 = await uninstallPluginOp(pluginId_3, pluginScope); + if (!result_0.success) { + throw new Error(result_0.message); + } + reverseDependents = result_0.reverseDependents; + break; + } + case 'update': + { + if (isBuiltin) break; // guarded above; narrows pluginScope + const result = await updatePluginOp(pluginId_3, pluginScope); + if (!result.success) { + throw new Error(result.message); + } + // If already up to date, show message and exit + if (result.alreadyUpToDate) { + setResult(`${selectedPlugin.plugin.name} is already at the latest version (${result.newVersion}).`); + if (onManageComplete) { + await onManageComplete(); + } + setParentViewState({ + type: 'menu' + }); + return; + } + // Success - will show standard message below + break; + } + } + + // Operations (enable, disable, uninstall, update) now use centralized functions + // that handle their own settings updates, so we only need to clear caches here + clearAllCaches(); + + // Prompt for manifest.userConfig + channel userConfig if the plugin ends + // up enabled. Re-read settings rather than keying on `operation === + // 'enable'`: install enables on install, so the menu shows "Disable" + // first. PluginOptionsFlow itself checks getUnconfiguredOptions — if + // nothing needs filling, it calls onDone('skipped') immediately. + const pluginIdNow = `${selectedPlugin.plugin.name}@${selectedPlugin.marketplace}`; + const settingsAfter = getSettings_DEPRECATED(); + const enabledAfter = settingsAfter?.enabledPlugins?.[pluginIdNow] !== false; + if (enabledAfter) { + setIsProcessing(false); + setViewState({ + type: 'plugin-options' + }); + return; + } + const operationName = operation === 'enable' ? 'Enabled' : operation === 'disable' ? 'Disabled' : operation === 'update' ? 'Updated' : 'Uninstalled'; + + // Single-line warning — notification timeout is ~8s, multi-line would scroll off. + // The persistent record is in the Errors tab (dependency-unsatisfied after reload). + const depWarn = reverseDependents && reverseDependents.length > 0 ? ` · required by ${reverseDependents.join(', ')}` : ''; + const message = `✓ ${operationName} ${selectedPlugin.plugin.name}${depWarn}. Run /reload-plugins to apply.`; + setResult(message); + if (onManageComplete) { + await onManageComplete(); + } + setParentViewState({ + type: 'menu' + }); + } catch (error_0) { + setIsProcessing(false); + const errorMessage = error_0 instanceof Error ? error_0.message : String(error_0); + setProcessError(`Failed to ${operation}: ${errorMessage}`); + logError(toError(error_0)); + } + }; + + // Latest-ref: lets the auto-action effect call the current closure without + // adding handleSingleOperation (recreated every render) to its deps. + const handleSingleOperationRef = useRef(handleSingleOperation); + handleSingleOperationRef.current = handleSingleOperation; + + // Auto-execute the action prop (/plugin uninstall X, /plugin enable X, etc.) + // once auto-navigation has landed on plugin-details. + useEffect(() => { + if (viewState === 'plugin-details' && selectedPlugin && pendingAutoActionRef.current) { + const pending = pendingAutoActionRef.current; + pendingAutoActionRef.current = undefined; + void handleSingleOperationRef.current(pending); + } + }, [viewState, selectedPlugin]); + + // Handle toggle enable/disable + const handleToggle = React.useCallback(() => { + if (selectedIndex >= filteredItems.length) return; + const item_7 = filteredItems[selectedIndex]; + if (item_7?.type === 'flagged-plugin') return; + if (item_7?.type === 'plugin') { + const pluginId_4 = `${item_7.plugin.name}@${item_7.marketplace}`; + const mergedSettings_0 = getSettings_DEPRECATED(); + const currentPending = pendingToggles.get(pluginId_4); + const isEnabled_0 = mergedSettings_0?.enabledPlugins?.[pluginId_4] !== false; + const pluginScope_0 = item_7.scope; + const isBuiltin_0 = pluginScope_0 === 'builtin'; + if (isBuiltin_0 || isInstallableScope(pluginScope_0)) { + const newPending = new Map(pendingToggles); + // Omit scope — see handleSingleOperation's enable/disable comment. + if (currentPending) { + // Cancel: reverse the operation back to the original state + newPending.delete(pluginId_4); + void (async () => { + try { + if (currentPending === 'will-disable') { + await enablePluginOp(pluginId_4); + } else { + await disablePluginOp(pluginId_4); + } + clearAllCaches(); + } catch (err_0) { + logError(err_0); + } + })(); + } else { + newPending.set(pluginId_4, isEnabled_0 ? 'will-disable' : 'will-enable'); + void (async () => { + try { + if (isEnabled_0) { + await disablePluginOp(pluginId_4); + } else { + await enablePluginOp(pluginId_4); + } + clearAllCaches(); + } catch (err_1) { + logError(err_1); + } + })(); + } + setPendingToggles(newPending); + } + } else if (item_7?.type === 'mcp') { + void toggleMcpServer(item_7.client.name); + } + }, [selectedIndex, filteredItems, pendingToggles, pluginStates, toggleMcpServer]); + + // Handle accept (Enter) in plugin-list + const handleAccept = React.useCallback(() => { + if (selectedIndex >= filteredItems.length) return; + const item_8 = filteredItems[selectedIndex]; + if (item_8?.type === 'plugin') { + const state_0 = pluginStates.find(s_4 => s_4.plugin.name === item_8.plugin.name && s_4.marketplace === item_8.marketplace); + if (state_0) { + setSelectedPlugin(state_0); + setViewState('plugin-details'); + setDetailsMenuIndex(0); + setProcessError(null); + } + } else if (item_8?.type === 'flagged-plugin') { + setViewState({ + type: 'flagged-detail', + plugin: { + id: item_8.id, + name: item_8.name, + marketplace: item_8.marketplace, + reason: item_8.reason, + text: item_8.text, + flaggedAt: item_8.flaggedAt + } + }); + setProcessError(null); + } else if (item_8?.type === 'failed-plugin') { + setViewState({ + type: 'failed-plugin-details', + plugin: { + id: item_8.id, + name: item_8.name, + marketplace: item_8.marketplace, + errors: item_8.errors, + scope: item_8.scope + } + }); + setDetailsMenuIndex(0); + setProcessError(null); + } else if (item_8?.type === 'mcp') { + setViewState({ + type: 'mcp-detail', + client: item_8.client + }); + setProcessError(null); + } + }, [selectedIndex, filteredItems, pluginStates]); + + // Plugin-list navigation (non-search mode) + useKeybindings({ + 'select:previous': () => { + if (selectedIndex === 0) { + setIsSearchMode(true); + } else { + pagination.handleSelectionChange(selectedIndex - 1, setSelectedIndex); + } + }, + 'select:next': () => { + if (selectedIndex < filteredItems.length - 1) { + pagination.handleSelectionChange(selectedIndex + 1, setSelectedIndex); + } + }, + 'select:accept': handleAccept + }, { + context: 'Select', + isActive: viewState === 'plugin-list' && !isSearchMode + }); + useKeybindings({ + 'plugin:toggle': handleToggle + }, { + context: 'Plugin', + isActive: viewState === 'plugin-list' && !isSearchMode + }); + + // Handle dismiss action in flagged-detail view + const handleFlaggedDismiss = React.useCallback(() => { + if (typeof viewState !== 'object' || viewState.type !== 'flagged-detail') return; + void removeFlaggedPlugin(viewState.plugin.id); + setViewState('plugin-list'); + }, [viewState]); + useKeybindings({ + 'select:accept': handleFlaggedDismiss + }, { + context: 'Select', + isActive: typeof viewState === 'object' && viewState.type === 'flagged-detail' + }); + + // Build details menu items (needed for navigation) + const detailsMenuItems = React.useMemo(() => { + if (viewState !== 'plugin-details' || !selectedPlugin) return []; + const mergedSettings_1 = getSettings_DEPRECATED(); + const pluginId_5 = `${selectedPlugin.plugin.name}@${selectedPlugin.marketplace}`; + const isEnabled_1 = mergedSettings_1?.enabledPlugins?.[pluginId_5] !== false; + const isBuiltin_1 = selectedPlugin.marketplace === 'builtin'; + const menuItems: Array<{ + label: string; + action: () => void; + }> = []; + menuItems.push({ + label: isEnabled_1 ? 'Disable plugin' : 'Enable plugin', + action: () => void handleSingleOperation(isEnabled_1 ? 'disable' : 'enable') + }); + + // Update/Uninstall options — not available for built-in plugins + if (!isBuiltin_1) { + menuItems.push({ + label: selectedPlugin.pendingUpdate ? 'Unmark for update' : 'Mark for update', + action: async () => { + try { + const localError = await checkIfLocalPlugin(selectedPlugin.plugin.name, selectedPlugin.marketplace); + if (localError) { + setProcessError(localError); + return; + } + const newStates = [...pluginStates]; + const index = newStates.findIndex(s_5 => s_5.plugin.name === selectedPlugin.plugin.name && s_5.marketplace === selectedPlugin.marketplace); + if (index !== -1) { + newStates[index]!.pendingUpdate = !selectedPlugin.pendingUpdate; + setPluginStates(newStates); + setSelectedPlugin({ + ...selectedPlugin, + pendingUpdate: !selectedPlugin.pendingUpdate + }); + } + } catch (error_1) { + setProcessError(error_1 instanceof Error ? error_1.message : 'Failed to check plugin update availability'); + } + } + }); + if (selectedPluginHasMcpb) { + menuItems.push({ + label: 'Configure', + action: async () => { + setIsLoadingConfig(true); + try { + const mcpServersSpec_0 = selectedPlugin.plugin.manifest.mcpServers; + let mcpbPath: string | null = null; + if (typeof mcpServersSpec_0 === 'string' && isMcpbSource(mcpServersSpec_0)) { + mcpbPath = mcpServersSpec_0; + } else if (Array.isArray(mcpServersSpec_0)) { + for (const spec_0 of mcpServersSpec_0) { + if (typeof spec_0 === 'string' && isMcpbSource(spec_0)) { + mcpbPath = spec_0; + break; + } + } + } + if (!mcpbPath) { + setProcessError('No MCPB file found in plugin'); + setIsLoadingConfig(false); + return; + } + const pluginId_6 = `${selectedPlugin.plugin.name}@${selectedPlugin.marketplace}`; + const result_1 = await loadMcpbFile(mcpbPath, selectedPlugin.plugin.path, pluginId_6, undefined, undefined, true); + if ('status' in result_1 && result_1.status === 'needs-config') { + setConfigNeeded(result_1); + setViewState('configuring'); + } else { + setProcessError('Failed to load MCPB for configuration'); + } + } catch (err_2) { + const errorMsg = errorMessage(err_2); + setProcessError(`Failed to load configuration: ${errorMsg}`); + } finally { + setIsLoadingConfig(false); + } + } + }); + } + if (selectedPlugin.plugin.manifest.userConfig && Object.keys(selectedPlugin.plugin.manifest.userConfig).length > 0) { + menuItems.push({ + label: 'Configure options', + action: () => { + setViewState({ + type: 'configuring-options', + schema: selectedPlugin.plugin.manifest.userConfig! + }); + } + }); + } + menuItems.push({ + label: 'Update now', + action: () => void handleSingleOperation('update') + }); + menuItems.push({ + label: 'Uninstall', + action: () => void handleSingleOperation('uninstall') + }); + } + if (selectedPlugin.plugin.manifest.homepage) { + menuItems.push({ + label: 'Open homepage', + action: () => void openBrowser(selectedPlugin.plugin.manifest.homepage!) + }); + } + if (selectedPlugin.plugin.manifest.repository) { + menuItems.push({ + // Generic label — manifest.repository can be GitLab, Bitbucket, + // Azure DevOps, etc. (gh-31598). pluginDetailsHelpers.tsx:74 keeps + // 'View on GitHub' because that path has an explicit isGitHub check. + label: 'View repository', + action: () => void openBrowser(selectedPlugin.plugin.manifest.repository!) + }); + } + menuItems.push({ + label: 'Back to plugin list', + action: () => { + setViewState('plugin-list'); + setSelectedPlugin(null); + setProcessError(null); + } + }); + return menuItems; + }, [viewState, selectedPlugin, selectedPluginHasMcpb, pluginStates]); + + // Plugin-details navigation + useKeybindings({ + 'select:previous': () => { + if (detailsMenuIndex > 0) { + setDetailsMenuIndex(detailsMenuIndex - 1); + } + }, + 'select:next': () => { + if (detailsMenuIndex < detailsMenuItems.length - 1) { + setDetailsMenuIndex(detailsMenuIndex + 1); + } + }, + 'select:accept': () => { + if (detailsMenuItems[detailsMenuIndex]) { + detailsMenuItems[detailsMenuIndex]!.action(); + } + } + }, { + context: 'Select', + isActive: viewState === 'plugin-details' && !!selectedPlugin + }); + + // Failed-plugin-details: only "Uninstall" option, handle Enter + useKeybindings({ + 'select:accept': () => { + if (typeof viewState === 'object' && viewState.type === 'failed-plugin-details') { + void (async () => { + setIsProcessing(true); + setProcessError(null); + const pluginId_7 = viewState.plugin.id; + const pluginScope_1 = viewState.plugin.scope; + // Pass scope to uninstallPluginOp so it can find the correct V2 + // installation record and clean up on-disk files. Fall back to + // default scope if not installable (e.g. 'managed', though that + // case is guarded by isActive below). deleteDataDir=false: this + // is a recovery path for a plugin that failed to load — it may + // be reinstallable, so don't nuke ${CLAUDE_PLUGIN_DATA} silently. + // The normal uninstall path prompts; this one preserves. + const result_2 = isInstallableScope(pluginScope_1) ? await uninstallPluginOp(pluginId_7, pluginScope_1, false) : await uninstallPluginOp(pluginId_7, 'user', false); + let success = result_2.success; + if (!success) { + // Plugin was never installed (only in enabledPlugins settings). + // Remove directly from all editable settings sources. + const editableSources = ['userSettings' as const, 'projectSettings' as const, 'localSettings' as const]; + for (const source of editableSources) { + const settings = getSettingsForSource(source); + if (settings?.enabledPlugins?.[pluginId_7] !== undefined) { + updateSettingsForSource(source, { + enabledPlugins: { + ...settings.enabledPlugins, + [pluginId_7]: undefined + } + }); + success = true; + } + } + // Clear memoized caches so next loadAllPlugins() picks up settings changes + clearAllCaches(); + } + if (success) { + if (onManageComplete) { + await onManageComplete(); + } + setIsProcessing(false); + // Return to list (don't setResult — that closes the whole dialog) + setViewState('plugin-list'); + } else { + setIsProcessing(false); + setProcessError(result_2.message); + } + })(); + } + } + }, { + context: 'Select', + isActive: typeof viewState === 'object' && viewState.type === 'failed-plugin-details' && viewState.plugin.scope !== 'managed' + }); + + // Confirm-project-uninstall: y/enter disables in settings.local.json, n/escape cancels + useKeybindings({ + 'confirm:yes': () => { + if (!selectedPlugin) return; + setIsProcessing(true); + setProcessError(null); + const pluginId_8 = `${selectedPlugin.plugin.name}@${selectedPlugin.marketplace}`; + // Write `false` directly — disablePluginOp's cross-scope guard would + // reject this (plugin isn't in localSettings yet; the override IS the + // point). + const { + error: error_2 + } = updateSettingsForSource('localSettings', { + enabledPlugins: { + ...getSettingsForSource('localSettings')?.enabledPlugins, + [pluginId_8]: false + } + }); + if (error_2) { + setIsProcessing(false); + setProcessError(`Failed to write settings: ${error_2.message}`); + return; + } + clearAllCaches(); + setResult(`✓ Disabled ${selectedPlugin.plugin.name} in .claude/settings.local.json. Run /reload-plugins to apply.`); + if (onManageComplete) void onManageComplete(); + setParentViewState({ + type: 'menu' + }); + }, + 'confirm:no': () => { + setViewState('plugin-details'); + setProcessError(null); + } + }, { + context: 'Confirmation', + isActive: viewState === 'confirm-project-uninstall' && !!selectedPlugin && !isProcessing + }); + + // Confirm-data-cleanup: y uninstalls + deletes data dir, n uninstalls + keeps, + // esc cancels. Raw useInput because: (1) the Confirmation context maps + // enter→confirm:yes, which would make Enter delete the data directory — a + // destructive default the UI text ("y to delete · n to keep") doesn't + // advertise; (2) unlike confirm-project-uninstall (which uses useKeybindings + // where n and escape both map to confirm:no), here n and escape are DIFFERENT + // actions (keep-data vs cancel), so this deliberately stays on raw useInput. + // eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw y/n/esc; Enter must not trigger destructive delete + useInput((input, key) => { + if (!selectedPlugin) return; + const pluginId_9 = `${selectedPlugin.plugin.name}@${selectedPlugin.marketplace}`; + const pluginScope_2 = selectedPlugin.scope; + // Dialog is only reachable from the uninstall case (which guards on + // isBuiltin), but TS can't track that across viewState transitions. + if (!pluginScope_2 || pluginScope_2 === 'builtin' || !isInstallableScope(pluginScope_2)) return; + const doUninstall = async (deleteDataDir: boolean) => { + setIsProcessing(true); + setProcessError(null); + try { + const result_3 = await uninstallPluginOp(pluginId_9, pluginScope_2, deleteDataDir); + if (!result_3.success) throw new Error(result_3.message); + clearAllCaches(); + const suffix = deleteDataDir ? '' : ' · data preserved'; + setResult(`${figures.tick} ${result_3.message}${suffix}`); + if (onManageComplete) void onManageComplete(); + setParentViewState({ + type: 'menu' + }); + } catch (e_0) { + setIsProcessing(false); + setProcessError(e_0 instanceof Error ? e_0.message : String(e_0)); + } + }; + if (input === 'y' || input === 'Y') { + void doUninstall(true); + } else if (input === 'n' || input === 'N') { + void doUninstall(false); + } else if (key.escape) { + setViewState('plugin-details'); + setProcessError(null); + } + }, { + isActive: typeof viewState === 'object' && viewState.type === 'confirm-data-cleanup' && !!selectedPlugin && !isProcessing + }); + + // Reset selection when search query changes + React.useEffect(() => { + setSelectedIndex(0); + }, [searchQuery]); + + // Handle input for entering search mode (text input handled by useSearchInput hook) + // eslint-disable-next-line custom-rules/prefer-use-keybindings -- useInput needed for raw search mode text input + useInput((input_0, key_0) => { + const keyIsNotCtrlOrMeta = !key_0.ctrl && !key_0.meta; + if (isSearchMode) { + // Text input is handled by useSearchInput hook + return; + } + + // Enter search mode with '/' or any printable character (except navigation keys) + if (input_0 === '/' && keyIsNotCtrlOrMeta) { + setIsSearchMode(true); + setSearchQuery(''); + setSelectedIndex(0); + } else if (keyIsNotCtrlOrMeta && input_0.length > 0 && !/^\s+$/.test(input_0) && input_0 !== 'j' && input_0 !== 'k' && input_0 !== ' ') { + setIsSearchMode(true); + setSearchQuery(input_0); + setSelectedIndex(0); + } + }, { + isActive: viewState === 'plugin-list' + }); + + // Loading state + if (loading) { + return Loading installed plugins…; + } + + // No plugins or MCPs installed + if (unifiedItems.length === 0) { + return + + Manage plugins + + No plugins or MCP servers installed. + + Esc to go back + + ; + } + if (typeof viewState === 'object' && viewState.type === 'plugin-options' && selectedPlugin) { + const pluginId_10 = `${selectedPlugin.plugin.name}@${selectedPlugin.marketplace}`; + function finish(msg: string): void { + setResult(msg); + // Plugin is enabled regardless of whether config was saved or + // skipped — onManageComplete → markPluginsChanged → the + // persistent "run /reload-plugins" notice. + if (onManageComplete) { + void onManageComplete(); + } + setParentViewState({ + type: 'menu' + }); + } + return { + switch (outcome) { + case 'configured': + finish(`✓ Enabled and configured ${selectedPlugin.plugin.name}. Run /reload-plugins to apply.`); + break; + case 'skipped': + finish(`✓ Enabled ${selectedPlugin.plugin.name}. Run /reload-plugins to apply.`); + break; + case 'error': + finish(`Failed to save configuration: ${detail}`); + break; + } + }} />; + } + + // Configure options (from the Manage menu) + if (typeof viewState === 'object' && viewState.type === 'configuring-options' && selectedPlugin) { + const pluginId_11 = `${selectedPlugin.plugin.name}@${selectedPlugin.marketplace}`; + return { + try { + savePluginOptions(pluginId_11, values, viewState.schema); + clearAllCaches(); + setResult('Configuration saved. Run /reload-plugins for changes to take effect.'); + } catch (err_3) { + setProcessError(`Failed to save configuration: ${errorMessage(err_3)}`); + } + setViewState('plugin-details'); + }} onCancel={() => setViewState('plugin-details')} />; + } + + // Configuration view + if (viewState === 'configuring' && configNeeded && selectedPlugin) { + const pluginId_12 = `${selectedPlugin.plugin.name}@${selectedPlugin.marketplace}`; + async function handleSave(config: UserConfigValues) { + if (!configNeeded || !selectedPlugin) return; + try { + // Find MCPB path again + const mcpServersSpec_1 = selectedPlugin.plugin.manifest.mcpServers; + let mcpbPath_0: string | null = null; + if (typeof mcpServersSpec_1 === 'string' && isMcpbSource(mcpServersSpec_1)) { + mcpbPath_0 = mcpServersSpec_1; + } else if (Array.isArray(mcpServersSpec_1)) { + for (const spec_1 of mcpServersSpec_1) { + if (typeof spec_1 === 'string' && isMcpbSource(spec_1)) { + mcpbPath_0 = spec_1; + break; + } + } + } + if (!mcpbPath_0) { + setProcessError('No MCPB file found'); + setViewState('plugin-details'); + return; + } + + // Reload with provided config + await loadMcpbFile(mcpbPath_0, selectedPlugin.plugin.path, pluginId_12, undefined, config); + + // Success - go back to details + setProcessError(null); + setConfigNeeded(null); + setViewState('plugin-details'); + setResult('Configuration saved. Run /reload-plugins for changes to take effect.'); + } catch (err_4) { + const errorMsg_0 = errorMessage(err_4); + setProcessError(`Failed to save configuration: ${errorMsg_0}`); + setViewState('plugin-details'); + } + } + function handleCancel() { + setConfigNeeded(null); + setViewState('plugin-details'); + } + return ; + } + + // Flagged plugin detail view + if (typeof viewState === 'object' && viewState.type === 'flagged-detail') { + const fp = viewState.plugin; + return + + + {fp.name} @ {fp.marketplace} + + + + + Status: + Removed + + + + + Removed from marketplace · reason: {fp.reason} + + {fp.text} + + Flagged on {new Date(fp.flaggedAt).toLocaleDateString()} + + + + + + {figures.pointer} + Dismiss + + + + + + + + ; + } + + // Confirm-project-uninstall: warn about shared .claude/settings.json, + // offer to disable in settings.local.json instead. + if (viewState === 'confirm-project-uninstall' && selectedPlugin) { + return + + {selectedPlugin.plugin.name} is enabled in .claude/settings.json + (shared with your team) + + + Disable it just for you in .claude/settings.local.json? + + This has the same effect as uninstalling, without affecting other + contributors. + + + {processError && + {processError} + } + + {isProcessing ? Disabling… : + + + } + + ; + } + + // Confirm-data-cleanup: prompt before deleting ${CLAUDE_PLUGIN_DATA} dir + if (typeof viewState === 'object' && viewState.type === 'confirm-data-cleanup' && selectedPlugin) { + return + + {selectedPlugin.plugin.name} has {viewState.size.human} of persistent + data + + + Delete it along with the plugin? + + {pluginDataDirPath(`${selectedPlugin.plugin.name}@${selectedPlugin.marketplace}`)} + + + {processError && + {processError} + } + + {isProcessing ? Uninstalling… : + y to delete · n to keep ·{' '} + esc to cancel + } + + ; + } + + // Plugin details view + if (viewState === 'plugin-details' && selectedPlugin) { + const mergedSettings_2 = getSettings_DEPRECATED(); // Use merged settings to respect all layers + const pluginId_13 = `${selectedPlugin.plugin.name}@${selectedPlugin.marketplace}`; + const isEnabled_2 = mergedSettings_2?.enabledPlugins?.[pluginId_13] !== false; + + // Compute plugin errors section + const filteredPluginErrors = pluginErrors.filter(e_1 => 'plugin' in e_1 && e_1.plugin === selectedPlugin.plugin.name || e_1.source === pluginId_13 || e_1.source.startsWith(`${selectedPlugin.plugin.name}@`)); + const pluginErrorsSection = filteredPluginErrors.length === 0 ? null : + + {filteredPluginErrors.length}{' '} + {plural(filteredPluginErrors.length, 'error')}: + + {filteredPluginErrors.map((error_3, i_0) => { + const guidance = getErrorGuidance(error_3); + return + {formatErrorMessage(error_3)} + {guidance && + {figures.arrowRight} {guidance} + } + ; + })} + ; + return + + + {selectedPlugin.plugin.name} @ {selectedPlugin.marketplace} + + + + {/* Scope */} + + Scope: + {selectedPlugin.scope || 'user'} + + + {/* Plugin details */} + {selectedPlugin.plugin.manifest.version && + Version: + {selectedPlugin.plugin.manifest.version} + } + + {selectedPlugin.plugin.manifest.description && + {selectedPlugin.plugin.manifest.description} + } + + {selectedPlugin.plugin.manifest.author && + Author: + {selectedPlugin.plugin.manifest.author.name} + } + + {/* Current status */} + + Status: + + {isEnabled_2 ? 'Enabled' : 'Disabled'} + + {selectedPlugin.pendingUpdate && · Marked for update} + + + {/* Installed components */} + + + {/* Plugin errors */} + {pluginErrorsSection} + + {/* Menu */} + + {detailsMenuItems.map((item_9, index_0) => { + const isSelected = index_0 === detailsMenuIndex; + return + {isSelected && {figures.pointer} } + {!isSelected && {' '}} + + {item_9.label} + + ; + })} + + + {/* Processing state */} + {isProcessing && + Processing… + } + + {/* Error message */} + {processError && + {processError} + } + + + + + + + + + + + ; + } + + // Failed plugin detail view + if (typeof viewState === 'object' && viewState.type === 'failed-plugin-details') { + const failedPlugin_0 = viewState.plugin; + const firstError = failedPlugin_0.errors[0]; + const errorMessage_0 = firstError ? formatErrorMessage(firstError) : 'Failed to load'; + return + + {failedPlugin_0.name} + @ {failedPlugin_0.marketplace} + ({failedPlugin_0.scope}) + + {errorMessage_0} + + {failedPlugin_0.scope === 'managed' ? + + Managed by your organization — contact your admin + + : + {figures.pointer} + Remove + } + + {isProcessing && Processing…} + {processError && {processError}} + + + + + {failedPlugin_0.scope !== 'managed' && } + + + + + ; + } + + // MCP detail view + if (typeof viewState === 'object' && viewState.type === 'mcp-detail') { + const client_3 = viewState.client; + const serverToolsCount = filterToolsByServer(mcpTools, client_3.name).length; + + // Common handlers for MCP menus + const handleMcpViewTools = () => { + setViewState({ + type: 'mcp-tools', + client: client_3 + }); + }; + const handleMcpCancel = () => { + setViewState('plugin-list'); + }; + const handleMcpComplete = (result_4?: string) => { + if (result_4) { + setResult(result_4); + } + setViewState('plugin-list'); + }; + + // Transform MCPServerConnection to appropriate ServerInfo type + const scope_5 = client_3.config.scope; + const configType = client_3.config.type; + if (configType === 'stdio') { + const server: StdioServerInfo = { + name: client_3.name, + client: client_3, + scope: scope_5, + transport: 'stdio', + config: client_3.config as McpStdioServerConfig + }; + return ; + } else if (configType === 'sse') { + const server_0: SSEServerInfo = { + name: client_3.name, + client: client_3, + scope: scope_5, + transport: 'sse', + isAuthenticated: undefined, + config: client_3.config as McpSSEServerConfig + }; + return ; + } else if (configType === 'http') { + const server_1: HTTPServerInfo = { + name: client_3.name, + client: client_3, + scope: scope_5, + transport: 'http', + isAuthenticated: undefined, + config: client_3.config as McpHTTPServerConfig + }; + return ; + } else if (configType === 'claudeai-proxy') { + const server_2: ClaudeAIServerInfo = { + name: client_3.name, + client: client_3, + scope: scope_5, + transport: 'claudeai-proxy', + isAuthenticated: undefined, + config: client_3.config as McpClaudeAIProxyServerConfig + }; + return ; + } + + // Fallback - shouldn't happen but handle gracefully + setViewState('plugin-list'); + return null; + } + + // MCP tools view + if (typeof viewState === 'object' && viewState.type === 'mcp-tools') { + const client_4 = viewState.client; + const scope_6 = client_4.config.scope; + const configType_0 = client_4.config.type; + + // Build ServerInfo for MCPToolListView + let server_3: StdioServerInfo | SSEServerInfo | HTTPServerInfo | ClaudeAIServerInfo; + if (configType_0 === 'stdio') { + server_3 = { + name: client_4.name, + client: client_4, + scope: scope_6, + transport: 'stdio', + config: client_4.config as McpStdioServerConfig + }; + } else if (configType_0 === 'sse') { + server_3 = { + name: client_4.name, + client: client_4, + scope: scope_6, + transport: 'sse', + isAuthenticated: undefined, + config: client_4.config as McpSSEServerConfig + }; + } else if (configType_0 === 'http') { + server_3 = { + name: client_4.name, + client: client_4, + scope: scope_6, + transport: 'http', + isAuthenticated: undefined, + config: client_4.config as McpHTTPServerConfig + }; + } else { + server_3 = { + name: client_4.name, + client: client_4, + scope: scope_6, + transport: 'claudeai-proxy', + isAuthenticated: undefined, + config: client_4.config as McpClaudeAIProxyServerConfig + }; + } + return { + setViewState({ + type: 'mcp-tool-detail', + client: client_4, + tool + }); + }} onBack={() => setViewState({ + type: 'mcp-detail', + client: client_4 + })} />; + } + + // MCP tool detail view + if (typeof viewState === 'object' && viewState.type === 'mcp-tool-detail') { + const { + client: client_5, + tool: tool_0 + } = viewState; + const scope_7 = client_5.config.scope; + const configType_1 = client_5.config.type; + + // Build ServerInfo for MCPToolDetailView + let server_4: StdioServerInfo | SSEServerInfo | HTTPServerInfo | ClaudeAIServerInfo; + if (configType_1 === 'stdio') { + server_4 = { + name: client_5.name, + client: client_5, + scope: scope_7, + transport: 'stdio', + config: client_5.config as McpStdioServerConfig + }; + } else if (configType_1 === 'sse') { + server_4 = { + name: client_5.name, + client: client_5, + scope: scope_7, + transport: 'sse', + isAuthenticated: undefined, + config: client_5.config as McpSSEServerConfig + }; + } else if (configType_1 === 'http') { + server_4 = { + name: client_5.name, + client: client_5, + scope: scope_7, + transport: 'http', + isAuthenticated: undefined, + config: client_5.config as McpHTTPServerConfig + }; + } else { + server_4 = { + name: client_5.name, + client: client_5, + scope: scope_7, + transport: 'claudeai-proxy', + isAuthenticated: undefined, + config: client_5.config as McpClaudeAIProxyServerConfig + }; + } + return setViewState({ + type: 'mcp-tools', + client: client_5 + })} />; + } + + // Plugin list view (main management interface) + const visibleItems = pagination.getVisibleItems(filteredItems); + return + {/* Search box */} + + + + + {/* No search results */} + {filteredItems.length === 0 && searchQuery && + No items match "{searchQuery}" + } + + {/* Scroll up indicator */} + {pagination.scrollPosition.canScrollUp && + {figures.arrowUp} more above + } + + {/* Unified list of plugins and MCPs grouped by scope */} + {visibleItems.map((item_10, visibleIndex) => { + const actualIndex = pagination.toActualIndex(visibleIndex); + const isSelected_0 = actualIndex === selectedIndex && !isSearchMode; + + // Check if we need to show a scope header + const prevItem = visibleIndex > 0 ? visibleItems[visibleIndex - 1] : null; + const showScopeHeader = !prevItem || prevItem.scope !== item_10.scope; + + // Get scope label + const getScopeLabel = (scope_8: string): string => { + switch (scope_8) { + case 'flagged': + return 'Flagged'; + case 'project': + return 'Project'; + case 'local': + return 'Local'; + case 'user': + return 'User'; + case 'enterprise': + return 'Enterprise'; + case 'managed': + return 'Managed'; + case 'builtin': + return 'Built-in'; + case 'dynamic': + return 'Built-in'; + default: + return scope_8; + } + }; + return + {showScopeHeader && 0 ? 1 : 0} paddingLeft={2}> + + {getScopeLabel(item_10.scope)} + + } + + ; + })} + + {/* Scroll down indicator */} + {pagination.scrollPosition.canScrollDown && + {figures.arrowDown} more below + } + + {/* Help text */} + + + + type to search + + + + + + + + {/* Reload disclaimer for plugin changes */} + {pendingToggles.size > 0 && + + Run /reload-plugins to apply changes + + } + ; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwiRGlyZW50IiwiZnMiLCJwYXRoIiwiUmVhY3QiLCJ1c2VDYWxsYmFjayIsInVzZUVmZmVjdCIsInVzZU1lbW8iLCJ1c2VSZWYiLCJ1c2VTdGF0ZSIsIkNvbmZpZ3VyYWJsZVNob3J0Y3V0SGludCIsIkJ5bGluZSIsIk1DUFJlbW90ZVNlcnZlck1lbnUiLCJNQ1BTdGRpb1NlcnZlck1lbnUiLCJNQ1BUb29sRGV0YWlsVmlldyIsIk1DUFRvb2xMaXN0VmlldyIsIkNsYXVkZUFJU2VydmVySW5mbyIsIkhUVFBTZXJ2ZXJJbmZvIiwiU1NFU2VydmVySW5mbyIsIlN0ZGlvU2VydmVySW5mbyIsIlNlYXJjaEJveCIsInVzZVNlYXJjaElucHV0IiwidXNlVGVybWluYWxTaXplIiwiQm94IiwiVGV4dCIsInVzZUlucHV0IiwidXNlVGVybWluYWxGb2N1cyIsInVzZUtleWJpbmRpbmciLCJ1c2VLZXliaW5kaW5ncyIsImdldEJ1aWx0aW5QbHVnaW5EZWZpbml0aW9uIiwidXNlTWNwVG9nZ2xlRW5hYmxlZCIsIk1DUFNlcnZlckNvbm5lY3Rpb24iLCJNY3BDbGF1ZGVBSVByb3h5U2VydmVyQ29uZmlnIiwiTWNwSFRUUFNlcnZlckNvbmZpZyIsIk1jcFNTRVNlcnZlckNvbmZpZyIsIk1jcFN0ZGlvU2VydmVyQ29uZmlnIiwiZmlsdGVyVG9vbHNCeVNlcnZlciIsImRpc2FibGVQbHVnaW5PcCIsImVuYWJsZVBsdWdpbk9wIiwiZ2V0UGx1Z2luSW5zdGFsbGF0aW9uRnJvbVYyIiwiaXNJbnN0YWxsYWJsZVNjb3BlIiwiaXNQbHVnaW5FbmFibGVkQXRQcm9qZWN0U2NvcGUiLCJ1bmluc3RhbGxQbHVnaW5PcCIsInVwZGF0ZVBsdWdpbk9wIiwidXNlQXBwU3RhdGUiLCJUb29sIiwiTG9hZGVkUGx1Z2luIiwiUGx1Z2luRXJyb3IiLCJjb3VudCIsIm9wZW5Ccm93c2VyIiwibG9nRm9yRGVidWdnaW5nIiwiZXJyb3JNZXNzYWdlIiwidG9FcnJvciIsImxvZ0Vycm9yIiwiY2xlYXJBbGxDYWNoZXMiLCJsb2FkSW5zdGFsbGVkUGx1Z2luc1YyIiwiZ2V0TWFya2V0cGxhY2UiLCJpc01jcGJTb3VyY2UiLCJsb2FkTWNwYkZpbGUiLCJNY3BiTmVlZHNDb25maWdSZXN1bHQiLCJVc2VyQ29uZmlnVmFsdWVzIiwiZ2V0UGx1Z2luRGF0YURpclNpemUiLCJwbHVnaW5EYXRhRGlyUGF0aCIsImdldEZsYWdnZWRQbHVnaW5zIiwibWFya0ZsYWdnZWRQbHVnaW5zU2VlbiIsInJlbW92ZUZsYWdnZWRQbHVnaW4iLCJQZXJzaXN0YWJsZVBsdWdpblNjb3BlIiwicGFyc2VQbHVnaW5JZGVudGlmaWVyIiwibG9hZEFsbFBsdWdpbnMiLCJsb2FkUGx1Z2luT3B0aW9ucyIsIlBsdWdpbk9wdGlvblNjaGVtYSIsInNhdmVQbHVnaW5PcHRpb25zIiwiaXNQbHVnaW5CbG9ja2VkQnlQb2xpY3kiLCJnZXRQbHVnaW5FZGl0YWJsZVNjb3BlcyIsImdldFNldHRpbmdzX0RFUFJFQ0FURUQiLCJnZXRTZXR0aW5nc0ZvclNvdXJjZSIsInVwZGF0ZVNldHRpbmdzRm9yU291cmNlIiwianNvblBhcnNlIiwicGx1cmFsIiwiZm9ybWF0RXJyb3JNZXNzYWdlIiwiZ2V0RXJyb3JHdWlkYW5jZSIsIlBsdWdpbk9wdGlvbnNEaWFsb2ciLCJQbHVnaW5PcHRpb25zRmxvdyIsIlZpZXdTdGF0ZSIsIlBhcmVudFZpZXdTdGF0ZSIsIlVuaWZpZWRJbnN0YWxsZWRDZWxsIiwiVW5pZmllZEluc3RhbGxlZEl0ZW0iLCJ1c2VQYWdpbmF0aW9uIiwiUHJvcHMiLCJzZXRWaWV3U3RhdGUiLCJzdGF0ZSIsInNldFJlc3VsdCIsInJlc3VsdCIsIm9uTWFuYWdlQ29tcGxldGUiLCJQcm9taXNlIiwib25TZWFyY2hNb2RlQ2hhbmdlIiwiaXNBY3RpdmUiLCJ0YXJnZXRQbHVnaW4iLCJ0YXJnZXRNYXJrZXRwbGFjZSIsImFjdGlvbiIsIkZsYWdnZWRQbHVnaW5JbmZvIiwiaWQiLCJuYW1lIiwibWFya2V0cGxhY2UiLCJyZWFzb24iLCJ0ZXh0IiwiZmxhZ2dlZEF0IiwiRmFpbGVkUGx1Z2luSW5mbyIsImVycm9ycyIsInNjb3BlIiwidHlwZSIsInNjaGVtYSIsInNpemUiLCJieXRlcyIsImh1bWFuIiwicGx1Z2luIiwiY2xpZW50IiwidG9vbCIsIk1hcmtldHBsYWNlSW5mbyIsImluc3RhbGxlZFBsdWdpbnMiLCJlbmFibGVkQ291bnQiLCJkaXNhYmxlZENvdW50IiwiUGx1Z2luU3RhdGUiLCJwZW5kaW5nRW5hYmxlIiwicGVuZGluZ1VwZGF0ZSIsImdldEJhc2VGaWxlTmFtZXMiLCJkaXJQYXRoIiwiZW50cmllcyIsInJlYWRkaXIiLCJ3aXRoRmlsZVR5cGVzIiwiZmlsdGVyIiwiZW50cnkiLCJpc0ZpbGUiLCJlbmRzV2l0aCIsIm1hcCIsImJhc2VOYW1lIiwiYmFzZW5hbWUiLCJlcnJvciIsImVycm9yTXNnIiwibGV2ZWwiLCJnZXRTa2lsbERpck5hbWVzIiwic2tpbGxOYW1lcyIsImlzRGlyZWN0b3J5IiwiaXNTeW1ib2xpY0xpbmsiLCJza2lsbEZpbGVQYXRoIiwiam9pbiIsInN0Iiwic3RhdCIsInB1c2giLCJQbHVnaW5Db21wb25lbnRzRGlzcGxheSIsIlJlYWN0Tm9kZSIsImNvbXBvbmVudHMiLCJzZXRDb21wb25lbnRzIiwiY29tbWFuZHMiLCJSZWNvcmQiLCJhZ2VudHMiLCJza2lsbHMiLCJob29rcyIsIm1jcFNlcnZlcnMiLCJsb2FkaW5nIiwic2V0TG9hZGluZyIsInNldEVycm9yIiwibG9hZENvbXBvbmVudHMiLCJidWlsdGluRGVmIiwicyIsImhvb2tFdmVudHMiLCJPYmplY3QiLCJrZXlzIiwibWNwU2VydmVyTmFtZXMiLCJsZW5ndGgiLCJtYXJrZXRwbGFjZURhdGEiLCJwbHVnaW5FbnRyeSIsInBsdWdpbnMiLCJmaW5kIiwicCIsImNvbW1hbmRQYXRoTGlzdCIsImNvbW1hbmRzUGF0aCIsImNvbW1hbmRzUGF0aHMiLCJjb21tYW5kTGlzdCIsImNvbW1hbmRQYXRoIiwiYmFzZU5hbWVzIiwiYWdlbnRQYXRoTGlzdCIsImFnZW50c1BhdGgiLCJhZ2VudHNQYXRocyIsImFnZW50TGlzdCIsImFnZW50UGF0aCIsInNraWxsUGF0aExpc3QiLCJza2lsbHNQYXRoIiwic2tpbGxzUGF0aHMiLCJza2lsbExpc3QiLCJza2lsbFBhdGgiLCJza2lsbERpck5hbWVzIiwiaG9va3NMaXN0IiwiaG9va3NDb25maWciLCJtY3BTZXJ2ZXJzTGlzdCIsImVyciIsIkVycm9yIiwibWVzc2FnZSIsImhhc0NvbXBvbmVudHMiLCJBcnJheSIsImlzQXJyYXkiLCJTdHJpbmciLCJjaGVja0lmTG9jYWxQbHVnaW4iLCJwbHVnaW5OYW1lIiwibWFya2V0cGxhY2VOYW1lIiwic291cmNlIiwiZmlsdGVyTWFuYWdlZERpc2FibGVkUGx1Z2lucyIsInNwbGl0IiwiTWFuYWdlUGx1Z2lucyIsInNldFBhcmVudFZpZXdTdGF0ZSIsIm1jcENsaWVudHMiLCJtY3AiLCJjbGllbnRzIiwibWNwVG9vbHMiLCJ0b29scyIsInBsdWdpbkVycm9ycyIsImZsYWdnZWRQbHVnaW5zIiwiaXNTZWFyY2hNb2RlIiwic2V0SXNTZWFyY2hNb2RlUmF3Iiwic2V0SXNTZWFyY2hNb2RlIiwiYWN0aXZlIiwiaXNUZXJtaW5hbEZvY3VzZWQiLCJjb2x1bW5zIiwidGVybWluYWxXaWR0aCIsInZpZXdTdGF0ZSIsInF1ZXJ5Iiwic2VhcmNoUXVlcnkiLCJzZXRRdWVyeSIsInNldFNlYXJjaFF1ZXJ5IiwiY3Vyc29yT2Zmc2V0Iiwic2VhcmNoQ3Vyc29yT2Zmc2V0Iiwib25FeGl0Iiwic2VsZWN0ZWRQbHVnaW4iLCJzZXRTZWxlY3RlZFBsdWdpbiIsIm1hcmtldHBsYWNlcyIsInNldE1hcmtldHBsYWNlcyIsInBsdWdpblN0YXRlcyIsInNldFBsdWdpblN0YXRlcyIsInBlbmRpbmdUb2dnbGVzIiwic2V0UGVuZGluZ1RvZ2dsZXMiLCJNYXAiLCJoYXNBdXRvTmF2aWdhdGVkIiwicGVuZGluZ0F1dG9BY3Rpb25SZWYiLCJ1bmRlZmluZWQiLCJ0b2dnbGVNY3BTZXJ2ZXIiLCJoYW5kbGVCYWNrIiwic2V0UHJvY2Vzc0Vycm9yIiwic2V0Q29uZmlnTmVlZGVkIiwiY29udGV4dCIsImdldE1jcFN0YXR1cyIsInVuaWZpZWRJdGVtcyIsIm1lcmdlZFNldHRpbmdzIiwicGx1Z2luTWNwTWFwIiwiZGlzcGxheU5hbWUiLCJzdGFydHNXaXRoIiwicGFydHMiLCJzZXJ2ZXJOYW1lIiwic2xpY2UiLCJleGlzdGluZyIsImdldCIsInNldCIsIlBsdWdpbldpdGhDaGlsZHJlbiIsIml0ZW0iLCJvcmlnaW5hbFNjb3BlIiwiY2hpbGRNY3BzIiwicGx1Z2luc1dpdGhDaGlsZHJlbiIsInBsdWdpbklkIiwiaXNFbmFibGVkIiwiZW5hYmxlZFBsdWdpbnMiLCJlIiwiaXNCdWlsdGluIiwiZGVzY3JpcHRpb24iLCJtYW5pZmVzdCIsImVycm9yQ291bnQiLCJwZW5kaW5nVG9nZ2xlIiwibWF0Y2hlZFBsdWdpbklkcyIsIlNldCIsIm1hdGNoZWRQbHVnaW5OYW1lcyIsIm9ycGhhbkVycm9yc0J5U291cmNlIiwiaGFzIiwicGx1Z2luU2NvcGVzIiwiZmFpbGVkUGx1Z2luSXRlbXMiLCJwYXJzZWQiLCJyYXdTY29wZSIsInN0YW5kYWxvbmVNY3BzIiwiY29uZmlnIiwic3RhdHVzIiwic2NvcGVPcmRlciIsImZsYWdnZWQiLCJwcm9qZWN0IiwibG9jYWwiLCJ1c2VyIiwiZW50ZXJwcmlzZSIsIm1hbmFnZWQiLCJkeW5hbWljIiwiYnVpbHRpbiIsInVuaWZpZWQiLCJpdGVtc0J5U2NvcGUiLCJkaXNwbGF5U2NvcGUiLCJpbmRlbnRlZCIsImZhaWxlZFBsdWdpbiIsInNvcnRlZFNjb3BlcyIsInNvcnQiLCJhIiwiYiIsIml0ZW1zIiwicGx1Z2luR3JvdXBzIiwic3RhbmRhbG9uZU1jcHNJblNjb3BlIiwiaSIsImdyb3VwIiwibmV4dEl0ZW0iLCJsb2NhbGVDb21wYXJlIiwiZmxhZ2dlZElkcyIsImZpbHRlcmVkSXRlbXMiLCJsb3dlclF1ZXJ5IiwidG9Mb3dlckNhc2UiLCJpbmNsdWRlcyIsInNlbGVjdGVkSW5kZXgiLCJzZXRTZWxlY3RlZEluZGV4IiwicGFnaW5hdGlvbiIsInRvdGFsSXRlbXMiLCJtYXhWaXNpYmxlIiwiZGV0YWlsc01lbnVJbmRleCIsInNldERldGFpbHNNZW51SW5kZXgiLCJpc1Byb2Nlc3NpbmciLCJzZXRJc1Byb2Nlc3NpbmciLCJwcm9jZXNzRXJyb3IiLCJjb25maWdOZWVkZWQiLCJfaXNMb2FkaW5nQ29uZmlnIiwic2V0SXNMb2FkaW5nQ29uZmlnIiwic2VsZWN0ZWRQbHVnaW5IYXNNY3BiIiwic2V0U2VsZWN0ZWRQbHVnaW5IYXNNY3BiIiwiZGV0ZWN0TWNwYiIsIm1jcFNlcnZlcnNTcGVjIiwiaGFzTWNwYiIsInNvbWUiLCJtYXJrZXRwbGFjZURpciIsIm1hcmtldHBsYWNlSnNvblBhdGgiLCJjb250ZW50IiwicmVhZEZpbGUiLCJzcGVjIiwibG9hZEluc3RhbGxlZFBsdWdpbnMiLCJlbmFibGVkIiwiZGlzYWJsZWQiLCJhbGxQbHVnaW5zIiwicGx1Z2luc0J5TWFya2V0cGxhY2UiLCJtYXJrZXRwbGFjZUluZm9zIiwiYWxsU3RhdGVzIiwiY3VycmVudCIsInRhcmdldE5hbWUiLCJ0YXJnZXRNa3RGcm9tSWQiLCJlZmZlY3RpdmVUYXJnZXRNYXJrZXRwbGFjZSIsIm1hcmtldHBsYWNlc1RvU2VhcmNoIiwibSIsInBsdWdpblN0YXRlIiwiZmFpbGVkSXRlbSIsImhhbmRsZVNpbmdsZU9wZXJhdGlvbiIsIm9wZXJhdGlvbiIsInBsdWdpblNjb3BlIiwicmV2ZXJzZURlcGVuZGVudHMiLCJlbmFibGVSZXN1bHQiLCJzdWNjZXNzIiwiZGlzYWJsZVJlc3VsdCIsImluc3RhbGxzIiwiaXNMYXN0U2NvcGUiLCJkYXRhU2l6ZSIsImFscmVhZHlVcFRvRGF0ZSIsIm5ld1ZlcnNpb24iLCJwbHVnaW5JZE5vdyIsInNldHRpbmdzQWZ0ZXIiLCJlbmFibGVkQWZ0ZXIiLCJvcGVyYXRpb25OYW1lIiwiZGVwV2FybiIsImhhbmRsZVNpbmdsZU9wZXJhdGlvblJlZiIsInBlbmRpbmciLCJoYW5kbGVUb2dnbGUiLCJjdXJyZW50UGVuZGluZyIsIm5ld1BlbmRpbmciLCJkZWxldGUiLCJoYW5kbGVBY2NlcHQiLCJzZWxlY3Q6cHJldmlvdXMiLCJoYW5kbGVTZWxlY3Rpb25DaGFuZ2UiLCJzZWxlY3Q6bmV4dCIsImhhbmRsZUZsYWdnZWREaXNtaXNzIiwiZGV0YWlsc01lbnVJdGVtcyIsIm1lbnVJdGVtcyIsImxhYmVsIiwibG9jYWxFcnJvciIsIm5ld1N0YXRlcyIsImluZGV4IiwiZmluZEluZGV4IiwibWNwYlBhdGgiLCJ1c2VyQ29uZmlnIiwiaG9tZXBhZ2UiLCJyZXBvc2l0b3J5Iiwic2VsZWN0OmFjY2VwdCIsImVkaXRhYmxlU291cmNlcyIsImNvbnN0Iiwic2V0dGluZ3MiLCJjb25maXJtOnllcyIsImNvbmZpcm06bm8iLCJpbnB1dCIsImtleSIsImRvVW5pbnN0YWxsIiwiZGVsZXRlRGF0YURpciIsInN1ZmZpeCIsInRpY2siLCJlc2NhcGUiLCJrZXlJc05vdEN0cmxPck1ldGEiLCJjdHJsIiwibWV0YSIsInRlc3QiLCJmaW5pc2giLCJtc2ciLCJvdXRjb21lIiwiZGV0YWlsIiwidmFsdWVzIiwiaGFuZGxlU2F2ZSIsImhhbmRsZUNhbmNlbCIsImNvbmZpZ1NjaGVtYSIsImV4aXN0aW5nQ29uZmlnIiwiZnAiLCJEYXRlIiwidG9Mb2NhbGVEYXRlU3RyaW5nIiwicG9pbnRlciIsImZpbHRlcmVkUGx1Z2luRXJyb3JzIiwicGx1Z2luRXJyb3JzU2VjdGlvbiIsImd1aWRhbmNlIiwiYXJyb3dSaWdodCIsInZlcnNpb24iLCJhdXRob3IiLCJpc1NlbGVjdGVkIiwiZmlyc3RFcnJvciIsInNlcnZlclRvb2xzQ291bnQiLCJoYW5kbGVNY3BWaWV3VG9vbHMiLCJoYW5kbGVNY3BDYW5jZWwiLCJoYW5kbGVNY3BDb21wbGV0ZSIsImNvbmZpZ1R5cGUiLCJzZXJ2ZXIiLCJ0cmFuc3BvcnQiLCJpc0F1dGhlbnRpY2F0ZWQiLCJ2aXNpYmxlSXRlbXMiLCJnZXRWaXNpYmxlSXRlbXMiLCJzY3JvbGxQb3NpdGlvbiIsImNhblNjcm9sbFVwIiwiYXJyb3dVcCIsInZpc2libGVJbmRleCIsImFjdHVhbEluZGV4IiwidG9BY3R1YWxJbmRleCIsInByZXZJdGVtIiwic2hvd1Njb3BlSGVhZGVyIiwiZ2V0U2NvcGVMYWJlbCIsImNhblNjcm9sbERvd24iLCJhcnJvd0Rvd24iXSwic291cmNlcyI6WyJNYW5hZ2VQbHVnaW5zLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgZmlndXJlcyBmcm9tICdmaWd1cmVzJ1xuaW1wb3J0IHR5cGUgeyBEaXJlbnQgfSBmcm9tICdmcydcbmltcG9ydCAqIGFzIGZzIGZyb20gJ2ZzL3Byb21pc2VzJ1xuaW1wb3J0ICogYXMgcGF0aCBmcm9tICdwYXRoJ1xuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyB1c2VDYWxsYmFjaywgdXNlRWZmZWN0LCB1c2VNZW1vLCB1c2VSZWYsIHVzZVN0YXRlIH0gZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBDb25maWd1cmFibGVTaG9ydGN1dEhpbnQgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL0NvbmZpZ3VyYWJsZVNob3J0Y3V0SGludC5qcydcbmltcG9ydCB7IEJ5bGluZSB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvZGVzaWduLXN5c3RlbS9CeWxpbmUuanMnXG5pbXBvcnQgeyBNQ1BSZW1vdGVTZXJ2ZXJNZW51IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9tY3AvTUNQUmVtb3RlU2VydmVyTWVudS5qcydcbmltcG9ydCB7IE1DUFN0ZGlvU2VydmVyTWVudSB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvbWNwL01DUFN0ZGlvU2VydmVyTWVudS5qcydcbmltcG9ydCB7IE1DUFRvb2xEZXRhaWxWaWV3IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9tY3AvTUNQVG9vbERldGFpbFZpZXcuanMnXG5pbXBvcnQgeyBNQ1BUb29sTGlzdFZpZXcgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL21jcC9NQ1BUb29sTGlzdFZpZXcuanMnXG5pbXBvcnQgdHlwZSB7XG4gIENsYXVkZUFJU2VydmVySW5mbyxcbiAgSFRUUFNlcnZlckluZm8sXG4gIFNTRVNlcnZlckluZm8sXG4gIFN0ZGlvU2VydmVySW5mbyxcbn0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9tY3AvdHlwZXMuanMnXG5pbXBvcnQgeyBTZWFyY2hCb3ggfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL1NlYXJjaEJveC5qcydcbmltcG9ydCB7IHVzZVNlYXJjaElucHV0IH0gZnJvbSAnLi4vLi4vaG9va3MvdXNlU2VhcmNoSW5wdXQuanMnXG5pbXBvcnQgeyB1c2VUZXJtaW5hbFNpemUgfSBmcm9tICcuLi8uLi9ob29rcy91c2VUZXJtaW5hbFNpemUuanMnXG4vLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgY3VzdG9tLXJ1bGVzL3ByZWZlci11c2Uta2V5YmluZGluZ3MgLS0gdXNlSW5wdXQgbmVlZGVkIGZvciByYXcgc2VhcmNoIG1vZGUgdGV4dCBpbnB1dFxuaW1wb3J0IHsgQm94LCBUZXh0LCB1c2VJbnB1dCwgdXNlVGVybWluYWxGb2N1cyB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB7XG4gIHVzZUtleWJpbmRpbmcsXG4gIHVzZUtleWJpbmRpbmdzLFxufSBmcm9tICcuLi8uLi9rZXliaW5kaW5ncy91c2VLZXliaW5kaW5nLmpzJ1xuaW1wb3J0IHsgZ2V0QnVpbHRpblBsdWdpbkRlZmluaXRpb24gfSBmcm9tICcuLi8uLi9wbHVnaW5zL2J1aWx0aW5QbHVnaW5zLmpzJ1xuaW1wb3J0IHsgdXNlTWNwVG9nZ2xlRW5hYmxlZCB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL21jcC9NQ1BDb25uZWN0aW9uTWFuYWdlci5qcydcbmltcG9ydCB0eXBlIHtcbiAgTUNQU2VydmVyQ29ubmVjdGlvbixcbiAgTWNwQ2xhdWRlQUlQcm94eVNlcnZlckNvbmZpZyxcbiAgTWNwSFRUUFNlcnZlckNvbmZpZyxcbiAgTWNwU1NFU2VydmVyQ29uZmlnLFxuICBNY3BTdGRpb1NlcnZlckNvbmZpZyxcbn0gZnJvbSAnLi4vLi4vc2VydmljZXMvbWNwL3R5cGVzLmpzJ1xuaW1wb3J0IHsgZmlsdGVyVG9vbHNCeVNlcnZlciB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL21jcC91dGlscy5qcydcbmltcG9ydCB7XG4gIGRpc2FibGVQbHVnaW5PcCxcbiAgZW5hYmxlUGx1Z2luT3AsXG4gIGdldFBsdWdpbkluc3RhbGxhdGlvbkZyb21WMixcbiAgaXNJbnN0YWxsYWJsZVNjb3BlLFxuICBpc1BsdWdpbkVuYWJsZWRBdFByb2plY3RTY29wZSxcbiAgdW5pbnN0YWxsUGx1Z2luT3AsXG4gIHVwZGF0ZVBsdWdpbk9wLFxufSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9wbHVnaW5zL3BsdWdpbk9wZXJhdGlvbnMuanMnXG5pbXBvcnQgeyB1c2VBcHBTdGF0ZSB9IGZyb20gJy4uLy4uL3N0YXRlL0FwcFN0YXRlLmpzJ1xuaW1wb3J0IHR5cGUgeyBUb29sIH0gZnJvbSAnLi4vLi4vVG9vbC5qcydcbmltcG9ydCB0eXBlIHsgTG9hZGVkUGx1Z2luLCBQbHVnaW5FcnJvciB9IGZyb20gJy4uLy4uL3R5cGVzL3BsdWdpbi5qcydcbmltcG9ydCB7IGNvdW50IH0gZnJvbSAnLi4vLi4vdXRpbHMvYXJyYXkuanMnXG5pbXBvcnQgeyBvcGVuQnJvd3NlciB9IGZyb20gJy4uLy4uL3V0aWxzL2Jyb3dzZXIuanMnXG5pbXBvcnQgeyBsb2dGb3JEZWJ1Z2dpbmcgfSBmcm9tICcuLi8uLi91dGlscy9kZWJ1Zy5qcydcbmltcG9ydCB7IGVycm9yTWVzc2FnZSwgdG9FcnJvciB9IGZyb20gJy4uLy4uL3V0aWxzL2Vycm9ycy5qcydcbmltcG9ydCB7IGxvZ0Vycm9yIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nLmpzJ1xuaW1wb3J0IHsgY2xlYXJBbGxDYWNoZXMgfSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL2NhY2hlVXRpbHMuanMnXG5pbXBvcnQgeyBsb2FkSW5zdGFsbGVkUGx1Z2luc1YyIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9pbnN0YWxsZWRQbHVnaW5zTWFuYWdlci5qcydcbmltcG9ydCB7IGdldE1hcmtldHBsYWNlIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9tYXJrZXRwbGFjZU1hbmFnZXIuanMnXG5pbXBvcnQge1xuICBpc01jcGJTb3VyY2UsXG4gIGxvYWRNY3BiRmlsZSxcbiAgdHlwZSBNY3BiTmVlZHNDb25maWdSZXN1bHQsXG4gIHR5cGUgVXNlckNvbmZpZ1ZhbHVlcyxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9tY3BiSGFuZGxlci5qcydcbmltcG9ydCB7XG4gIGdldFBsdWdpbkRhdGFEaXJTaXplLFxuICBwbHVnaW5EYXRhRGlyUGF0aCxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9wbHVnaW5EaXJlY3Rvcmllcy5qcydcbmltcG9ydCB7XG4gIGdldEZsYWdnZWRQbHVnaW5zLFxuICBtYXJrRmxhZ2dlZFBsdWdpbnNTZWVuLFxuICByZW1vdmVGbGFnZ2VkUGx1Z2luLFxufSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL3BsdWdpbkZsYWdnaW5nLmpzJ1xuaW1wb3J0IHtcbiAgdHlwZSBQZXJzaXN0YWJsZVBsdWdpblNjb3BlLFxuICBwYXJzZVBsdWdpbklkZW50aWZpZXIsXG59IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvcGx1Z2luSWRlbnRpZmllci5qcydcbmltcG9ydCB7IGxvYWRBbGxQbHVnaW5zIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9wbHVnaW5Mb2FkZXIuanMnXG5pbXBvcnQge1xuICBsb2FkUGx1Z2luT3B0aW9ucyxcbiAgdHlwZSBQbHVnaW5PcHRpb25TY2hlbWEsXG4gIHNhdmVQbHVnaW5PcHRpb25zLFxufSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL3BsdWdpbk9wdGlvbnNTdG9yYWdlLmpzJ1xuaW1wb3J0IHsgaXNQbHVnaW5CbG9ja2VkQnlQb2xpY3kgfSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL3BsdWdpblBvbGljeS5qcydcbmltcG9ydCB7IGdldFBsdWdpbkVkaXRhYmxlU2NvcGVzIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9wbHVnaW5TdGFydHVwQ2hlY2suanMnXG5pbXBvcnQge1xuICBnZXRTZXR0aW5nc19ERVBSRUNBVEVELFxuICBnZXRTZXR0aW5nc0ZvclNvdXJjZSxcbiAgdXBkYXRlU2V0dGluZ3NGb3JTb3VyY2UsXG59IGZyb20gJy4uLy4uL3V0aWxzL3NldHRpbmdzL3NldHRpbmdzLmpzJ1xuaW1wb3J0IHsganNvblBhcnNlIH0gZnJvbSAnLi4vLi4vdXRpbHMvc2xvd09wZXJhdGlvbnMuanMnXG5pbXBvcnQgeyBwbHVyYWwgfSBmcm9tICcuLi8uLi91dGlscy9zdHJpbmdVdGlscy5qcydcbmltcG9ydCB7IGZvcm1hdEVycm9yTWVzc2FnZSwgZ2V0RXJyb3JHdWlkYW5jZSB9IGZyb20gJy4vUGx1Z2luRXJyb3JzLmpzJ1xuaW1wb3J0IHsgUGx1Z2luT3B0aW9uc0RpYWxvZyB9IGZyb20gJy4vUGx1Z2luT3B0aW9uc0RpYWxvZy5qcydcbmltcG9ydCB7IFBsdWdpbk9wdGlvbnNGbG93IH0gZnJvbSAnLi9QbHVnaW5PcHRpb25zRmxvdy5qcydcbmltcG9ydCB0eXBlIHsgVmlld1N0YXRlIGFzIFBhcmVudFZpZXdTdGF0ZSB9IGZyb20gJy4vdHlwZXMuanMnXG5pbXBvcnQgeyBVbmlmaWVkSW5zdGFsbGVkQ2VsbCB9IGZyb20gJy4vVW5pZmllZEluc3RhbGxlZENlbGwuanMnXG5pbXBvcnQgdHlwZSB7IFVuaWZpZWRJbnN0YWxsZWRJdGVtIH0gZnJvbSAnLi91bmlmaWVkVHlwZXMuanMnXG5pbXBvcnQgeyB1c2VQYWdpbmF0aW9uIH0gZnJvbSAnLi91c2VQYWdpbmF0aW9uLmpzJ1xuXG50eXBlIFByb3BzID0ge1xuICBzZXRWaWV3U3RhdGU6IChzdGF0ZTogUGFyZW50Vmlld1N0YXRlKSA9PiB2b2lkXG4gIHNldFJlc3VsdDogKHJlc3VsdDogc3RyaW5nIHwgbnVsbCkgPT4gdm9pZFxuICBvbk1hbmFnZUNvbXBsZXRlPzogKCkgPT4gdm9pZCB8IFByb21pc2U8dm9pZD5cbiAgb25TZWFyY2hNb2RlQ2hhbmdlPzogKGlzQWN0aXZlOiBib29sZWFuKSA9PiB2b2lkXG4gIHRhcmdldFBsdWdpbj86IHN0cmluZ1xuICB0YXJnZXRNYXJrZXRwbGFjZT86IHN0cmluZ1xuICBhY3Rpb24/OiAnZW5hYmxlJyB8ICdkaXNhYmxlJyB8ICd1bmluc3RhbGwnXG59XG5cbnR5cGUgRmxhZ2dlZFBsdWdpbkluZm8gPSB7XG4gIGlkOiBzdHJpbmdcbiAgbmFtZTogc3RyaW5nXG4gIG1hcmtldHBsYWNlOiBzdHJpbmdcbiAgcmVhc29uOiBzdHJpbmdcbiAgdGV4dDogc3RyaW5nXG4gIGZsYWdnZWRBdDogc3RyaW5nXG59XG5cbnR5cGUgRmFpbGVkUGx1Z2luSW5mbyA9IHtcbiAgaWQ6IHN0cmluZ1xuICBuYW1lOiBzdHJpbmdcbiAgbWFya2V0cGxhY2U6IHN0cmluZ1xuICBlcnJvcnM6IFBsdWdpbkVycm9yW11cbiAgc2NvcGU6IFBlcnNpc3RhYmxlUGx1Z2luU2NvcGVcbn1cblxudHlwZSBWaWV3U3RhdGUgPVxuICB8ICdwbHVnaW4tbGlzdCdcbiAgfCAncGx1Z2luLWRldGFpbHMnXG4gIHwgJ2NvbmZpZ3VyaW5nJ1xuICB8IHsgdHlwZTogJ3BsdWdpbi1vcHRpb25zJyB9XG4gIHwgeyB0eXBlOiAnY29uZmlndXJpbmctb3B0aW9ucyc7IHNjaGVtYTogUGx1Z2luT3B0aW9uU2NoZW1hIH1cbiAgfCAnY29uZmlybS1wcm9qZWN0LXVuaW5zdGFsbCdcbiAgfCB7IHR5cGU6ICdjb25maXJtLWRhdGEtY2xlYW51cCc7IHNpemU6IHsgYnl0ZXM6IG51bWJlcjsgaHVtYW46IHN0cmluZyB9IH1cbiAgfCB7IHR5cGU6ICdmbGFnZ2VkLWRldGFpbCc7IHBsdWdpbjogRmxhZ2dlZFBsdWdpbkluZm8gfVxuICB8IHsgdHlwZTogJ2ZhaWxlZC1wbHVnaW4tZGV0YWlscyc7IHBsdWdpbjogRmFpbGVkUGx1Z2luSW5mbyB9XG4gIHwgeyB0eXBlOiAnbWNwLWRldGFpbCc7IGNsaWVudDogTUNQU2VydmVyQ29ubmVjdGlvbiB9XG4gIHwgeyB0eXBlOiAnbWNwLXRvb2xzJzsgY2xpZW50OiBNQ1BTZXJ2ZXJDb25uZWN0aW9uIH1cbiAgfCB7IHR5cGU6ICdtY3AtdG9vbC1kZXRhaWwnOyBjbGllbnQ6IE1DUFNlcnZlckNvbm5lY3Rpb247IHRvb2w6IFRvb2wgfVxuXG50eXBlIE1hcmtldHBsYWNlSW5mbyA9IHtcbiAgbmFtZTogc3RyaW5nXG4gIGluc3RhbGxlZFBsdWdpbnM6IExvYWRlZFBsdWdpbltdXG4gIGVuYWJsZWRDb3VudD86IG51bWJlclxuICBkaXNhYmxlZENvdW50PzogbnVtYmVyXG59XG5cbnR5cGUgUGx1Z2luU3RhdGUgPSB7XG4gIHBsdWdpbjogTG9hZGVkUGx1Z2luXG4gIG1hcmtldHBsYWNlOiBzdHJpbmdcbiAgc2NvcGU/OiAndXNlcicgfCAncHJvamVjdCcgfCAnbG9jYWwnIHwgJ21hbmFnZWQnIHwgJ2J1aWx0aW4nXG4gIHBlbmRpbmdFbmFibGU/OiBib29sZWFuIC8vIFRvZ2dsZSBlbmFibGUvZGlzYWJsZVxuICBwZW5kaW5nVXBkYXRlPzogYm9vbGVhbiAvLyBNYXJrZWQgZm9yIHVwZGF0ZVxufVxuXG4vKipcbiAqIEdldCBsaXN0IG9mIGJhc2UgZmlsZSBuYW1lcyAod2l0aG91dCAubWQgZXh0ZW5zaW9uKSBmcm9tIGEgZGlyZWN0b3J5XG4gKiBAcGFyYW0gZGlyUGF0aCBUaGUgZGlyZWN0b3J5IHBhdGggdG8gbGlzdCBmaWxlcyBmcm9tXG4gKiBAcmV0dXJucyBBcnJheSBvZiBiYXNlIGZpbGUgbmFtZXMgd2l0aG91dCAubWQgZXh0ZW5zaW9uXG4gKiBAZXhhbXBsZVxuICogLy8gR2l2ZW4gZGlyZWN0b3J5IGNvbnRhaW5zOiBhZ2VudC1zZGstdmVyaWZpZXItcHkubWQsIGFnZW50LXNkay12ZXJpZmllci10cy5tZCwgUkVBRE1FLnR4dFxuICogYXdhaXQgZ2V0QmFzZUZpbGVOYW1lcygnL3BhdGgvdG8vYWdlbnRzJylcbiAqIC8vIFJldHVybnM6IFsnYWdlbnQtc2RrLXZlcmlmaWVyLXB5JywgJ2FnZW50LXNkay12ZXJpZmllci10cyddXG4gKi9cbmFzeW5jIGZ1bmN0aW9uIGdldEJhc2VGaWxlTmFtZXMoZGlyUGF0aDogc3RyaW5nKTogUHJvbWlzZTxzdHJpbmdbXT4ge1xuICB0cnkge1xuICAgIGNvbnN0IGVudHJpZXMgPSBhd2FpdCBmcy5yZWFkZGlyKGRpclBhdGgsIHsgd2l0aEZpbGVUeXBlczogdHJ1ZSB9KVxuICAgIHJldHVybiBlbnRyaWVzXG4gICAgICAuZmlsdGVyKChlbnRyeTogRGlyZW50KSA9PiBlbnRyeS5pc0ZpbGUoKSAmJiBlbnRyeS5uYW1lLmVuZHNXaXRoKCcubWQnKSlcbiAgICAgIC5tYXAoKGVudHJ5OiBEaXJlbnQpID0+IHtcbiAgICAgICAgLy8gUmVtb3ZlIC5tZCBleHRlbnNpb24gc3BlY2lmaWNhbGx5XG4gICAgICAgIGNvbnN0IGJhc2VOYW1lID0gcGF0aC5iYXNlbmFtZShlbnRyeS5uYW1lLCAnLm1kJylcbiAgICAgICAgcmV0dXJuIGJhc2VOYW1lXG4gICAgICB9KVxuICB9IGNhdGNoIChlcnJvcikge1xuICAgIGNvbnN0IGVycm9yTXNnID0gZXJyb3JNZXNzYWdlKGVycm9yKVxuICAgIGxvZ0ZvckRlYnVnZ2luZyhcbiAgICAgIGBGYWlsZWQgdG8gcmVhZCBwbHVnaW4gY29tcG9uZW50cyBmcm9tICR7ZGlyUGF0aH06ICR7ZXJyb3JNc2d9YCxcbiAgICAgIHsgbGV2ZWw6ICdlcnJvcicgfSxcbiAgICApXG4gICAgbG9nRXJyb3IodG9FcnJvcihlcnJvcikpXG4gICAgLy8gUmV0dXJuIGVtcHR5IGFycmF5IHRvIGFsbG93IGdyYWNlZnVsIGRlZ3JhZGF0aW9uIC0gcGx1Z2luIGRldGFpbHMgY2FuIHN0aWxsIGJlIHNob3duXG4gICAgcmV0dXJuIFtdXG4gIH1cbn1cblxuLyoqXG4gKiBHZXQgbGlzdCBvZiBza2lsbCBkaXJlY3RvcnkgbmFtZXMgZnJvbSBhIHNraWxscyBkaXJlY3RvcnlcbiAqIFNraWxscyBhcmUgZGlyZWN0b3JpZXMgY29udGFpbmluZyBhIFNLSUxMLm1kIGZpbGVcbiAqIEBwYXJhbSBkaXJQYXRoIFRoZSBza2lsbHMgZGlyZWN0b3J5IHBhdGggdG8gc2NhblxuICogQHJldHVybnMgQXJyYXkgb2Ygc2tpbGwgZGlyZWN0b3J5IG5hbWVzIHRoYXQgY29udGFpbiBTS0lMTC5tZFxuICogQGV4YW1wbGVcbiAqIC8vIEdpdmVuIGRpcmVjdG9yeSBjb250YWluczogbXktc2tpbGwvU0tJTEwubWQsIGFub3RoZXItc2tpbGwvU0tJTEwubWQsIFJFQURNRS50eHRcbiAqIGF3YWl0IGdldFNraWxsRGlyTmFtZXMoJy9wYXRoL3RvL3NraWxscycpXG4gKiAvLyBSZXR1cm5zOiBbJ215LXNraWxsJywgJ2Fub3RoZXItc2tpbGwnXVxuICovXG5hc3luYyBmdW5jdGlvbiBnZXRTa2lsbERpck5hbWVzKGRpclBhdGg6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nW10+IHtcbiAgdHJ5IHtcbiAgICBjb25zdCBlbnRyaWVzID0gYXdhaXQgZnMucmVhZGRpcihkaXJQYXRoLCB7IHdpdGhGaWxlVHlwZXM6IHRydWUgfSlcbiAgICBjb25zdCBza2lsbE5hbWVzOiBzdHJpbmdbXSA9IFtdXG5cbiAgICBmb3IgKGNvbnN0IGVudHJ5IG9mIGVudHJpZXMpIHtcbiAgICAgIC8vIENoZWNrIGlmIGl0J3MgYSBkaXJlY3Rvcnkgb3Igc3ltbGluayAoc3ltbGlua3MgbWF5IHBvaW50IHRvIHNraWxsIGRpcmVjdG9yaWVzKVxuICAgICAgaWYgKGVudHJ5LmlzRGlyZWN0b3J5KCkgfHwgZW50cnkuaXNTeW1ib2xpY0xpbmsoKSkge1xuICAgICAgICAvLyBDaGVjayBpZiB0aGlzIGRpcmVjdG9yeSBjb250YWlucyBhIFNLSUxMLm1kIGZpbGVcbiAgICAgICAgY29uc3Qgc2tpbGxGaWxlUGF0aCA9IHBhdGguam9pbihkaXJQYXRoLCBlbnRyeS5uYW1lLCAnU0tJTEwubWQnKVxuICAgICAgICB0cnkge1xuICAgICAgICAgIGNvbnN0IHN0ID0gYXdhaXQgZnMuc3RhdChza2lsbEZpbGVQYXRoKVxuICAgICAgICAgIGlmIChzdC5pc0ZpbGUoKSkge1xuICAgICAgICAgICAgc2tpbGxOYW1lcy5wdXNoKGVudHJ5Lm5hbWUpXG4gICAgICAgICAgfVxuICAgICAgICB9IGNhdGNoIHtcbiAgICAgICAgICAvLyBObyBTS0lMTC5tZCBmaWxlIGluIHRoaXMgZGlyZWN0b3J5LCBza2lwIGl0XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gc2tpbGxOYW1lc1xuICB9IGNhdGNoIChlcnJvcikge1xuICAgIGNvbnN0IGVycm9yTXNnID0gZXJyb3JNZXNzYWdlKGVycm9yKVxuICAgIGxvZ0ZvckRlYnVnZ2luZyhcbiAgICAgIGBGYWlsZWQgdG8gcmVhZCBza2lsbCBkaXJlY3RvcmllcyBmcm9tICR7ZGlyUGF0aH06ICR7ZXJyb3JNc2d9YCxcbiAgICAgIHsgbGV2ZWw6ICdlcnJvcicgfSxcbiAgICApXG4gICAgbG9nRXJyb3IodG9FcnJvcihlcnJvcikpXG4gICAgLy8gUmV0dXJuIGVtcHR5IGFycmF5IHRvIGFsbG93IGdyYWNlZnVsIGRlZ3JhZGF0aW9uIC0gcGx1Z2luIGRldGFpbHMgY2FuIHN0aWxsIGJlIHNob3duXG4gICAgcmV0dXJuIFtdXG4gIH1cbn1cblxuLy8gQ29tcG9uZW50IHRvIGRpc3BsYXkgaW5zdGFsbGVkIHBsdWdpbiBjb21wb25lbnRzXG5mdW5jdGlvbiBQbHVnaW5Db21wb25lbnRzRGlzcGxheSh7XG4gIHBsdWdpbixcbiAgbWFya2V0cGxhY2UsXG59OiB7XG4gIHBsdWdpbjogTG9hZGVkUGx1Z2luXG4gIG1hcmtldHBsYWNlOiBzdHJpbmdcbn0pOiBSZWFjdC5SZWFjdE5vZGUge1xuICBjb25zdCBbY29tcG9uZW50cywgc2V0Q29tcG9uZW50c10gPSB1c2VTdGF0ZTx7XG4gICAgY29tbWFuZHM/OiBzdHJpbmcgfCBzdHJpbmdbXSB8IFJlY29yZDxzdHJpbmcsIHVua25vd24+IHwgbnVsbFxuICAgIGFnZW50cz86IHN0cmluZyB8IHN0cmluZ1tdIHwgUmVjb3JkPHN0cmluZywgdW5rbm93bj4gfCBudWxsXG4gICAgc2tpbGxzPzogc3RyaW5nIHwgc3RyaW5nW10gfCBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB8IG51bGxcbiAgICBob29rcz86IHVua25vd25cbiAgICBtY3BTZXJ2ZXJzPzogdW5rbm93blxuICB9IHwgbnVsbD4obnVsbClcbiAgY29uc3QgW2xvYWRpbmcsIHNldExvYWRpbmddID0gdXNlU3RhdGUodHJ1ZSlcbiAgY29uc3QgW2Vycm9yLCBzZXRFcnJvcl0gPSB1c2VTdGF0ZTxzdHJpbmcgfCBudWxsPihudWxsKVxuXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgYXN5bmMgZnVuY3Rpb24gbG9hZENvbXBvbmVudHMoKSB7XG4gICAgICB0cnkge1xuICAgICAgICAvLyBCdWlsdC1pbiBwbHVnaW5zIGRvbid0IGhhdmUgYSBtYXJrZXRwbGFjZSBlbnRyeSDigJQgcmVhZCBmcm9tIHRoZVxuICAgICAgICAvLyByZWdpc3RlcmVkIGRlZmluaXRpb24gZGlyZWN0bHkuXG4gICAgICAgIGlmIChtYXJrZXRwbGFjZSA9PT0gJ2J1aWx0aW4nKSB7XG4gICAgICAgICAgY29uc3QgYnVpbHRpbkRlZiA9IGdldEJ1aWx0aW5QbHVnaW5EZWZpbml0aW9uKHBsdWdpbi5uYW1lKVxuICAgICAgICAgIGlmIChidWlsdGluRGVmKSB7XG4gICAgICAgICAgICBjb25zdCBza2lsbE5hbWVzID0gYnVpbHRpbkRlZi5za2lsbHM/Lm1hcChzID0+IHMubmFtZSkgPz8gW11cbiAgICAgICAgICAgIGNvbnN0IGhvb2tFdmVudHMgPSBidWlsdGluRGVmLmhvb2tzXG4gICAgICAgICAgICAgID8gT2JqZWN0LmtleXMoYnVpbHRpbkRlZi5ob29rcylcbiAgICAgICAgICAgICAgOiBbXVxuICAgICAgICAgICAgY29uc3QgbWNwU2VydmVyTmFtZXMgPSBidWlsdGluRGVmLm1jcFNlcnZlcnNcbiAgICAgICAgICAgICAgPyBPYmplY3Qua2V5cyhidWlsdGluRGVmLm1jcFNlcnZlcnMpXG4gICAgICAgICAgICAgIDogW11cbiAgICAgICAgICAgIHNldENvbXBvbmVudHMoe1xuICAgICAgICAgICAgICBjb21tYW5kczogbnVsbCxcbiAgICAgICAgICAgICAgYWdlbnRzOiBudWxsLFxuICAgICAgICAgICAgICBza2lsbHM6IHNraWxsTmFtZXMubGVuZ3RoID4gMCA/IHNraWxsTmFtZXMgOiBudWxsLFxuICAgICAgICAgICAgICBob29rczogaG9va0V2ZW50cy5sZW5ndGggPiAwID8gaG9va0V2ZW50cyA6IG51bGwsXG4gICAgICAgICAgICAgIG1jcFNlcnZlcnM6IG1jcFNlcnZlck5hbWVzLmxlbmd0aCA+IDAgPyBtY3BTZXJ2ZXJOYW1lcyA6IG51bGwsXG4gICAgICAgICAgICB9KVxuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzZXRFcnJvcihgQnVpbHQtaW4gcGx1Z2luICR7cGx1Z2luLm5hbWV9IG5vdCBmb3VuZGApXG4gICAgICAgICAgfVxuICAgICAgICAgIHNldExvYWRpbmcoZmFsc2UpXG4gICAgICAgICAgcmV0dXJuXG4gICAgICAgIH1cblxuICAgICAgICBjb25zdCBtYXJrZXRwbGFjZURhdGEgPSBhd2FpdCBnZXRNYXJrZXRwbGFjZShtYXJrZXRwbGFjZSlcbiAgICAgICAgLy8gRmluZCB0aGUgcGx1Z2luIGVudHJ5IGluIHRoZSBhcnJheVxuICAgICAgICBjb25zdCBwbHVnaW5FbnRyeSA9IG1hcmtldHBsYWNlRGF0YS5wbHVnaW5zLmZpbmQoXG4gICAgICAgICAgcCA9PiBwLm5hbWUgPT09IHBsdWdpbi5uYW1lLFxuICAgICAgICApXG4gICAgICAgIGlmIChwbHVnaW5FbnRyeSkge1xuICAgICAgICAgIC8vIENvbWJpbmUgY29tbWFuZHMgZnJvbSBib3RoIHNvdXJjZXNcbiAgICAgICAgICBjb25zdCBjb21tYW5kUGF0aExpc3QgPSBbXVxuICAgICAgICAgIGlmIChwbHVnaW4uY29tbWFuZHNQYXRoKSB7XG4gICAgICAgICAgICBjb21tYW5kUGF0aExpc3QucHVzaChwbHVnaW4uY29tbWFuZHNQYXRoKVxuICAgICAgICAgIH1cbiAgICAgICAgICBpZiAocGx1Z2luLmNvbW1hbmRzUGF0aHMpIHtcbiAgICAgICAgICAgIGNvbW1hbmRQYXRoTGlzdC5wdXNoKC4uLnBsdWdpbi5jb21tYW5kc1BhdGhzKVxuICAgICAgICAgIH1cblxuICAgICAgICAgIC8vIEdldCBiYXNlIGZpbGUgbmFtZXMgZnJvbSBhbGwgY29tbWFuZCBwYXRoc1xuICAgICAgICAgIGNvbnN0IGNvbW1hbmRMaXN0OiBzdHJpbmdbXSA9IFtdXG4gICAgICAgICAgZm9yIChjb25zdCBjb21tYW5kUGF0aCBvZiBjb21tYW5kUGF0aExpc3QpIHtcbiAgICAgICAgICAgIGlmICh0eXBlb2YgY29tbWFuZFBhdGggPT09ICdzdHJpbmcnKSB7XG4gICAgICAgICAgICAgIC8vIGNvbW1hbmRQYXRoIGlzIGFscmVhZHkgYSBmdWxsIHBhdGhcbiAgICAgICAgICAgICAgY29uc3QgYmFzZU5hbWVzID0gYXdhaXQgZ2V0QmFzZUZpbGVOYW1lcyhjb21tYW5kUGF0aClcbiAgICAgICAgICAgICAgY29tbWFuZExpc3QucHVzaCguLi5iYXNlTmFtZXMpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgLy8gQ29tYmluZSBhZ2VudHMgZnJvbSBib3RoIHNvdXJjZXNcbiAgICAgICAgICBjb25zdCBhZ2VudFBhdGhMaXN0ID0gW11cbiAgICAgICAgICBpZiAocGx1Z2luLmFnZW50c1BhdGgpIHtcbiAgICAgICAgICAgIGFnZW50UGF0aExpc3QucHVzaChwbHVnaW4uYWdlbnRzUGF0aClcbiAgICAgICAgICB9XG4gICAgICAgICAgaWYgKHBsdWdpbi5hZ2VudHNQYXRocykge1xuICAgICAgICAgICAgYWdlbnRQYXRoTGlzdC5wdXNoKC4uLnBsdWdpbi5hZ2VudHNQYXRocylcbiAgICAgICAgICB9XG5cbiAgICAgICAgICAvLyBHZXQgYmFzZSBmaWxlIG5hbWVzIGZyb20gYWxsIGFnZW50IHBhdGhzXG4gICAgICAgICAgY29uc3QgYWdlbnRMaXN0OiBzdHJpbmdbXSA9IFtdXG4gICAgICAgICAgZm9yIChjb25zdCBhZ2VudFBhdGggb2YgYWdlbnRQYXRoTGlzdCkge1xuICAgICAgICAgICAgaWYgKHR5cGVvZiBhZ2VudFBhdGggPT09ICdzdHJpbmcnKSB7XG4gICAgICAgICAgICAgIC8vIGFnZW50UGF0aCBpcyBhbHJlYWR5IGEgZnVsbCBwYXRoXG4gICAgICAgICAgICAgIGNvbnN0IGJhc2VOYW1lcyA9IGF3YWl0IGdldEJhc2VGaWxlTmFtZXMoYWdlbnRQYXRoKVxuICAgICAgICAgICAgICBhZ2VudExpc3QucHVzaCguLi5iYXNlTmFtZXMpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgLy8gQ29tYmluZSBza2lsbHMgZnJvbSBib3RoIHNvdXJjZXNcbiAgICAgICAgICBjb25zdCBza2lsbFBhdGhMaXN0ID0gW11cbiAgICAgICAgICBpZiAocGx1Z2luLnNraWxsc1BhdGgpIHtcbiAgICAgICAgICAgIHNraWxsUGF0aExpc3QucHVzaChwbHVnaW4uc2tpbGxzUGF0aClcbiAgICAgICAgICB9XG4gICAgICAgICAgaWYgKHBsdWdpbi5za2lsbHNQYXRocykge1xuICAgICAgICAgICAgc2tpbGxQYXRoTGlzdC5wdXNoKC4uLnBsdWdpbi5za2lsbHNQYXRocylcbiAgICAgICAgICB9XG5cbiAgICAgICAgICAvLyBHZXQgc2tpbGwgZGlyZWN0b3J5IG5hbWVzIGZyb20gYWxsIHNraWxsIHBhdGhzXG4gICAgICAgICAgLy8gU2tpbGxzIGFyZSBkaXJlY3RvcmllcyBjb250YWluaW5nIFNLSUxMLm1kIGZpbGVzXG4gICAgICAgICAgY29uc3Qgc2tpbGxMaXN0OiBzdHJpbmdbXSA9IFtdXG4gICAgICAgICAgZm9yIChjb25zdCBza2lsbFBhdGggb2Ygc2tpbGxQYXRoTGlzdCkge1xuICAgICAgICAgICAgaWYgKHR5cGVvZiBza2lsbFBhdGggPT09ICdzdHJpbmcnKSB7XG4gICAgICAgICAgICAgIC8vIHNraWxsUGF0aCBpcyBhbHJlYWR5IGEgZnVsbCBwYXRoIHRvIGEgc2tpbGxzIGRpcmVjdG9yeVxuICAgICAgICAgICAgICBjb25zdCBza2lsbERpck5hbWVzID0gYXdhaXQgZ2V0U2tpbGxEaXJOYW1lcyhza2lsbFBhdGgpXG4gICAgICAgICAgICAgIHNraWxsTGlzdC5wdXNoKC4uLnNraWxsRGlyTmFtZXMpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgLy8gQ29tYmluZSBob29rcyBmcm9tIGJvdGggc291cmNlc1xuICAgICAgICAgIGNvbnN0IGhvb2tzTGlzdCA9IFtdXG4gICAgICAgICAgaWYgKHBsdWdpbi5ob29rc0NvbmZpZykge1xuICAgICAgICAgICAgaG9va3NMaXN0LnB1c2goT2JqZWN0LmtleXMocGx1Z2luLmhvb2tzQ29uZmlnKSlcbiAgICAgICAgICB9XG4gICAgICAgICAgaWYgKHBsdWdpbkVudHJ5Lmhvb2tzKSB7XG4gICAgICAgICAgICBob29rc0xpc3QucHVzaChwbHVnaW5FbnRyeS5ob29rcylcbiAgICAgICAgICB9XG5cbiAgICAgICAgICAvLyBDb21iaW5lIE1DUCBzZXJ2ZXJzIGZyb20gYm90aCBzb3VyY2VzXG4gICAgICAgICAgY29uc3QgbWNwU2VydmVyc0xpc3QgPSBbXVxuICAgICAgICAgIGlmIChwbHVnaW4ubWNwU2VydmVycykge1xuICAgICAgICAgICAgbWNwU2VydmVyc0xpc3QucHVzaChPYmplY3Qua2V5cyhwbHVnaW4ubWNwU2VydmVycykpXG4gICAgICAgICAgfVxuICAgICAgICAgIGlmIChwbHVnaW5FbnRyeS5tY3BTZXJ2ZXJzKSB7XG4gICAgICAgICAgICBtY3BTZXJ2ZXJzTGlzdC5wdXNoKHBsdWdpbkVudHJ5Lm1jcFNlcnZlcnMpXG4gICAgICAgICAgfVxuXG4gICAgICAgICAgc2V0Q29tcG9uZW50cyh7XG4gICAgICAgICAgICBjb21tYW5kczogY29tbWFuZExpc3QubGVuZ3RoID4gMCA/IGNvbW1hbmRMaXN0IDogbnVsbCxcbiAgICAgICAgICAgIGFnZW50czogYWdlbnRMaXN0Lmxlbmd0aCA+IDAgPyBhZ2VudExpc3QgOiBudWxsLFxuICAgICAgICAgICAgc2tpbGxzOiBza2lsbExpc3QubGVuZ3RoID4gMCA/IHNraWxsTGlzdCA6IG51bGwsXG4gICAgICAgICAgICBob29rczogaG9va3NMaXN0Lmxlbmd0aCA+IDAgPyBob29rc0xpc3QgOiBudWxsLFxuICAgICAgICAgICAgbWNwU2VydmVyczogbWNwU2VydmVyc0xpc3QubGVuZ3RoID4gMCA/IG1jcFNlcnZlcnNMaXN0IDogbnVsbCxcbiAgICAgICAgICB9KVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHNldEVycm9yKGBQbHVnaW4gJHtwbHVnaW4ubmFtZX0gbm90IGZvdW5kIGluIG1hcmtldHBsYWNlYClcbiAgICAgICAgfVxuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIHNldEVycm9yKFxuICAgICAgICAgIGVyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiAnRmFpbGVkIHRvIGxvYWQgY29tcG9uZW50cycsXG4gICAgICAgIClcbiAgICAgIH0gZmluYWxseSB7XG4gICAgICAgIHNldExvYWRpbmcoZmFsc2UpXG4gICAgICB9XG4gICAgfVxuICAgIHZvaWQgbG9hZENvbXBvbmVudHMoKVxuICB9LCBbXG4gICAgcGx1Z2luLm5hbWUsXG4gICAgcGx1Z2luLmNvbW1hbmRzUGF0aCxcbiAgICBwbHVnaW4uY29tbWFuZHNQYXRocyxcbiAgICBwbHVnaW4uYWdlbnRzUGF0aCxcbiAgICBwbHVnaW4uYWdlbnRzUGF0aHMsXG4gICAgcGx1Z2luLnNraWxsc1BhdGgsXG4gICAgcGx1Z2luLnNraWxsc1BhdGhzLFxuICAgIHBsdWdpbi5ob29rc0NvbmZpZyxcbiAgICBwbHVnaW4ubWNwU2VydmVycyxcbiAgICBtYXJrZXRwbGFjZSxcbiAgXSlcblxuICBpZiAobG9hZGluZykge1xuICAgIHJldHVybiBudWxsIC8vIERvbid0IHNob3cgbG9hZGluZyBzdGF0ZSBmb3IgY2xlYW5lciBVSVxuICB9XG5cbiAgaWYgKGVycm9yKSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiIG1hcmdpbkJvdHRvbT17MX0+XG4gICAgICAgIDxUZXh0IGJvbGQ+Q29tcG9uZW50czo8L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPkVycm9yOiB7ZXJyb3J9PC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgaWYgKCFjb21wb25lbnRzKSB7XG4gICAgcmV0dXJuIG51bGwgLy8gTm8gY29tcG9uZW50cyBpbmZvIGF2YWlsYWJsZVxuICB9XG5cbiAgY29uc3QgaGFzQ29tcG9uZW50cyA9XG4gICAgY29tcG9uZW50cy5jb21tYW5kcyB8fFxuICAgIGNvbXBvbmVudHMuYWdlbnRzIHx8XG4gICAgY29tcG9uZW50cy5za2lsbHMgfHxcbiAgICBjb21wb25lbnRzLmhvb2tzIHx8XG4gICAgY29tcG9uZW50cy5tY3BTZXJ2ZXJzXG5cbiAgaWYgKCFoYXNDb21wb25lbnRzKSB7XG4gICAgcmV0dXJuIG51bGwgLy8gTm8gY29tcG9uZW50cyBkZWZpbmVkXG4gIH1cblxuICByZXR1cm4gKFxuICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiIG1hcmdpbkJvdHRvbT17MX0+XG4gICAgICA8VGV4dCBib2xkPkluc3RhbGxlZCBjb21wb25lbnRzOjwvVGV4dD5cbiAgICAgIHtjb21wb25lbnRzLmNvbW1hbmRzID8gKFxuICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICDigKIgQ29tbWFuZHM6eycgJ31cbiAgICAgICAgICB7dHlwZW9mIGNvbXBvbmVudHMuY29tbWFuZHMgPT09ICdzdHJpbmcnXG4gICAgICAgICAgICA/IGNvbXBvbmVudHMuY29tbWFuZHNcbiAgICAgICAgICAgIDogQXJyYXkuaXNBcnJheShjb21wb25lbnRzLmNvbW1hbmRzKVxuICAgICAgICAgICAgICA/IGNvbXBvbmVudHMuY29tbWFuZHMuam9pbignLCAnKVxuICAgICAgICAgICAgICA6IE9iamVjdC5rZXlzKGNvbXBvbmVudHMuY29tbWFuZHMpLmpvaW4oJywgJyl9XG4gICAgICAgIDwvVGV4dD5cbiAgICAgICkgOiBudWxsfVxuICAgICAge2NvbXBvbmVudHMuYWdlbnRzID8gKFxuICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICDigKIgQWdlbnRzOnsnICd9XG4gICAgICAgICAge3R5cGVvZiBjb21wb25lbnRzLmFnZW50cyA9PT0gJ3N0cmluZydcbiAgICAgICAgICAgID8gY29tcG9uZW50cy5hZ2VudHNcbiAgICAgICAgICAgIDogQXJyYXkuaXNBcnJheShjb21wb25lbnRzLmFnZW50cylcbiAgICAgICAgICAgICAgPyBjb21wb25lbnRzLmFnZW50cy5qb2luKCcsICcpXG4gICAgICAgICAgICAgIDogT2JqZWN0LmtleXMoY29tcG9uZW50cy5hZ2VudHMpLmpvaW4oJywgJyl9XG4gICAgICAgIDwvVGV4dD5cbiAgICAgICkgOiBudWxsfVxuICAgICAge2NvbXBvbmVudHMuc2tpbGxzID8gKFxuICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICDigKIgU2tpbGxzOnsnICd9XG4gICAgICAgICAge3R5cGVvZiBjb21wb25lbnRzLnNraWxscyA9PT0gJ3N0cmluZydcbiAgICAgICAgICAgID8gY29tcG9uZW50cy5za2lsbHNcbiAgICAgICAgICAgIDogQXJyYXkuaXNBcnJheShjb21wb25lbnRzLnNraWxscylcbiAgICAgICAgICAgICAgPyBjb21wb25lbnRzLnNraWxscy5qb2luKCcsICcpXG4gICAgICAgICAgICAgIDogT2JqZWN0LmtleXMoY29tcG9uZW50cy5za2lsbHMpLmpvaW4oJywgJyl9XG4gICAgICAgIDwvVGV4dD5cbiAgICAgICkgOiBudWxsfVxuICAgICAge2NvbXBvbmVudHMuaG9va3MgPyAoXG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgIOKAoiBIb29rczp7JyAnfVxuICAgICAgICAgIHt0eXBlb2YgY29tcG9uZW50cy5ob29rcyA9PT0gJ3N0cmluZydcbiAgICAgICAgICAgID8gY29tcG9uZW50cy5ob29rc1xuICAgICAgICAgICAgOiBBcnJheS5pc0FycmF5KGNvbXBvbmVudHMuaG9va3MpXG4gICAgICAgICAgICAgID8gY29tcG9uZW50cy5ob29rcy5tYXAoU3RyaW5nKS5qb2luKCcsICcpXG4gICAgICAgICAgICAgIDogdHlwZW9mIGNvbXBvbmVudHMuaG9va3MgPT09ICdvYmplY3QnICYmXG4gICAgICAgICAgICAgICAgICBjb21wb25lbnRzLmhvb2tzICE9PSBudWxsXG4gICAgICAgICAgICAgICAgPyBPYmplY3Qua2V5cyhjb21wb25lbnRzLmhvb2tzKS5qb2luKCcsICcpXG4gICAgICAgICAgICAgICAgOiBTdHJpbmcoY29tcG9uZW50cy5ob29rcyl9XG4gICAgICAgIDwvVGV4dD5cbiAgICAgICkgOiBudWxsfVxuICAgICAge2NvbXBvbmVudHMubWNwU2VydmVycyA/IChcbiAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAg4oCiIE1DUCBTZXJ2ZXJzOnsnICd9XG4gICAgICAgICAge3R5cGVvZiBjb21wb25lbnRzLm1jcFNlcnZlcnMgPT09ICdzdHJpbmcnXG4gICAgICAgICAgICA/IGNvbXBvbmVudHMubWNwU2VydmVyc1xuICAgICAgICAgICAgOiBBcnJheS5pc0FycmF5KGNvbXBvbmVudHMubWNwU2VydmVycylcbiAgICAgICAgICAgICAgPyBjb21wb25lbnRzLm1jcFNlcnZlcnMubWFwKFN0cmluZykuam9pbignLCAnKVxuICAgICAgICAgICAgICA6IHR5cGVvZiBjb21wb25lbnRzLm1jcFNlcnZlcnMgPT09ICdvYmplY3QnICYmXG4gICAgICAgICAgICAgICAgICBjb21wb25lbnRzLm1jcFNlcnZlcnMgIT09IG51bGxcbiAgICAgICAgICAgICAgICA/IE9iamVjdC5rZXlzKGNvbXBvbmVudHMubWNwU2VydmVycykuam9pbignLCAnKVxuICAgICAgICAgICAgICAgIDogU3RyaW5nKGNvbXBvbmVudHMubWNwU2VydmVycyl9XG4gICAgICAgIDwvVGV4dD5cbiAgICAgICkgOiBudWxsfVxuICAgIDwvQm94PlxuICApXG59XG5cbi8qKlxuICogQ2hlY2sgaWYgYSBwbHVnaW4gaXMgZnJvbSBhIGxvY2FsIHNvdXJjZSBhbmQgY2Fubm90IGJlIHJlbW90ZWx5IHVwZGF0ZWRcbiAqIEByZXR1cm5zIEVycm9yIG1lc3NhZ2UgaWYgbG9jYWwsIG51bGwgaWYgcmVtb3RlL3VwZGF0YWJsZVxuICovXG5hc3luYyBmdW5jdGlvbiBjaGVja0lmTG9jYWxQbHVnaW4oXG4gIHBsdWdpbk5hbWU6IHN0cmluZyxcbiAgbWFya2V0cGxhY2VOYW1lOiBzdHJpbmcsXG4pOiBQcm9taXNlPHN0cmluZyB8IG51bGw+IHtcbiAgY29uc3QgbWFya2V0cGxhY2UgPSBhd2FpdCBnZXRNYXJrZXRwbGFjZShtYXJrZXRwbGFjZU5hbWUpXG4gIGNvbnN0IGVudHJ5ID0gbWFya2V0cGxhY2U/LnBsdWdpbnMuZmluZChwID0+IHAubmFtZSA9PT0gcGx1Z2luTmFtZSlcblxuICBpZiAoZW50cnkgJiYgdHlwZW9mIGVudHJ5LnNvdXJjZSA9PT0gJ3N0cmluZycpIHtcbiAgICByZXR1cm4gYExvY2FsIHBsdWdpbnMgY2Fubm90IGJlIHVwZGF0ZWQgcmVtb3RlbHkuIFRvIHVwZGF0ZSwgbW9kaWZ5IHRoZSBzb3VyY2UgYXQ6ICR7ZW50cnkuc291cmNlfWBcbiAgfVxuXG4gIHJldHVybiBudWxsXG59XG5cbi8qKlxuICogRmlsdGVyIG91dCBwbHVnaW5zIHRoYXQgYXJlIGZvcmNlLWRpc2FibGVkIGJ5IG9yZyBwb2xpY3kgKHBvbGljeVNldHRpbmdzKS5cbiAqIFRoZXNlIGFyZSBibG9ja2VkIGJ5IHRoZSBvcmdhbml6YXRpb24gYW5kIGNhbm5vdCBiZSByZS1lbmFibGVkIGJ5IHRoZSB1c2VyLlxuICogQ2hlY2tzIHBvbGljeVNldHRpbmdzIGRpcmVjdGx5IHJhdGhlciB0aGFuIGluc3RhbGxhdGlvbiBzY29wZSwgc2luY2UgbWFuYWdlZFxuICogc2V0dGluZ3MgZG9uJ3QgY3JlYXRlIGluc3RhbGxhdGlvbiByZWNvcmRzIHdpdGggc2NvcGUgJ21hbmFnZWQnLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZmlsdGVyTWFuYWdlZERpc2FibGVkUGx1Z2lucyhcbiAgcGx1Z2luczogTG9hZGVkUGx1Z2luW10sXG4pOiBMb2FkZWRQbHVnaW5bXSB7XG4gIHJldHVybiBwbHVnaW5zLmZpbHRlcihwbHVnaW4gPT4ge1xuICAgIGNvbnN0IG1hcmtldHBsYWNlID0gcGx1Z2luLnNvdXJjZS5zcGxpdCgnQCcpWzFdIHx8ICdsb2NhbCdcbiAgICByZXR1cm4gIWlzUGx1Z2luQmxvY2tlZEJ5UG9saWN5KGAke3BsdWdpbi5uYW1lfUAke21hcmtldHBsYWNlfWApXG4gIH0pXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBNYW5hZ2VQbHVnaW5zKHtcbiAgc2V0Vmlld1N0YXRlOiBzZXRQYXJlbnRWaWV3U3RhdGUsXG4gIHNldFJlc3VsdCxcbiAgb25NYW5hZ2VDb21wbGV0ZSxcbiAgb25TZWFyY2hNb2RlQ2hhbmdlLFxuICB0YXJnZXRQbHVnaW4sXG4gIHRhcmdldE1hcmtldHBsYWNlLFxuICBhY3Rpb24sXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIC8vIEFwcCBzdGF0ZSBmb3IgTUNQIGFjY2Vzc1xuICBjb25zdCBtY3BDbGllbnRzID0gdXNlQXBwU3RhdGUocyA9PiBzLm1jcC5jbGllbnRzKVxuICBjb25zdCBtY3BUb29scyA9IHVzZUFwcFN0YXRlKHMgPT4gcy5tY3AudG9vbHMpXG4gIGNvbnN0IHBsdWdpbkVycm9ycyA9IHVzZUFwcFN0YXRlKHMgPT4gcy5wbHVnaW5zLmVycm9ycylcbiAgY29uc3QgZmxhZ2dlZFBsdWdpbnMgPSBnZXRGbGFnZ2VkUGx1Z2lucygpXG5cbiAgLy8gU2VhcmNoIHN0YXRlXG4gIGNvbnN0IFtpc1NlYXJjaE1vZGUsIHNldElzU2VhcmNoTW9kZVJhd10gPSB1c2VTdGF0ZShmYWxzZSlcbiAgY29uc3Qgc2V0SXNTZWFyY2hNb2RlID0gdXNlQ2FsbGJhY2soXG4gICAgKGFjdGl2ZTogYm9vbGVhbikgPT4ge1xuICAgICAgc2V0SXNTZWFyY2hNb2RlUmF3KGFjdGl2ZSlcbiAgICAgIG9uU2VhcmNoTW9kZUNoYW5nZT8uKGFjdGl2ZSlcbiAgICB9LFxuICAgIFtvblNlYXJjaE1vZGVDaGFuZ2VdLFxuICApXG4gIGNvbnN0IGlzVGVybWluYWxGb2N1c2VkID0gdXNlVGVybWluYWxGb2N1cygpXG4gIGNvbnN0IHsgY29sdW1uczogdGVybWluYWxXaWR0aCB9ID0gdXNlVGVybWluYWxTaXplKClcblxuICAvLyBWaWV3IHN0YXRlXG4gIGNvbnN0IFt2aWV3U3RhdGUsIHNldFZpZXdTdGF0ZV0gPSB1c2VTdGF0ZTxWaWV3U3RhdGU+KCdwbHVnaW4tbGlzdCcpXG5cbiAgY29uc3Qge1xuICAgIHF1ZXJ5OiBzZWFyY2hRdWVyeSxcbiAgICBzZXRRdWVyeTogc2V0U2VhcmNoUXVlcnksXG4gICAgY3Vyc29yT2Zmc2V0OiBzZWFyY2hDdXJzb3JPZmZzZXQsXG4gIH0gPSB1c2VTZWFyY2hJbnB1dCh7XG4gICAgaXNBY3RpdmU6IHZpZXdTdGF0ZSA9PT0gJ3BsdWdpbi1saXN0JyAmJiBpc1NlYXJjaE1vZGUsXG4gICAgb25FeGl0OiAoKSA9PiB7XG4gICAgICBzZXRJc1NlYXJjaE1vZGUoZmFsc2UpXG4gICAgfSxcbiAgfSlcbiAgY29uc3QgW3NlbGVjdGVkUGx1Z2luLCBzZXRTZWxlY3RlZFBsdWdpbl0gPSB1c2VTdGF0ZTxQbHVnaW5TdGF0ZSB8IG51bGw+KG51bGwpXG5cbiAgLy8gRGF0YSBzdGF0ZVxuICBjb25zdCBbbWFya2V0cGxhY2VzLCBzZXRNYXJrZXRwbGFjZXNdID0gdXNlU3RhdGU8TWFya2V0cGxhY2VJbmZvW10+KFtdKVxuICBjb25zdCBbcGx1Z2luU3RhdGVzLCBzZXRQbHVnaW5TdGF0ZXNdID0gdXNlU3RhdGU8UGx1Z2luU3RhdGVbXT4oW10pXG4gIGNvbnN0IFtsb2FkaW5nLCBzZXRMb2FkaW5nXSA9IHVzZVN0YXRlKHRydWUpXG4gIGNvbnN0IFtwZW5kaW5nVG9nZ2xlcywgc2V0UGVuZGluZ1RvZ2dsZXNdID0gdXNlU3RhdGU8XG4gICAgTWFwPHN0cmluZywgJ3dpbGwtZW5hYmxlJyB8ICd3aWxsLWRpc2FibGUnPlxuICA+KG5ldyBNYXAoKSlcblxuICAvLyBHdWFyZCB0byBwcmV2ZW50IGF1dG8tbmF2aWdhdGlvbiBmcm9tIHJlLXRyaWdnZXJpbmcgYWZ0ZXIgdGhlIHVzZXJcbiAgLy8gbmF2aWdhdGVzIGF3YXkgKHRhcmdldFBsdWdpbiBpcyBuZXZlciBjbGVhcmVkIGJ5IHRoZSBwYXJlbnQpLlxuICBjb25zdCBoYXNBdXRvTmF2aWdhdGVkID0gdXNlUmVmKGZhbHNlKVxuICAvLyBBdXRvLWFjdGlvbiAoZW5hYmxlL2Rpc2FibGUvdW5pbnN0YWxsKSB0byBmaXJlIGFmdGVyIGF1dG8tbmF2aWdhdGlvbiBsYW5kcy5cbiAgLy8gUmVmLCBub3Qgc3RhdGU6IGl0J3MgY29uc3VtZWQgYnkgYSBvbmUtc2hvdCBlZmZlY3QgdGhhdCBhbHJlYWR5IHJlLXJ1bnMgb25cbiAgLy8gdmlld1N0YXRlL3NlbGVjdGVkUGx1Z2luLCBzbyBhIHJlbmRlci10cmlnZ2VyaW5nIHN0YXRlIHZhciB3b3VsZCBiZSByZWR1bmRhbnQuXG4gIGNvbnN0IHBlbmRpbmdBdXRvQWN0aW9uUmVmID0gdXNlUmVmPFxuICAgICdlbmFibGUnIHwgJ2Rpc2FibGUnIHwgJ3VuaW5zdGFsbCcgfCB1bmRlZmluZWRcbiAgPih1bmRlZmluZWQpXG5cbiAgLy8gTUNQIHRvZ2dsZSBob29rXG4gIGNvbnN0IHRvZ2dsZU1jcFNlcnZlciA9IHVzZU1jcFRvZ2dsZUVuYWJsZWQoKVxuXG4gIC8vIEhhbmRsZSBlc2NhcGUgdG8gZ28gYmFjayAtIHZpZXdTdGF0ZS1kZXBlbmRlbnQgbmF2aWdhdGlvblxuICBjb25zdCBoYW5kbGVCYWNrID0gUmVhY3QudXNlQ2FsbGJhY2soKCkgPT4ge1xuICAgIGlmICh2aWV3U3RhdGUgPT09ICdwbHVnaW4tZGV0YWlscycpIHtcbiAgICAgIHNldFZpZXdTdGF0ZSgncGx1Z2luLWxpc3QnKVxuICAgICAgc2V0U2VsZWN0ZWRQbHVnaW4obnVsbClcbiAgICAgIHNldFByb2Nlc3NFcnJvcihudWxsKVxuICAgIH0gZWxzZSBpZiAoXG4gICAgICB0eXBlb2Ygdmlld1N0YXRlID09PSAnb2JqZWN0JyAmJlxuICAgICAgdmlld1N0YXRlLnR5cGUgPT09ICdmYWlsZWQtcGx1Z2luLWRldGFpbHMnXG4gICAgKSB7XG4gICAgICBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1saXN0JylcbiAgICAgIHNldFByb2Nlc3NFcnJvcihudWxsKVxuICAgIH0gZWxzZSBpZiAodmlld1N0YXRlID09PSAnY29uZmlndXJpbmcnKSB7XG4gICAgICBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1kZXRhaWxzJylcbiAgICAgIHNldENvbmZpZ05lZWRlZChudWxsKVxuICAgIH0gZWxzZSBpZiAoXG4gICAgICB0eXBlb2Ygdmlld1N0YXRlID09PSAnb2JqZWN0JyAmJlxuICAgICAgKHZpZXdTdGF0ZS50eXBlID09PSAncGx1Z2luLW9wdGlvbnMnIHx8XG4gICAgICAgIHZpZXdTdGF0ZS50eXBlID09PSAnY29uZmlndXJpbmctb3B0aW9ucycpXG4gICAgKSB7XG4gICAgICAvLyBDYW5jZWwgbWlkLXNlcXVlbmNlIOKAlCBwbHVnaW4gaXMgYWxyZWFkeSBlbmFibGVkLCBqdXN0IGJhaWwgdG8gbGlzdC5cbiAgICAgIC8vIFVzZXIgY2FuIGNvbmZpZ3VyZSBsYXRlciB2aWEgdGhlIENvbmZpZ3VyZSBvcHRpb25zIG1lbnUgaWYgdGhleSB3YW50LlxuICAgICAgc2V0Vmlld1N0YXRlKCdwbHVnaW4tbGlzdCcpXG4gICAgICBzZXRTZWxlY3RlZFBsdWdpbihudWxsKVxuICAgICAgc2V0UmVzdWx0KFxuICAgICAgICAnUGx1Z2luIGVuYWJsZWQuIENvbmZpZ3VyYXRpb24gc2tpcHBlZCDigJQgcnVuIC9yZWxvYWQtcGx1Z2lucyB0byBhcHBseS4nLFxuICAgICAgKVxuICAgICAgaWYgKG9uTWFuYWdlQ29tcGxldGUpIHtcbiAgICAgICAgdm9pZCBvbk1hbmFnZUNvbXBsZXRlKClcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKFxuICAgICAgdHlwZW9mIHZpZXdTdGF0ZSA9PT0gJ29iamVjdCcgJiZcbiAgICAgIHZpZXdTdGF0ZS50eXBlID09PSAnZmxhZ2dlZC1kZXRhaWwnXG4gICAgKSB7XG4gICAgICBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1saXN0JylcbiAgICAgIHNldFByb2Nlc3NFcnJvcihudWxsKVxuICAgIH0gZWxzZSBpZiAoXG4gICAgICB0eXBlb2Ygdmlld1N0YXRlID09PSAnb2JqZWN0JyAmJlxuICAgICAgdmlld1N0YXRlLnR5cGUgPT09ICdtY3AtZGV0YWlsJ1xuICAgICkge1xuICAgICAgc2V0Vmlld1N0YXRlKCdwbHVnaW4tbGlzdCcpXG4gICAgICBzZXRQcm9jZXNzRXJyb3IobnVsbClcbiAgICB9IGVsc2UgaWYgKFxuICAgICAgdHlwZW9mIHZpZXdTdGF0ZSA9PT0gJ29iamVjdCcgJiZcbiAgICAgIHZpZXdTdGF0ZS50eXBlID09PSAnbWNwLXRvb2xzJ1xuICAgICkge1xuICAgICAgc2V0Vmlld1N0YXRlKHsgdHlwZTogJ21jcC1kZXRhaWwnLCBjbGllbnQ6IHZpZXdTdGF0ZS5jbGllbnQgfSlcbiAgICB9IGVsc2UgaWYgKFxuICAgICAgdHlwZW9mIHZpZXdTdGF0ZSA9PT0gJ29iamVjdCcgJiZcbiAgICAgIHZpZXdTdGF0ZS50eXBlID09PSAnbWNwLXRvb2wtZGV0YWlsJ1xuICAgICkge1xuICAgICAgc2V0Vmlld1N0YXRlKHsgdHlwZTogJ21jcC10b29scycsIGNsaWVudDogdmlld1N0YXRlLmNsaWVudCB9KVxuICAgIH0gZWxzZSB7XG4gICAgICBpZiAocGVuZGluZ1RvZ2dsZXMuc2l6ZSA+IDApIHtcbiAgICAgICAgc2V0UmVzdWx0KCdSdW4gL3JlbG9hZC1wbHVnaW5zIHRvIGFwcGx5IHBsdWdpbiBjaGFuZ2VzLicpXG4gICAgICAgIHJldHVyblxuICAgICAgfVxuICAgICAgc2V0UGFyZW50Vmlld1N0YXRlKHsgdHlwZTogJ21lbnUnIH0pXG4gICAgfVxuICB9LCBbdmlld1N0YXRlLCBzZXRQYXJlbnRWaWV3U3RhdGUsIHBlbmRpbmdUb2dnbGVzLCBzZXRSZXN1bHRdKVxuXG4gIC8vIEVzY2FwZSB3aGVuIG5vdCBpbiBzZWFyY2ggbW9kZSAtIGdvIGJhY2suXG4gIC8vIEV4Y2x1ZGVzIGNvbmZpcm0tcHJvamVjdC11bmluc3RhbGwgKGhhcyBpdHMgb3duIGNvbmZpcm06bm8gaGFuZGxlciBpblxuICAvLyBDb25maXJtYXRpb24gY29udGV4dCDigJQgbGV0dGluZyB0aGlzIGZpcmUgd291bGQgY3JlYXRlIGNvbXBldGluZyBoYW5kbGVycylcbiAgLy8gYW5kIGNvbmZpcm0tZGF0YS1jbGVhbnVwICh1c2VzIHJhdyB1c2VJbnB1dCB3aGVyZSBuIGFuZCBlc2NhcGUgYXJlXG4gIC8vIERJRkZFUkVOVCBhY3Rpb25zOiBrZWVwLWRhdGEgdnMgY2FuY2VsKS5cbiAgdXNlS2V5YmluZGluZygnY29uZmlybTpubycsIGhhbmRsZUJhY2ssIHtcbiAgICBjb250ZXh0OiAnQ29uZmlybWF0aW9uJyxcbiAgICBpc0FjdGl2ZTpcbiAgICAgICh2aWV3U3RhdGUgIT09ICdwbHVnaW4tbGlzdCcgfHwgIWlzU2VhcmNoTW9kZSkgJiZcbiAgICAgIHZpZXdTdGF0ZSAhPT0gJ2NvbmZpcm0tcHJvamVjdC11bmluc3RhbGwnICYmXG4gICAgICAhKFxuICAgICAgICB0eXBlb2Ygdmlld1N0YXRlID09PSAnb2JqZWN0JyAmJlxuICAgICAgICB2aWV3U3RhdGUudHlwZSA9PT0gJ2NvbmZpcm0tZGF0YS1jbGVhbnVwJ1xuICAgICAgKSxcbiAgfSlcblxuICAvLyBIZWxwZXIgdG8gZ2V0IE1DUCBzdGF0dXNcbiAgY29uc3QgZ2V0TWNwU3RhdHVzID0gKFxuICAgIGNsaWVudDogTUNQU2VydmVyQ29ubmVjdGlvbixcbiAgKTogJ2Nvbm5lY3RlZCcgfCAnZGlzYWJsZWQnIHwgJ3BlbmRpbmcnIHwgJ25lZWRzLWF1dGgnIHwgJ2ZhaWxlZCcgPT4ge1xuICAgIGlmIChjbGllbnQudHlwZSA9PT0gJ2Nvbm5lY3RlZCcpIHJldHVybiAnY29ubmVjdGVkJ1xuICAgIGlmIChjbGllbnQudHlwZSA9PT0gJ2Rpc2FibGVkJykgcmV0dXJuICdkaXNhYmxlZCdcbiAgICBpZiAoY2xpZW50LnR5cGUgPT09ICdwZW5kaW5nJykgcmV0dXJuICdwZW5kaW5nJ1xuICAgIGlmIChjbGllbnQudHlwZSA9PT0gJ25lZWRzLWF1dGgnKSByZXR1cm4gJ25lZWRzLWF1dGgnXG4gICAgcmV0dXJuICdmYWlsZWQnXG4gIH1cblxuICAvLyBEZXJpdmUgdW5pZmllZCBpdGVtcyBmcm9tIHBsdWdpbnMgYW5kIE1DUCBzZXJ2ZXJzXG4gIGNvbnN0IHVuaWZpZWRJdGVtcyA9IHVzZU1lbW8oKCkgPT4ge1xuICAgIGNvbnN0IG1lcmdlZFNldHRpbmdzID0gZ2V0U2V0dGluZ3NfREVQUkVDQVRFRCgpXG5cbiAgICAvLyBCdWlsZCBtYXAgb2YgcGx1Z2luIG5hbWUgLT4gY2hpbGQgTUNQc1xuICAgIC8vIFBsdWdpbiBNQ1BzIGhhdmUgbmFtZXMgbGlrZSBcInBsdWdpbjpwbHVnaW5OYW1lOnNlcnZlck5hbWVcIlxuICAgIGNvbnN0IHBsdWdpbk1jcE1hcCA9IG5ldyBNYXA8XG4gICAgICBzdHJpbmcsXG4gICAgICBBcnJheTx7IGRpc3BsYXlOYW1lOiBzdHJpbmc7IGNsaWVudDogTUNQU2VydmVyQ29ubmVjdGlvbiB9PlxuICAgID4oKVxuICAgIGZvciAoY29uc3QgY2xpZW50IG9mIG1jcENsaWVudHMpIHtcbiAgICAgIGlmIChjbGllbnQubmFtZS5zdGFydHNXaXRoKCdwbHVnaW46JykpIHtcbiAgICAgICAgY29uc3QgcGFydHMgPSBjbGllbnQubmFtZS5zcGxpdCgnOicpXG4gICAgICAgIGlmIChwYXJ0cy5sZW5ndGggPj0gMykge1xuICAgICAgICAgIGNvbnN0IHBsdWdpbk5hbWUgPSBwYXJ0c1sxXSFcbiAgICAgICAgICBjb25zdCBzZXJ2ZXJOYW1lID0gcGFydHMuc2xpY2UoMikuam9pbignOicpXG4gICAgICAgICAgY29uc3QgZXhpc3RpbmcgPSBwbHVnaW5NY3BNYXAuZ2V0KHBsdWdpbk5hbWUpIHx8IFtdXG4gICAgICAgICAgZXhpc3RpbmcucHVzaCh7IGRpc3BsYXlOYW1lOiBzZXJ2ZXJOYW1lLCBjbGllbnQgfSlcbiAgICAgICAgICBwbHVnaW5NY3BNYXAuc2V0KHBsdWdpbk5hbWUsIGV4aXN0aW5nKVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuXG4gICAgLy8gQnVpbGQgcGx1Z2luIGl0ZW1zICh1bnNvcnRlZCBmb3Igbm93KVxuICAgIHR5cGUgUGx1Z2luV2l0aENoaWxkcmVuID0ge1xuICAgICAgaXRlbTogVW5pZmllZEluc3RhbGxlZEl0ZW0gJiB7IHR5cGU6ICdwbHVnaW4nIH1cbiAgICAgIG9yaWdpbmFsU2NvcGU6ICd1c2VyJyB8ICdwcm9qZWN0JyB8ICdsb2NhbCcgfCAnbWFuYWdlZCcgfCAnYnVpbHRpbidcbiAgICAgIGNoaWxkTWNwczogQXJyYXk8eyBkaXNwbGF5TmFtZTogc3RyaW5nOyBjbGllbnQ6IE1DUFNlcnZlckNvbm5lY3Rpb24gfT5cbiAgICB9XG4gICAgY29uc3QgcGx1Z2luc1dpdGhDaGlsZHJlbjogUGx1Z2luV2l0aENoaWxkcmVuW10gPSBbXVxuXG4gICAgZm9yIChjb25zdCBzdGF0ZSBvZiBwbHVnaW5TdGF0ZXMpIHtcbiAgICAgIGNvbnN0IHBsdWdpbklkID0gYCR7c3RhdGUucGx1Z2luLm5hbWV9QCR7c3RhdGUubWFya2V0cGxhY2V9YFxuICAgICAgY29uc3QgaXNFbmFibGVkID0gbWVyZ2VkU2V0dGluZ3M/LmVuYWJsZWRQbHVnaW5zPy5bcGx1Z2luSWRdICE9PSBmYWxzZVxuICAgICAgY29uc3QgZXJyb3JzID0gcGx1Z2luRXJyb3JzLmZpbHRlcihcbiAgICAgICAgZSA9PlxuICAgICAgICAgICgncGx1Z2luJyBpbiBlICYmIGUucGx1Z2luID09PSBzdGF0ZS5wbHVnaW4ubmFtZSkgfHxcbiAgICAgICAgICBlLnNvdXJjZSA9PT0gcGx1Z2luSWQgfHxcbiAgICAgICAgICBlLnNvdXJjZS5zdGFydHNXaXRoKGAke3N0YXRlLnBsdWdpbi5uYW1lfUBgKSxcbiAgICAgIClcblxuICAgICAgLy8gQnVpbHQtaW4gcGx1Z2lucyB1c2UgJ2J1aWx0aW4nIHNjb3BlOyBvdGhlcnMgbG9vayB1cCBmcm9tIFYyIGRhdGEuXG4gICAgICBjb25zdCBvcmlnaW5hbFNjb3BlID0gc3RhdGUucGx1Z2luLmlzQnVpbHRpblxuICAgICAgICA/ICdidWlsdGluJ1xuICAgICAgICA6IHN0YXRlLnNjb3BlIHx8ICd1c2VyJ1xuXG4gICAgICBwbHVnaW5zV2l0aENoaWxkcmVuLnB1c2goe1xuICAgICAgICBpdGVtOiB7XG4gICAgICAgICAgdHlwZTogJ3BsdWdpbicsXG4gICAgICAgICAgaWQ6IHBsdWdpbklkLFxuICAgICAgICAgIG5hbWU6IHN0YXRlLnBsdWdpbi5uYW1lLFxuICAgICAgICAgIGRlc2NyaXB0aW9uOiBzdGF0ZS5wbHVnaW4ubWFuaWZlc3QuZGVzY3JpcHRpb24sXG4gICAgICAgICAgbWFya2V0cGxhY2U6IHN0YXRlLm1hcmtldHBsYWNlLFxuICAgICAgICAgIHNjb3BlOiBvcmlnaW5hbFNjb3BlLFxuICAgICAgICAgIGlzRW5hYmxlZCxcbiAgICAgICAgICBlcnJvckNvdW50OiBlcnJvcnMubGVuZ3RoLFxuICAgICAgICAgIGVycm9ycyxcbiAgICAgICAgICBwbHVnaW46IHN0YXRlLnBsdWdpbixcbiAgICAgICAgICBwZW5kaW5nRW5hYmxlOiBzdGF0ZS5wZW5kaW5nRW5hYmxlLFxuICAgICAgICAgIHBlbmRpbmdVcGRhdGU6IHN0YXRlLnBlbmRpbmdVcGRhdGUsXG4gICAgICAgICAgcGVuZGluZ1RvZ2dsZTogcGVuZGluZ1RvZ2dsZXMuZ2V0KHBsdWdpbklkKSxcbiAgICAgICAgfSxcbiAgICAgICAgb3JpZ2luYWxTY29wZSxcbiAgICAgICAgY2hpbGRNY3BzOiBwbHVnaW5NY3BNYXAuZ2V0KHN0YXRlLnBsdWdpbi5uYW1lKSB8fCBbXSxcbiAgICAgIH0pXG4gICAgfVxuXG4gICAgLy8gRmluZCBvcnBoYW4gZXJyb3JzIChlcnJvcnMgZm9yIHBsdWdpbnMgdGhhdCBmYWlsZWQgdG8gbG9hZCBlbnRpcmVseSlcbiAgICBjb25zdCBtYXRjaGVkUGx1Z2luSWRzID0gbmV3IFNldChcbiAgICAgIHBsdWdpbnNXaXRoQ2hpbGRyZW4ubWFwKCh7IGl0ZW0gfSkgPT4gaXRlbS5pZCksXG4gICAgKVxuICAgIGNvbnN0IG1hdGNoZWRQbHVnaW5OYW1lcyA9IG5ldyBTZXQoXG4gICAgICBwbHVnaW5zV2l0aENoaWxkcmVuLm1hcCgoeyBpdGVtIH0pID0+IGl0ZW0ubmFtZSksXG4gICAgKVxuICAgIGNvbnN0IG9ycGhhbkVycm9yc0J5U291cmNlID0gbmV3IE1hcDxzdHJpbmcsIHR5cGVvZiBwbHVnaW5FcnJvcnM+KClcbiAgICBmb3IgKGNvbnN0IGVycm9yIG9mIHBsdWdpbkVycm9ycykge1xuICAgICAgaWYgKFxuICAgICAgICBtYXRjaGVkUGx1Z2luSWRzLmhhcyhlcnJvci5zb3VyY2UpIHx8XG4gICAgICAgICgncGx1Z2luJyBpbiBlcnJvciAmJlxuICAgICAgICAgIHR5cGVvZiBlcnJvci5wbHVnaW4gPT09ICdzdHJpbmcnICYmXG4gICAgICAgICAgbWF0Y2hlZFBsdWdpbk5hbWVzLmhhcyhlcnJvci5wbHVnaW4pKVxuICAgICAgKSB7XG4gICAgICAgIGNvbnRpbnVlXG4gICAgICB9XG4gICAgICBjb25zdCBleGlzdGluZyA9IG9ycGhhbkVycm9yc0J5U291cmNlLmdldChlcnJvci5zb3VyY2UpIHx8IFtdXG4gICAgICBleGlzdGluZy5wdXNoKGVycm9yKVxuICAgICAgb3JwaGFuRXJyb3JzQnlTb3VyY2Uuc2V0KGVycm9yLnNvdXJjZSwgZXhpc3RpbmcpXG4gICAgfVxuICAgIGNvbnN0IHBsdWdpblNjb3BlcyA9IGdldFBsdWdpbkVkaXRhYmxlU2NvcGVzKClcbiAgICBjb25zdCBmYWlsZWRQbHVnaW5JdGVtczogVW5pZmllZEluc3RhbGxlZEl0ZW1bXSA9IFtdXG4gICAgZm9yIChjb25zdCBbcGx1Z2luSWQsIGVycm9yc10gb2Ygb3JwaGFuRXJyb3JzQnlTb3VyY2UpIHtcbiAgICAgIC8vIFNraXAgcGx1Z2lucyB0aGF0IGFyZSBhbHJlYWR5IHNob3duIGluIHRoZSBmbGFnZ2VkIHNlY3Rpb25cbiAgICAgIGlmIChwbHVnaW5JZCBpbiBmbGFnZ2VkUGx1Z2lucykgY29udGludWVcbiAgICAgIGNvbnN0IHBhcnNlZCA9IHBhcnNlUGx1Z2luSWRlbnRpZmllcihwbHVnaW5JZClcbiAgICAgIGNvbnN0IHBsdWdpbk5hbWUgPSBwYXJzZWQubmFtZSB8fCBwbHVnaW5JZFxuICAgICAgY29uc3QgbWFya2V0cGxhY2UgPSBwYXJzZWQubWFya2V0cGxhY2UgfHwgJ3Vua25vd24nXG4gICAgICBjb25zdCByYXdTY29wZSA9IHBsdWdpblNjb3Blcy5nZXQocGx1Z2luSWQpXG4gICAgICAvLyAnZmxhZycgaXMgc2Vzc2lvbi1vbmx5IChmcm9tIC0tcGx1Z2luLWRpciAvIGZsYWdTZXR0aW5ncykgYW5kIHVuZGVmaW5lZFxuICAgICAgLy8gbWVhbnMgdGhlIHBsdWdpbiBpc24ndCBpbiBhbnkgc2V0dGluZ3Mgc291cmNlLiBEZWZhdWx0IGJvdGggdG8gJ3VzZXInXG4gICAgICAvLyBzaW5jZSBVbmlmaWVkSW5zdGFsbGVkSXRlbSBkb2Vzbid0IGhhdmUgYSAnZmxhZycgc2NvcGUgdmFyaWFudC5cbiAgICAgIGNvbnN0IHNjb3BlID1cbiAgICAgICAgcmF3U2NvcGUgPT09ICdmbGFnJyB8fCByYXdTY29wZSA9PT0gdW5kZWZpbmVkID8gJ3VzZXInIDogcmF3U2NvcGVcbiAgICAgIGZhaWxlZFBsdWdpbkl0ZW1zLnB1c2goe1xuICAgICAgICB0eXBlOiAnZmFpbGVkLXBsdWdpbicsXG4gICAgICAgIGlkOiBwbHVnaW5JZCxcbiAgICAgICAgbmFtZTogcGx1Z2luTmFtZSxcbiAgICAgICAgbWFya2V0cGxhY2UsXG4gICAgICAgIHNjb3BlLFxuICAgICAgICBlcnJvckNvdW50OiBlcnJvcnMubGVuZ3RoLFxuICAgICAgICBlcnJvcnMsXG4gICAgICB9KVxuICAgIH1cblxuICAgIC8vIEJ1aWxkIHN0YW5kYWxvbmUgTUNQIGl0ZW1zXG4gICAgY29uc3Qgc3RhbmRhbG9uZU1jcHM6IFVuaWZpZWRJbnN0YWxsZWRJdGVtW10gPSBbXVxuICAgIGZvciAoY29uc3QgY2xpZW50IG9mIG1jcENsaWVudHMpIHtcbiAgICAgIGlmIChjbGllbnQubmFtZSA9PT0gJ2lkZScpIGNvbnRpbnVlXG4gICAgICBpZiAoY2xpZW50Lm5hbWUuc3RhcnRzV2l0aCgncGx1Z2luOicpKSBjb250aW51ZVxuXG4gICAgICBzdGFuZGFsb25lTWNwcy5wdXNoKHtcbiAgICAgICAgdHlwZTogJ21jcCcsXG4gICAgICAgIGlkOiBgbWNwOiR7Y2xpZW50Lm5hbWV9YCxcbiAgICAgICAgbmFtZTogY2xpZW50Lm5hbWUsXG4gICAgICAgIGRlc2NyaXB0aW9uOiB1bmRlZmluZWQsXG4gICAgICAgIHNjb3BlOiBjbGllbnQuY29uZmlnLnNjb3BlLFxuICAgICAgICBzdGF0dXM6IGdldE1jcFN0YXR1cyhjbGllbnQpLFxuICAgICAgICBjbGllbnQsXG4gICAgICB9KVxuICAgIH1cblxuICAgIC8vIERlZmluZSBzY29wZSBvcmRlciBmb3IgZGlzcGxheVxuICAgIGNvbnN0IHNjb3BlT3JkZXI6IFJlY29yZDxzdHJpbmcsIG51bWJlcj4gPSB7XG4gICAgICBmbGFnZ2VkOiAtMSxcbiAgICAgIHByb2plY3Q6IDAsXG4gICAgICBsb2NhbDogMSxcbiAgICAgIHVzZXI6IDIsXG4gICAgICBlbnRlcnByaXNlOiAzLFxuICAgICAgbWFuYWdlZDogNCxcbiAgICAgIGR5bmFtaWM6IDUsXG4gICAgICBidWlsdGluOiA2LFxuICAgIH1cblxuICAgIC8vIEJ1aWxkIGZpbmFsIGxpc3QgYnkgbWVyZ2luZyBwbHVnaW5zICh3aXRoIHRoZWlyIGNoaWxkIE1DUHMpIGFuZCBzdGFuZGFsb25lIE1DUHNcbiAgICAvLyBHcm91cCBieSBzY29wZSB0byBhdm9pZCBkdXBsaWNhdGUgc2NvcGUgaGVhZGVyc1xuICAgIGNvbnN0IHVuaWZpZWQ6IFVuaWZpZWRJbnN0YWxsZWRJdGVtW10gPSBbXVxuXG4gICAgLy8gQ3JlYXRlIGEgbWFwIG9mIHNjb3BlIC0+IGl0ZW1zIGZvciBwcm9wZXIgbWVyZ2luZ1xuICAgIGNvbnN0IGl0ZW1zQnlTY29wZSA9IG5ldyBNYXA8c3RyaW5nLCBVbmlmaWVkSW5zdGFsbGVkSXRlbVtdPigpXG5cbiAgICAvLyBBZGQgcGx1Z2lucyB3aXRoIHRoZWlyIGNoaWxkIE1DUHNcbiAgICBmb3IgKGNvbnN0IHsgaXRlbSwgb3JpZ2luYWxTY29wZSwgY2hpbGRNY3BzIH0gb2YgcGx1Z2luc1dpdGhDaGlsZHJlbikge1xuICAgICAgY29uc3Qgc2NvcGUgPSBpdGVtLnNjb3BlXG4gICAgICBpZiAoIWl0ZW1zQnlTY29wZS5oYXMoc2NvcGUpKSB7XG4gICAgICAgIGl0ZW1zQnlTY29wZS5zZXQoc2NvcGUsIFtdKVxuICAgICAgfVxuICAgICAgaXRlbXNCeVNjb3BlLmdldChzY29wZSkhLnB1c2goaXRlbSlcbiAgICAgIC8vIEFkZCBjaGlsZCBNQ1BzIHJpZ2h0IGFmdGVyIHRoZSBwbHVnaW4sIGluZGVudGVkICh1c2Ugb3JpZ2luYWwgc2NvcGUsIG5vdCAnZmxhZ2dlZCcpLlxuICAgICAgLy8gQnVpbHQtaW4gcGx1Z2lucyBtYXAgdG8gJ3VzZXInIGZvciBkaXNwbGF5IHNpbmNlIE1DUCBDb25maWdTY29wZSBkb2Vzbid0IGluY2x1ZGUgJ2J1aWx0aW4nLlxuICAgICAgZm9yIChjb25zdCB7IGRpc3BsYXlOYW1lLCBjbGllbnQgfSBvZiBjaGlsZE1jcHMpIHtcbiAgICAgICAgY29uc3QgZGlzcGxheVNjb3BlID1cbiAgICAgICAgICBvcmlnaW5hbFNjb3BlID09PSAnYnVpbHRpbicgPyAndXNlcicgOiBvcmlnaW5hbFNjb3BlXG4gICAgICAgIGlmICghaXRlbXNCeVNjb3BlLmhhcyhkaXNwbGF5U2NvcGUpKSB7XG4gICAgICAgICAgaXRlbXNCeVNjb3BlLnNldChkaXNwbGF5U2NvcGUsIFtdKVxuICAgICAgICB9XG4gICAgICAgIGl0ZW1zQnlTY29wZS5nZXQoZGlzcGxheVNjb3BlKSEucHVzaCh7XG4gICAgICAgICAgdHlwZTogJ21jcCcsXG4gICAgICAgICAgaWQ6IGBtY3A6JHtjbGllbnQubmFtZX1gLFxuICAgICAgICAgIG5hbWU6IGRpc3BsYXlOYW1lLFxuICAgICAgICAgIGRlc2NyaXB0aW9uOiB1bmRlZmluZWQsXG4gICAgICAgICAgc2NvcGU6IGRpc3BsYXlTY29wZSxcbiAgICAgICAgICBzdGF0dXM6IGdldE1jcFN0YXR1cyhjbGllbnQpLFxuICAgICAgICAgIGNsaWVudCxcbiAgICAgICAgICBpbmRlbnRlZDogdHJ1ZSxcbiAgICAgICAgfSlcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBBZGQgc3RhbmRhbG9uZSBNQ1BzIHRvIHRoZWlyIHJlc3BlY3RpdmUgc2NvcGUgZ3JvdXBzXG4gICAgZm9yIChjb25zdCBtY3Agb2Ygc3RhbmRhbG9uZU1jcHMpIHtcbiAgICAgIGNvbnN0IHNjb3BlID0gbWNwLnNjb3BlXG4gICAgICBpZiAoIWl0ZW1zQnlTY29wZS5oYXMoc2NvcGUpKSB7XG4gICAgICAgIGl0ZW1zQnlTY29wZS5zZXQoc2NvcGUsIFtdKVxuICAgICAgfVxuICAgICAgaXRlbXNCeVNjb3BlLmdldChzY29wZSkhLnB1c2gobWNwKVxuICAgIH1cblxuICAgIC8vIEFkZCBmYWlsZWQgcGx1Z2lucyB0byB0aGVpciByZXNwZWN0aXZlIHNjb3BlIGdyb3Vwc1xuICAgIGZvciAoY29uc3QgZmFpbGVkUGx1Z2luIG9mIGZhaWxlZFBsdWdpbkl0ZW1zKSB7XG4gICAgICBjb25zdCBzY29wZSA9IGZhaWxlZFBsdWdpbi5zY29wZVxuICAgICAgaWYgKCFpdGVtc0J5U2NvcGUuaGFzKHNjb3BlKSkge1xuICAgICAgICBpdGVtc0J5U2NvcGUuc2V0KHNjb3BlLCBbXSlcbiAgICAgIH1cbiAgICAgIGl0ZW1zQnlTY29wZS5nZXQoc2NvcGUpIS5wdXNoKGZhaWxlZFBsdWdpbilcbiAgICB9XG5cbiAgICAvLyBBZGQgZmxhZ2dlZCAoZGVsaXN0ZWQpIHBsdWdpbnMgZnJvbSB1c2VyIHNldHRpbmdzLlxuICAgIC8vIFJlYXNvbi90ZXh0IGFyZSBsb29rZWQgdXAgZnJvbSB0aGUgY2FjaGVkIHNlY3VyaXR5IG1lc3NhZ2VzIGZpbGUuXG4gICAgZm9yIChjb25zdCBbcGx1Z2luSWQsIGVudHJ5XSBvZiBPYmplY3QuZW50cmllcyhmbGFnZ2VkUGx1Z2lucykpIHtcbiAgICAgIGNvbnN0IHBhcnNlZCA9IHBhcnNlUGx1Z2luSWRlbnRpZmllcihwbHVnaW5JZClcbiAgICAgIGNvbnN0IHBsdWdpbk5hbWUgPSBwYXJzZWQubmFtZSB8fCBwbHVnaW5JZFxuICAgICAgY29uc3QgbWFya2V0cGxhY2UgPSBwYXJzZWQubWFya2V0cGxhY2UgfHwgJ3Vua25vd24nXG4gICAgICBpZiAoIWl0ZW1zQnlTY29wZS5oYXMoJ2ZsYWdnZWQnKSkge1xuICAgICAgICBpdGVtc0J5U2NvcGUuc2V0KCdmbGFnZ2VkJywgW10pXG4gICAgICB9XG4gICAgICBpdGVtc0J5U2NvcGUuZ2V0KCdmbGFnZ2VkJykhLnB1c2goe1xuICAgICAgICB0eXBlOiAnZmxhZ2dlZC1wbHVnaW4nLFxuICAgICAgICBpZDogcGx1Z2luSWQsXG4gICAgICAgIG5hbWU6IHBsdWdpbk5hbWUsXG4gICAgICAgIG1hcmtldHBsYWNlLFxuICAgICAgICBzY29wZTogJ2ZsYWdnZWQnLFxuICAgICAgICByZWFzb246ICdkZWxpc3RlZCcsXG4gICAgICAgIHRleHQ6ICdSZW1vdmVkIGZyb20gbWFya2V0cGxhY2UnLFxuICAgICAgICBmbGFnZ2VkQXQ6IGVudHJ5LmZsYWdnZWRBdCxcbiAgICAgIH0pXG4gICAgfVxuXG4gICAgLy8gU29ydCBzY29wZXMgYW5kIGJ1aWxkIGZpbmFsIGxpc3RcbiAgICBjb25zdCBzb3J0ZWRTY29wZXMgPSBbLi4uaXRlbXNCeVNjb3BlLmtleXMoKV0uc29ydChcbiAgICAgIChhLCBiKSA9PiAoc2NvcGVPcmRlclthXSA/PyA5OSkgLSAoc2NvcGVPcmRlcltiXSA/PyA5OSksXG4gICAgKVxuXG4gICAgZm9yIChjb25zdCBzY29wZSBvZiBzb3J0ZWRTY29wZXMpIHtcbiAgICAgIGNvbnN0IGl0ZW1zID0gaXRlbXNCeVNjb3BlLmdldChzY29wZSkhXG5cbiAgICAgIC8vIFNlcGFyYXRlIGl0ZW1zIGludG8gcGx1Z2luIGdyb3VwcyAod2l0aCB0aGVpciBjaGlsZCBNQ1BzKSBhbmQgc3RhbmRhbG9uZSBNQ1BzXG4gICAgICAvLyBUaGlzIHByZXNlcnZlcyBwYXJlbnQtY2hpbGQgcmVsYXRpb25zaGlwcyB0aGF0IHdvdWxkIGJlIGJyb2tlbiBieSBuYWl2ZSBzb3J0aW5nXG4gICAgICBjb25zdCBwbHVnaW5Hcm91cHM6IFVuaWZpZWRJbnN0YWxsZWRJdGVtW11bXSA9IFtdXG4gICAgICBjb25zdCBzdGFuZGFsb25lTWNwc0luU2NvcGU6IFVuaWZpZWRJbnN0YWxsZWRJdGVtW10gPSBbXVxuXG4gICAgICBsZXQgaSA9IDBcbiAgICAgIHdoaWxlIChpIDwgaXRlbXMubGVuZ3RoKSB7XG4gICAgICAgIGNvbnN0IGl0ZW0gPSBpdGVtc1tpXSFcbiAgICAgICAgaWYgKFxuICAgICAgICAgIGl0ZW0udHlwZSA9PT0gJ3BsdWdpbicgfHxcbiAgICAgICAgICBpdGVtLnR5cGUgPT09ICdmYWlsZWQtcGx1Z2luJyB8fFxuICAgICAgICAgIGl0ZW0udHlwZSA9PT0gJ2ZsYWdnZWQtcGx1Z2luJ1xuICAgICAgICApIHtcbiAgICAgICAgICAvLyBDb2xsZWN0IHRoZSBwbHVnaW4gYW5kIGl0cyBjaGlsZCBNQ1BzIGFzIGEgZ3JvdXBcbiAgICAgICAgICBjb25zdCBncm91cDogVW5pZmllZEluc3RhbGxlZEl0ZW1bXSA9IFtpdGVtXVxuICAgICAgICAgIGkrK1xuICAgICAgICAgIC8vIExvb2sgYWhlYWQgZm9yIGluZGVudGVkIGNoaWxkIE1DUHNcbiAgICAgICAgICBsZXQgbmV4dEl0ZW0gPSBpdGVtc1tpXVxuICAgICAgICAgIHdoaWxlIChuZXh0SXRlbT8udHlwZSA9PT0gJ21jcCcgJiYgbmV4dEl0ZW0uaW5kZW50ZWQpIHtcbiAgICAgICAgICAgIGdyb3VwLnB1c2gobmV4dEl0ZW0pXG4gICAgICAgICAgICBpKytcbiAgICAgICAgICAgIG5leHRJdGVtID0gaXRlbXNbaV1cbiAgICAgICAgICB9XG4gICAgICAgICAgcGx1Z2luR3JvdXBzLnB1c2goZ3JvdXApXG4gICAgICAgIH0gZWxzZSBpZiAoaXRlbS50eXBlID09PSAnbWNwJyAmJiAhaXRlbS5pbmRlbnRlZCkge1xuICAgICAgICAgIC8vIFN0YW5kYWxvbmUgTUNQIChub3QgYSBjaGlsZCBvZiBhIHBsdWdpbilcbiAgICAgICAgICBzdGFuZGFsb25lTWNwc0luU2NvcGUucHVzaChpdGVtKVxuICAgICAgICAgIGkrK1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIC8vIFNraXAgb3JwaGFuZWQgaW5kZW50ZWQgTUNQcyAoc2hvdWxkbid0IGhhcHBlbilcbiAgICAgICAgICBpKytcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyBTb3J0IHBsdWdpbiBncm91cHMgYnkgdGhlIHBsdWdpbiBuYW1lIChmaXJzdCBpdGVtIGluIGVhY2ggZ3JvdXApXG4gICAgICBwbHVnaW5Hcm91cHMuc29ydCgoYSwgYikgPT4gYVswXSEubmFtZS5sb2NhbGVDb21wYXJlKGJbMF0hLm5hbWUpKVxuXG4gICAgICAvLyBTb3J0IHN0YW5kYWxvbmUgTUNQcyBieSBuYW1lXG4gICAgICBzdGFuZGFsb25lTWNwc0luU2NvcGUuc29ydCgoYSwgYikgPT4gYS5uYW1lLmxvY2FsZUNvbXBhcmUoYi5uYW1lKSlcblxuICAgICAgLy8gQnVpbGQgZmluYWwgbGlzdDogcGx1Z2lucyAod2l0aCB0aGVpciBjaGlsZHJlbikgZmlyc3QsIHRoZW4gc3RhbmRhbG9uZSBNQ1BzXG4gICAgICBmb3IgKGNvbnN0IGdyb3VwIG9mIHBsdWdpbkdyb3Vwcykge1xuICAgICAgICB1bmlmaWVkLnB1c2goLi4uZ3JvdXApXG4gICAgICB9XG4gICAgICB1bmlmaWVkLnB1c2goLi4uc3RhbmRhbG9uZU1jcHNJblNjb3BlKVxuICAgIH1cblxuICAgIHJldHVybiB1bmlmaWVkXG4gIH0sIFtwbHVnaW5TdGF0ZXMsIG1jcENsaWVudHMsIHBsdWdpbkVycm9ycywgcGVuZGluZ1RvZ2dsZXMsIGZsYWdnZWRQbHVnaW5zXSlcblxuICAvLyBNYXJrIGZsYWdnZWQgcGx1Z2lucyBhcyBzZWVuIHdoZW4gdGhlIEluc3RhbGxlZCB2aWV3IHJlbmRlcnMgdGhlbS5cbiAgLy8gQWZ0ZXIgNDggaG91cnMgZnJvbSBzZWVuQXQsIHRoZXkgYXV0by1jbGVhciBvbiBuZXh0IGxvYWQuXG4gIGNvbnN0IGZsYWdnZWRJZHMgPSB1c2VNZW1vKFxuICAgICgpID0+XG4gICAgICB1bmlmaWVkSXRlbXNcbiAgICAgICAgLmZpbHRlcihpdGVtID0+IGl0ZW0udHlwZSA9PT0gJ2ZsYWdnZWQtcGx1Z2luJylcbiAgICAgICAgLm1hcChpdGVtID0+IGl0ZW0uaWQpLFxuICAgIFt1bmlmaWVkSXRlbXNdLFxuICApXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgaWYgKGZsYWdnZWRJZHMubGVuZ3RoID4gMCkge1xuICAgICAgdm9pZCBtYXJrRmxhZ2dlZFBsdWdpbnNTZWVuKGZsYWdnZWRJZHMpXG4gICAgfVxuICB9LCBbZmxhZ2dlZElkc10pXG5cbiAgLy8gRmlsdGVyIGl0ZW1zIGJhc2VkIG9uIHNlYXJjaCBxdWVyeSAobWF0Y2hlcyBuYW1lIG9yIGRlc2NyaXB0aW9uKVxuICBjb25zdCBmaWx0ZXJlZEl0ZW1zID0gdXNlTWVtbygoKSA9PiB7XG4gICAgaWYgKCFzZWFyY2hRdWVyeSkgcmV0dXJuIHVuaWZpZWRJdGVtc1xuICAgIGNvbnN0IGxvd2VyUXVlcnkgPSBzZWFyY2hRdWVyeS50b0xvd2VyQ2FzZSgpXG4gICAgcmV0dXJuIHVuaWZpZWRJdGVtcy5maWx0ZXIoXG4gICAgICBpdGVtID0+XG4gICAgICAgIGl0ZW0ubmFtZS50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKGxvd2VyUXVlcnkpIHx8XG4gICAgICAgICgnZGVzY3JpcHRpb24nIGluIGl0ZW0gJiZcbiAgICAgICAgICBpdGVtLmRlc2NyaXB0aW9uPy50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKGxvd2VyUXVlcnkpKSxcbiAgICApXG4gIH0sIFt1bmlmaWVkSXRlbXMsIHNlYXJjaFF1ZXJ5XSlcblxuICAvLyBTZWxlY3Rpb24gc3RhdGVcbiAgY29uc3QgW3NlbGVjdGVkSW5kZXgsIHNldFNlbGVjdGVkSW5kZXhdID0gdXNlU3RhdGUoMClcblxuICAvLyBQYWdpbmF0aW9uIGZvciB1bmlmaWVkIGxpc3QgKGNvbnRpbnVvdXMgc2Nyb2xsaW5nKVxuICBjb25zdCBwYWdpbmF0aW9uID0gdXNlUGFnaW5hdGlvbjxVbmlmaWVkSW5zdGFsbGVkSXRlbT4oe1xuICAgIHRvdGFsSXRlbXM6IGZpbHRlcmVkSXRlbXMubGVuZ3RoLFxuICAgIHNlbGVjdGVkSW5kZXgsXG4gICAgbWF4VmlzaWJsZTogOCxcbiAgfSlcblxuICAvLyBEZXRhaWxzIHZpZXcgc3RhdGVcbiAgY29uc3QgW2RldGFpbHNNZW51SW5kZXgsIHNldERldGFpbHNNZW51SW5kZXhdID0gdXNlU3RhdGUoMClcbiAgY29uc3QgW2lzUHJvY2Vzc2luZywgc2V0SXNQcm9jZXNzaW5nXSA9IHVzZVN0YXRlKGZhbHNlKVxuICBjb25zdCBbcHJvY2Vzc0Vycm9yLCBzZXRQcm9jZXNzRXJyb3JdID0gdXNlU3RhdGU8c3RyaW5nIHwgbnVsbD4obnVsbClcblxuICAvLyBDb25maWd1cmF0aW9uIHN0YXRlXG4gIGNvbnN0IFtjb25maWdOZWVkZWQsIHNldENvbmZpZ05lZWRlZF0gPVxuICAgIHVzZVN0YXRlPE1jcGJOZWVkc0NvbmZpZ1Jlc3VsdCB8IG51bGw+KG51bGwpXG4gIGNvbnN0IFtfaXNMb2FkaW5nQ29uZmlnLCBzZXRJc0xvYWRpbmdDb25maWddID0gdXNlU3RhdGUoZmFsc2UpXG4gIGNvbnN0IFtzZWxlY3RlZFBsdWdpbkhhc01jcGIsIHNldFNlbGVjdGVkUGx1Z2luSGFzTWNwYl0gPSB1c2VTdGF0ZShmYWxzZSlcblxuICAvLyBEZXRlY3QgaWYgc2VsZWN0ZWQgcGx1Z2luIGhhcyBNQ1BCXG4gIC8vIFJlYWRzIHJhdyBtYXJrZXRwbGFjZS5qc29uIHRvIHdvcmsgd2l0aCBvbGQgY2FjaGVkIG1hcmtldHBsYWNlc1xuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIGlmICghc2VsZWN0ZWRQbHVnaW4pIHtcbiAgICAgIHNldFNlbGVjdGVkUGx1Z2luSGFzTWNwYihmYWxzZSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIGFzeW5jIGZ1bmN0aW9uIGRldGVjdE1jcGIoKSB7XG4gICAgICAvLyBDaGVjayBwbHVnaW4gbWFuaWZlc3QgZmlyc3RcbiAgICAgIGNvbnN0IG1jcFNlcnZlcnNTcGVjID0gc2VsZWN0ZWRQbHVnaW4hLnBsdWdpbi5tYW5pZmVzdC5tY3BTZXJ2ZXJzXG4gICAgICBsZXQgaGFzTWNwYiA9IGZhbHNlXG5cbiAgICAgIGlmIChtY3BTZXJ2ZXJzU3BlYykge1xuICAgICAgICBoYXNNY3BiID1cbiAgICAgICAgICAodHlwZW9mIG1jcFNlcnZlcnNTcGVjID09PSAnc3RyaW5nJyAmJlxuICAgICAgICAgICAgaXNNY3BiU291cmNlKG1jcFNlcnZlcnNTcGVjKSkgfHxcbiAgICAgICAgICAoQXJyYXkuaXNBcnJheShtY3BTZXJ2ZXJzU3BlYykgJiZcbiAgICAgICAgICAgIG1jcFNlcnZlcnNTcGVjLnNvbWUocyA9PiB0eXBlb2YgcyA9PT0gJ3N0cmluZycgJiYgaXNNY3BiU291cmNlKHMpKSlcbiAgICAgIH1cblxuICAgICAgLy8gSWYgbm90IGluIG1hbmlmZXN0LCByZWFkIHJhdyBtYXJrZXRwbGFjZS5qc29uIGRpcmVjdGx5IChieXBhc3Npbmcgc2NoZW1hIHZhbGlkYXRpb24pXG4gICAgICAvLyBUaGlzIHdvcmtzIGV2ZW4gd2l0aCBvbGQgY2FjaGVkIG1hcmtldHBsYWNlcyBmcm9tIGJlZm9yZSBNQ1BCIHN1cHBvcnRcbiAgICAgIGlmICghaGFzTWNwYikge1xuICAgICAgICB0cnkge1xuICAgICAgICAgIGNvbnN0IG1hcmtldHBsYWNlRGlyID0gcGF0aC5qb2luKHNlbGVjdGVkUGx1Z2luIS5wbHVnaW4ucGF0aCwgJy4uJylcbiAgICAgICAgICBjb25zdCBtYXJrZXRwbGFjZUpzb25QYXRoID0gcGF0aC5qb2luKFxuICAgICAgICAgICAgbWFya2V0cGxhY2VEaXIsXG4gICAgICAgICAgICAnLmNsYXVkZS1wbHVnaW4nLFxuICAgICAgICAgICAgJ21hcmtldHBsYWNlLmpzb24nLFxuICAgICAgICAgIClcblxuICAgICAgICAgIGNvbnN0IGNvbnRlbnQgPSBhd2FpdCBmcy5yZWFkRmlsZShtYXJrZXRwbGFjZUpzb25QYXRoLCAndXRmLTgnKVxuICAgICAgICAgIGNvbnN0IG1hcmtldHBsYWNlID0ganNvblBhcnNlKGNvbnRlbnQpXG5cbiAgICAgICAgICBjb25zdCBlbnRyeSA9IG1hcmtldHBsYWNlLnBsdWdpbnM/LmZpbmQoXG4gICAgICAgICAgICAocDogeyBuYW1lOiBzdHJpbmcgfSkgPT4gcC5uYW1lID09PSBzZWxlY3RlZFBsdWdpbiEucGx1Z2luLm5hbWUsXG4gICAgICAgICAgKVxuXG4gICAgICAgICAgaWYgKGVudHJ5Py5tY3BTZXJ2ZXJzKSB7XG4gICAgICAgICAgICBjb25zdCBzcGVjID0gZW50cnkubWNwU2VydmVyc1xuICAgICAgICAgICAgaGFzTWNwYiA9XG4gICAgICAgICAgICAgICh0eXBlb2Ygc3BlYyA9PT0gJ3N0cmluZycgJiYgaXNNY3BiU291cmNlKHNwZWMpKSB8fFxuICAgICAgICAgICAgICAoQXJyYXkuaXNBcnJheShzcGVjKSAmJlxuICAgICAgICAgICAgICAgIHNwZWMuc29tZShcbiAgICAgICAgICAgICAgICAgIChzOiB1bmtub3duKSA9PiB0eXBlb2YgcyA9PT0gJ3N0cmluZycgJiYgaXNNY3BiU291cmNlKHMpLFxuICAgICAgICAgICAgICAgICkpXG4gICAgICAgICAgfVxuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICBsb2dGb3JEZWJ1Z2dpbmcoYEZhaWxlZCB0byByZWFkIHJhdyBtYXJrZXRwbGFjZS5qc29uOiAke2Vycn1gKVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHNldFNlbGVjdGVkUGx1Z2luSGFzTWNwYihoYXNNY3BiKVxuICAgIH1cblxuICAgIHZvaWQgZGV0ZWN0TWNwYigpXG4gIH0sIFtzZWxlY3RlZFBsdWdpbl0pXG5cbiAgLy8gTG9hZCBpbnN0YWxsZWQgcGx1Z2lucyBncm91cGVkIGJ5IG1hcmtldHBsYWNlXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgYXN5bmMgZnVuY3Rpb24gbG9hZEluc3RhbGxlZFBsdWdpbnMoKSB7XG4gICAgICBzZXRMb2FkaW5nKHRydWUpXG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCB7IGVuYWJsZWQsIGRpc2FibGVkIH0gPSBhd2FpdCBsb2FkQWxsUGx1Z2lucygpXG4gICAgICAgIGNvbnN0IG1lcmdlZFNldHRpbmdzID0gZ2V0U2V0dGluZ3NfREVQUkVDQVRFRCgpIC8vIFVzZSBtZXJnZWQgc2V0dGluZ3MgdG8gcmVzcGVjdCBhbGwgbGF5ZXJzXG5cbiAgICAgICAgY29uc3QgYWxsUGx1Z2lucyA9IGZpbHRlck1hbmFnZWREaXNhYmxlZFBsdWdpbnMoW1xuICAgICAgICAgIC4uLmVuYWJsZWQsXG4gICAgICAgICAgLi4uZGlzYWJsZWQsXG4gICAgICAgIF0pXG5cbiAgICAgICAgLy8gR3JvdXAgcGx1Z2lucyBieSBtYXJrZXRwbGFjZVxuICAgICAgICBjb25zdCBwbHVnaW5zQnlNYXJrZXRwbGFjZTogUmVjb3JkPHN0cmluZywgTG9hZGVkUGx1Z2luW10+ID0ge31cbiAgICAgICAgZm9yIChjb25zdCBwbHVnaW4gb2YgYWxsUGx1Z2lucykge1xuICAgICAgICAgIGNvbnN0IG1hcmtldHBsYWNlID0gcGx1Z2luLnNvdXJjZS5zcGxpdCgnQCcpWzFdIHx8ICdsb2NhbCdcbiAgICAgICAgICBpZiAoIXBsdWdpbnNCeU1hcmtldHBsYWNlW21hcmtldHBsYWNlXSkge1xuICAgICAgICAgICAgcGx1Z2luc0J5TWFya2V0cGxhY2VbbWFya2V0cGxhY2VdID0gW11cbiAgICAgICAgICB9XG4gICAgICAgICAgcGx1Z2luc0J5TWFya2V0cGxhY2VbbWFya2V0cGxhY2VdIS5wdXNoKHBsdWdpbilcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIENyZWF0ZSBtYXJrZXRwbGFjZSBpbmZvIGFycmF5IHdpdGggZW5hYmxlZC9kaXNhYmxlZCBjb3VudHNcbiAgICAgICAgY29uc3QgbWFya2V0cGxhY2VJbmZvczogTWFya2V0cGxhY2VJbmZvW10gPSBbXVxuICAgICAgICBmb3IgKGNvbnN0IFtuYW1lLCBwbHVnaW5zXSBvZiBPYmplY3QuZW50cmllcyhwbHVnaW5zQnlNYXJrZXRwbGFjZSkpIHtcbiAgICAgICAgICBjb25zdCBlbmFibGVkQ291bnQgPSBjb3VudChwbHVnaW5zLCBwID0+IHtcbiAgICAgICAgICAgIGNvbnN0IHBsdWdpbklkID0gYCR7cC5uYW1lfUAke25hbWV9YFxuICAgICAgICAgICAgcmV0dXJuIG1lcmdlZFNldHRpbmdzPy5lbmFibGVkUGx1Z2lucz8uW3BsdWdpbklkXSAhPT0gZmFsc2VcbiAgICAgICAgICB9KVxuICAgICAgICAgIGNvbnN0IGRpc2FibGVkQ291bnQgPSBwbHVnaW5zLmxlbmd0aCAtIGVuYWJsZWRDb3VudFxuXG4gICAgICAgICAgbWFya2V0cGxhY2VJbmZvcy5wdXNoKHtcbiAgICAgICAgICAgIG5hbWUsXG4gICAgICAgICAgICBpbnN0YWxsZWRQbHVnaW5zOiBwbHVnaW5zLFxuICAgICAgICAgICAgZW5hYmxlZENvdW50LFxuICAgICAgICAgICAgZGlzYWJsZWRDb3VudCxcbiAgICAgICAgICB9KVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gU29ydCBtYXJrZXRwbGFjZXM6IGNsYXVkZS1wbHVnaW4tZGlyZWN0b3J5IGZpcnN0LCB0aGVuIGFscGhhYmV0aWNhbGx5XG4gICAgICAgIG1hcmtldHBsYWNlSW5mb3Muc29ydCgoYSwgYikgPT4ge1xuICAgICAgICAgIGlmIChhLm5hbWUgPT09ICdjbGF1ZGUtcGx1Z2luLWRpcmVjdG9yeScpIHJldHVybiAtMVxuICAgICAgICAgIGlmIChiLm5hbWUgPT09ICdjbGF1ZGUtcGx1Z2luLWRpcmVjdG9yeScpIHJldHVybiAxXG4gICAgICAgICAgcmV0dXJuIGEubmFtZS5sb2NhbGVDb21wYXJlKGIubmFtZSlcbiAgICAgICAgfSlcblxuICAgICAgICBzZXRNYXJrZXRwbGFjZXMobWFya2V0cGxhY2VJbmZvcylcblxuICAgICAgICAvLyBCdWlsZCBmbGF0IGxpc3Qgb2YgYWxsIHBsdWdpbiBzdGF0ZXNcbiAgICAgICAgY29uc3QgYWxsU3RhdGVzOiBQbHVnaW5TdGF0ZVtdID0gW11cbiAgICAgICAgZm9yIChjb25zdCBtYXJrZXRwbGFjZSBvZiBtYXJrZXRwbGFjZUluZm9zKSB7XG4gICAgICAgICAgZm9yIChjb25zdCBwbHVnaW4gb2YgbWFya2V0cGxhY2UuaW5zdGFsbGVkUGx1Z2lucykge1xuICAgICAgICAgICAgY29uc3QgcGx1Z2luSWQgPSBgJHtwbHVnaW4ubmFtZX1AJHttYXJrZXRwbGFjZS5uYW1lfWBcbiAgICAgICAgICAgIC8vIEJ1aWx0LWluIHBsdWdpbnMgZG9uJ3QgaGF2ZSBWMiBpbnN0YWxsIGVudHJpZXMg4oCUIHNraXAgdGhlIGxvb2t1cC5cbiAgICAgICAgICAgIGNvbnN0IHNjb3BlID0gcGx1Z2luLmlzQnVpbHRpblxuICAgICAgICAgICAgICA/ICdidWlsdGluJ1xuICAgICAgICAgICAgICA6IGdldFBsdWdpbkluc3RhbGxhdGlvbkZyb21WMihwbHVnaW5JZCkuc2NvcGVcblxuICAgICAgICAgICAgYWxsU3RhdGVzLnB1c2goe1xuICAgICAgICAgICAgICBwbHVnaW4sXG4gICAgICAgICAgICAgIG1hcmtldHBsYWNlOiBtYXJrZXRwbGFjZS5uYW1lLFxuICAgICAgICAgICAgICBzY29wZSxcbiAgICAgICAgICAgICAgcGVuZGluZ0VuYWJsZTogdW5kZWZpbmVkLFxuICAgICAgICAgICAgICBwZW5kaW5nVXBkYXRlOiBmYWxzZSxcbiAgICAgICAgICAgIH0pXG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIHNldFBsdWdpblN0YXRlcyhhbGxTdGF0ZXMpXG4gICAgICAgIHNldFNlbGVjdGVkSW5kZXgoMClcbiAgICAgIH0gZmluYWxseSB7XG4gICAgICAgIHNldExvYWRpbmcoZmFsc2UpXG4gICAgICB9XG4gICAgfVxuXG4gICAgdm9pZCBsb2FkSW5zdGFsbGVkUGx1Z2lucygpXG4gIH0sIFtdKVxuXG4gIC8vIEF1dG8tbmF2aWdhdGUgdG8gdGFyZ2V0IHBsdWdpbiBpZiBzcGVjaWZpZWQgKG9uY2Ugb25seSlcbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICBpZiAoaGFzQXV0b05hdmlnYXRlZC5jdXJyZW50KSByZXR1cm5cbiAgICBpZiAodGFyZ2V0UGx1Z2luICYmIG1hcmtldHBsYWNlcy5sZW5ndGggPiAwICYmICFsb2FkaW5nKSB7XG4gICAgICAvLyB0YXJnZXRQbHVnaW4gbWF5IGJlIGBuYW1lYCBvciBgbmFtZUBtYXJrZXRwbGFjZWAgKHBhcnNlQXJncyBwYXNzZXMgdGhlXG4gICAgICAvLyByYXcgYXJnIHRocm91Z2gpLiBQYXJzZSBpdCBzbyBwLm5hbWUgbWF0Y2hpbmcgd29ya3MgZWl0aGVyIHdheS5cbiAgICAgIGNvbnN0IHsgbmFtZTogdGFyZ2V0TmFtZSwgbWFya2V0cGxhY2U6IHRhcmdldE1rdEZyb21JZCB9ID1cbiAgICAgICAgcGFyc2VQbHVnaW5JZGVudGlmaWVyKHRhcmdldFBsdWdpbilcbiAgICAgIGNvbnN0IGVmZmVjdGl2ZVRhcmdldE1hcmtldHBsYWNlID0gdGFyZ2V0TWFya2V0cGxhY2UgPz8gdGFyZ2V0TWt0RnJvbUlkXG5cbiAgICAgIC8vIFVzZSB0YXJnZXRNYXJrZXRwbGFjZSBpZiBwcm92aWRlZCwgb3RoZXJ3aXNlIHNlYXJjaCBhbGxcbiAgICAgIGNvbnN0IG1hcmtldHBsYWNlc1RvU2VhcmNoID0gZWZmZWN0aXZlVGFyZ2V0TWFya2V0cGxhY2VcbiAgICAgICAgPyBtYXJrZXRwbGFjZXMuZmlsdGVyKG0gPT4gbS5uYW1lID09PSBlZmZlY3RpdmVUYXJnZXRNYXJrZXRwbGFjZSlcbiAgICAgICAgOiBtYXJrZXRwbGFjZXNcblxuICAgICAgLy8gRmlyc3QgY2hlY2sgc3VjY2Vzc2Z1bGx5IGxvYWRlZCBwbHVnaW5zXG4gICAgICBmb3IgKGNvbnN0IG1hcmtldHBsYWNlIG9mIG1hcmtldHBsYWNlc1RvU2VhcmNoKSB7XG4gICAgICAgIGNvbnN0IHBsdWdpbiA9IG1hcmtldHBsYWNlLmluc3RhbGxlZFBsdWdpbnMuZmluZChcbiAgICAgICAgICBwID0+IHAubmFtZSA9PT0gdGFyZ2V0TmFtZSxcbiAgICAgICAgKVxuICAgICAgICBpZiAocGx1Z2luKSB7XG4gICAgICAgICAgLy8gR2V0IHNjb3BlIGZyb20gVjIgZGF0YSBmb3IgcHJvcGVyIG9wZXJhdGlvbiBoYW5kbGluZ1xuICAgICAgICAgIGNvbnN0IHBsdWdpbklkID0gYCR7cGx1Z2luLm5hbWV9QCR7bWFya2V0cGxhY2UubmFtZX1gXG4gICAgICAgICAgY29uc3QgeyBzY29wZSB9ID0gZ2V0UGx1Z2luSW5zdGFsbGF0aW9uRnJvbVYyKHBsdWdpbklkKVxuXG4gICAgICAgICAgY29uc3QgcGx1Z2luU3RhdGU6IFBsdWdpblN0YXRlID0ge1xuICAgICAgICAgICAgcGx1Z2luLFxuICAgICAgICAgICAgbWFya2V0cGxhY2U6IG1hcmtldHBsYWNlLm5hbWUsXG4gICAgICAgICAgICBzY29wZSxcbiAgICAgICAgICAgIHBlbmRpbmdFbmFibGU6IHVuZGVmaW5lZCxcbiAgICAgICAgICAgIHBlbmRpbmdVcGRhdGU6IGZhbHNlLFxuICAgICAgICAgIH1cbiAgICAgICAgICBzZXRTZWxlY3RlZFBsdWdpbihwbHVnaW5TdGF0ZSlcbiAgICAgICAgICBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1kZXRhaWxzJylcbiAgICAgICAgICBwZW5kaW5nQXV0b0FjdGlvblJlZi5jdXJyZW50ID0gYWN0aW9uXG4gICAgICAgICAgaGFzQXV0b05hdmlnYXRlZC5jdXJyZW50ID0gdHJ1ZVxuICAgICAgICAgIHJldHVyblxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIEZhbGwgYmFjayB0byBmYWlsZWQgcGx1Z2lucyAodGhvc2Ugd2l0aCBlcnJvcnMgYnV0IG5vdCBsb2FkZWQpXG4gICAgICBjb25zdCBmYWlsZWRJdGVtID0gdW5pZmllZEl0ZW1zLmZpbmQoXG4gICAgICAgIGl0ZW0gPT4gaXRlbS50eXBlID09PSAnZmFpbGVkLXBsdWdpbicgJiYgaXRlbS5uYW1lID09PSB0YXJnZXROYW1lLFxuICAgICAgKVxuICAgICAgaWYgKGZhaWxlZEl0ZW0gJiYgZmFpbGVkSXRlbS50eXBlID09PSAnZmFpbGVkLXBsdWdpbicpIHtcbiAgICAgICAgc2V0Vmlld1N0YXRlKHtcbiAgICAgICAgICB0eXBlOiAnZmFpbGVkLXBsdWdpbi1kZXRhaWxzJyxcbiAgICAgICAgICBwbHVnaW46IHtcbiAgICAgICAgICAgIGlkOiBmYWlsZWRJdGVtLmlkLFxuICAgICAgICAgICAgbmFtZTogZmFpbGVkSXRlbS5uYW1lLFxuICAgICAgICAgICAgbWFya2V0cGxhY2U6IGZhaWxlZEl0ZW0ubWFya2V0cGxhY2UsXG4gICAgICAgICAgICBlcnJvcnM6IGZhaWxlZEl0ZW0uZXJyb3JzLFxuICAgICAgICAgICAgc2NvcGU6IGZhaWxlZEl0ZW0uc2NvcGUsXG4gICAgICAgICAgfSxcbiAgICAgICAgfSlcbiAgICAgICAgaGFzQXV0b05hdmlnYXRlZC5jdXJyZW50ID0gdHJ1ZVxuICAgICAgfVxuXG4gICAgICAvLyBObyBtYXRjaCBpbiBsb2FkZWQgT1IgZmFpbGVkIHBsdWdpbnMg4oCUIGNsb3NlIHRoZSBkaWFsb2cgd2l0aCBhXG4gICAgICAvLyBtZXNzYWdlIHJhdGhlciB0aGFuIHNpbGVudGx5IGxhbmRpbmcgb24gdGhlIHBsdWdpbiBsaXN0LiBPbmx5IGRvXG4gICAgICAvLyB0aGlzIHdoZW4gYW4gYWN0aW9uIHdhcyByZXF1ZXN0ZWQgKGUuZy4gL3BsdWdpbiB1bmluc3RhbGwgWCk7XG4gICAgICAvLyBwbGFpbiBuYXZpZ2F0aW9uICgvcGx1Z2luIG1hbmFnZSkgc2hvdWxkIHN0aWxsIGp1c3Qgc2hvdyB0aGUgbGlzdC5cbiAgICAgIGlmICghaGFzQXV0b05hdmlnYXRlZC5jdXJyZW50ICYmIGFjdGlvbikge1xuICAgICAgICBoYXNBdXRvTmF2aWdhdGVkLmN1cnJlbnQgPSB0cnVlXG4gICAgICAgIHNldFJlc3VsdChgUGx1Z2luIFwiJHt0YXJnZXRQbHVnaW59XCIgaXMgbm90IGluc3RhbGxlZCBpbiB0aGlzIHByb2plY3RgKVxuICAgICAgfVxuICAgIH1cbiAgfSwgW1xuICAgIHRhcmdldFBsdWdpbixcbiAgICB0YXJnZXRNYXJrZXRwbGFjZSxcbiAgICBtYXJrZXRwbGFjZXMsXG4gICAgbG9hZGluZyxcbiAgICB1bmlmaWVkSXRlbXMsXG4gICAgYWN0aW9uLFxuICAgIHNldFJlc3VsdCxcbiAgXSlcblxuICAvLyBIYW5kbGUgc2luZ2xlIHBsdWdpbiBvcGVyYXRpb25zIGZyb20gZGV0YWlscyB2aWV3XG4gIGNvbnN0IGhhbmRsZVNpbmdsZU9wZXJhdGlvbiA9IGFzeW5jIChcbiAgICBvcGVyYXRpb246ICdlbmFibGUnIHwgJ2Rpc2FibGUnIHwgJ3VwZGF0ZScgfCAndW5pbnN0YWxsJyxcbiAgKSA9PiB7XG4gICAgaWYgKCFzZWxlY3RlZFBsdWdpbikgcmV0dXJuXG5cbiAgICBjb25zdCBwbHVnaW5TY29wZSA9IHNlbGVjdGVkUGx1Z2luLnNjb3BlIHx8ICd1c2VyJ1xuICAgIGNvbnN0IGlzQnVpbHRpbiA9IHBsdWdpblNjb3BlID09PSAnYnVpbHRpbidcblxuICAgIC8vIEJ1aWx0LWluIHBsdWdpbnMgY2FuIG9ubHkgYmUgZW5hYmxlZC9kaXNhYmxlZCwgbm90IHVwZGF0ZWQvdW5pbnN0YWxsZWQuXG4gICAgaWYgKGlzQnVpbHRpbiAmJiAob3BlcmF0aW9uID09PSAndXBkYXRlJyB8fCBvcGVyYXRpb24gPT09ICd1bmluc3RhbGwnKSkge1xuICAgICAgc2V0UHJvY2Vzc0Vycm9yKCdCdWlsdC1pbiBwbHVnaW5zIGNhbm5vdCBiZSB1cGRhdGVkIG9yIHVuaW5zdGFsbGVkLicpXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICAvLyBNYW5hZ2VkIHNjb3BlIHBsdWdpbnMgY2FuIG9ubHkgYmUgdXBkYXRlZCwgbm90IGVuYWJsZWQvZGlzYWJsZWQvdW5pbnN0YWxsZWRcbiAgICBpZiAoXG4gICAgICAhaXNCdWlsdGluICYmXG4gICAgICAhaXNJbnN0YWxsYWJsZVNjb3BlKHBsdWdpblNjb3BlKSAmJlxuICAgICAgb3BlcmF0aW9uICE9PSAndXBkYXRlJ1xuICAgICkge1xuICAgICAgc2V0UHJvY2Vzc0Vycm9yKFxuICAgICAgICAnVGhpcyBwbHVnaW4gaXMgbWFuYWdlZCBieSB5b3VyIG9yZ2FuaXphdGlvbi4gQ29udGFjdCB5b3VyIGFkbWluIHRvIGRpc2FibGUgaXQuJyxcbiAgICAgIClcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIHNldElzUHJvY2Vzc2luZyh0cnVlKVxuICAgIHNldFByb2Nlc3NFcnJvcihudWxsKVxuXG4gICAgdHJ5IHtcbiAgICAgIGNvbnN0IHBsdWdpbklkID0gYCR7c2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm5hbWV9QCR7c2VsZWN0ZWRQbHVnaW4ubWFya2V0cGxhY2V9YFxuICAgICAgbGV0IHJldmVyc2VEZXBlbmRlbnRzOiBzdHJpbmdbXSB8IHVuZGVmaW5lZFxuXG4gICAgICAvLyBlbmFibGUvZGlzYWJsZSBvbWl0IHNjb3BlIOKAlCBwbHVnaW5TY29wZSBpcyB0aGUgaW5zdGFsbCBzY29wZSBmcm9tXG4gICAgICAvLyBpbnN0YWxsZWRfcGx1Z2lucy5qc29uICh3aGVyZSBmaWxlcyBhcmUgY2FjaGVkKSwgd2hpY2ggY2FuIGRpdmVyZ2VcbiAgICAgIC8vIGZyb20gdGhlIHNldHRpbmdzIHNjb3BlICh3aGVyZSBlbmFibGVtZW50IGxpdmVzKS4gUGFzc2luZyBpdCB0cmlwc1xuICAgICAgLy8gdGhlIGNyb3NzLXNjb3BlIGd1YXJkLiBBdXRvLWRldGVjdCBmaW5kcyB0aGUgcmlnaHQgc2NvcGUuICMzODA4NFxuICAgICAgc3dpdGNoIChvcGVyYXRpb24pIHtcbiAgICAgICAgY2FzZSAnZW5hYmxlJzoge1xuICAgICAgICAgIGNvbnN0IGVuYWJsZVJlc3VsdCA9IGF3YWl0IGVuYWJsZVBsdWdpbk9wKHBsdWdpbklkKVxuICAgICAgICAgIGlmICghZW5hYmxlUmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihlbmFibGVSZXN1bHQubWVzc2FnZSlcbiAgICAgICAgICB9XG4gICAgICAgICAgYnJlYWtcbiAgICAgICAgfVxuICAgICAgICBjYXNlICdkaXNhYmxlJzoge1xuICAgICAgICAgIGNvbnN0IGRpc2FibGVSZXN1bHQgPSBhd2FpdCBkaXNhYmxlUGx1Z2luT3AocGx1Z2luSWQpXG4gICAgICAgICAgaWYgKCFkaXNhYmxlUmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihkaXNhYmxlUmVzdWx0Lm1lc3NhZ2UpXG4gICAgICAgICAgfVxuICAgICAgICAgIHJldmVyc2VEZXBlbmRlbnRzID0gZGlzYWJsZVJlc3VsdC5yZXZlcnNlRGVwZW5kZW50c1xuICAgICAgICAgIGJyZWFrXG4gICAgICAgIH1cbiAgICAgICAgY2FzZSAndW5pbnN0YWxsJzoge1xuICAgICAgICAgIGlmIChpc0J1aWx0aW4pIGJyZWFrIC8vIGd1YXJkZWQgYWJvdmU7IG5hcnJvd3MgcGx1Z2luU2NvcGVcbiAgICAgICAgICBpZiAoIWlzSW5zdGFsbGFibGVTY29wZShwbHVnaW5TY29wZSkpIGJyZWFrXG4gICAgICAgICAgLy8gSWYgdGhlIHBsdWdpbiBpcyBlbmFibGVkIGluIC5jbGF1ZGUvc2V0dGluZ3MuanNvbiAoc2hhcmVkIHdpdGggdGhlXG4gICAgICAgICAgLy8gdGVhbSksIGRpdmVydCB0byBhIGNvbmZpcm1hdGlvbiBkaWFsb2cgdGhhdCBvZmZlcnMgdG8gZGlzYWJsZSBpblxuICAgICAgICAgIC8vIHNldHRpbmdzLmxvY2FsLmpzb24gaW5zdGVhZC4gQ2hlY2sgdGhlIHNldHRpbmdzIGZpbGUgZGlyZWN0bHkg4oCUXG4gICAgICAgICAgLy8gYHBsdWdpblNjb3BlYCAoZnJvbSBpbnN0YWxsZWRfcGx1Z2lucy5qc29uKSBjYW4gYmUgJ3VzZXInIGV2ZW4gd2hlblxuICAgICAgICAgIC8vIHRoZSBwbHVnaW4gaXMgQUxTTyBwcm9qZWN0LWVuYWJsZWQsIGFuZCB1bmluc3RhbGxpbmcgdGhlIHVzZXItc2NvcGVcbiAgICAgICAgICAvLyBpbnN0YWxsIHdvdWxkIGxlYXZlIHRoZSBwcm9qZWN0IGVuYWJsZW1lbnQgYWN0aXZlLlxuICAgICAgICAgIGlmIChpc1BsdWdpbkVuYWJsZWRBdFByb2plY3RTY29wZShwbHVnaW5JZCkpIHtcbiAgICAgICAgICAgIHNldElzUHJvY2Vzc2luZyhmYWxzZSlcbiAgICAgICAgICAgIHNldFZpZXdTdGF0ZSgnY29uZmlybS1wcm9qZWN0LXVuaW5zdGFsbCcpXG4gICAgICAgICAgICByZXR1cm5cbiAgICAgICAgICB9XG4gICAgICAgICAgLy8gSWYgdGhlIHBsdWdpbiBoYXMgcGVyc2lzdGVudCBkYXRhICgke0NMQVVERV9QTFVHSU5fREFUQX0pIEFORCB0aGlzXG4gICAgICAgICAgLy8gaXMgdGhlIGxhc3Qgc2NvcGUsIHByb21wdCBiZWZvcmUgZGVsZXRpbmcgaXQuIEZvciBtdWx0aS1zY29wZVxuICAgICAgICAgIC8vIGluc3RhbGxzLCB0aGUgb3AncyBpc0xhc3RTY29wZSBjaGVjayB3b24ndCBkZWxldGUgcmVnYXJkbGVzcyBvZlxuICAgICAgICAgIC8vIHRoZSB1c2VyJ3MgeS9uIOKAlCBzaG93aW5nIHRoZSBkaWFsb2cgd291bGQgbWlzbGVhZCAoXCJ5XCIg4oaSIG5vdGhpbmdcbiAgICAgICAgICAvLyBoYXBwZW5zKS4gTGVuZ3RoIGNoZWNrIG1pcnJvcnMgcGx1Z2luT3BlcmF0aW9ucy50czo1MTMuXG4gICAgICAgICAgY29uc3QgaW5zdGFsbHMgPSBsb2FkSW5zdGFsbGVkUGx1Z2luc1YyKCkucGx1Z2luc1twbHVnaW5JZF1cbiAgICAgICAgICBjb25zdCBpc0xhc3RTY29wZSA9ICFpbnN0YWxscyB8fCBpbnN0YWxscy5sZW5ndGggPD0gMVxuICAgICAgICAgIGNvbnN0IGRhdGFTaXplID0gaXNMYXN0U2NvcGVcbiAgICAgICAgICAgID8gYXdhaXQgZ2V0UGx1Z2luRGF0YURpclNpemUocGx1Z2luSWQpXG4gICAgICAgICAgICA6IG51bGxcbiAgICAgICAgICBpZiAoZGF0YVNpemUpIHtcbiAgICAgICAgICAgIHNldElzUHJvY2Vzc2luZyhmYWxzZSlcbiAgICAgICAgICAgIHNldFZpZXdTdGF0ZSh7IHR5cGU6ICdjb25maXJtLWRhdGEtY2xlYW51cCcsIHNpemU6IGRhdGFTaXplIH0pXG4gICAgICAgICAgICByZXR1cm5cbiAgICAgICAgICB9XG4gICAgICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdW5pbnN0YWxsUGx1Z2luT3AocGx1Z2luSWQsIHBsdWdpblNjb3BlKVxuICAgICAgICAgIGlmICghcmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihyZXN1bHQubWVzc2FnZSlcbiAgICAgICAgICB9XG4gICAgICAgICAgcmV2ZXJzZURlcGVuZGVudHMgPSByZXN1bHQucmV2ZXJzZURlcGVuZGVudHNcbiAgICAgICAgICBicmVha1xuICAgICAgICB9XG4gICAgICAgIGNhc2UgJ3VwZGF0ZSc6IHtcbiAgICAgICAgICBpZiAoaXNCdWlsdGluKSBicmVhayAvLyBndWFyZGVkIGFib3ZlOyBuYXJyb3dzIHBsdWdpblNjb3BlXG4gICAgICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgdXBkYXRlUGx1Z2luT3AocGx1Z2luSWQsIHBsdWdpblNjb3BlKVxuICAgICAgICAgIGlmICghcmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcihyZXN1bHQubWVzc2FnZSlcbiAgICAgICAgICB9XG4gICAgICAgICAgLy8gSWYgYWxyZWFkeSB1cCB0byBkYXRlLCBzaG93IG1lc3NhZ2UgYW5kIGV4aXRcbiAgICAgICAgICBpZiAocmVzdWx0LmFscmVhZHlVcFRvRGF0ZSkge1xuICAgICAgICAgICAgc2V0UmVzdWx0KFxuICAgICAgICAgICAgICBgJHtzZWxlY3RlZFBsdWdpbi5wbHVnaW4ubmFtZX0gaXMgYWxyZWFkeSBhdCB0aGUgbGF0ZXN0IHZlcnNpb24gKCR7cmVzdWx0Lm5ld1ZlcnNpb259KS5gLFxuICAgICAgICAgICAgKVxuICAgICAgICAgICAgaWYgKG9uTWFuYWdlQ29tcGxldGUpIHtcbiAgICAgICAgICAgICAgYXdhaXQgb25NYW5hZ2VDb21wbGV0ZSgpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBzZXRQYXJlbnRWaWV3U3RhdGUoeyB0eXBlOiAnbWVudScgfSlcbiAgICAgICAgICAgIHJldHVyblxuICAgICAgICAgIH1cbiAgICAgICAgICAvLyBTdWNjZXNzIC0gd2lsbCBzaG93IHN0YW5kYXJkIG1lc3NhZ2UgYmVsb3dcbiAgICAgICAgICBicmVha1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIE9wZXJhdGlvbnMgKGVuYWJsZSwgZGlzYWJsZSwgdW5pbnN0YWxsLCB1cGRhdGUpIG5vdyB1c2UgY2VudHJhbGl6ZWQgZnVuY3Rpb25zXG4gICAgICAvLyB0aGF0IGhhbmRsZSB0aGVpciBvd24gc2V0dGluZ3MgdXBkYXRlcywgc28gd2Ugb25seSBuZWVkIHRvIGNsZWFyIGNhY2hlcyBoZXJlXG4gICAgICBjbGVhckFsbENhY2hlcygpXG5cbiAgICAgIC8vIFByb21wdCBmb3IgbWFuaWZlc3QudXNlckNvbmZpZyArIGNoYW5uZWwgdXNlckNvbmZpZyBpZiB0aGUgcGx1Z2luIGVuZHNcbiAgICAgIC8vIHVwIGVuYWJsZWQuIFJlLXJlYWQgc2V0dGluZ3MgcmF0aGVyIHRoYW4ga2V5aW5nIG9uIGBvcGVyYXRpb24gPT09XG4gICAgICAvLyAnZW5hYmxlJ2A6IGluc3RhbGwgZW5hYmxlcyBvbiBpbnN0YWxsLCBzbyB0aGUgbWVudSBzaG93cyBcIkRpc2FibGVcIlxuICAgICAgLy8gZmlyc3QuIFBsdWdpbk9wdGlvbnNGbG93IGl0c2VsZiBjaGVja3MgZ2V0VW5jb25maWd1cmVkT3B0aW9ucyDigJQgaWZcbiAgICAgIC8vIG5vdGhpbmcgbmVlZHMgZmlsbGluZywgaXQgY2FsbHMgb25Eb25lKCdza2lwcGVkJykgaW1tZWRpYXRlbHkuXG4gICAgICBjb25zdCBwbHVnaW5JZE5vdyA9IGAke3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5uYW1lfUAke3NlbGVjdGVkUGx1Z2luLm1hcmtldHBsYWNlfWBcbiAgICAgIGNvbnN0IHNldHRpbmdzQWZ0ZXIgPSBnZXRTZXR0aW5nc19ERVBSRUNBVEVEKClcbiAgICAgIGNvbnN0IGVuYWJsZWRBZnRlciA9XG4gICAgICAgIHNldHRpbmdzQWZ0ZXI/LmVuYWJsZWRQbHVnaW5zPy5bcGx1Z2luSWROb3ddICE9PSBmYWxzZVxuICAgICAgaWYgKGVuYWJsZWRBZnRlcikge1xuICAgICAgICBzZXRJc1Byb2Nlc3NpbmcoZmFsc2UpXG4gICAgICAgIHNldFZpZXdTdGF0ZSh7IHR5cGU6ICdwbHVnaW4tb3B0aW9ucycgfSlcbiAgICAgICAgcmV0dXJuXG4gICAgICB9XG5cbiAgICAgIGNvbnN0IG9wZXJhdGlvbk5hbWUgPVxuICAgICAgICBvcGVyYXRpb24gPT09ICdlbmFibGUnXG4gICAgICAgICAgPyAnRW5hYmxlZCdcbiAgICAgICAgICA6IG9wZXJhdGlvbiA9PT0gJ2Rpc2FibGUnXG4gICAgICAgICAgICA/ICdEaXNhYmxlZCdcbiAgICAgICAgICAgIDogb3BlcmF0aW9uID09PSAndXBkYXRlJ1xuICAgICAgICAgICAgICA/ICdVcGRhdGVkJ1xuICAgICAgICAgICAgICA6ICdVbmluc3RhbGxlZCdcblxuICAgICAgLy8gU2luZ2xlLWxpbmUgd2FybmluZyDigJQgbm90aWZpY2F0aW9uIHRpbWVvdXQgaXMgfjhzLCBtdWx0aS1saW5lIHdvdWxkIHNjcm9sbCBvZmYuXG4gICAgICAvLyBUaGUgcGVyc2lzdGVudCByZWNvcmQgaXMgaW4gdGhlIEVycm9ycyB0YWIgKGRlcGVuZGVuY3ktdW5zYXRpc2ZpZWQgYWZ0ZXIgcmVsb2FkKS5cbiAgICAgIGNvbnN0IGRlcFdhcm4gPVxuICAgICAgICByZXZlcnNlRGVwZW5kZW50cyAmJiByZXZlcnNlRGVwZW5kZW50cy5sZW5ndGggPiAwXG4gICAgICAgICAgPyBgIMK3IHJlcXVpcmVkIGJ5ICR7cmV2ZXJzZURlcGVuZGVudHMuam9pbignLCAnKX1gXG4gICAgICAgICAgOiAnJ1xuICAgICAgY29uc3QgbWVzc2FnZSA9IGDinJMgJHtvcGVyYXRpb25OYW1lfSAke3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5uYW1lfSR7ZGVwV2Fybn0uIFJ1biAvcmVsb2FkLXBsdWdpbnMgdG8gYXBwbHkuYFxuICAgICAgc2V0UmVzdWx0KG1lc3NhZ2UpXG5cbiAgICAgIGlmIChvbk1hbmFnZUNvbXBsZXRlKSB7XG4gICAgICAgIGF3YWl0IG9uTWFuYWdlQ29tcGxldGUoKVxuICAgICAgfVxuXG4gICAgICBzZXRQYXJlbnRWaWV3U3RhdGUoeyB0eXBlOiAnbWVudScgfSlcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgc2V0SXNQcm9jZXNzaW5nKGZhbHNlKVxuICAgICAgY29uc3QgZXJyb3JNZXNzYWdlID1cbiAgICAgICAgZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpXG4gICAgICBzZXRQcm9jZXNzRXJyb3IoYEZhaWxlZCB0byAke29wZXJhdGlvbn06ICR7ZXJyb3JNZXNzYWdlfWApXG4gICAgICBsb2dFcnJvcih0b0Vycm9yKGVycm9yKSlcbiAgICB9XG4gIH1cblxuICAvLyBMYXRlc3QtcmVmOiBsZXRzIHRoZSBhdXRvLWFjdGlvbiBlZmZlY3QgY2FsbCB0aGUgY3VycmVudCBjbG9zdXJlIHdpdGhvdXRcbiAgLy8gYWRkaW5nIGhhbmRsZVNpbmdsZU9wZXJhdGlvbiAocmVjcmVhdGVkIGV2ZXJ5IHJlbmRlcikgdG8gaXRzIGRlcHMuXG4gIGNvbnN0IGhhbmRsZVNpbmdsZU9wZXJhdGlvblJlZiA9IHVzZVJlZihoYW5kbGVTaW5nbGVPcGVyYXRpb24pXG4gIGhhbmRsZVNpbmdsZU9wZXJhdGlvblJlZi5jdXJyZW50ID0gaGFuZGxlU2luZ2xlT3BlcmF0aW9uXG5cbiAgLy8gQXV0by1leGVjdXRlIHRoZSBhY3Rpb24gcHJvcCAoL3BsdWdpbiB1bmluc3RhbGwgWCwgL3BsdWdpbiBlbmFibGUgWCwgZXRjLilcbiAgLy8gb25jZSBhdXRvLW5hdmlnYXRpb24gaGFzIGxhbmRlZCBvbiBwbHVnaW4tZGV0YWlscy5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICBpZiAoXG4gICAgICB2aWV3U3RhdGUgPT09ICdwbHVnaW4tZGV0YWlscycgJiZcbiAgICAgIHNlbGVjdGVkUGx1Z2luICYmXG4gICAgICBwZW5kaW5nQXV0b0FjdGlvblJlZi5jdXJyZW50XG4gICAgKSB7XG4gICAgICBjb25zdCBwZW5kaW5nID0gcGVuZGluZ0F1dG9BY3Rpb25SZWYuY3VycmVudFxuICAgICAgcGVuZGluZ0F1dG9BY3Rpb25SZWYuY3VycmVudCA9IHVuZGVmaW5lZFxuICAgICAgdm9pZCBoYW5kbGVTaW5nbGVPcGVyYXRpb25SZWYuY3VycmVudChwZW5kaW5nKVxuICAgIH1cbiAgfSwgW3ZpZXdTdGF0ZSwgc2VsZWN0ZWRQbHVnaW5dKVxuXG4gIC8vIEhhbmRsZSB0b2dnbGUgZW5hYmxlL2Rpc2FibGVcbiAgY29uc3QgaGFuZGxlVG9nZ2xlID0gUmVhY3QudXNlQ2FsbGJhY2soKCkgPT4ge1xuICAgIGlmIChzZWxlY3RlZEluZGV4ID49IGZpbHRlcmVkSXRlbXMubGVuZ3RoKSByZXR1cm5cbiAgICBjb25zdCBpdGVtID0gZmlsdGVyZWRJdGVtc1tzZWxlY3RlZEluZGV4XVxuICAgIGlmIChpdGVtPy50eXBlID09PSAnZmxhZ2dlZC1wbHVnaW4nKSByZXR1cm5cbiAgICBpZiAoaXRlbT8udHlwZSA9PT0gJ3BsdWdpbicpIHtcbiAgICAgIGNvbnN0IHBsdWdpbklkID0gYCR7aXRlbS5wbHVnaW4ubmFtZX1AJHtpdGVtLm1hcmtldHBsYWNlfWBcbiAgICAgIGNvbnN0IG1lcmdlZFNldHRpbmdzID0gZ2V0U2V0dGluZ3NfREVQUkVDQVRFRCgpXG4gICAgICBjb25zdCBjdXJyZW50UGVuZGluZyA9IHBlbmRpbmdUb2dnbGVzLmdldChwbHVnaW5JZClcbiAgICAgIGNvbnN0IGlzRW5hYmxlZCA9IG1lcmdlZFNldHRpbmdzPy5lbmFibGVkUGx1Z2lucz8uW3BsdWdpbklkXSAhPT0gZmFsc2VcbiAgICAgIGNvbnN0IHBsdWdpblNjb3BlID0gaXRlbS5zY29wZVxuICAgICAgY29uc3QgaXNCdWlsdGluID0gcGx1Z2luU2NvcGUgPT09ICdidWlsdGluJ1xuICAgICAgaWYgKGlzQnVpbHRpbiB8fCBpc0luc3RhbGxhYmxlU2NvcGUocGx1Z2luU2NvcGUpKSB7XG4gICAgICAgIGNvbnN0IG5ld1BlbmRpbmcgPSBuZXcgTWFwKHBlbmRpbmdUb2dnbGVzKVxuICAgICAgICAvLyBPbWl0IHNjb3BlIOKAlCBzZWUgaGFuZGxlU2luZ2xlT3BlcmF0aW9uJ3MgZW5hYmxlL2Rpc2FibGUgY29tbWVudC5cbiAgICAgICAgaWYgKGN1cnJlbnRQZW5kaW5nKSB7XG4gICAgICAgICAgLy8gQ2FuY2VsOiByZXZlcnNlIHRoZSBvcGVyYXRpb24gYmFjayB0byB0aGUgb3JpZ2luYWwgc3RhdGVcbiAgICAgICAgICBuZXdQZW5kaW5nLmRlbGV0ZShwbHVnaW5JZClcbiAgICAgICAgICB2b2lkIChhc3luYyAoKSA9PiB7XG4gICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICBpZiAoY3VycmVudFBlbmRpbmcgPT09ICd3aWxsLWRpc2FibGUnKSB7XG4gICAgICAgICAgICAgICAgYXdhaXQgZW5hYmxlUGx1Z2luT3AocGx1Z2luSWQpXG4gICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgYXdhaXQgZGlzYWJsZVBsdWdpbk9wKHBsdWdpbklkKVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIGNsZWFyQWxsQ2FjaGVzKClcbiAgICAgICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgICBsb2dFcnJvcihlcnIpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSkoKVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIG5ld1BlbmRpbmcuc2V0KHBsdWdpbklkLCBpc0VuYWJsZWQgPyAnd2lsbC1kaXNhYmxlJyA6ICd3aWxsLWVuYWJsZScpXG4gICAgICAgICAgdm9pZCAoYXN5bmMgKCkgPT4ge1xuICAgICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgICAgaWYgKGlzRW5hYmxlZCkge1xuICAgICAgICAgICAgICAgIGF3YWl0IGRpc2FibGVQbHVnaW5PcChwbHVnaW5JZClcbiAgICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICBhd2FpdCBlbmFibGVQbHVnaW5PcChwbHVnaW5JZClcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICBjbGVhckFsbENhY2hlcygpXG4gICAgICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgICAgbG9nRXJyb3IoZXJyKVxuICAgICAgICAgICAgfVxuICAgICAgICAgIH0pKClcbiAgICAgICAgfVxuICAgICAgICBzZXRQZW5kaW5nVG9nZ2xlcyhuZXdQZW5kaW5nKVxuICAgICAgfVxuICAgIH0gZWxzZSBpZiAoaXRlbT8udHlwZSA9PT0gJ21jcCcpIHtcbiAgICAgIHZvaWQgdG9nZ2xlTWNwU2VydmVyKGl0ZW0uY2xpZW50Lm5hbWUpXG4gICAgfVxuICB9LCBbXG4gICAgc2VsZWN0ZWRJbmRleCxcbiAgICBmaWx0ZXJlZEl0ZW1zLFxuICAgIHBlbmRpbmdUb2dnbGVzLFxuICAgIHBsdWdpblN0YXRlcyxcbiAgICB0b2dnbGVNY3BTZXJ2ZXIsXG4gIF0pXG5cbiAgLy8gSGFuZGxlIGFjY2VwdCAoRW50ZXIpIGluIHBsdWdpbi1saXN0XG4gIGNvbnN0IGhhbmRsZUFjY2VwdCA9IFJlYWN0LnVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICBpZiAoc2VsZWN0ZWRJbmRleCA+PSBmaWx0ZXJlZEl0ZW1zLmxlbmd0aCkgcmV0dXJuXG4gICAgY29uc3QgaXRlbSA9IGZpbHRlcmVkSXRlbXNbc2VsZWN0ZWRJbmRleF1cbiAgICBpZiAoaXRlbT8udHlwZSA9PT0gJ3BsdWdpbicpIHtcbiAgICAgIGNvbnN0IHN0YXRlID0gcGx1Z2luU3RhdGVzLmZpbmQoXG4gICAgICAgIHMgPT5cbiAgICAgICAgICBzLnBsdWdpbi5uYW1lID09PSBpdGVtLnBsdWdpbi5uYW1lICYmXG4gICAgICAgICAgcy5tYXJrZXRwbGFjZSA9PT0gaXRlbS5tYXJrZXRwbGFjZSxcbiAgICAgIClcbiAgICAgIGlmIChzdGF0ZSkge1xuICAgICAgICBzZXRTZWxlY3RlZFBsdWdpbihzdGF0ZSlcbiAgICAgICAgc2V0Vmlld1N0YXRlKCdwbHVnaW4tZGV0YWlscycpXG4gICAgICAgIHNldERldGFpbHNNZW51SW5kZXgoMClcbiAgICAgICAgc2V0UHJvY2Vzc0Vycm9yKG51bGwpXG4gICAgICB9XG4gICAgfSBlbHNlIGlmIChpdGVtPy50eXBlID09PSAnZmxhZ2dlZC1wbHVnaW4nKSB7XG4gICAgICBzZXRWaWV3U3RhdGUoe1xuICAgICAgICB0eXBlOiAnZmxhZ2dlZC1kZXRhaWwnLFxuICAgICAgICBwbHVnaW46IHtcbiAgICAgICAgICBpZDogaXRlbS5pZCxcbiAgICAgICAgICBuYW1lOiBpdGVtLm5hbWUsXG4gICAgICAgICAgbWFya2V0cGxhY2U6IGl0ZW0ubWFya2V0cGxhY2UsXG4gICAgICAgICAgcmVhc29uOiBpdGVtLnJlYXNvbixcbiAgICAgICAgICB0ZXh0OiBpdGVtLnRleHQsXG4gICAgICAgICAgZmxhZ2dlZEF0OiBpdGVtLmZsYWdnZWRBdCxcbiAgICAgICAgfSxcbiAgICAgIH0pXG4gICAgICBzZXRQcm9jZXNzRXJyb3IobnVsbClcbiAgICB9IGVsc2UgaWYgKGl0ZW0/LnR5cGUgPT09ICdmYWlsZWQtcGx1Z2luJykge1xuICAgICAgc2V0Vmlld1N0YXRlKHtcbiAgICAgICAgdHlwZTogJ2ZhaWxlZC1wbHVnaW4tZGV0YWlscycsXG4gICAgICAgIHBsdWdpbjoge1xuICAgICAgICAgIGlkOiBpdGVtLmlkLFxuICAgICAgICAgIG5hbWU6IGl0ZW0ubmFtZSxcbiAgICAgICAgICBtYXJrZXRwbGFjZTogaXRlbS5tYXJrZXRwbGFjZSxcbiAgICAgICAgICBlcnJvcnM6IGl0ZW0uZXJyb3JzLFxuICAgICAgICAgIHNjb3BlOiBpdGVtLnNjb3BlLFxuICAgICAgICB9LFxuICAgICAgfSlcbiAgICAgIHNldERldGFpbHNNZW51SW5kZXgoMClcbiAgICAgIHNldFByb2Nlc3NFcnJvcihudWxsKVxuICAgIH0gZWxzZSBpZiAoaXRlbT8udHlwZSA9PT0gJ21jcCcpIHtcbiAgICAgIHNldFZpZXdTdGF0ZSh7IHR5cGU6ICdtY3AtZGV0YWlsJywgY2xpZW50OiBpdGVtLmNsaWVudCB9KVxuICAgICAgc2V0UHJvY2Vzc0Vycm9yKG51bGwpXG4gICAgfVxuICB9LCBbc2VsZWN0ZWRJbmRleCwgZmlsdGVyZWRJdGVtcywgcGx1Z2luU3RhdGVzXSlcblxuICAvLyBQbHVnaW4tbGlzdCBuYXZpZ2F0aW9uIChub24tc2VhcmNoIG1vZGUpXG4gIHVzZUtleWJpbmRpbmdzKFxuICAgIHtcbiAgICAgICdzZWxlY3Q6cHJldmlvdXMnOiAoKSA9PiB7XG4gICAgICAgIGlmIChzZWxlY3RlZEluZGV4ID09PSAwKSB7XG4gICAgICAgICAgc2V0SXNTZWFyY2hNb2RlKHRydWUpXG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcGFnaW5hdGlvbi5oYW5kbGVTZWxlY3Rpb25DaGFuZ2Uoc2VsZWN0ZWRJbmRleCAtIDEsIHNldFNlbGVjdGVkSW5kZXgpXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICAnc2VsZWN0Om5leHQnOiAoKSA9PiB7XG4gICAgICAgIGlmIChzZWxlY3RlZEluZGV4IDwgZmlsdGVyZWRJdGVtcy5sZW5ndGggLSAxKSB7XG4gICAgICAgICAgcGFnaW5hdGlvbi5oYW5kbGVTZWxlY3Rpb25DaGFuZ2Uoc2VsZWN0ZWRJbmRleCArIDEsIHNldFNlbGVjdGVkSW5kZXgpXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICAnc2VsZWN0OmFjY2VwdCc6IGhhbmRsZUFjY2VwdCxcbiAgICB9LFxuICAgIHtcbiAgICAgIGNvbnRleHQ6ICdTZWxlY3QnLFxuICAgICAgaXNBY3RpdmU6IHZpZXdTdGF0ZSA9PT0gJ3BsdWdpbi1saXN0JyAmJiAhaXNTZWFyY2hNb2RlLFxuICAgIH0sXG4gIClcblxuICB1c2VLZXliaW5kaW5ncyhcbiAgICB7ICdwbHVnaW46dG9nZ2xlJzogaGFuZGxlVG9nZ2xlIH0sXG4gICAge1xuICAgICAgY29udGV4dDogJ1BsdWdpbicsXG4gICAgICBpc0FjdGl2ZTogdmlld1N0YXRlID09PSAncGx1Z2luLWxpc3QnICYmICFpc1NlYXJjaE1vZGUsXG4gICAgfSxcbiAgKVxuXG4gIC8vIEhhbmRsZSBkaXNtaXNzIGFjdGlvbiBpbiBmbGFnZ2VkLWRldGFpbCB2aWV3XG4gIGNvbnN0IGhhbmRsZUZsYWdnZWREaXNtaXNzID0gUmVhY3QudXNlQ2FsbGJhY2soKCkgPT4ge1xuICAgIGlmICh0eXBlb2Ygdmlld1N0YXRlICE9PSAnb2JqZWN0JyB8fCB2aWV3U3RhdGUudHlwZSAhPT0gJ2ZsYWdnZWQtZGV0YWlsJylcbiAgICAgIHJldHVyblxuICAgIHZvaWQgcmVtb3ZlRmxhZ2dlZFBsdWdpbih2aWV3U3RhdGUucGx1Z2luLmlkKVxuICAgIHNldFZpZXdTdGF0ZSgncGx1Z2luLWxpc3QnKVxuICB9LCBbdmlld1N0YXRlXSlcblxuICB1c2VLZXliaW5kaW5ncyhcbiAgICB7ICdzZWxlY3Q6YWNjZXB0JzogaGFuZGxlRmxhZ2dlZERpc21pc3MgfSxcbiAgICB7XG4gICAgICBjb250ZXh0OiAnU2VsZWN0JyxcbiAgICAgIGlzQWN0aXZlOlxuICAgICAgICB0eXBlb2Ygdmlld1N0YXRlID09PSAnb2JqZWN0JyAmJiB2aWV3U3RhdGUudHlwZSA9PT0gJ2ZsYWdnZWQtZGV0YWlsJyxcbiAgICB9LFxuICApXG5cbiAgLy8gQnVpbGQgZGV0YWlscyBtZW51IGl0ZW1zIChuZWVkZWQgZm9yIG5hdmlnYXRpb24pXG4gIGNvbnN0IGRldGFpbHNNZW51SXRlbXMgPSBSZWFjdC51c2VNZW1vKCgpID0+IHtcbiAgICBpZiAodmlld1N0YXRlICE9PSAncGx1Z2luLWRldGFpbHMnIHx8ICFzZWxlY3RlZFBsdWdpbikgcmV0dXJuIFtdXG5cbiAgICBjb25zdCBtZXJnZWRTZXR0aW5ncyA9IGdldFNldHRpbmdzX0RFUFJFQ0FURUQoKVxuICAgIGNvbnN0IHBsdWdpbklkID0gYCR7c2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm5hbWV9QCR7c2VsZWN0ZWRQbHVnaW4ubWFya2V0cGxhY2V9YFxuICAgIGNvbnN0IGlzRW5hYmxlZCA9IG1lcmdlZFNldHRpbmdzPy5lbmFibGVkUGx1Z2lucz8uW3BsdWdpbklkXSAhPT0gZmFsc2VcbiAgICBjb25zdCBpc0J1aWx0aW4gPSBzZWxlY3RlZFBsdWdpbi5tYXJrZXRwbGFjZSA9PT0gJ2J1aWx0aW4nXG5cbiAgICBjb25zdCBtZW51SXRlbXM6IEFycmF5PHsgbGFiZWw6IHN0cmluZzsgYWN0aW9uOiAoKSA9PiB2b2lkIH0+ID0gW11cblxuICAgIG1lbnVJdGVtcy5wdXNoKHtcbiAgICAgIGxhYmVsOiBpc0VuYWJsZWQgPyAnRGlzYWJsZSBwbHVnaW4nIDogJ0VuYWJsZSBwbHVnaW4nLFxuICAgICAgYWN0aW9uOiAoKSA9PlxuICAgICAgICB2b2lkIGhhbmRsZVNpbmdsZU9wZXJhdGlvbihpc0VuYWJsZWQgPyAnZGlzYWJsZScgOiAnZW5hYmxlJyksXG4gICAgfSlcblxuICAgIC8vIFVwZGF0ZS9Vbmluc3RhbGwgb3B0aW9ucyDigJQgbm90IGF2YWlsYWJsZSBmb3IgYnVpbHQtaW4gcGx1Z2luc1xuICAgIGlmICghaXNCdWlsdGluKSB7XG4gICAgICBtZW51SXRlbXMucHVzaCh7XG4gICAgICAgIGxhYmVsOiBzZWxlY3RlZFBsdWdpbi5wZW5kaW5nVXBkYXRlXG4gICAgICAgICAgPyAnVW5tYXJrIGZvciB1cGRhdGUnXG4gICAgICAgICAgOiAnTWFyayBmb3IgdXBkYXRlJyxcbiAgICAgICAgYWN0aW9uOiBhc3luYyAoKSA9PiB7XG4gICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGNvbnN0IGxvY2FsRXJyb3IgPSBhd2FpdCBjaGVja0lmTG9jYWxQbHVnaW4oXG4gICAgICAgICAgICAgIHNlbGVjdGVkUGx1Z2luLnBsdWdpbi5uYW1lLFxuICAgICAgICAgICAgICBzZWxlY3RlZFBsdWdpbi5tYXJrZXRwbGFjZSxcbiAgICAgICAgICAgIClcblxuICAgICAgICAgICAgaWYgKGxvY2FsRXJyb3IpIHtcbiAgICAgICAgICAgICAgc2V0UHJvY2Vzc0Vycm9yKGxvY2FsRXJyb3IpXG4gICAgICAgICAgICAgIHJldHVyblxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjb25zdCBuZXdTdGF0ZXMgPSBbLi4ucGx1Z2luU3RhdGVzXVxuICAgICAgICAgICAgY29uc3QgaW5kZXggPSBuZXdTdGF0ZXMuZmluZEluZGV4KFxuICAgICAgICAgICAgICBzID0+XG4gICAgICAgICAgICAgICAgcy5wbHVnaW4ubmFtZSA9PT0gc2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm5hbWUgJiZcbiAgICAgICAgICAgICAgICBzLm1hcmtldHBsYWNlID09PSBzZWxlY3RlZFBsdWdpbi5tYXJrZXRwbGFjZSxcbiAgICAgICAgICAgIClcbiAgICAgICAgICAgIGlmIChpbmRleCAhPT0gLTEpIHtcbiAgICAgICAgICAgICAgbmV3U3RhdGVzW2luZGV4XSEucGVuZGluZ1VwZGF0ZSA9ICFzZWxlY3RlZFBsdWdpbi5wZW5kaW5nVXBkYXRlXG4gICAgICAgICAgICAgIHNldFBsdWdpblN0YXRlcyhuZXdTdGF0ZXMpXG4gICAgICAgICAgICAgIHNldFNlbGVjdGVkUGx1Z2luKHtcbiAgICAgICAgICAgICAgICAuLi5zZWxlY3RlZFBsdWdpbixcbiAgICAgICAgICAgICAgICBwZW5kaW5nVXBkYXRlOiAhc2VsZWN0ZWRQbHVnaW4ucGVuZGluZ1VwZGF0ZSxcbiAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgc2V0UHJvY2Vzc0Vycm9yKFxuICAgICAgICAgICAgICBlcnJvciBpbnN0YW5jZW9mIEVycm9yXG4gICAgICAgICAgICAgICAgPyBlcnJvci5tZXNzYWdlXG4gICAgICAgICAgICAgICAgOiAnRmFpbGVkIHRvIGNoZWNrIHBsdWdpbiB1cGRhdGUgYXZhaWxhYmlsaXR5JyxcbiAgICAgICAgICAgIClcbiAgICAgICAgICB9XG4gICAgICAgIH0sXG4gICAgICB9KVxuXG4gICAgICBpZiAoc2VsZWN0ZWRQbHVnaW5IYXNNY3BiKSB7XG4gICAgICAgIG1lbnVJdGVtcy5wdXNoKHtcbiAgICAgICAgICBsYWJlbDogJ0NvbmZpZ3VyZScsXG4gICAgICAgICAgYWN0aW9uOiBhc3luYyAoKSA9PiB7XG4gICAgICAgICAgICBzZXRJc0xvYWRpbmdDb25maWcodHJ1ZSlcbiAgICAgICAgICAgIHRyeSB7XG4gICAgICAgICAgICAgIGNvbnN0IG1jcFNlcnZlcnNTcGVjID0gc2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm1hbmlmZXN0Lm1jcFNlcnZlcnNcblxuICAgICAgICAgICAgICBsZXQgbWNwYlBhdGg6IHN0cmluZyB8IG51bGwgPSBudWxsXG4gICAgICAgICAgICAgIGlmIChcbiAgICAgICAgICAgICAgICB0eXBlb2YgbWNwU2VydmVyc1NwZWMgPT09ICdzdHJpbmcnICYmXG4gICAgICAgICAgICAgICAgaXNNY3BiU291cmNlKG1jcFNlcnZlcnNTcGVjKVxuICAgICAgICAgICAgICApIHtcbiAgICAgICAgICAgICAgICBtY3BiUGF0aCA9IG1jcFNlcnZlcnNTcGVjXG4gICAgICAgICAgICAgIH0gZWxzZSBpZiAoQXJyYXkuaXNBcnJheShtY3BTZXJ2ZXJzU3BlYykpIHtcbiAgICAgICAgICAgICAgICBmb3IgKGNvbnN0IHNwZWMgb2YgbWNwU2VydmVyc1NwZWMpIHtcbiAgICAgICAgICAgICAgICAgIGlmICh0eXBlb2Ygc3BlYyA9PT0gJ3N0cmluZycgJiYgaXNNY3BiU291cmNlKHNwZWMpKSB7XG4gICAgICAgICAgICAgICAgICAgIG1jcGJQYXRoID0gc3BlY1xuICAgICAgICAgICAgICAgICAgICBicmVha1xuICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgIGlmICghbWNwYlBhdGgpIHtcbiAgICAgICAgICAgICAgICBzZXRQcm9jZXNzRXJyb3IoJ05vIE1DUEIgZmlsZSBmb3VuZCBpbiBwbHVnaW4nKVxuICAgICAgICAgICAgICAgIHNldElzTG9hZGluZ0NvbmZpZyhmYWxzZSlcbiAgICAgICAgICAgICAgICByZXR1cm5cbiAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgIGNvbnN0IHBsdWdpbklkID0gYCR7c2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm5hbWV9QCR7c2VsZWN0ZWRQbHVnaW4ubWFya2V0cGxhY2V9YFxuICAgICAgICAgICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBsb2FkTWNwYkZpbGUoXG4gICAgICAgICAgICAgICAgbWNwYlBhdGgsXG4gICAgICAgICAgICAgICAgc2VsZWN0ZWRQbHVnaW4ucGx1Z2luLnBhdGgsXG4gICAgICAgICAgICAgICAgcGx1Z2luSWQsXG4gICAgICAgICAgICAgICAgdW5kZWZpbmVkLFxuICAgICAgICAgICAgICAgIHVuZGVmaW5lZCxcbiAgICAgICAgICAgICAgICB0cnVlLFxuICAgICAgICAgICAgICApXG5cbiAgICAgICAgICAgICAgaWYgKCdzdGF0dXMnIGluIHJlc3VsdCAmJiByZXN1bHQuc3RhdHVzID09PSAnbmVlZHMtY29uZmlnJykge1xuICAgICAgICAgICAgICAgIHNldENvbmZpZ05lZWRlZChyZXN1bHQpXG4gICAgICAgICAgICAgICAgc2V0Vmlld1N0YXRlKCdjb25maWd1cmluZycpXG4gICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgc2V0UHJvY2Vzc0Vycm9yKCdGYWlsZWQgdG8gbG9hZCBNQ1BCIGZvciBjb25maWd1cmF0aW9uJylcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgICAgICAgIGNvbnN0IGVycm9yTXNnID0gZXJyb3JNZXNzYWdlKGVycilcbiAgICAgICAgICAgICAgc2V0UHJvY2Vzc0Vycm9yKGBGYWlsZWQgdG8gbG9hZCBjb25maWd1cmF0aW9uOiAke2Vycm9yTXNnfWApXG4gICAgICAgICAgICB9IGZpbmFsbHkge1xuICAgICAgICAgICAgICBzZXRJc0xvYWRpbmdDb25maWcoZmFsc2UpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgfSxcbiAgICAgICAgfSlcbiAgICAgIH1cblxuICAgICAgaWYgKFxuICAgICAgICBzZWxlY3RlZFBsdWdpbi5wbHVnaW4ubWFuaWZlc3QudXNlckNvbmZpZyAmJlxuICAgICAgICBPYmplY3Qua2V5cyhzZWxlY3RlZFBsdWdpbi5wbHVnaW4ubWFuaWZlc3QudXNlckNvbmZpZykubGVuZ3RoID4gMFxuICAgICAgKSB7XG4gICAgICAgIG1lbnVJdGVtcy5wdXNoKHtcbiAgICAgICAgICBsYWJlbDogJ0NvbmZpZ3VyZSBvcHRpb25zJyxcbiAgICAgICAgICBhY3Rpb246ICgpID0+IHtcbiAgICAgICAgICAgIHNldFZpZXdTdGF0ZSh7XG4gICAgICAgICAgICAgIHR5cGU6ICdjb25maWd1cmluZy1vcHRpb25zJyxcbiAgICAgICAgICAgICAgc2NoZW1hOiBzZWxlY3RlZFBsdWdpbi5wbHVnaW4ubWFuaWZlc3QudXNlckNvbmZpZyEsXG4gICAgICAgICAgICB9KVxuICAgICAgICAgIH0sXG4gICAgICAgIH0pXG4gICAgICB9XG5cbiAgICAgIG1lbnVJdGVtcy5wdXNoKHtcbiAgICAgICAgbGFiZWw6ICdVcGRhdGUgbm93JyxcbiAgICAgICAgYWN0aW9uOiAoKSA9PiB2b2lkIGhhbmRsZVNpbmdsZU9wZXJhdGlvbigndXBkYXRlJyksXG4gICAgICB9KVxuXG4gICAgICBtZW51SXRlbXMucHVzaCh7XG4gICAgICAgIGxhYmVsOiAnVW5pbnN0YWxsJyxcbiAgICAgICAgYWN0aW9uOiAoKSA9PiB2b2lkIGhhbmRsZVNpbmdsZU9wZXJhdGlvbigndW5pbnN0YWxsJyksXG4gICAgICB9KVxuICAgIH1cblxuICAgIGlmIChzZWxlY3RlZFBsdWdpbi5wbHVnaW4ubWFuaWZlc3QuaG9tZXBhZ2UpIHtcbiAgICAgIG1lbnVJdGVtcy5wdXNoKHtcbiAgICAgICAgbGFiZWw6ICdPcGVuIGhvbWVwYWdlJyxcbiAgICAgICAgYWN0aW9uOiAoKSA9PlxuICAgICAgICAgIHZvaWQgb3BlbkJyb3dzZXIoc2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm1hbmlmZXN0LmhvbWVwYWdlISksXG4gICAgICB9KVxuICAgIH1cblxuICAgIGlmIChzZWxlY3RlZFBsdWdpbi5wbHVnaW4ubWFuaWZlc3QucmVwb3NpdG9yeSkge1xuICAgICAgbWVudUl0ZW1zLnB1c2goe1xuICAgICAgICAvLyBHZW5lcmljIGxhYmVsIOKAlCBtYW5pZmVzdC5yZXBvc2l0b3J5IGNhbiBiZSBHaXRMYWIsIEJpdGJ1Y2tldCxcbiAgICAgICAgLy8gQXp1cmUgRGV2T3BzLCBldGMuIChnaC0zMTU5OCkuIHBsdWdpbkRldGFpbHNIZWxwZXJzLnRzeDo3NCBrZWVwc1xuICAgICAgICAvLyAnVmlldyBvbiBHaXRIdWInIGJlY2F1c2UgdGhhdCBwYXRoIGhhcyBhbiBleHBsaWNpdCBpc0dpdEh1YiBjaGVjay5cbiAgICAgICAgbGFiZWw6ICdWaWV3IHJlcG9zaXRvcnknLFxuICAgICAgICBhY3Rpb246ICgpID0+XG4gICAgICAgICAgdm9pZCBvcGVuQnJvd3NlcihzZWxlY3RlZFBsdWdpbi5wbHVnaW4ubWFuaWZlc3QucmVwb3NpdG9yeSEpLFxuICAgICAgfSlcbiAgICB9XG5cbiAgICBtZW51SXRlbXMucHVzaCh7XG4gICAgICBsYWJlbDogJ0JhY2sgdG8gcGx1Z2luIGxpc3QnLFxuICAgICAgYWN0aW9uOiAoKSA9PiB7XG4gICAgICAgIHNldFZpZXdTdGF0ZSgncGx1Z2luLWxpc3QnKVxuICAgICAgICBzZXRTZWxlY3RlZFBsdWdpbihudWxsKVxuICAgICAgICBzZXRQcm9jZXNzRXJyb3IobnVsbClcbiAgICAgIH0sXG4gICAgfSlcblxuICAgIHJldHVybiBtZW51SXRlbXNcbiAgfSwgW3ZpZXdTdGF0ZSwgc2VsZWN0ZWRQbHVnaW4sIHNlbGVjdGVkUGx1Z2luSGFzTWNwYiwgcGx1Z2luU3RhdGVzXSlcblxuICAvLyBQbHVnaW4tZGV0YWlscyBuYXZpZ2F0aW9uXG4gIHVzZUtleWJpbmRpbmdzKFxuICAgIHtcbiAgICAgICdzZWxlY3Q6cHJldmlvdXMnOiAoKSA9PiB7XG4gICAgICAgIGlmIChkZXRhaWxzTWVudUluZGV4ID4gMCkge1xuICAgICAgICAgIHNldERldGFpbHNNZW51SW5kZXgoZGV0YWlsc01lbnVJbmRleCAtIDEpXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICAnc2VsZWN0Om5leHQnOiAoKSA9PiB7XG4gICAgICAgIGlmIChkZXRhaWxzTWVudUluZGV4IDwgZGV0YWlsc01lbnVJdGVtcy5sZW5ndGggLSAxKSB7XG4gICAgICAgICAgc2V0RGV0YWlsc01lbnVJbmRleChkZXRhaWxzTWVudUluZGV4ICsgMSlcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgICdzZWxlY3Q6YWNjZXB0JzogKCkgPT4ge1xuICAgICAgICBpZiAoZGV0YWlsc01lbnVJdGVtc1tkZXRhaWxzTWVudUluZGV4XSkge1xuICAgICAgICAgIGRldGFpbHNNZW51SXRlbXNbZGV0YWlsc01lbnVJbmRleF0hLmFjdGlvbigpXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfSxcbiAgICB7XG4gICAgICBjb250ZXh0OiAnU2VsZWN0JyxcbiAgICAgIGlzQWN0aXZlOiB2aWV3U3RhdGUgPT09ICdwbHVnaW4tZGV0YWlscycgJiYgISFzZWxlY3RlZFBsdWdpbixcbiAgICB9LFxuICApXG5cbiAgLy8gRmFpbGVkLXBsdWdpbi1kZXRhaWxzOiBvbmx5IFwiVW5pbnN0YWxsXCIgb3B0aW9uLCBoYW5kbGUgRW50ZXJcbiAgdXNlS2V5YmluZGluZ3MoXG4gICAge1xuICAgICAgJ3NlbGVjdDphY2NlcHQnOiAoKSA9PiB7XG4gICAgICAgIGlmIChcbiAgICAgICAgICB0eXBlb2Ygdmlld1N0YXRlID09PSAnb2JqZWN0JyAmJlxuICAgICAgICAgIHZpZXdTdGF0ZS50eXBlID09PSAnZmFpbGVkLXBsdWdpbi1kZXRhaWxzJ1xuICAgICAgICApIHtcbiAgICAgICAgICB2b2lkIChhc3luYyAoKSA9PiB7XG4gICAgICAgICAgICBzZXRJc1Byb2Nlc3NpbmcodHJ1ZSlcbiAgICAgICAgICAgIHNldFByb2Nlc3NFcnJvcihudWxsKVxuICAgICAgICAgICAgY29uc3QgcGx1Z2luSWQgPSB2aWV3U3RhdGUucGx1Z2luLmlkXG4gICAgICAgICAgICBjb25zdCBwbHVnaW5TY29wZSA9IHZpZXdTdGF0ZS5wbHVnaW4uc2NvcGVcbiAgICAgICAgICAgIC8vIFBhc3Mgc2NvcGUgdG8gdW5pbnN0YWxsUGx1Z2luT3Agc28gaXQgY2FuIGZpbmQgdGhlIGNvcnJlY3QgVjJcbiAgICAgICAgICAgIC8vIGluc3RhbGxhdGlvbiByZWNvcmQgYW5kIGNsZWFuIHVwIG9uLWRpc2sgZmlsZXMuIEZhbGwgYmFjayB0b1xuICAgICAgICAgICAgLy8gZGVmYXVsdCBzY29wZSBpZiBub3QgaW5zdGFsbGFibGUgKGUuZy4gJ21hbmFnZWQnLCB0aG91Z2ggdGhhdFxuICAgICAgICAgICAgLy8gY2FzZSBpcyBndWFyZGVkIGJ5IGlzQWN0aXZlIGJlbG93KS4gZGVsZXRlRGF0YURpcj1mYWxzZTogdGhpc1xuICAgICAgICAgICAgLy8gaXMgYSByZWNvdmVyeSBwYXRoIGZvciBhIHBsdWdpbiB0aGF0IGZhaWxlZCB0byBsb2FkIOKAlCBpdCBtYXlcbiAgICAgICAgICAgIC8vIGJlIHJlaW5zdGFsbGFibGUsIHNvIGRvbid0IG51a2UgJHtDTEFVREVfUExVR0lOX0RBVEF9IHNpbGVudGx5LlxuICAgICAgICAgICAgLy8gVGhlIG5vcm1hbCB1bmluc3RhbGwgcGF0aCBwcm9tcHRzOyB0aGlzIG9uZSBwcmVzZXJ2ZXMuXG4gICAgICAgICAgICBjb25zdCByZXN1bHQgPSBpc0luc3RhbGxhYmxlU2NvcGUocGx1Z2luU2NvcGUpXG4gICAgICAgICAgICAgID8gYXdhaXQgdW5pbnN0YWxsUGx1Z2luT3AocGx1Z2luSWQsIHBsdWdpblNjb3BlLCBmYWxzZSlcbiAgICAgICAgICAgICAgOiBhd2FpdCB1bmluc3RhbGxQbHVnaW5PcChwbHVnaW5JZCwgJ3VzZXInLCBmYWxzZSlcbiAgICAgICAgICAgIGxldCBzdWNjZXNzID0gcmVzdWx0LnN1Y2Nlc3NcbiAgICAgICAgICAgIGlmICghc3VjY2Vzcykge1xuICAgICAgICAgICAgICAvLyBQbHVnaW4gd2FzIG5ldmVyIGluc3RhbGxlZCAob25seSBpbiBlbmFibGVkUGx1Z2lucyBzZXR0aW5ncykuXG4gICAgICAgICAgICAgIC8vIFJlbW92ZSBkaXJlY3RseSBmcm9tIGFsbCBlZGl0YWJsZSBzZXR0aW5ncyBzb3VyY2VzLlxuICAgICAgICAgICAgICBjb25zdCBlZGl0YWJsZVNvdXJjZXMgPSBbXG4gICAgICAgICAgICAgICAgJ3VzZXJTZXR0aW5ncycgYXMgY29uc3QsXG4gICAgICAgICAgICAgICAgJ3Byb2plY3RTZXR0aW5ncycgYXMgY29uc3QsXG4gICAgICAgICAgICAgICAgJ2xvY2FsU2V0dGluZ3MnIGFzIGNvbnN0LFxuICAgICAgICAgICAgICBdXG4gICAgICAgICAgICAgIGZvciAoY29uc3Qgc291cmNlIG9mIGVkaXRhYmxlU291cmNlcykge1xuICAgICAgICAgICAgICAgIGNvbnN0IHNldHRpbmdzID0gZ2V0U2V0dGluZ3NGb3JTb3VyY2Uoc291cmNlKVxuICAgICAgICAgICAgICAgIGlmIChzZXR0aW5ncz8uZW5hYmxlZFBsdWdpbnM/LltwbHVnaW5JZF0gIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgICAgdXBkYXRlU2V0dGluZ3NGb3JTb3VyY2Uoc291cmNlLCB7XG4gICAgICAgICAgICAgICAgICAgIGVuYWJsZWRQbHVnaW5zOiB7XG4gICAgICAgICAgICAgICAgICAgICAgLi4uc2V0dGluZ3MuZW5hYmxlZFBsdWdpbnMsXG4gICAgICAgICAgICAgICAgICAgICAgW3BsdWdpbklkXTogdW5kZWZpbmVkLFxuICAgICAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICAgIHN1Y2Nlc3MgPSB0cnVlXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIC8vIENsZWFyIG1lbW9pemVkIGNhY2hlcyBzbyBuZXh0IGxvYWRBbGxQbHVnaW5zKCkgcGlja3MgdXAgc2V0dGluZ3MgY2hhbmdlc1xuICAgICAgICAgICAgICBjbGVhckFsbENhY2hlcygpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoc3VjY2Vzcykge1xuICAgICAgICAgICAgICBpZiAob25NYW5hZ2VDb21wbGV0ZSkge1xuICAgICAgICAgICAgICAgIGF3YWl0IG9uTWFuYWdlQ29tcGxldGUoKVxuICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIHNldElzUHJvY2Vzc2luZyhmYWxzZSlcbiAgICAgICAgICAgICAgLy8gUmV0dXJuIHRvIGxpc3QgKGRvbid0IHNldFJlc3VsdCDigJQgdGhhdCBjbG9zZXMgdGhlIHdob2xlIGRpYWxvZylcbiAgICAgICAgICAgICAgc2V0Vmlld1N0YXRlKCdwbHVnaW4tbGlzdCcpXG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICBzZXRJc1Byb2Nlc3NpbmcoZmFsc2UpXG4gICAgICAgICAgICAgIHNldFByb2Nlc3NFcnJvcihyZXN1bHQubWVzc2FnZSlcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9KSgpXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgfSxcbiAgICB7XG4gICAgICBjb250ZXh0OiAnU2VsZWN0JyxcbiAgICAgIGlzQWN0aXZlOlxuICAgICAgICB0eXBlb2Ygdmlld1N0YXRlID09PSAnb2JqZWN0JyAmJlxuICAgICAgICB2aWV3U3RhdGUudHlwZSA9PT0gJ2ZhaWxlZC1wbHVnaW4tZGV0YWlscycgJiZcbiAgICAgICAgdmlld1N0YXRlLnBsdWdpbi5zY29wZSAhPT0gJ21hbmFnZWQnLFxuICAgIH0sXG4gIClcblxuICAvLyBDb25maXJtLXByb2plY3QtdW5pbnN0YWxsOiB5L2VudGVyIGRpc2FibGVzIGluIHNldHRpbmdzLmxvY2FsLmpzb24sIG4vZXNjYXBlIGNhbmNlbHNcbiAgdXNlS2V5YmluZGluZ3MoXG4gICAge1xuICAgICAgJ2NvbmZpcm06eWVzJzogKCkgPT4ge1xuICAgICAgICBpZiAoIXNlbGVjdGVkUGx1Z2luKSByZXR1cm5cbiAgICAgICAgc2V0SXNQcm9jZXNzaW5nKHRydWUpXG4gICAgICAgIHNldFByb2Nlc3NFcnJvcihudWxsKVxuICAgICAgICBjb25zdCBwbHVnaW5JZCA9IGAke3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5uYW1lfUAke3NlbGVjdGVkUGx1Z2luLm1hcmtldHBsYWNlfWBcbiAgICAgICAgLy8gV3JpdGUgYGZhbHNlYCBkaXJlY3RseSDigJQgZGlzYWJsZVBsdWdpbk9wJ3MgY3Jvc3Mtc2NvcGUgZ3VhcmQgd291bGRcbiAgICAgICAgLy8gcmVqZWN0IHRoaXMgKHBsdWdpbiBpc24ndCBpbiBsb2NhbFNldHRpbmdzIHlldDsgdGhlIG92ZXJyaWRlIElTIHRoZVxuICAgICAgICAvLyBwb2ludCkuXG4gICAgICAgIGNvbnN0IHsgZXJyb3IgfSA9IHVwZGF0ZVNldHRpbmdzRm9yU291cmNlKCdsb2NhbFNldHRpbmdzJywge1xuICAgICAgICAgIGVuYWJsZWRQbHVnaW5zOiB7XG4gICAgICAgICAgICAuLi5nZXRTZXR0aW5nc0ZvclNvdXJjZSgnbG9jYWxTZXR0aW5ncycpPy5lbmFibGVkUGx1Z2lucyxcbiAgICAgICAgICAgIFtwbHVnaW5JZF06IGZhbHNlLFxuICAgICAgICAgIH0sXG4gICAgICAgIH0pXG4gICAgICAgIGlmIChlcnJvcikge1xuICAgICAgICAgIHNldElzUHJvY2Vzc2luZyhmYWxzZSlcbiAgICAgICAgICBzZXRQcm9jZXNzRXJyb3IoYEZhaWxlZCB0byB3cml0ZSBzZXR0aW5nczogJHtlcnJvci5tZXNzYWdlfWApXG4gICAgICAgICAgcmV0dXJuXG4gICAgICAgIH1cbiAgICAgICAgY2xlYXJBbGxDYWNoZXMoKVxuICAgICAgICBzZXRSZXN1bHQoXG4gICAgICAgICAgYOKckyBEaXNhYmxlZCAke3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5uYW1lfSBpbiAuY2xhdWRlL3NldHRpbmdzLmxvY2FsLmpzb24uIFJ1biAvcmVsb2FkLXBsdWdpbnMgdG8gYXBwbHkuYCxcbiAgICAgICAgKVxuICAgICAgICBpZiAob25NYW5hZ2VDb21wbGV0ZSkgdm9pZCBvbk1hbmFnZUNvbXBsZXRlKClcbiAgICAgICAgc2V0UGFyZW50Vmlld1N0YXRlKHsgdHlwZTogJ21lbnUnIH0pXG4gICAgICB9LFxuICAgICAgJ2NvbmZpcm06bm8nOiAoKSA9PiB7XG4gICAgICAgIHNldFZpZXdTdGF0ZSgncGx1Z2luLWRldGFpbHMnKVxuICAgICAgICBzZXRQcm9jZXNzRXJyb3IobnVsbClcbiAgICAgIH0sXG4gICAgfSxcbiAgICB7XG4gICAgICBjb250ZXh0OiAnQ29uZmlybWF0aW9uJyxcbiAgICAgIGlzQWN0aXZlOlxuICAgICAgICB2aWV3U3RhdGUgPT09ICdjb25maXJtLXByb2plY3QtdW5pbnN0YWxsJyAmJlxuICAgICAgICAhIXNlbGVjdGVkUGx1Z2luICYmXG4gICAgICAgICFpc1Byb2Nlc3NpbmcsXG4gICAgfSxcbiAgKVxuXG4gIC8vIENvbmZpcm0tZGF0YS1jbGVhbnVwOiB5IHVuaW5zdGFsbHMgKyBkZWxldGVzIGRhdGEgZGlyLCBuIHVuaW5zdGFsbHMgKyBrZWVwcyxcbiAgLy8gZXNjIGNhbmNlbHMuIFJhdyB1c2VJbnB1dCBiZWNhdXNlOiAoMSkgdGhlIENvbmZpcm1hdGlvbiBjb250ZXh0IG1hcHNcbiAgLy8gZW50ZXLihpJjb25maXJtOnllcywgd2hpY2ggd291bGQgbWFrZSBFbnRlciBkZWxldGUgdGhlIGRhdGEgZGlyZWN0b3J5IOKAlCBhXG4gIC8vIGRlc3RydWN0aXZlIGRlZmF1bHQgdGhlIFVJIHRleHQgKFwieSB0byBkZWxldGUgwrcgbiB0byBrZWVwXCIpIGRvZXNuJ3RcbiAgLy8gYWR2ZXJ0aXNlOyAoMikgdW5saWtlIGNvbmZpcm0tcHJvamVjdC11bmluc3RhbGwgKHdoaWNoIHVzZXMgdXNlS2V5YmluZGluZ3NcbiAgLy8gd2hlcmUgbiBhbmQgZXNjYXBlIGJvdGggbWFwIHRvIGNvbmZpcm06bm8pLCBoZXJlIG4gYW5kIGVzY2FwZSBhcmUgRElGRkVSRU5UXG4gIC8vIGFjdGlvbnMgKGtlZXAtZGF0YSB2cyBjYW5jZWwpLCBzbyB0aGlzIGRlbGliZXJhdGVseSBzdGF5cyBvbiByYXcgdXNlSW5wdXQuXG4gIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBjdXN0b20tcnVsZXMvcHJlZmVyLXVzZS1rZXliaW5kaW5ncyAtLSByYXcgeS9uL2VzYzsgRW50ZXIgbXVzdCBub3QgdHJpZ2dlciBkZXN0cnVjdGl2ZSBkZWxldGVcbiAgdXNlSW5wdXQoXG4gICAgKGlucHV0LCBrZXkpID0+IHtcbiAgICAgIGlmICghc2VsZWN0ZWRQbHVnaW4pIHJldHVyblxuICAgICAgY29uc3QgcGx1Z2luSWQgPSBgJHtzZWxlY3RlZFBsdWdpbi5wbHVnaW4ubmFtZX1AJHtzZWxlY3RlZFBsdWdpbi5tYXJrZXRwbGFjZX1gXG4gICAgICBjb25zdCBwbHVnaW5TY29wZSA9IHNlbGVjdGVkUGx1Z2luLnNjb3BlXG4gICAgICAvLyBEaWFsb2cgaXMgb25seSByZWFjaGFibGUgZnJvbSB0aGUgdW5pbnN0YWxsIGNhc2UgKHdoaWNoIGd1YXJkcyBvblxuICAgICAgLy8gaXNCdWlsdGluKSwgYnV0IFRTIGNhbid0IHRyYWNrIHRoYXQgYWNyb3NzIHZpZXdTdGF0ZSB0cmFuc2l0aW9ucy5cbiAgICAgIGlmIChcbiAgICAgICAgIXBsdWdpblNjb3BlIHx8XG4gICAgICAgIHBsdWdpblNjb3BlID09PSAnYnVpbHRpbicgfHxcbiAgICAgICAgIWlzSW5zdGFsbGFibGVTY29wZShwbHVnaW5TY29wZSlcbiAgICAgIClcbiAgICAgICAgcmV0dXJuXG4gICAgICBjb25zdCBkb1VuaW5zdGFsbCA9IGFzeW5jIChkZWxldGVEYXRhRGlyOiBib29sZWFuKSA9PiB7XG4gICAgICAgIHNldElzUHJvY2Vzc2luZyh0cnVlKVxuICAgICAgICBzZXRQcm9jZXNzRXJyb3IobnVsbClcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB1bmluc3RhbGxQbHVnaW5PcChcbiAgICAgICAgICAgIHBsdWdpbklkLFxuICAgICAgICAgICAgcGx1Z2luU2NvcGUsXG4gICAgICAgICAgICBkZWxldGVEYXRhRGlyLFxuICAgICAgICAgIClcbiAgICAgICAgICBpZiAoIXJlc3VsdC5zdWNjZXNzKSB0aHJvdyBuZXcgRXJyb3IocmVzdWx0Lm1lc3NhZ2UpXG4gICAgICAgICAgY2xlYXJBbGxDYWNoZXMoKVxuICAgICAgICAgIGNvbnN0IHN1ZmZpeCA9IGRlbGV0ZURhdGFEaXIgPyAnJyA6ICcgwrcgZGF0YSBwcmVzZXJ2ZWQnXG4gICAgICAgICAgc2V0UmVzdWx0KGAke2ZpZ3VyZXMudGlja30gJHtyZXN1bHQubWVzc2FnZX0ke3N1ZmZpeH1gKVxuICAgICAgICAgIGlmIChvbk1hbmFnZUNvbXBsZXRlKSB2b2lkIG9uTWFuYWdlQ29tcGxldGUoKVxuICAgICAgICAgIHNldFBhcmVudFZpZXdTdGF0ZSh7IHR5cGU6ICdtZW51JyB9KVxuICAgICAgICB9IGNhdGNoIChlKSB7XG4gICAgICAgICAgc2V0SXNQcm9jZXNzaW5nKGZhbHNlKVxuICAgICAgICAgIHNldFByb2Nlc3NFcnJvcihlIGluc3RhbmNlb2YgRXJyb3IgPyBlLm1lc3NhZ2UgOiBTdHJpbmcoZSkpXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmIChpbnB1dCA9PT0gJ3knIHx8IGlucHV0ID09PSAnWScpIHtcbiAgICAgICAgdm9pZCBkb1VuaW5zdGFsbCh0cnVlKVxuICAgICAgfSBlbHNlIGlmIChpbnB1dCA9PT0gJ24nIHx8IGlucHV0ID09PSAnTicpIHtcbiAgICAgICAgdm9pZCBkb1VuaW5zdGFsbChmYWxzZSlcbiAgICAgIH0gZWxzZSBpZiAoa2V5LmVzY2FwZSkge1xuICAgICAgICBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1kZXRhaWxzJylcbiAgICAgICAgc2V0UHJvY2Vzc0Vycm9yKG51bGwpXG4gICAgICB9XG4gICAgfSxcbiAgICB7XG4gICAgICBpc0FjdGl2ZTpcbiAgICAgICAgdHlwZW9mIHZpZXdTdGF0ZSA9PT0gJ29iamVjdCcgJiZcbiAgICAgICAgdmlld1N0YXRlLnR5cGUgPT09ICdjb25maXJtLWRhdGEtY2xlYW51cCcgJiZcbiAgICAgICAgISFzZWxlY3RlZFBsdWdpbiAmJlxuICAgICAgICAhaXNQcm9jZXNzaW5nLFxuICAgIH0sXG4gIClcblxuICAvLyBSZXNldCBzZWxlY3Rpb24gd2hlbiBzZWFyY2ggcXVlcnkgY2hhbmdlc1xuICBSZWFjdC51c2VFZmZlY3QoKCkgPT4ge1xuICAgIHNldFNlbGVjdGVkSW5kZXgoMClcbiAgfSwgW3NlYXJjaFF1ZXJ5XSlcblxuICAvLyBIYW5kbGUgaW5wdXQgZm9yIGVudGVyaW5nIHNlYXJjaCBtb2RlICh0ZXh0IGlucHV0IGhhbmRsZWQgYnkgdXNlU2VhcmNoSW5wdXQgaG9vaylcbiAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGN1c3RvbS1ydWxlcy9wcmVmZXItdXNlLWtleWJpbmRpbmdzIC0tIHVzZUlucHV0IG5lZWRlZCBmb3IgcmF3IHNlYXJjaCBtb2RlIHRleHQgaW5wdXRcbiAgdXNlSW5wdXQoXG4gICAgKGlucHV0LCBrZXkpID0+IHtcbiAgICAgIGNvbnN0IGtleUlzTm90Q3RybE9yTWV0YSA9ICFrZXkuY3RybCAmJiAha2V5Lm1ldGFcbiAgICAgIGlmIChpc1NlYXJjaE1vZGUpIHtcbiAgICAgICAgLy8gVGV4dCBpbnB1dCBpcyBoYW5kbGVkIGJ5IHVzZVNlYXJjaElucHV0IGhvb2tcbiAgICAgICAgcmV0dXJuXG4gICAgICB9XG5cbiAgICAgIC8vIEVudGVyIHNlYXJjaCBtb2RlIHdpdGggJy8nIG9yIGFueSBwcmludGFibGUgY2hhcmFjdGVyIChleGNlcHQgbmF2aWdhdGlvbiBrZXlzKVxuICAgICAgaWYgKGlucHV0ID09PSAnLycgJiYga2V5SXNOb3RDdHJsT3JNZXRhKSB7XG4gICAgICAgIHNldElzU2VhcmNoTW9kZSh0cnVlKVxuICAgICAgICBzZXRTZWFyY2hRdWVyeSgnJylcbiAgICAgICAgc2V0U2VsZWN0ZWRJbmRleCgwKVxuICAgICAgfSBlbHNlIGlmIChcbiAgICAgICAga2V5SXNOb3RDdHJsT3JNZXRhICYmXG4gICAgICAgIGlucHV0Lmxlbmd0aCA+IDAgJiZcbiAgICAgICAgIS9eXFxzKyQvLnRlc3QoaW5wdXQpICYmXG4gICAgICAgIGlucHV0ICE9PSAnaicgJiZcbiAgICAgICAgaW5wdXQgIT09ICdrJyAmJlxuICAgICAgICBpbnB1dCAhPT0gJyAnXG4gICAgICApIHtcbiAgICAgICAgc2V0SXNTZWFyY2hNb2RlKHRydWUpXG4gICAgICAgIHNldFNlYXJjaFF1ZXJ5KGlucHV0KVxuICAgICAgICBzZXRTZWxlY3RlZEluZGV4KDApXG4gICAgICB9XG4gICAgfSxcbiAgICB7IGlzQWN0aXZlOiB2aWV3U3RhdGUgPT09ICdwbHVnaW4tbGlzdCcgfSxcbiAgKVxuXG4gIC8vIExvYWRpbmcgc3RhdGVcbiAgaWYgKGxvYWRpbmcpIHtcbiAgICByZXR1cm4gPFRleHQ+TG9hZGluZyBpbnN0YWxsZWQgcGx1Z2luc+KApjwvVGV4dD5cbiAgfVxuXG4gIC8vIE5vIHBsdWdpbnMgb3IgTUNQcyBpbnN0YWxsZWRcbiAgaWYgKHVuaWZpZWRJdGVtcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gKFxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgIDxCb3ggbWFyZ2luQm90dG9tPXsxfT5cbiAgICAgICAgICA8VGV4dCBib2xkPk1hbmFnZSBwbHVnaW5zPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICAgPFRleHQ+Tm8gcGx1Z2lucyBvciBNQ1Agc2VydmVycyBpbnN0YWxsZWQuPC9UZXh0PlxuICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+RXNjIHRvIGdvIGJhY2s8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgaWYgKFxuICAgIHR5cGVvZiB2aWV3U3RhdGUgPT09ICdvYmplY3QnICYmXG4gICAgdmlld1N0YXRlLnR5cGUgPT09ICdwbHVnaW4tb3B0aW9ucycgJiZcbiAgICBzZWxlY3RlZFBsdWdpblxuICApIHtcbiAgICBjb25zdCBwbHVnaW5JZCA9IGAke3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5uYW1lfUAke3NlbGVjdGVkUGx1Z2luLm1hcmtldHBsYWNlfWBcbiAgICBmdW5jdGlvbiBmaW5pc2gobXNnOiBzdHJpbmcpOiB2b2lkIHtcbiAgICAgIHNldFJlc3VsdChtc2cpXG4gICAgICAvLyBQbHVnaW4gaXMgZW5hYmxlZCByZWdhcmRsZXNzIG9mIHdoZXRoZXIgY29uZmlnIHdhcyBzYXZlZCBvclxuICAgICAgLy8gc2tpcHBlZCDigJQgb25NYW5hZ2VDb21wbGV0ZSDihpIgbWFya1BsdWdpbnNDaGFuZ2VkIOKGkiB0aGVcbiAgICAgIC8vIHBlcnNpc3RlbnQgXCJydW4gL3JlbG9hZC1wbHVnaW5zXCIgbm90aWNlLlxuICAgICAgaWYgKG9uTWFuYWdlQ29tcGxldGUpIHtcbiAgICAgICAgdm9pZCBvbk1hbmFnZUNvbXBsZXRlKClcbiAgICAgIH1cbiAgICAgIHNldFBhcmVudFZpZXdTdGF0ZSh7IHR5cGU6ICdtZW51JyB9KVxuICAgIH1cbiAgICByZXR1cm4gKFxuICAgICAgPFBsdWdpbk9wdGlvbnNGbG93XG4gICAgICAgIHBsdWdpbj17c2VsZWN0ZWRQbHVnaW4ucGx1Z2lufVxuICAgICAgICBwbHVnaW5JZD17cGx1Z2luSWR9XG4gICAgICAgIG9uRG9uZT17KG91dGNvbWUsIGRldGFpbCkgPT4ge1xuICAgICAgICAgIHN3aXRjaCAob3V0Y29tZSkge1xuICAgICAgICAgICAgY2FzZSAnY29uZmlndXJlZCc6XG4gICAgICAgICAgICAgIGZpbmlzaChcbiAgICAgICAgICAgICAgICBg4pyTIEVuYWJsZWQgYW5kIGNvbmZpZ3VyZWQgJHtzZWxlY3RlZFBsdWdpbi5wbHVnaW4ubmFtZX0uIFJ1biAvcmVsb2FkLXBsdWdpbnMgdG8gYXBwbHkuYCxcbiAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgICBicmVha1xuICAgICAgICAgICAgY2FzZSAnc2tpcHBlZCc6XG4gICAgICAgICAgICAgIGZpbmlzaChcbiAgICAgICAgICAgICAgICBg4pyTIEVuYWJsZWQgJHtzZWxlY3RlZFBsdWdpbi5wbHVnaW4ubmFtZX0uIFJ1biAvcmVsb2FkLXBsdWdpbnMgdG8gYXBwbHkuYCxcbiAgICAgICAgICAgICAgKVxuICAgICAgICAgICAgICBicmVha1xuICAgICAgICAgICAgY2FzZSAnZXJyb3InOlxuICAgICAgICAgICAgICBmaW5pc2goYEZhaWxlZCB0byBzYXZlIGNvbmZpZ3VyYXRpb246ICR7ZGV0YWlsfWApXG4gICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgfVxuICAgICAgICB9fVxuICAgICAgLz5cbiAgICApXG4gIH1cblxuICAvLyBDb25maWd1cmUgb3B0aW9ucyAoZnJvbSB0aGUgTWFuYWdlIG1lbnUpXG4gIGlmIChcbiAgICB0eXBlb2Ygdmlld1N0YXRlID09PSAnb2JqZWN0JyAmJlxuICAgIHZpZXdTdGF0ZS50eXBlID09PSAnY29uZmlndXJpbmctb3B0aW9ucycgJiZcbiAgICBzZWxlY3RlZFBsdWdpblxuICApIHtcbiAgICBjb25zdCBwbHVnaW5JZCA9IGAke3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5uYW1lfUAke3NlbGVjdGVkUGx1Z2luLm1hcmtldHBsYWNlfWBcbiAgICByZXR1cm4gKFxuICAgICAgPFBsdWdpbk9wdGlvbnNEaWFsb2dcbiAgICAgICAgdGl0bGU9e2BDb25maWd1cmUgJHtzZWxlY3RlZFBsdWdpbi5wbHVnaW4ubmFtZX1gfVxuICAgICAgICBzdWJ0aXRsZT1cIlBsdWdpbiBvcHRpb25zXCJcbiAgICAgICAgY29uZmlnU2NoZW1hPXt2aWV3U3RhdGUuc2NoZW1hfVxuICAgICAgICBpbml0aWFsVmFsdWVzPXtsb2FkUGx1Z2luT3B0aW9ucyhwbHVnaW5JZCl9XG4gICAgICAgIG9uU2F2ZT17dmFsdWVzID0+IHtcbiAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgc2F2ZVBsdWdpbk9wdGlvbnMocGx1Z2luSWQsIHZhbHVlcywgdmlld1N0YXRlLnNjaGVtYSlcbiAgICAgICAgICAgIGNsZWFyQWxsQ2FjaGVzKClcbiAgICAgICAgICAgIHNldFJlc3VsdChcbiAgICAgICAgICAgICAgJ0NvbmZpZ3VyYXRpb24gc2F2ZWQuIFJ1biAvcmVsb2FkLXBsdWdpbnMgZm9yIGNoYW5nZXMgdG8gdGFrZSBlZmZlY3QuJyxcbiAgICAgICAgICAgIClcbiAgICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgIHNldFByb2Nlc3NFcnJvcihcbiAgICAgICAgICAgICAgYEZhaWxlZCB0byBzYXZlIGNvbmZpZ3VyYXRpb246ICR7ZXJyb3JNZXNzYWdlKGVycil9YCxcbiAgICAgICAgICAgIClcbiAgICAgICAgICB9XG4gICAgICAgICAgc2V0Vmlld1N0YXRlKCdwbHVnaW4tZGV0YWlscycpXG4gICAgICAgIH19XG4gICAgICAgIG9uQ2FuY2VsPXsoKSA9PiBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1kZXRhaWxzJyl9XG4gICAgICAvPlxuICAgIClcbiAgfVxuXG4gIC8vIENvbmZpZ3VyYXRpb24gdmlld1xuICBpZiAodmlld1N0YXRlID09PSAnY29uZmlndXJpbmcnICYmIGNvbmZpZ05lZWRlZCAmJiBzZWxlY3RlZFBsdWdpbikge1xuICAgIGNvbnN0IHBsdWdpbklkID0gYCR7c2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm5hbWV9QCR7c2VsZWN0ZWRQbHVnaW4ubWFya2V0cGxhY2V9YFxuXG4gICAgYXN5bmMgZnVuY3Rpb24gaGFuZGxlU2F2ZShjb25maWc6IFVzZXJDb25maWdWYWx1ZXMpIHtcbiAgICAgIGlmICghY29uZmlnTmVlZGVkIHx8ICFzZWxlY3RlZFBsdWdpbikgcmV0dXJuXG5cbiAgICAgIHRyeSB7XG4gICAgICAgIC8vIEZpbmQgTUNQQiBwYXRoIGFnYWluXG4gICAgICAgIGNvbnN0IG1jcFNlcnZlcnNTcGVjID0gc2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm1hbmlmZXN0Lm1jcFNlcnZlcnNcbiAgICAgICAgbGV0IG1jcGJQYXRoOiBzdHJpbmcgfCBudWxsID0gbnVsbFxuXG4gICAgICAgIGlmIChcbiAgICAgICAgICB0eXBlb2YgbWNwU2VydmVyc1NwZWMgPT09ICdzdHJpbmcnICYmXG4gICAgICAgICAgaXNNY3BiU291cmNlKG1jcFNlcnZlcnNTcGVjKVxuICAgICAgICApIHtcbiAgICAgICAgICBtY3BiUGF0aCA9IG1jcFNlcnZlcnNTcGVjXG4gICAgICAgIH0gZWxzZSBpZiAoQXJyYXkuaXNBcnJheShtY3BTZXJ2ZXJzU3BlYykpIHtcbiAgICAgICAgICBmb3IgKGNvbnN0IHNwZWMgb2YgbWNwU2VydmVyc1NwZWMpIHtcbiAgICAgICAgICAgIGlmICh0eXBlb2Ygc3BlYyA9PT0gJ3N0cmluZycgJiYgaXNNY3BiU291cmNlKHNwZWMpKSB7XG4gICAgICAgICAgICAgIG1jcGJQYXRoID0gc3BlY1xuICAgICAgICAgICAgICBicmVha1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghbWNwYlBhdGgpIHtcbiAgICAgICAgICBzZXRQcm9jZXNzRXJyb3IoJ05vIE1DUEIgZmlsZSBmb3VuZCcpXG4gICAgICAgICAgc2V0Vmlld1N0YXRlKCdwbHVnaW4tZGV0YWlscycpXG4gICAgICAgICAgcmV0dXJuXG4gICAgICAgIH1cblxuICAgICAgICAvLyBSZWxvYWQgd2l0aCBwcm92aWRlZCBjb25maWdcbiAgICAgICAgYXdhaXQgbG9hZE1jcGJGaWxlKFxuICAgICAgICAgIG1jcGJQYXRoLFxuICAgICAgICAgIHNlbGVjdGVkUGx1Z2luLnBsdWdpbi5wYXRoLFxuICAgICAgICAgIHBsdWdpbklkLFxuICAgICAgICAgIHVuZGVmaW5lZCxcbiAgICAgICAgICBjb25maWcsXG4gICAgICAgIClcblxuICAgICAgICAvLyBTdWNjZXNzIC0gZ28gYmFjayB0byBkZXRhaWxzXG4gICAgICAgIHNldFByb2Nlc3NFcnJvcihudWxsKVxuICAgICAgICBzZXRDb25maWdOZWVkZWQobnVsbClcbiAgICAgICAgc2V0Vmlld1N0YXRlKCdwbHVnaW4tZGV0YWlscycpXG4gICAgICAgIHNldFJlc3VsdChcbiAgICAgICAgICAnQ29uZmlndXJhdGlvbiBzYXZlZC4gUnVuIC9yZWxvYWQtcGx1Z2lucyBmb3IgY2hhbmdlcyB0byB0YWtlIGVmZmVjdC4nLFxuICAgICAgICApXG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgY29uc3QgZXJyb3JNc2cgPSBlcnJvck1lc3NhZ2UoZXJyKVxuICAgICAgICBzZXRQcm9jZXNzRXJyb3IoYEZhaWxlZCB0byBzYXZlIGNvbmZpZ3VyYXRpb246ICR7ZXJyb3JNc2d9YClcbiAgICAgICAgc2V0Vmlld1N0YXRlKCdwbHVnaW4tZGV0YWlscycpXG4gICAgICB9XG4gICAgfVxuXG4gICAgZnVuY3Rpb24gaGFuZGxlQ2FuY2VsKCkge1xuICAgICAgc2V0Q29uZmlnTmVlZGVkKG51bGwpXG4gICAgICBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1kZXRhaWxzJylcbiAgICB9XG5cbiAgICByZXR1cm4gKFxuICAgICAgPFBsdWdpbk9wdGlvbnNEaWFsb2dcbiAgICAgICAgdGl0bGU9e2BDb25maWd1cmUgJHtjb25maWdOZWVkZWQubWFuaWZlc3QubmFtZX1gfVxuICAgICAgICBzdWJ0aXRsZT17YFBsdWdpbjogJHtzZWxlY3RlZFBsdWdpbi5wbHVnaW4ubmFtZX1gfVxuICAgICAgICBjb25maWdTY2hlbWE9e2NvbmZpZ05lZWRlZC5jb25maWdTY2hlbWF9XG4gICAgICAgIGluaXRpYWxWYWx1ZXM9e2NvbmZpZ05lZWRlZC5leGlzdGluZ0NvbmZpZ31cbiAgICAgICAgb25TYXZlPXtoYW5kbGVTYXZlfVxuICAgICAgICBvbkNhbmNlbD17aGFuZGxlQ2FuY2VsfVxuICAgICAgLz5cbiAgICApXG4gIH1cblxuICAvLyBGbGFnZ2VkIHBsdWdpbiBkZXRhaWwgdmlld1xuICBpZiAodHlwZW9mIHZpZXdTdGF0ZSA9PT0gJ29iamVjdCcgJiYgdmlld1N0YXRlLnR5cGUgPT09ICdmbGFnZ2VkLWRldGFpbCcpIHtcbiAgICBjb25zdCBmcCA9IHZpZXdTdGF0ZS5wbHVnaW5cbiAgICByZXR1cm4gKFxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgIDxCb3g+XG4gICAgICAgICAgPFRleHQgYm9sZD5cbiAgICAgICAgICAgIHtmcC5uYW1lfSBAIHtmcC5tYXJrZXRwbGFjZX1cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuXG4gICAgICAgIDxCb3ggbWFyZ2luQm90dG9tPXsxfT5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5TdGF0dXM6IDwvVGV4dD5cbiAgICAgICAgICA8VGV4dCBjb2xvcj1cImVycm9yXCI+UmVtb3ZlZDwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG5cbiAgICAgICAgPEJveCBtYXJnaW5Cb3R0b209ezF9IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgICA8VGV4dCBjb2xvcj1cImVycm9yXCI+XG4gICAgICAgICAgICBSZW1vdmVkIGZyb20gbWFya2V0cGxhY2UgwrcgcmVhc29uOiB7ZnAucmVhc29ufVxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICA8VGV4dD57ZnAudGV4dH08L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgICBGbGFnZ2VkIG9uIHtuZXcgRGF0ZShmcC5mbGFnZ2VkQXQpLnRvTG9jYWxlRGF0ZVN0cmluZygpfVxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG5cbiAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgICA8Qm94PlxuICAgICAgICAgICAgPFRleHQ+e2ZpZ3VyZXMucG9pbnRlcn0gPC9UZXh0PlxuICAgICAgICAgICAgPFRleHQgY29sb3I9XCJzdWdnZXN0aW9uXCI+RGlzbWlzczwvVGV4dD5cbiAgICAgICAgICA8L0JveD5cbiAgICAgICAgPC9Cb3g+XG5cbiAgICAgICAgPEJ5bGluZT5cbiAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICBhY3Rpb249XCJzZWxlY3Q6YWNjZXB0XCJcbiAgICAgICAgICAgIGNvbnRleHQ9XCJTZWxlY3RcIlxuICAgICAgICAgICAgZmFsbGJhY2s9XCJFbnRlclwiXG4gICAgICAgICAgICBkZXNjcmlwdGlvbj1cImRpc21pc3NcIlxuICAgICAgICAgIC8+XG4gICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTpub1wiXG4gICAgICAgICAgICBjb250ZXh0PVwiQ29uZmlybWF0aW9uXCJcbiAgICAgICAgICAgIGZhbGxiYWNrPVwiRXNjXCJcbiAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwiYmFja1wiXG4gICAgICAgICAgLz5cbiAgICAgICAgPC9CeWxpbmU+XG4gICAgICA8L0JveD5cbiAgICApXG4gIH1cblxuICAvLyBDb25maXJtLXByb2plY3QtdW5pbnN0YWxsOiB3YXJuIGFib3V0IHNoYXJlZCAuY2xhdWRlL3NldHRpbmdzLmpzb24sXG4gIC8vIG9mZmVyIHRvIGRpc2FibGUgaW4gc2V0dGluZ3MubG9jYWwuanNvbiBpbnN0ZWFkLlxuICBpZiAodmlld1N0YXRlID09PSAnY29uZmlybS1wcm9qZWN0LXVuaW5zdGFsbCcgJiYgc2VsZWN0ZWRQbHVnaW4pIHtcbiAgICByZXR1cm4gKFxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgIDxUZXh0IGJvbGQgY29sb3I9XCJ3YXJuaW5nXCI+XG4gICAgICAgICAge3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5uYW1lfSBpcyBlbmFibGVkIGluIC5jbGF1ZGUvc2V0dGluZ3MuanNvblxuICAgICAgICAgIChzaGFyZWQgd2l0aCB5b3VyIHRlYW0pXG4gICAgICAgIDwvVGV4dD5cbiAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgICA8VGV4dD5EaXNhYmxlIGl0IGp1c3QgZm9yIHlvdSBpbiAuY2xhdWRlL3NldHRpbmdzLmxvY2FsLmpzb24/PC9UZXh0PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgICAgVGhpcyBoYXMgdGhlIHNhbWUgZWZmZWN0IGFzIHVuaW5zdGFsbGluZywgd2l0aG91dCBhZmZlY3Rpbmcgb3RoZXJcbiAgICAgICAgICAgIGNvbnRyaWJ1dG9ycy5cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgICB7cHJvY2Vzc0Vycm9yICYmIChcbiAgICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgICA8VGV4dCBjb2xvcj1cImVycm9yXCI+e3Byb2Nlc3NFcnJvcn08L1RleHQ+XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICl9XG4gICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICB7aXNQcm9jZXNzaW5nID8gKFxuICAgICAgICAgICAgPFRleHQgZGltQ29sb3I+RGlzYWJsaW5n4oCmPC9UZXh0PlxuICAgICAgICAgICkgOiAoXG4gICAgICAgICAgICA8QnlsaW5lPlxuICAgICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTp5ZXNcIlxuICAgICAgICAgICAgICAgIGNvbnRleHQ9XCJDb25maXJtYXRpb25cIlxuICAgICAgICAgICAgICAgIGZhbGxiYWNrPVwieVwiXG4gICAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJkaXNhYmxlXCJcbiAgICAgICAgICAgICAgLz5cbiAgICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICAgIGFjdGlvbj1cImNvbmZpcm06bm9cIlxuICAgICAgICAgICAgICAgIGNvbnRleHQ9XCJDb25maXJtYXRpb25cIlxuICAgICAgICAgICAgICAgIGZhbGxiYWNrPVwiRXNjXCJcbiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cImNhbmNlbFwiXG4gICAgICAgICAgICAgIC8+XG4gICAgICAgICAgICA8L0J5bGluZT5cbiAgICAgICAgICApfVxuICAgICAgICA8L0JveD5cbiAgICAgIDwvQm94PlxuICAgIClcbiAgfVxuXG4gIC8vIENvbmZpcm0tZGF0YS1jbGVhbnVwOiBwcm9tcHQgYmVmb3JlIGRlbGV0aW5nICR7Q0xBVURFX1BMVUdJTl9EQVRBfSBkaXJcbiAgaWYgKFxuICAgIHR5cGVvZiB2aWV3U3RhdGUgPT09ICdvYmplY3QnICYmXG4gICAgdmlld1N0YXRlLnR5cGUgPT09ICdjb25maXJtLWRhdGEtY2xlYW51cCcgJiZcbiAgICBzZWxlY3RlZFBsdWdpblxuICApIHtcbiAgICByZXR1cm4gKFxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgIDxUZXh0IGJvbGQ+XG4gICAgICAgICAge3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5uYW1lfSBoYXMge3ZpZXdTdGF0ZS5zaXplLmh1bWFufSBvZiBwZXJzaXN0ZW50XG4gICAgICAgICAgZGF0YVxuICAgICAgICA8L1RleHQ+XG4gICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfSBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgICAgPFRleHQ+RGVsZXRlIGl0IGFsb25nIHdpdGggdGhlIHBsdWdpbj88L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgICAgICB7cGx1Z2luRGF0YURpclBhdGgoXG4gICAgICAgICAgICAgIGAke3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5uYW1lfUAke3NlbGVjdGVkUGx1Z2luLm1hcmtldHBsYWNlfWAsXG4gICAgICAgICAgICApfVxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICAgIHtwcm9jZXNzRXJyb3IgJiYgKFxuICAgICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICAgIDxUZXh0IGNvbG9yPVwiZXJyb3JcIj57cHJvY2Vzc0Vycm9yfTwvVGV4dD5cbiAgICAgICAgICA8L0JveD5cbiAgICAgICAgKX1cbiAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgIHtpc1Byb2Nlc3NpbmcgPyAoXG4gICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5Vbmluc3RhbGxpbmfigKY8L1RleHQ+XG4gICAgICAgICAgKSA6IChcbiAgICAgICAgICAgIDxUZXh0PlxuICAgICAgICAgICAgICA8VGV4dCBib2xkPnk8L1RleHQ+IHRvIGRlbGV0ZSDCtyA8VGV4dCBib2xkPm48L1RleHQ+IHRvIGtlZXAgwrd7JyAnfVxuICAgICAgICAgICAgICA8VGV4dCBib2xkPmVzYzwvVGV4dD4gdG8gY2FuY2VsXG4gICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgKX1cbiAgICAgICAgPC9Cb3g+XG4gICAgICA8L0JveD5cbiAgICApXG4gIH1cblxuICAvLyBQbHVnaW4gZGV0YWlscyB2aWV3XG4gIGlmICh2aWV3U3RhdGUgPT09ICdwbHVnaW4tZGV0YWlscycgJiYgc2VsZWN0ZWRQbHVnaW4pIHtcbiAgICBjb25zdCBtZXJnZWRTZXR0aW5ncyA9IGdldFNldHRpbmdzX0RFUFJFQ0FURUQoKSAvLyBVc2UgbWVyZ2VkIHNldHRpbmdzIHRvIHJlc3BlY3QgYWxsIGxheWVyc1xuICAgIGNvbnN0IHBsdWdpbklkID0gYCR7c2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm5hbWV9QCR7c2VsZWN0ZWRQbHVnaW4ubWFya2V0cGxhY2V9YFxuICAgIGNvbnN0IGlzRW5hYmxlZCA9IG1lcmdlZFNldHRpbmdzPy5lbmFibGVkUGx1Z2lucz8uW3BsdWdpbklkXSAhPT0gZmFsc2VcblxuICAgIC8vIENvbXB1dGUgcGx1Z2luIGVycm9ycyBzZWN0aW9uXG4gICAgY29uc3QgZmlsdGVyZWRQbHVnaW5FcnJvcnMgPSBwbHVnaW5FcnJvcnMuZmlsdGVyKFxuICAgICAgZSA9PlxuICAgICAgICAoJ3BsdWdpbicgaW4gZSAmJiBlLnBsdWdpbiA9PT0gc2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm5hbWUpIHx8XG4gICAgICAgIGUuc291cmNlID09PSBwbHVnaW5JZCB8fFxuICAgICAgICBlLnNvdXJjZS5zdGFydHNXaXRoKGAke3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5uYW1lfUBgKSxcbiAgICApXG4gICAgY29uc3QgcGx1Z2luRXJyb3JzU2VjdGlvbiA9XG4gICAgICBmaWx0ZXJlZFBsdWdpbkVycm9ycy5sZW5ndGggPT09IDAgPyBudWxsIDogKFxuICAgICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIiBtYXJnaW5Cb3R0b209ezF9PlxuICAgICAgICAgIDxUZXh0IGJvbGQgY29sb3I9XCJlcnJvclwiPlxuICAgICAgICAgICAge2ZpbHRlcmVkUGx1Z2luRXJyb3JzLmxlbmd0aH17JyAnfVxuICAgICAgICAgICAge3BsdXJhbChmaWx0ZXJlZFBsdWdpbkVycm9ycy5sZW5ndGgsICdlcnJvcicpfTpcbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAge2ZpbHRlcmVkUGx1Z2luRXJyb3JzLm1hcCgoZXJyb3IsIGkpID0+IHtcbiAgICAgICAgICAgIGNvbnN0IGd1aWRhbmNlID0gZ2V0RXJyb3JHdWlkYW5jZShlcnJvcilcbiAgICAgICAgICAgIHJldHVybiAoXG4gICAgICAgICAgICAgIDxCb3gga2V5PXtpfSBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCIgbWFyZ2luTGVmdD17Mn0+XG4gICAgICAgICAgICAgICAgPFRleHQgY29sb3I9XCJlcnJvclwiPntmb3JtYXRFcnJvck1lc3NhZ2UoZXJyb3IpfTwvVGV4dD5cbiAgICAgICAgICAgICAgICB7Z3VpZGFuY2UgJiYgKFxuICAgICAgICAgICAgICAgICAgPFRleHQgZGltQ29sb3IgaXRhbGljPlxuICAgICAgICAgICAgICAgICAgICB7ZmlndXJlcy5hcnJvd1JpZ2h0fSB7Z3VpZGFuY2V9XG4gICAgICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgICAgKX1cbiAgICAgICAgICAgICAgPC9Cb3g+XG4gICAgICAgICAgICApXG4gICAgICAgICAgfSl9XG4gICAgICAgIDwvQm94PlxuICAgICAgKVxuXG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8Qm94PlxuICAgICAgICAgIDxUZXh0IGJvbGQ+XG4gICAgICAgICAgICB7c2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm5hbWV9IEAge3NlbGVjdGVkUGx1Z2luLm1hcmtldHBsYWNlfVxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG5cbiAgICAgICAgey8qIFNjb3BlICovfVxuICAgICAgICA8Qm94PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPlNjb3BlOiA8L1RleHQ+XG4gICAgICAgICAgPFRleHQ+e3NlbGVjdGVkUGx1Z2luLnNjb3BlIHx8ICd1c2VyJ308L1RleHQ+XG4gICAgICAgIDwvQm94PlxuXG4gICAgICAgIHsvKiBQbHVnaW4gZGV0YWlscyAqL31cbiAgICAgICAge3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5tYW5pZmVzdC52ZXJzaW9uICYmIChcbiAgICAgICAgICA8Qm94PlxuICAgICAgICAgICAgPFRleHQgZGltQ29sb3I+VmVyc2lvbjogPC9UZXh0PlxuICAgICAgICAgICAgPFRleHQ+e3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5tYW5pZmVzdC52ZXJzaW9ufTwvVGV4dD5cbiAgICAgICAgICA8L0JveD5cbiAgICAgICAgKX1cblxuICAgICAgICB7c2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm1hbmlmZXN0LmRlc2NyaXB0aW9uICYmIChcbiAgICAgICAgICA8Qm94IG1hcmdpbkJvdHRvbT17MX0+XG4gICAgICAgICAgICA8VGV4dD57c2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm1hbmlmZXN0LmRlc2NyaXB0aW9ufTwvVGV4dD5cbiAgICAgICAgICA8L0JveD5cbiAgICAgICAgKX1cblxuICAgICAgICB7c2VsZWN0ZWRQbHVnaW4ucGx1Z2luLm1hbmlmZXN0LmF1dGhvciAmJiAoXG4gICAgICAgICAgPEJveD5cbiAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPkF1dGhvcjogPC9UZXh0PlxuICAgICAgICAgICAgPFRleHQ+e3NlbGVjdGVkUGx1Z2luLnBsdWdpbi5tYW5pZmVzdC5hdXRob3IubmFtZX08L1RleHQ+XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICl9XG5cbiAgICAgICAgey8qIEN1cnJlbnQgc3RhdHVzICovfVxuICAgICAgICA8Qm94IG1hcmdpbkJvdHRvbT17MX0+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+U3RhdHVzOiA8L1RleHQ+XG4gICAgICAgICAgPFRleHQgY29sb3I9e2lzRW5hYmxlZCA/ICdzdWNjZXNzJyA6ICd3YXJuaW5nJ30+XG4gICAgICAgICAgICB7aXNFbmFibGVkID8gJ0VuYWJsZWQnIDogJ0Rpc2FibGVkJ31cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAge3NlbGVjdGVkUGx1Z2luLnBlbmRpbmdVcGRhdGUgJiYgKFxuICAgICAgICAgICAgPFRleHQgY29sb3I9XCJzdWdnZXN0aW9uXCI+IMK3IE1hcmtlZCBmb3IgdXBkYXRlPC9UZXh0PlxuICAgICAgICAgICl9XG4gICAgICAgIDwvQm94PlxuXG4gICAgICAgIHsvKiBJbnN0YWxsZWQgY29tcG9uZW50cyAqL31cbiAgICAgICAgPFBsdWdpbkNvbXBvbmVudHNEaXNwbGF5XG4gICAgICAgICAgcGx1Z2luPXtzZWxlY3RlZFBsdWdpbi5wbHVnaW59XG4gICAgICAgICAgbWFya2V0cGxhY2U9e3NlbGVjdGVkUGx1Z2luLm1hcmtldHBsYWNlfVxuICAgICAgICAvPlxuXG4gICAgICAgIHsvKiBQbHVnaW4gZXJyb3JzICovfVxuICAgICAgICB7cGx1Z2luRXJyb3JzU2VjdGlvbn1cblxuICAgICAgICB7LyogTWVudSAqL31cbiAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgICB7ZGV0YWlsc01lbnVJdGVtcy5tYXAoKGl0ZW0sIGluZGV4KSA9PiB7XG4gICAgICAgICAgICBjb25zdCBpc1NlbGVjdGVkID0gaW5kZXggPT09IGRldGFpbHNNZW51SW5kZXhcblxuICAgICAgICAgICAgcmV0dXJuIChcbiAgICAgICAgICAgICAgPEJveCBrZXk9e2luZGV4fT5cbiAgICAgICAgICAgICAgICB7aXNTZWxlY3RlZCAmJiA8VGV4dD57ZmlndXJlcy5wb2ludGVyfSA8L1RleHQ+fVxuICAgICAgICAgICAgICAgIHshaXNTZWxlY3RlZCAmJiA8VGV4dD57JyAgJ308L1RleHQ+fVxuICAgICAgICAgICAgICAgIDxUZXh0XG4gICAgICAgICAgICAgICAgICBib2xkPXtpc1NlbGVjdGVkfVxuICAgICAgICAgICAgICAgICAgY29sb3I9e1xuICAgICAgICAgICAgICAgICAgICBpdGVtLmxhYmVsLmluY2x1ZGVzKCdVbmluc3RhbGwnKVxuICAgICAgICAgICAgICAgICAgICAgID8gJ2Vycm9yJ1xuICAgICAgICAgICAgICAgICAgICAgIDogaXRlbS5sYWJlbC5pbmNsdWRlcygnVXBkYXRlJylcbiAgICAgICAgICAgICAgICAgICAgICAgID8gJ3N1Z2dlc3Rpb24nXG4gICAgICAgICAgICAgICAgICAgICAgICA6IHVuZGVmaW5lZFxuICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgID5cbiAgICAgICAgICAgICAgICAgIHtpdGVtLmxhYmVsfVxuICAgICAgICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICAgICAgPC9Cb3g+XG4gICAgICAgICAgICApXG4gICAgICAgICAgfSl9XG4gICAgICAgIDwvQm94PlxuXG4gICAgICAgIHsvKiBQcm9jZXNzaW5nIHN0YXRlICovfVxuICAgICAgICB7aXNQcm9jZXNzaW5nICYmIChcbiAgICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgICA8VGV4dD5Qcm9jZXNzaW5n4oCmPC9UZXh0PlxuICAgICAgICAgIDwvQm94PlxuICAgICAgICApfVxuXG4gICAgICAgIHsvKiBFcnJvciBtZXNzYWdlICovfVxuICAgICAgICB7cHJvY2Vzc0Vycm9yICYmIChcbiAgICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgICA8VGV4dCBjb2xvcj1cImVycm9yXCI+e3Byb2Nlc3NFcnJvcn08L1RleHQ+XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICl9XG5cbiAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yIGl0YWxpYz5cbiAgICAgICAgICAgIDxCeWxpbmU+XG4gICAgICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgICAgICBhY3Rpb249XCJzZWxlY3Q6cHJldmlvdXNcIlxuICAgICAgICAgICAgICAgIGNvbnRleHQ9XCJTZWxlY3RcIlxuICAgICAgICAgICAgICAgIGZhbGxiYWNrPVwi4oaRXCJcbiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cIm5hdmlnYXRlXCJcbiAgICAgICAgICAgICAgLz5cbiAgICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICAgIGFjdGlvbj1cInNlbGVjdDphY2NlcHRcIlxuICAgICAgICAgICAgICAgIGNvbnRleHQ9XCJTZWxlY3RcIlxuICAgICAgICAgICAgICAgIGZhbGxiYWNrPVwiRW50ZXJcIlxuICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwic2VsZWN0XCJcbiAgICAgICAgICAgICAgLz5cbiAgICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICAgIGFjdGlvbj1cImNvbmZpcm06bm9cIlxuICAgICAgICAgICAgICAgIGNvbnRleHQ9XCJDb25maXJtYXRpb25cIlxuICAgICAgICAgICAgICAgIGZhbGxiYWNrPVwiRXNjXCJcbiAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cImJhY2tcIlxuICAgICAgICAgICAgICAvPlxuICAgICAgICAgICAgPC9CeWxpbmU+XG4gICAgICAgICAgPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgIDwvQm94PlxuICAgIClcbiAgfVxuXG4gIC8vIEZhaWxlZCBwbHVnaW4gZGV0YWlsIHZpZXdcbiAgaWYgKFxuICAgIHR5cGVvZiB2aWV3U3RhdGUgPT09ICdvYmplY3QnICYmXG4gICAgdmlld1N0YXRlLnR5cGUgPT09ICdmYWlsZWQtcGx1Z2luLWRldGFpbHMnXG4gICkge1xuICAgIGNvbnN0IGZhaWxlZFBsdWdpbiA9IHZpZXdTdGF0ZS5wbHVnaW5cblxuICAgIGNvbnN0IGZpcnN0RXJyb3IgPSBmYWlsZWRQbHVnaW4uZXJyb3JzWzBdXG4gICAgY29uc3QgZXJyb3JNZXNzYWdlID0gZmlyc3RFcnJvclxuICAgICAgPyBmb3JtYXRFcnJvck1lc3NhZ2UoZmlyc3RFcnJvcilcbiAgICAgIDogJ0ZhaWxlZCB0byBsb2FkJ1xuXG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8VGV4dD5cbiAgICAgICAgICA8VGV4dCBib2xkPntmYWlsZWRQbHVnaW4ubmFtZX08L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IEAge2ZhaWxlZFBsdWdpbi5tYXJrZXRwbGFjZX08L1RleHQ+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+ICh7ZmFpbGVkUGx1Z2luLnNjb3BlfSk8L1RleHQ+XG4gICAgICAgIDwvVGV4dD5cbiAgICAgICAgPFRleHQgY29sb3I9XCJlcnJvclwiPntlcnJvck1lc3NhZ2V9PC9UZXh0PlxuXG4gICAgICAgIHtmYWlsZWRQbHVnaW4uc2NvcGUgPT09ICdtYW5hZ2VkJyA/IChcbiAgICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICAgICAgTWFuYWdlZCBieSB5b3VyIG9yZ2FuaXphdGlvbiDigJQgY29udGFjdCB5b3VyIGFkbWluXG4gICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICkgOiAoXG4gICAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgICAgPFRleHQgY29sb3I9XCJzdWdnZXN0aW9uXCI+e2ZpZ3VyZXMucG9pbnRlcn0gPC9UZXh0PlxuICAgICAgICAgICAgPFRleHQgYm9sZD5SZW1vdmU8L1RleHQ+XG4gICAgICAgICAgPC9Cb3g+XG4gICAgICAgICl9XG5cbiAgICAgICAge2lzUHJvY2Vzc2luZyAmJiA8VGV4dD5Qcm9jZXNzaW5n4oCmPC9UZXh0Pn1cbiAgICAgICAge3Byb2Nlc3NFcnJvciAmJiA8VGV4dCBjb2xvcj1cImVycm9yXCI+e3Byb2Nlc3NFcnJvcn08L1RleHQ+fVxuXG4gICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvciBpdGFsaWM+XG4gICAgICAgICAgICA8QnlsaW5lPlxuICAgICAgICAgICAgICB7ZmFpbGVkUGx1Z2luLnNjb3BlICE9PSAnbWFuYWdlZCcgJiYgKFxuICAgICAgICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgICAgICAgIGFjdGlvbj1cInNlbGVjdDphY2NlcHRcIlxuICAgICAgICAgICAgICAgICAgY29udGV4dD1cIlNlbGVjdFwiXG4gICAgICAgICAgICAgICAgICBmYWxsYmFjaz1cIkVudGVyXCJcbiAgICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwicmVtb3ZlXCJcbiAgICAgICAgICAgICAgICAvPlxuICAgICAgICAgICAgICApfVxuICAgICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTpub1wiXG4gICAgICAgICAgICAgICAgY29udGV4dD1cIkNvbmZpcm1hdGlvblwiXG4gICAgICAgICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwiYmFja1wiXG4gICAgICAgICAgICAgIC8+XG4gICAgICAgICAgICA8L0J5bGluZT5cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgLy8gTUNQIGRldGFpbCB2aWV3XG4gIGlmICh0eXBlb2Ygdmlld1N0YXRlID09PSAnb2JqZWN0JyAmJiB2aWV3U3RhdGUudHlwZSA9PT0gJ21jcC1kZXRhaWwnKSB7XG4gICAgY29uc3QgY2xpZW50ID0gdmlld1N0YXRlLmNsaWVudFxuICAgIGNvbnN0IHNlcnZlclRvb2xzQ291bnQgPSBmaWx0ZXJUb29sc0J5U2VydmVyKG1jcFRvb2xzLCBjbGllbnQubmFtZSkubGVuZ3RoXG5cbiAgICAvLyBDb21tb24gaGFuZGxlcnMgZm9yIE1DUCBtZW51c1xuICAgIGNvbnN0IGhhbmRsZU1jcFZpZXdUb29scyA9ICgpID0+IHtcbiAgICAgIHNldFZpZXdTdGF0ZSh7IHR5cGU6ICdtY3AtdG9vbHMnLCBjbGllbnQgfSlcbiAgICB9XG5cbiAgICBjb25zdCBoYW5kbGVNY3BDYW5jZWwgPSAoKSA9PiB7XG4gICAgICBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1saXN0JylcbiAgICB9XG5cbiAgICBjb25zdCBoYW5kbGVNY3BDb21wbGV0ZSA9IChyZXN1bHQ/OiBzdHJpbmcpID0+IHtcbiAgICAgIGlmIChyZXN1bHQpIHtcbiAgICAgICAgc2V0UmVzdWx0KHJlc3VsdClcbiAgICAgIH1cbiAgICAgIHNldFZpZXdTdGF0ZSgncGx1Z2luLWxpc3QnKVxuICAgIH1cblxuICAgIC8vIFRyYW5zZm9ybSBNQ1BTZXJ2ZXJDb25uZWN0aW9uIHRvIGFwcHJvcHJpYXRlIFNlcnZlckluZm8gdHlwZVxuICAgIGNvbnN0IHNjb3BlID0gY2xpZW50LmNvbmZpZy5zY29wZVxuICAgIGNvbnN0IGNvbmZpZ1R5cGUgPSBjbGllbnQuY29uZmlnLnR5cGVcblxuICAgIGlmIChjb25maWdUeXBlID09PSAnc3RkaW8nKSB7XG4gICAgICBjb25zdCBzZXJ2ZXI6IFN0ZGlvU2VydmVySW5mbyA9IHtcbiAgICAgICAgbmFtZTogY2xpZW50Lm5hbWUsXG4gICAgICAgIGNsaWVudCxcbiAgICAgICAgc2NvcGUsXG4gICAgICAgIHRyYW5zcG9ydDogJ3N0ZGlvJyxcbiAgICAgICAgY29uZmlnOiBjbGllbnQuY29uZmlnIGFzIE1jcFN0ZGlvU2VydmVyQ29uZmlnLFxuICAgICAgfVxuICAgICAgcmV0dXJuIChcbiAgICAgICAgPE1DUFN0ZGlvU2VydmVyTWVudVxuICAgICAgICAgIHNlcnZlcj17c2VydmVyfVxuICAgICAgICAgIHNlcnZlclRvb2xzQ291bnQ9e3NlcnZlclRvb2xzQ291bnR9XG4gICAgICAgICAgb25WaWV3VG9vbHM9e2hhbmRsZU1jcFZpZXdUb29sc31cbiAgICAgICAgICBvbkNhbmNlbD17aGFuZGxlTWNwQ2FuY2VsfVxuICAgICAgICAgIG9uQ29tcGxldGU9e2hhbmRsZU1jcENvbXBsZXRlfVxuICAgICAgICAgIGJvcmRlcmxlc3NcbiAgICAgICAgLz5cbiAgICAgIClcbiAgICB9IGVsc2UgaWYgKGNvbmZpZ1R5cGUgPT09ICdzc2UnKSB7XG4gICAgICBjb25zdCBzZXJ2ZXI6IFNTRVNlcnZlckluZm8gPSB7XG4gICAgICAgIG5hbWU6IGNsaWVudC5uYW1lLFxuICAgICAgICBjbGllbnQsXG4gICAgICAgIHNjb3BlLFxuICAgICAgICB0cmFuc3BvcnQ6ICdzc2UnLFxuICAgICAgICBpc0F1dGhlbnRpY2F0ZWQ6IHVuZGVmaW5lZCxcbiAgICAgICAgY29uZmlnOiBjbGllbnQuY29uZmlnIGFzIE1jcFNTRVNlcnZlckNvbmZpZyxcbiAgICAgIH1cbiAgICAgIHJldHVybiAoXG4gICAgICAgIDxNQ1BSZW1vdGVTZXJ2ZXJNZW51XG4gICAgICAgICAgc2VydmVyPXtzZXJ2ZXJ9XG4gICAgICAgICAgc2VydmVyVG9vbHNDb3VudD17c2VydmVyVG9vbHNDb3VudH1cbiAgICAgICAgICBvblZpZXdUb29scz17aGFuZGxlTWNwVmlld1Rvb2xzfVxuICAgICAgICAgIG9uQ2FuY2VsPXtoYW5kbGVNY3BDYW5jZWx9XG4gICAgICAgICAgb25Db21wbGV0ZT17aGFuZGxlTWNwQ29tcGxldGV9XG4gICAgICAgICAgYm9yZGVybGVzc1xuICAgICAgICAvPlxuICAgICAgKVxuICAgIH0gZWxzZSBpZiAoY29uZmlnVHlwZSA9PT0gJ2h0dHAnKSB7XG4gICAgICBjb25zdCBzZXJ2ZXI6IEhUVFBTZXJ2ZXJJbmZvID0ge1xuICAgICAgICBuYW1lOiBjbGllbnQubmFtZSxcbiAgICAgICAgY2xpZW50LFxuICAgICAgICBzY29wZSxcbiAgICAgICAgdHJhbnNwb3J0OiAnaHR0cCcsXG4gICAgICAgIGlzQXV0aGVudGljYXRlZDogdW5kZWZpbmVkLFxuICAgICAgICBjb25maWc6IGNsaWVudC5jb25maWcgYXMgTWNwSFRUUFNlcnZlckNvbmZpZyxcbiAgICAgIH1cbiAgICAgIHJldHVybiAoXG4gICAgICAgIDxNQ1BSZW1vdGVTZXJ2ZXJNZW51XG4gICAgICAgICAgc2VydmVyPXtzZXJ2ZXJ9XG4gICAgICAgICAgc2VydmVyVG9vbHNDb3VudD17c2VydmVyVG9vbHNDb3VudH1cbiAgICAgICAgICBvblZpZXdUb29scz17aGFuZGxlTWNwVmlld1Rvb2xzfVxuICAgICAgICAgIG9uQ2FuY2VsPXtoYW5kbGVNY3BDYW5jZWx9XG4gICAgICAgICAgb25Db21wbGV0ZT17aGFuZGxlTWNwQ29tcGxldGV9XG4gICAgICAgICAgYm9yZGVybGVzc1xuICAgICAgICAvPlxuICAgICAgKVxuICAgIH0gZWxzZSBpZiAoY29uZmlnVHlwZSA9PT0gJ2NsYXVkZWFpLXByb3h5Jykge1xuICAgICAgY29uc3Qgc2VydmVyOiBDbGF1ZGVBSVNlcnZlckluZm8gPSB7XG4gICAgICAgIG5hbWU6IGNsaWVudC5uYW1lLFxuICAgICAgICBjbGllbnQsXG4gICAgICAgIHNjb3BlLFxuICAgICAgICB0cmFuc3BvcnQ6ICdjbGF1ZGVhaS1wcm94eScsXG4gICAgICAgIGlzQXV0aGVudGljYXRlZDogdW5kZWZpbmVkLFxuICAgICAgICBjb25maWc6IGNsaWVudC5jb25maWcgYXMgTWNwQ2xhdWRlQUlQcm94eVNlcnZlckNvbmZpZyxcbiAgICAgIH1cbiAgICAgIHJldHVybiAoXG4gICAgICAgIDxNQ1BSZW1vdGVTZXJ2ZXJNZW51XG4gICAgICAgICAgc2VydmVyPXtzZXJ2ZXJ9XG4gICAgICAgICAgc2VydmVyVG9vbHNDb3VudD17c2VydmVyVG9vbHNDb3VudH1cbiAgICAgICAgICBvblZpZXdUb29scz17aGFuZGxlTWNwVmlld1Rvb2xzfVxuICAgICAgICAgIG9uQ2FuY2VsPXtoYW5kbGVNY3BDYW5jZWx9XG4gICAgICAgICAgb25Db21wbGV0ZT17aGFuZGxlTWNwQ29tcGxldGV9XG4gICAgICAgICAgYm9yZGVybGVzc1xuICAgICAgICAvPlxuICAgICAgKVxuICAgIH1cblxuICAgIC8vIEZhbGxiYWNrIC0gc2hvdWxkbid0IGhhcHBlbiBidXQgaGFuZGxlIGdyYWNlZnVsbHlcbiAgICBzZXRWaWV3U3RhdGUoJ3BsdWdpbi1saXN0JylcbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgLy8gTUNQIHRvb2xzIHZpZXdcbiAgaWYgKHR5cGVvZiB2aWV3U3RhdGUgPT09ICdvYmplY3QnICYmIHZpZXdTdGF0ZS50eXBlID09PSAnbWNwLXRvb2xzJykge1xuICAgIGNvbnN0IGNsaWVudCA9IHZpZXdTdGF0ZS5jbGllbnRcbiAgICBjb25zdCBzY29wZSA9IGNsaWVudC5jb25maWcuc2NvcGVcbiAgICBjb25zdCBjb25maWdUeXBlID0gY2xpZW50LmNvbmZpZy50eXBlXG5cbiAgICAvLyBCdWlsZCBTZXJ2ZXJJbmZvIGZvciBNQ1BUb29sTGlzdFZpZXdcbiAgICBsZXQgc2VydmVyOlxuICAgICAgfCBTdGRpb1NlcnZlckluZm9cbiAgICAgIHwgU1NFU2VydmVySW5mb1xuICAgICAgfCBIVFRQU2VydmVySW5mb1xuICAgICAgfCBDbGF1ZGVBSVNlcnZlckluZm9cbiAgICBpZiAoY29uZmlnVHlwZSA9PT0gJ3N0ZGlvJykge1xuICAgICAgc2VydmVyID0ge1xuICAgICAgICBuYW1lOiBjbGllbnQubmFtZSxcbiAgICAgICAgY2xpZW50LFxuICAgICAgICBzY29wZSxcbiAgICAgICAgdHJhbnNwb3J0OiAnc3RkaW8nLFxuICAgICAgICBjb25maWc6IGNsaWVudC5jb25maWcgYXMgTWNwU3RkaW9TZXJ2ZXJDb25maWcsXG4gICAgICB9XG4gICAgfSBlbHNlIGlmIChjb25maWdUeXBlID09PSAnc3NlJykge1xuICAgICAgc2VydmVyID0ge1xuICAgICAgICBuYW1lOiBjbGllbnQubmFtZSxcbiAgICAgICAgY2xpZW50LFxuICAgICAgICBzY29wZSxcbiAgICAgICAgdHJhbnNwb3J0OiAnc3NlJyxcbiAgICAgICAgaXNBdXRoZW50aWNhdGVkOiB1bmRlZmluZWQsXG4gICAgICAgIGNvbmZpZzogY2xpZW50LmNvbmZpZyBhcyBNY3BTU0VTZXJ2ZXJDb25maWcsXG4gICAgICB9XG4gICAgfSBlbHNlIGlmIChjb25maWdUeXBlID09PSAnaHR0cCcpIHtcbiAgICAgIHNlcnZlciA9IHtcbiAgICAgICAgbmFtZTogY2xpZW50Lm5hbWUsXG4gICAgICAgIGNsaWVudCxcbiAgICAgICAgc2NvcGUsXG4gICAgICAgIHRyYW5zcG9ydDogJ2h0dHAnLFxuICAgICAgICBpc0F1dGhlbnRpY2F0ZWQ6IHVuZGVmaW5lZCxcbiAgICAgICAgY29uZmlnOiBjbGllbnQuY29uZmlnIGFzIE1jcEhUVFBTZXJ2ZXJDb25maWcsXG4gICAgICB9XG4gICAgfSBlbHNlIHtcbiAgICAgIHNlcnZlciA9IHtcbiAgICAgICAgbmFtZTogY2xpZW50Lm5hbWUsXG4gICAgICAgIGNsaWVudCxcbiAgICAgICAgc2NvcGUsXG4gICAgICAgIHRyYW5zcG9ydDogJ2NsYXVkZWFpLXByb3h5JyxcbiAgICAgICAgaXNBdXRoZW50aWNhdGVkOiB1bmRlZmluZWQsXG4gICAgICAgIGNvbmZpZzogY2xpZW50LmNvbmZpZyBhcyBNY3BDbGF1ZGVBSVByb3h5U2VydmVyQ29uZmlnLFxuICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiAoXG4gICAgICA8TUNQVG9vbExpc3RWaWV3XG4gICAgICAgIHNlcnZlcj17c2VydmVyfVxuICAgICAgICBvblNlbGVjdFRvb2w9eyh0b29sOiBUb29sKSA9PiB7XG4gICAgICAgICAgc2V0Vmlld1N0YXRlKHsgdHlwZTogJ21jcC10b29sLWRldGFpbCcsIGNsaWVudCwgdG9vbCB9KVxuICAgICAgICB9fVxuICAgICAgICBvbkJhY2s9eygpID0+IHNldFZpZXdTdGF0ZSh7IHR5cGU6ICdtY3AtZGV0YWlsJywgY2xpZW50IH0pfVxuICAgICAgLz5cbiAgICApXG4gIH1cblxuICAvLyBNQ1AgdG9vbCBkZXRhaWwgdmlld1xuICBpZiAodHlwZW9mIHZpZXdTdGF0ZSA9PT0gJ29iamVjdCcgJiYgdmlld1N0YXRlLnR5cGUgPT09ICdtY3AtdG9vbC1kZXRhaWwnKSB7XG4gICAgY29uc3QgeyBjbGllbnQsIHRvb2wgfSA9IHZpZXdTdGF0ZVxuICAgIGNvbnN0IHNjb3BlID0gY2xpZW50LmNvbmZpZy5zY29wZVxuICAgIGNvbnN0IGNvbmZpZ1R5cGUgPSBjbGllbnQuY29uZmlnLnR5cGVcblxuICAgIC8vIEJ1aWxkIFNlcnZlckluZm8gZm9yIE1DUFRvb2xEZXRhaWxWaWV3XG4gICAgbGV0IHNlcnZlcjpcbiAgICAgIHwgU3RkaW9TZXJ2ZXJJbmZvXG4gICAgICB8IFNTRVNlcnZlckluZm9cbiAgICAgIHwgSFRUUFNlcnZlckluZm9cbiAgICAgIHwgQ2xhdWRlQUlTZXJ2ZXJJbmZvXG4gICAgaWYgKGNvbmZpZ1R5cGUgPT09ICdzdGRpbycpIHtcbiAgICAgIHNlcnZlciA9IHtcbiAgICAgICAgbmFtZTogY2xpZW50Lm5hbWUsXG4gICAgICAgIGNsaWVudCxcbiAgICAgICAgc2NvcGUsXG4gICAgICAgIHRyYW5zcG9ydDogJ3N0ZGlvJyxcbiAgICAgICAgY29uZmlnOiBjbGllbnQuY29uZmlnIGFzIE1jcFN0ZGlvU2VydmVyQ29uZmlnLFxuICAgICAgfVxuICAgIH0gZWxzZSBpZiAoY29uZmlnVHlwZSA9PT0gJ3NzZScpIHtcbiAgICAgIHNlcnZlciA9IHtcbiAgICAgICAgbmFtZTogY2xpZW50Lm5hbWUsXG4gICAgICAgIGNsaWVudCxcbiAgICAgICAgc2NvcGUsXG4gICAgICAgIHRyYW5zcG9ydDogJ3NzZScsXG4gICAgICAgIGlzQXV0aGVudGljYXRlZDogdW5kZWZpbmVkLFxuICAgICAgICBjb25maWc6IGNsaWVudC5jb25maWcgYXMgTWNwU1NFU2VydmVyQ29uZmlnLFxuICAgICAgfVxuICAgIH0gZWxzZSBpZiAoY29uZmlnVHlwZSA9PT0gJ2h0dHAnKSB7XG4gICAgICBzZXJ2ZXIgPSB7XG4gICAgICAgIG5hbWU6IGNsaWVudC5uYW1lLFxuICAgICAgICBjbGllbnQsXG4gICAgICAgIHNjb3BlLFxuICAgICAgICB0cmFuc3BvcnQ6ICdodHRwJyxcbiAgICAgICAgaXNBdXRoZW50aWNhdGVkOiB1bmRlZmluZWQsXG4gICAgICAgIGNvbmZpZzogY2xpZW50LmNvbmZpZyBhcyBNY3BIVFRQU2VydmVyQ29uZmlnLFxuICAgICAgfVxuICAgIH0gZWxzZSB7XG4gICAgICBzZXJ2ZXIgPSB7XG4gICAgICAgIG5hbWU6IGNsaWVudC5uYW1lLFxuICAgICAgICBjbGllbnQsXG4gICAgICAgIHNjb3BlLFxuICAgICAgICB0cmFuc3BvcnQ6ICdjbGF1ZGVhaS1wcm94eScsXG4gICAgICAgIGlzQXV0aGVudGljYXRlZDogdW5kZWZpbmVkLFxuICAgICAgICBjb25maWc6IGNsaWVudC5jb25maWcgYXMgTWNwQ2xhdWRlQUlQcm94eVNlcnZlckNvbmZpZyxcbiAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gKFxuICAgICAgPE1DUFRvb2xEZXRhaWxWaWV3XG4gICAgICAgIHRvb2w9e3Rvb2x9XG4gICAgICAgIHNlcnZlcj17c2VydmVyfVxuICAgICAgICBvbkJhY2s9eygpID0+IHNldFZpZXdTdGF0ZSh7IHR5cGU6ICdtY3AtdG9vbHMnLCBjbGllbnQgfSl9XG4gICAgICAvPlxuICAgIClcbiAgfVxuXG4gIC8vIFBsdWdpbiBsaXN0IHZpZXcgKG1haW4gbWFuYWdlbWVudCBpbnRlcmZhY2UpXG4gIGNvbnN0IHZpc2libGVJdGVtcyA9IHBhZ2luYXRpb24uZ2V0VmlzaWJsZUl0ZW1zKGZpbHRlcmVkSXRlbXMpXG5cbiAgcmV0dXJuIChcbiAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgIHsvKiBTZWFyY2ggYm94ICovfVxuICAgICAgPEJveCBtYXJnaW5Cb3R0b209ezF9PlxuICAgICAgICA8U2VhcmNoQm94XG4gICAgICAgICAgcXVlcnk9e3NlYXJjaFF1ZXJ5fVxuICAgICAgICAgIGlzRm9jdXNlZD17aXNTZWFyY2hNb2RlfVxuICAgICAgICAgIGlzVGVybWluYWxGb2N1c2VkPXtpc1Rlcm1pbmFsRm9jdXNlZH1cbiAgICAgICAgICB3aWR0aD17dGVybWluYWxXaWR0aCAtIDR9XG4gICAgICAgICAgY3Vyc29yT2Zmc2V0PXtzZWFyY2hDdXJzb3JPZmZzZXR9XG4gICAgICAgIC8+XG4gICAgICA8L0JveD5cblxuICAgICAgey8qIE5vIHNlYXJjaCByZXN1bHRzICovfVxuICAgICAge2ZpbHRlcmVkSXRlbXMubGVuZ3RoID09PSAwICYmIHNlYXJjaFF1ZXJ5ICYmIChcbiAgICAgICAgPEJveCBtYXJnaW5Cb3R0b209ezF9PlxuICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPk5vIGl0ZW1zIG1hdGNoICZxdW90O3tzZWFyY2hRdWVyeX0mcXVvdDs8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgKX1cblxuICAgICAgey8qIFNjcm9sbCB1cCBpbmRpY2F0b3IgKi99XG4gICAgICB7cGFnaW5hdGlvbi5zY3JvbGxQb3NpdGlvbi5jYW5TY3JvbGxVcCAmJiAoXG4gICAgICAgIDxCb3g+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IHtmaWd1cmVzLmFycm93VXB9IG1vcmUgYWJvdmU8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgKX1cblxuICAgICAgey8qIFVuaWZpZWQgbGlzdCBvZiBwbHVnaW5zIGFuZCBNQ1BzIGdyb3VwZWQgYnkgc2NvcGUgKi99XG4gICAgICB7dmlzaWJsZUl0ZW1zLm1hcCgoaXRlbSwgdmlzaWJsZUluZGV4KSA9PiB7XG4gICAgICAgIGNvbnN0IGFjdHVhbEluZGV4ID0gcGFnaW5hdGlvbi50b0FjdHVhbEluZGV4KHZpc2libGVJbmRleClcbiAgICAgICAgY29uc3QgaXNTZWxlY3RlZCA9IGFjdHVhbEluZGV4ID09PSBzZWxlY3RlZEluZGV4ICYmICFpc1NlYXJjaE1vZGVcblxuICAgICAgICAvLyBDaGVjayBpZiB3ZSBuZWVkIHRvIHNob3cgYSBzY29wZSBoZWFkZXJcbiAgICAgICAgY29uc3QgcHJldkl0ZW0gPVxuICAgICAgICAgIHZpc2libGVJbmRleCA+IDAgPyB2aXNpYmxlSXRlbXNbdmlzaWJsZUluZGV4IC0gMV0gOiBudWxsXG4gICAgICAgIGNvbnN0IHNob3dTY29wZUhlYWRlciA9ICFwcmV2SXRlbSB8fCBwcmV2SXRlbS5zY29wZSAhPT0gaXRlbS5zY29wZVxuXG4gICAgICAgIC8vIEdldCBzY29wZSBsYWJlbFxuICAgICAgICBjb25zdCBnZXRTY29wZUxhYmVsID0gKHNjb3BlOiBzdHJpbmcpOiBzdHJpbmcgPT4ge1xuICAgICAgICAgIHN3aXRjaCAoc2NvcGUpIHtcbiAgICAgICAgICAgIGNhc2UgJ2ZsYWdnZWQnOlxuICAgICAgICAgICAgICByZXR1cm4gJ0ZsYWdnZWQnXG4gICAgICAgICAgICBjYXNlICdwcm9qZWN0JzpcbiAgICAgICAgICAgICAgcmV0dXJuICdQcm9qZWN0J1xuICAgICAgICAgICAgY2FzZSAnbG9jYWwnOlxuICAgICAgICAgICAgICByZXR1cm4gJ0xvY2FsJ1xuICAgICAgICAgICAgY2FzZSAndXNlcic6XG4gICAgICAgICAgICAgIHJldHVybiAnVXNlcidcbiAgICAgICAgICAgIGNhc2UgJ2VudGVycHJpc2UnOlxuICAgICAgICAgICAgICByZXR1cm4gJ0VudGVycHJpc2UnXG4gICAgICAgICAgICBjYXNlICdtYW5hZ2VkJzpcbiAgICAgICAgICAgICAgcmV0dXJuICdNYW5hZ2VkJ1xuICAgICAgICAgICAgY2FzZSAnYnVpbHRpbic6XG4gICAgICAgICAgICAgIHJldHVybiAnQnVpbHQtaW4nXG4gICAgICAgICAgICBjYXNlICdkeW5hbWljJzpcbiAgICAgICAgICAgICAgcmV0dXJuICdCdWlsdC1pbidcbiAgICAgICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgICAgIHJldHVybiBzY29wZVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiAoXG4gICAgICAgICAgPFJlYWN0LkZyYWdtZW50IGtleT17aXRlbS5pZH0+XG4gICAgICAgICAgICB7c2hvd1Njb3BlSGVhZGVyICYmIChcbiAgICAgICAgICAgICAgPEJveCBtYXJnaW5Ub3A9e3Zpc2libGVJbmRleCA+IDAgPyAxIDogMH0gcGFkZGluZ0xlZnQ9ezJ9PlxuICAgICAgICAgICAgICAgIDxUZXh0XG4gICAgICAgICAgICAgICAgICBkaW1Db2xvcj17aXRlbS5zY29wZSAhPT0gJ2ZsYWdnZWQnfVxuICAgICAgICAgICAgICAgICAgY29sb3I9e2l0ZW0uc2NvcGUgPT09ICdmbGFnZ2VkJyA/ICd3YXJuaW5nJyA6IHVuZGVmaW5lZH1cbiAgICAgICAgICAgICAgICAgIGJvbGQ9e2l0ZW0uc2NvcGUgPT09ICdmbGFnZ2VkJ31cbiAgICAgICAgICAgICAgICA+XG4gICAgICAgICAgICAgICAgICB7Z2V0U2NvcGVMYWJlbChpdGVtLnNjb3BlKX1cbiAgICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgIDwvQm94PlxuICAgICAgICAgICAgKX1cbiAgICAgICAgICAgIDxVbmlmaWVkSW5zdGFsbGVkQ2VsbCBpdGVtPXtpdGVtfSBpc1NlbGVjdGVkPXtpc1NlbGVjdGVkfSAvPlxuICAgICAgICAgIDwvUmVhY3QuRnJhZ21lbnQ+XG4gICAgICAgIClcbiAgICAgIH0pfVxuXG4gICAgICB7LyogU2Nyb2xsIGRvd24gaW5kaWNhdG9yICovfVxuICAgICAge3BhZ2luYXRpb24uc2Nyb2xsUG9zaXRpb24uY2FuU2Nyb2xsRG93biAmJiAoXG4gICAgICAgIDxCb3g+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+IHtmaWd1cmVzLmFycm93RG93bn0gbW9yZSBiZWxvdzwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICApfVxuXG4gICAgICB7LyogSGVscCB0ZXh0ICovfVxuICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9IG1hcmdpbkxlZnQ9ezF9PlxuICAgICAgICA8VGV4dCBkaW1Db2xvciBpdGFsaWM+XG4gICAgICAgICAgPEJ5bGluZT5cbiAgICAgICAgICAgIDxUZXh0PnR5cGUgdG8gc2VhcmNoPC9UZXh0PlxuICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICBhY3Rpb249XCJwbHVnaW46dG9nZ2xlXCJcbiAgICAgICAgICAgICAgY29udGV4dD1cIlBsdWdpblwiXG4gICAgICAgICAgICAgIGZhbGxiYWNrPVwiU3BhY2VcIlxuICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cInRvZ2dsZVwiXG4gICAgICAgICAgICAvPlxuICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICBhY3Rpb249XCJzZWxlY3Q6YWNjZXB0XCJcbiAgICAgICAgICAgICAgY29udGV4dD1cIlNlbGVjdFwiXG4gICAgICAgICAgICAgIGZhbGxiYWNrPVwiRW50ZXJcIlxuICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cImRldGFpbHNcIlxuICAgICAgICAgICAgLz5cbiAgICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgICAgYWN0aW9uPVwiY29uZmlybTpub1wiXG4gICAgICAgICAgICAgIGNvbnRleHQ9XCJDb25maXJtYXRpb25cIlxuICAgICAgICAgICAgICBmYWxsYmFjaz1cIkVzY1wiXG4gICAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwiYmFja1wiXG4gICAgICAgICAgICAvPlxuICAgICAgICAgIDwvQnlsaW5lPlxuICAgICAgICA8L1RleHQ+XG4gICAgICA8L0JveD5cblxuICAgICAgey8qIFJlbG9hZCBkaXNjbGFpbWVyIGZvciBwbHVnaW4gY2hhbmdlcyAqL31cbiAgICAgIHtwZW5kaW5nVG9nZ2xlcy5zaXplID4gMCAmJiAoXG4gICAgICAgIDxCb3ggbWFyZ2luTGVmdD17MX0+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3IgaXRhbGljPlxuICAgICAgICAgICAgUnVuIC9yZWxvYWQtcGx1Z2lucyB0byBhcHBseSBjaGFuZ2VzXG4gICAgICAgICAgPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICl9XG4gICAgPC9Cb3g+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IkFBQUEsT0FBT0EsT0FBTyxNQUFNLFNBQVM7QUFDN0IsY0FBY0MsTUFBTSxRQUFRLElBQUk7QUFDaEMsT0FBTyxLQUFLQyxFQUFFLE1BQU0sYUFBYTtBQUNqQyxPQUFPLEtBQUtDLElBQUksTUFBTSxNQUFNO0FBQzVCLE9BQU8sS0FBS0MsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0MsV0FBVyxFQUFFQyxTQUFTLEVBQUVDLE9BQU8sRUFBRUMsTUFBTSxFQUFFQyxRQUFRLFFBQVEsT0FBTztBQUN6RSxTQUFTQyx3QkFBd0IsUUFBUSw4Q0FBOEM7QUFDdkYsU0FBU0MsTUFBTSxRQUFRLDBDQUEwQztBQUNqRSxTQUFTQyxtQkFBbUIsUUFBUSw2Q0FBNkM7QUFDakYsU0FBU0Msa0JBQWtCLFFBQVEsNENBQTRDO0FBQy9FLFNBQVNDLGlCQUFpQixRQUFRLDJDQUEyQztBQUM3RSxTQUFTQyxlQUFlLFFBQVEseUNBQXlDO0FBQ3pFLGNBQ0VDLGtCQUFrQixFQUNsQkMsY0FBYyxFQUNkQyxhQUFhLEVBQ2JDLGVBQWUsUUFDViwrQkFBK0I7QUFDdEMsU0FBU0MsU0FBUyxRQUFRLCtCQUErQjtBQUN6RCxTQUFTQyxjQUFjLFFBQVEsK0JBQStCO0FBQzlELFNBQVNDLGVBQWUsUUFBUSxnQ0FBZ0M7QUFDaEU7QUFDQSxTQUFTQyxHQUFH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 \ No newline at end of file diff --git a/src/commands/plugin/PluginErrors.tsx b/src/commands/plugin/PluginErrors.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8991e8d2d7cd0088fbafc2b8581ae1a34102365e --- /dev/null +++ b/src/commands/plugin/PluginErrors.tsx @@ -0,0 +1,124 @@ +import { getPluginErrorMessage, type PluginError } from '../../types/plugin.js'; +export function formatErrorMessage(error: PluginError): string { + switch (error.type) { + case 'path-not-found': + return `${error.component} path not found: ${error.path}`; + case 'git-auth-failed': + return `Git ${error.authType.toUpperCase()} authentication failed for ${error.gitUrl}`; + case 'git-timeout': + return `Git ${error.operation} timed out for ${error.gitUrl}`; + case 'network-error': + return `Network error accessing ${error.url}${error.details ? `: ${error.details}` : ''}`; + case 'manifest-parse-error': + return `Failed to parse manifest at ${error.manifestPath}: ${error.parseError}`; + case 'manifest-validation-error': + return `Invalid manifest at ${error.manifestPath}: ${error.validationErrors.join(', ')}`; + case 'plugin-not-found': + return `Plugin "${error.pluginId}" not found in marketplace "${error.marketplace}"`; + case 'marketplace-not-found': + return `Marketplace "${error.marketplace}" not found`; + case 'marketplace-load-failed': + return `Failed to load marketplace "${error.marketplace}": ${error.reason}`; + case 'mcp-config-invalid': + return `Invalid MCP server config for "${error.serverName}": ${error.validationError}`; + case 'mcp-server-suppressed-duplicate': + { + const dup = error.duplicateOf.startsWith('plugin:') ? `server provided by plugin "${error.duplicateOf.split(':')[1] ?? '?'}"` : `already-configured "${error.duplicateOf}"`; + return `MCP server "${error.serverName}" skipped — same command/URL as ${dup}`; + } + case 'hook-load-failed': + return `Failed to load hooks from ${error.hookPath}: ${error.reason}`; + case 'component-load-failed': + return `Failed to load ${error.component} from ${error.path}: ${error.reason}`; + case 'mcpb-download-failed': + return `Failed to download MCPB from ${error.url}: ${error.reason}`; + case 'mcpb-extract-failed': + return `Failed to extract MCPB ${error.mcpbPath}: ${error.reason}`; + case 'mcpb-invalid-manifest': + return `MCPB manifest invalid at ${error.mcpbPath}: ${error.validationError}`; + case 'marketplace-blocked-by-policy': + return error.blockedByBlocklist ? `Marketplace "${error.marketplace}" is blocked by enterprise policy` : `Marketplace "${error.marketplace}" is not in the allowed marketplace list`; + case 'dependency-unsatisfied': + return error.reason === 'not-enabled' ? `Dependency "${error.dependency}" is disabled` : `Dependency "${error.dependency}" is not installed`; + case 'lsp-config-invalid': + return `Invalid LSP server config for "${error.serverName}": ${error.validationError}`; + case 'lsp-server-start-failed': + return `LSP server "${error.serverName}" failed to start: ${error.reason}`; + case 'lsp-server-crashed': + return error.signal ? `LSP server "${error.serverName}" crashed with signal ${error.signal}` : `LSP server "${error.serverName}" crashed with exit code ${error.exitCode ?? 'unknown'}`; + case 'lsp-request-timeout': + return `LSP server "${error.serverName}" timed out on ${error.method} after ${error.timeoutMs}ms`; + case 'lsp-request-failed': + return `LSP server "${error.serverName}" ${error.method} failed: ${error.error}`; + case 'plugin-cache-miss': + return `Plugin "${error.plugin}" not cached at ${error.installPath}`; + case 'generic-error': + return error.error; + } + const _exhaustive: never = error; + return getPluginErrorMessage(_exhaustive); +} +export function getErrorGuidance(error: PluginError): string | null { + switch (error.type) { + case 'path-not-found': + return 'Check that the path in your manifest or marketplace config is correct'; + case 'git-auth-failed': + return error.authType === 'ssh' ? 'Configure SSH keys or use HTTPS URL instead' : 'Configure credentials or use SSH URL instead'; + case 'git-timeout': + case 'network-error': + return 'Check your internet connection and try again'; + case 'manifest-parse-error': + return 'Check manifest file syntax in the plugin directory'; + case 'manifest-validation-error': + return 'Check manifest file follows the required schema'; + case 'plugin-not-found': + return `Plugin may not exist in marketplace "${error.marketplace}"`; + case 'marketplace-not-found': + return error.availableMarketplaces.length > 0 ? `Available marketplaces: ${error.availableMarketplaces.join(', ')}` : 'Add the marketplace first using /plugin marketplace add'; + case 'mcp-config-invalid': + return 'Check MCP server configuration in .mcp.json or manifest'; + case 'mcp-server-suppressed-duplicate': + { + // duplicateOf is "plugin:name:srv" when another plugin won dedup — + // users can't remove plugin-provided servers from their MCP config, + // so point them at the winning plugin instead. + if (error.duplicateOf.startsWith('plugin:')) { + const winningPlugin = error.duplicateOf.split(':')[1] ?? 'the other plugin'; + return `Disable plugin "${winningPlugin}" if you want this plugin's version instead`; + } + return `Remove "${error.duplicateOf}" from your MCP config if you want the plugin's version instead`; + } + case 'hook-load-failed': + return 'Check hooks.json file syntax and structure'; + case 'component-load-failed': + return `Check ${error.component} directory structure and file permissions`; + case 'mcpb-download-failed': + return 'Check your internet connection and URL accessibility'; + case 'mcpb-extract-failed': + return 'Verify the MCPB file is valid and not corrupted'; + case 'mcpb-invalid-manifest': + return 'Contact the plugin author about the invalid manifest'; + case 'marketplace-blocked-by-policy': + if (error.blockedByBlocklist) { + return 'This marketplace source is explicitly blocked by your administrator'; + } + return error.allowedSources.length > 0 ? `Allowed sources: ${error.allowedSources.join(', ')}` : 'Contact your administrator to configure allowed marketplace sources'; + case 'dependency-unsatisfied': + return error.reason === 'not-enabled' ? `Enable "${error.dependency}" or uninstall "${error.plugin}"` : `Install "${error.dependency}" or uninstall "${error.plugin}"`; + case 'lsp-config-invalid': + return 'Check LSP server configuration in the plugin manifest'; + case 'lsp-server-start-failed': + case 'lsp-server-crashed': + case 'lsp-request-timeout': + case 'lsp-request-failed': + return 'Check LSP server logs with --debug for details'; + case 'plugin-cache-miss': + return 'Run /plugins to refresh the plugin cache'; + case 'marketplace-load-failed': + case 'generic-error': + return null; + } + const _exhaustive: never = error; + return null; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJnZXRQbHVnaW5FcnJvck1lc3NhZ2UiLCJQbHVnaW5FcnJvciIsImZvcm1hdEVycm9yTWVzc2FnZSIsImVycm9yIiwidHlwZSIsImNvbXBvbmVudCIsInBhdGgiLCJhdXRoVHlwZSIsInRvVXBwZXJDYXNlIiwiZ2l0VXJsIiwib3BlcmF0aW9uIiwidXJsIiwiZGV0YWlscyIsIm1hbmlmZXN0UGF0aCIsInBhcnNlRXJyb3IiLCJ2YWxpZGF0aW9uRXJyb3JzIiwiam9pbiIsInBsdWdpbklkIiwibWFya2V0cGxhY2UiLCJyZWFzb24iLCJzZXJ2ZXJOYW1lIiwidmFsaWRhdGlvbkVycm9yIiwiZHVwIiwiZHVwbGljYXRlT2YiLCJzdGFydHNXaXRoIiwic3BsaXQiLCJob29rUGF0aCIsIm1jcGJQYXRoIiwiYmxvY2tlZEJ5QmxvY2tsaXN0IiwiZGVwZW5kZW5jeSIsInNpZ25hbCIsImV4aXRDb2RlIiwibWV0aG9kIiwidGltZW91dE1zIiwicGx1Z2luIiwiaW5zdGFsbFBhdGgiLCJfZXhoYXVzdGl2ZSIsImdldEVycm9yR3VpZGFuY2UiLCJhdmFpbGFibGVNYXJrZXRwbGFjZXMiLCJsZW5ndGgiLCJ3aW5uaW5nUGx1Z2luIiwiYWxsb3dlZFNvdXJjZXMiXSwic291cmNlcyI6WyJQbHVnaW5FcnJvcnMudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IGdldFBsdWdpbkVycm9yTWVzc2FnZSwgdHlwZSBQbHVnaW5FcnJvciB9IGZyb20gJy4uLy4uL3R5cGVzL3BsdWdpbi5qcydcblxuZXhwb3J0IGZ1bmN0aW9uIGZvcm1hdEVycm9yTWVzc2FnZShlcnJvcjogUGx1Z2luRXJyb3IpOiBzdHJpbmcge1xuICBzd2l0Y2ggKGVycm9yLnR5cGUpIHtcbiAgICBjYXNlICdwYXRoLW5vdC1mb3VuZCc6XG4gICAgICByZXR1cm4gYCR7ZXJyb3IuY29tcG9uZW50fSBwYXRoIG5vdCBmb3VuZDogJHtlcnJvci5wYXRofWBcbiAgICBjYXNlICdnaXQtYXV0aC1mYWlsZWQnOlxuICAgICAgcmV0dXJuIGBHaXQgJHtlcnJvci5hdXRoVHlwZS50b1VwcGVyQ2FzZSgpfSBhdXRoZW50aWNhdGlvbiBmYWlsZWQgZm9yICR7ZXJyb3IuZ2l0VXJsfWBcbiAgICBjYXNlICdnaXQtdGltZW91dCc6XG4gICAgICByZXR1cm4gYEdpdCAke2Vycm9yLm9wZXJhdGlvbn0gdGltZWQgb3V0IGZvciAke2Vycm9yLmdpdFVybH1gXG4gICAgY2FzZSAnbmV0d29yay1lcnJvcic6XG4gICAgICByZXR1cm4gYE5ldHdvcmsgZXJyb3IgYWNjZXNzaW5nICR7ZXJyb3IudXJsfSR7ZXJyb3IuZGV0YWlscyA/IGA6ICR7ZXJyb3IuZGV0YWlsc31gIDogJyd9YFxuICAgIGNhc2UgJ21hbmlmZXN0LXBhcnNlLWVycm9yJzpcbiAgICAgIHJldHVybiBgRmFpbGVkIHRvIHBhcnNlIG1hbmlmZXN0IGF0ICR7ZXJyb3IubWFuaWZlc3RQYXRofTogJHtlcnJvci5wYXJzZUVycm9yfWBcbiAgICBjYXNlICdtYW5pZmVzdC12YWxpZGF0aW9uLWVycm9yJzpcbiAgICAgIHJldHVybiBgSW52YWxpZCBtYW5pZmVzdCBhdCAke2Vycm9yLm1hbmlmZXN0UGF0aH06ICR7ZXJyb3IudmFsaWRhdGlvbkVycm9ycy5qb2luKCcsICcpfWBcbiAgICBjYXNlICdwbHVnaW4tbm90LWZvdW5kJzpcbiAgICAgIHJldHVybiBgUGx1Z2luIFwiJHtlcnJvci5wbHVnaW5JZH1cIiBub3QgZm91bmQgaW4gbWFya2V0cGxhY2UgXCIke2Vycm9yLm1hcmtldHBsYWNlfVwiYFxuICAgIGNhc2UgJ21hcmtldHBsYWNlLW5vdC1mb3VuZCc6XG4gICAgICByZXR1cm4gYE1hcmtldHBsYWNlIFwiJHtlcnJvci5tYXJrZXRwbGFjZX1cIiBub3QgZm91bmRgXG4gICAgY2FzZSAnbWFya2V0cGxhY2UtbG9hZC1mYWlsZWQnOlxuICAgICAgcmV0dXJuIGBGYWlsZWQgdG8gbG9hZCBtYXJrZXRwbGFjZSBcIiR7ZXJyb3IubWFya2V0cGxhY2V9XCI6ICR7ZXJyb3IucmVhc29ufWBcbiAgICBjYXNlICdtY3AtY29uZmlnLWludmFsaWQnOlxuICAgICAgcmV0dXJuIGBJbnZhbGlkIE1DUCBzZXJ2ZXIgY29uZmlnIGZvciBcIiR7ZXJyb3Iuc2VydmVyTmFtZX1cIjogJHtlcnJvci52YWxpZGF0aW9uRXJyb3J9YFxuICAgIGNhc2UgJ21jcC1zZXJ2ZXItc3VwcHJlc3NlZC1kdXBsaWNhdGUnOiB7XG4gICAgICBjb25zdCBkdXAgPSBlcnJvci5kdXBsaWNhdGVPZi5zdGFydHNXaXRoKCdwbHVnaW46JylcbiAgICAgICAgPyBgc2VydmVyIHByb3ZpZGVkIGJ5IHBsdWdpbiBcIiR7ZXJyb3IuZHVwbGljYXRlT2Yuc3BsaXQoJzonKVsxXSA/PyAnPyd9XCJgXG4gICAgICAgIDogYGFscmVhZHktY29uZmlndXJlZCBcIiR7ZXJyb3IuZHVwbGljYXRlT2Z9XCJgXG4gICAgICByZXR1cm4gYE1DUCBzZXJ2ZXIgXCIke2Vycm9yLnNlcnZlck5hbWV9XCIgc2tpcHBlZCDigJQgc2FtZSBjb21tYW5kL1VSTCBhcyAke2R1cH1gXG4gICAgfVxuICAgIGNhc2UgJ2hvb2stbG9hZC1mYWlsZWQnOlxuICAgICAgcmV0dXJuIGBGYWlsZWQgdG8gbG9hZCBob29rcyBmcm9tICR7ZXJyb3IuaG9va1BhdGh9OiAke2Vycm9yLnJlYXNvbn1gXG4gICAgY2FzZSAnY29tcG9uZW50LWxvYWQtZmFpbGVkJzpcbiAgICAgIHJldHVybiBgRmFpbGVkIHRvIGxvYWQgJHtlcnJvci5jb21wb25lbnR9IGZyb20gJHtlcnJvci5wYXRofTogJHtlcnJvci5yZWFzb259YFxuICAgIGNhc2UgJ21jcGItZG93bmxvYWQtZmFpbGVkJzpcbiAgICAgIHJldHVybiBgRmFpbGVkIHRvIGRvd25sb2FkIE1DUEIgZnJvbSAke2Vycm9yLnVybH06ICR7ZXJyb3IucmVhc29ufWBcbiAgICBjYXNlICdtY3BiLWV4dHJhY3QtZmFpbGVkJzpcbiAgICAgIHJldHVybiBgRmFpbGVkIHRvIGV4dHJhY3QgTUNQQiAke2Vycm9yLm1jcGJQYXRofTogJHtlcnJvci5yZWFzb259YFxuICAgIGNhc2UgJ21jcGItaW52YWxpZC1tYW5pZmVzdCc6XG4gICAgICByZXR1cm4gYE1DUEIgbWFuaWZlc3QgaW52YWxpZCBhdCAke2Vycm9yLm1jcGJQYXRofTogJHtlcnJvci52YWxpZGF0aW9uRXJyb3J9YFxuICAgIGNhc2UgJ21hcmtldHBsYWNlLWJsb2NrZWQtYnktcG9saWN5JzpcbiAgICAgIHJldHVybiBlcnJvci5ibG9ja2VkQnlCbG9ja2xpc3RcbiAgICAgICAgPyBgTWFya2V0cGxhY2UgXCIke2Vycm9yLm1hcmtldHBsYWNlfVwiIGlzIGJsb2NrZWQgYnkgZW50ZXJwcmlzZSBwb2xpY3lgXG4gICAgICAgIDogYE1hcmtldHBsYWNlIFwiJHtlcnJvci5tYXJrZXRwbGFjZX1cIiBpcyBub3QgaW4gdGhlIGFsbG93ZWQgbWFya2V0cGxhY2UgbGlzdGBcbiAgICBjYXNlICdkZXBlbmRlbmN5LXVuc2F0aXNmaWVkJzpcbiAgICAgIHJldHVybiBlcnJvci5yZWFzb24gPT09ICdub3QtZW5hYmxlZCdcbiAgICAgICAgPyBgRGVwZW5kZW5jeSBcIiR7ZXJyb3IuZGVwZW5kZW5jeX1cIiBpcyBkaXNhYmxlZGBcbiAgICAgICAgOiBgRGVwZW5kZW5jeSBcIiR7ZXJyb3IuZGVwZW5kZW5jeX1cIiBpcyBub3QgaW5zdGFsbGVkYFxuICAgIGNhc2UgJ2xzcC1jb25maWctaW52YWxpZCc6XG4gICAgICByZXR1cm4gYEludmFsaWQgTFNQIHNlcnZlciBjb25maWcgZm9yIFwiJHtlcnJvci5zZXJ2ZXJOYW1lfVwiOiAke2Vycm9yLnZhbGlkYXRpb25FcnJvcn1gXG4gICAgY2FzZSAnbHNwLXNlcnZlci1zdGFydC1mYWlsZWQnOlxuICAgICAgcmV0dXJuIGBMU1Agc2VydmVyIFwiJHtlcnJvci5zZXJ2ZXJOYW1lfVwiIGZhaWxlZCB0byBzdGFydDogJHtlcnJvci5yZWFzb259YFxuICAgIGNhc2UgJ2xzcC1zZXJ2ZXItY3Jhc2hlZCc6XG4gICAgICByZXR1cm4gZXJyb3Iuc2lnbmFsXG4gICAgICAgID8gYExTUCBzZXJ2ZXIgXCIke2Vycm9yLnNlcnZlck5hbWV9XCIgY3Jhc2hlZCB3aXRoIHNpZ25hbCAke2Vycm9yLnNpZ25hbH1gXG4gICAgICAgIDogYExTUCBzZXJ2ZXIgXCIke2Vycm9yLnNlcnZlck5hbWV9XCIgY3Jhc2hlZCB3aXRoIGV4aXQgY29kZSAke2Vycm9yLmV4aXRDb2RlID8/ICd1bmtub3duJ31gXG4gICAgY2FzZSAnbHNwLXJlcXVlc3QtdGltZW91dCc6XG4gICAgICByZXR1cm4gYExTUCBzZXJ2ZXIgXCIke2Vycm9yLnNlcnZlck5hbWV9XCIgdGltZWQgb3V0IG9uICR7ZXJyb3IubWV0aG9kfSBhZnRlciAke2Vycm9yLnRpbWVvdXRNc31tc2BcbiAgICBjYXNlICdsc3AtcmVxdWVzdC1mYWlsZWQnOlxuICAgICAgcmV0dXJuIGBMU1Agc2VydmVyIFwiJHtlcnJvci5zZXJ2ZXJOYW1lfVwiICR7ZXJyb3IubWV0aG9kfSBmYWlsZWQ6ICR7ZXJyb3IuZXJyb3J9YFxuICAgIGNhc2UgJ3BsdWdpbi1jYWNoZS1taXNzJzpcbiAgICAgIHJldHVybiBgUGx1Z2luIFwiJHtlcnJvci5wbHVnaW59XCIgbm90IGNhY2hlZCBhdCAke2Vycm9yLmluc3RhbGxQYXRofWBcbiAgICBjYXNlICdnZW5lcmljLWVycm9yJzpcbiAgICAgIHJldHVybiBlcnJvci5lcnJvclxuICB9XG4gIGNvbnN0IF9leGhhdXN0aXZlOiBuZXZlciA9IGVycm9yXG4gIHJldHVybiBnZXRQbHVnaW5FcnJvck1lc3NhZ2UoX2V4aGF1c3RpdmUpXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRFcnJvckd1aWRhbmNlKGVycm9yOiBQbHVnaW5FcnJvcik6IHN0cmluZyB8IG51bGwge1xuICBzd2l0Y2ggKGVycm9yLnR5cGUpIHtcbiAgICBjYXNlICdwYXRoLW5vdC1mb3VuZCc6XG4gICAgICByZXR1cm4gJ0NoZWNrIHRoYXQgdGhlIHBhdGggaW4geW91ciBtYW5pZmVzdCBvciBtYXJrZXRwbGFjZSBjb25maWcgaXMgY29ycmVjdCdcbiAgICBjYXNlICdnaXQtYXV0aC1mYWlsZWQnOlxuICAgICAgcmV0dXJuIGVycm9yLmF1dGhUeXBlID09PSAnc3NoJ1xuICAgICAgICA/ICdDb25maWd1cmUgU1NIIGtleXMgb3IgdXNlIEhUVFBTIFVSTCBpbnN0ZWFkJ1xuICAgICAgICA6ICdDb25maWd1cmUgY3JlZGVudGlhbHMgb3IgdXNlIFNTSCBVUkwgaW5zdGVhZCdcbiAgICBjYXNlICdnaXQtdGltZW91dCc6XG4gICAgY2FzZSAnbmV0d29yay1lcnJvcic6XG4gICAgICByZXR1cm4gJ0NoZWNrIHlvdXIgaW50ZXJuZXQgY29ubmVjdGlvbiBhbmQgdHJ5IGFnYWluJ1xuICAgIGNhc2UgJ21hbmlmZXN0LXBhcnNlLWVycm9yJzpcbiAgICAgIHJldHVybiAnQ2hlY2sgbWFuaWZlc3QgZmlsZSBzeW50YXggaW4gdGhlIHBsdWdpbiBkaXJlY3RvcnknXG4gICAgY2FzZSAnbWFuaWZlc3QtdmFsaWRhdGlvbi1lcnJvcic6XG4gICAgICByZXR1cm4gJ0NoZWNrIG1hbmlmZXN0IGZpbGUgZm9sbG93cyB0aGUgcmVxdWlyZWQgc2NoZW1hJ1xuICAgIGNhc2UgJ3BsdWdpbi1ub3QtZm91bmQnOlxuICAgICAgcmV0dXJuIGBQbHVnaW4gbWF5IG5vdCBleGlzdCBpbiBtYXJrZXRwbGFjZSBcIiR7ZXJyb3IubWFya2V0cGxhY2V9XCJgXG4gICAgY2FzZSAnbWFya2V0cGxhY2Utbm90LWZvdW5kJzpcbiAgICAgIHJldHVybiBlcnJvci5hdmFpbGFibGVNYXJrZXRwbGFjZXMubGVuZ3RoID4gMFxuICAgICAgICA/IGBBdmFpbGFibGUgbWFya2V0cGxhY2VzOiAke2Vycm9yLmF2YWlsYWJsZU1hcmtldHBsYWNlcy5qb2luKCcsICcpfWBcbiAgICAgICAgOiAnQWRkIHRoZSBtYXJrZXRwbGFjZSBmaXJzdCB1c2luZyAvcGx1Z2luIG1hcmtldHBsYWNlIGFkZCdcbiAgICBjYXNlICdtY3AtY29uZmlnLWludmFsaWQnOlxuICAgICAgcmV0dXJuICdDaGVjayBNQ1Agc2VydmVyIGNvbmZpZ3VyYXRpb24gaW4gLm1jcC5qc29uIG9yIG1hbmlmZXN0J1xuICAgIGNhc2UgJ21jcC1zZXJ2ZXItc3VwcHJlc3NlZC1kdXBsaWNhdGUnOiB7XG4gICAgICAvLyBkdXBsaWNhdGVPZiBpcyBcInBsdWdpbjpuYW1lOnNydlwiIHdoZW4gYW5vdGhlciBwbHVnaW4gd29uIGRlZHVwIOKAlFxuICAgICAgLy8gdXNlcnMgY2FuJ3QgcmVtb3ZlIHBsdWdpbi1wcm92aWRlZCBzZXJ2ZXJzIGZyb20gdGhlaXIgTUNQIGNvbmZpZyxcbiAgICAgIC8vIHNvIHBvaW50IHRoZW0gYXQgdGhlIHdpbm5pbmcgcGx1Z2luIGluc3RlYWQuXG4gICAgICBpZiAoZXJyb3IuZHVwbGljYXRlT2Yuc3RhcnRzV2l0aCgncGx1Z2luOicpKSB7XG4gICAgICAgIGNvbnN0IHdpbm5pbmdQbHVnaW4gPVxuICAgICAgICAgIGVycm9yLmR1cGxpY2F0ZU9mLnNwbGl0KCc6JylbMV0gPz8gJ3RoZSBvdGhlciBwbHVnaW4nXG4gICAgICAgIHJldHVybiBgRGlzYWJsZSBwbHVnaW4gXCIke3dpbm5pbmdQbHVnaW59XCIgaWYgeW91IHdhbnQgdGhpcyBwbHVnaW4ncyB2ZXJzaW9uIGluc3RlYWRgXG4gICAgICB9XG4gICAgICByZXR1cm4gYFJlbW92ZSBcIiR7ZXJyb3IuZHVwbGljYXRlT2Z9XCIgZnJvbSB5b3VyIE1DUCBjb25maWcgaWYgeW91IHdhbnQgdGhlIHBsdWdpbidzIHZlcnNpb24gaW5zdGVhZGBcbiAgICB9XG4gICAgY2FzZSAnaG9vay1sb2FkLWZhaWxlZCc6XG4gICAgICByZXR1cm4gJ0NoZWNrIGhvb2tzLmpzb24gZmlsZSBzeW50YXggYW5kIHN0cnVjdHVyZSdcbiAgICBjYXNlICdjb21wb25lbnQtbG9hZC1mYWlsZWQnOlxuICAgICAgcmV0dXJuIGBDaGVjayAke2Vycm9yLmNvbXBvbmVudH0gZGlyZWN0b3J5IHN0cnVjdHVyZSBhbmQgZmlsZSBwZXJtaXNzaW9uc2BcbiAgICBjYXNlICdtY3BiLWRvd25sb2FkLWZhaWxlZCc6XG4gICAgICByZXR1cm4gJ0NoZWNrIHlvdXIgaW50ZXJuZXQgY29ubmVjdGlvbiBhbmQgVVJMIGFjY2Vzc2liaWxpdHknXG4gICAgY2FzZSAnbWNwYi1leHRyYWN0LWZhaWxlZCc6XG4gICAgICByZXR1cm4gJ1ZlcmlmeSB0aGUgTUNQQiBmaWxlIGlzIHZhbGlkIGFuZCBub3QgY29ycnVwdGVkJ1xuICAgIGNhc2UgJ21jcGItaW52YWxpZC1tYW5pZmVzdCc6XG4gICAgICByZXR1cm4gJ0NvbnRhY3QgdGhlIHBsdWdpbiBhdXRob3IgYWJvdXQgdGhlIGludmFsaWQgbWFuaWZlc3QnXG4gICAgY2FzZSAnbWFya2V0cGxhY2UtYmxvY2tlZC1ieS1wb2xpY3knOlxuICAgICAgaWYgKGVycm9yLmJsb2NrZWRCeUJsb2NrbGlzdCkge1xuICAgICAgICByZXR1cm4gJ1RoaXMgbWFya2V0cGxhY2Ugc291cmNlIGlzIGV4cGxpY2l0bHkgYmxvY2tlZCBieSB5b3VyIGFkbWluaXN0cmF0b3InXG4gICAgICB9XG4gICAgICByZXR1cm4gZXJyb3IuYWxsb3dlZFNvdXJjZXMubGVuZ3RoID4gMFxuICAgICAgICA/IGBBbGxvd2VkIHNvdXJjZXM6ICR7ZXJyb3IuYWxsb3dlZFNvdXJjZXMuam9pbignLCAnKX1gXG4gICAgICAgIDogJ0NvbnRhY3QgeW91ciBhZG1pbmlzdHJhdG9yIHRvIGNvbmZpZ3VyZSBhbGxvd2VkIG1hcmtldHBsYWNlIHNvdXJjZXMnXG4gICAgY2FzZSAnZGVwZW5kZW5jeS11bnNhdGlzZmllZCc6XG4gICAgICByZXR1cm4gZXJyb3IucmVhc29uID09PSAnbm90LWVuYWJsZWQnXG4gICAgICAgID8gYEVuYWJsZSBcIiR7ZXJyb3IuZGVwZW5kZW5jeX1cIiBvciB1bmluc3RhbGwgXCIke2Vycm9yLnBsdWdpbn1cImBcbiAgICAgICAgOiBgSW5zdGFsbCBcIiR7ZXJyb3IuZGVwZW5kZW5jeX1cIiBvciB1bmluc3RhbGwgXCIke2Vycm9yLnBsdWdpbn1cImBcbiAgICBjYXNlICdsc3AtY29uZmlnLWludmFsaWQnOlxuICAgICAgcmV0dXJuICdDaGVjayBMU1Agc2VydmVyIGNvbmZpZ3VyYXRpb24gaW4gdGhlIHBsdWdpbiBtYW5pZmVzdCdcbiAgICBjYXNlICdsc3Atc2VydmVyLXN0YXJ0LWZhaWxlZCc6XG4gICAgY2FzZSAnbHNwLXNlcnZlci1jcmFzaGVkJzpcbiAgICBjYXNlICdsc3AtcmVxdWVzdC10aW1lb3V0JzpcbiAgICBjYXNlICdsc3AtcmVxdWVzdC1mYWlsZWQnOlxuICAgICAgcmV0dXJuICdDaGVjayBMU1Agc2VydmVyIGxvZ3Mgd2l0aCAtLWRlYnVnIGZvciBkZXRhaWxzJ1xuICAgIGNhc2UgJ3BsdWdpbi1jYWNoZS1taXNzJzpcbiAgICAgIHJldHVybiAnUnVuIC9wbHVnaW5zIHRvIHJlZnJlc2ggdGhlIHBsdWdpbiBjYWNoZSdcbiAgICBjYXNlICdtYXJrZXRwbGFjZS1sb2FkLWZhaWxlZCc6XG4gICAgY2FzZSAnZ2VuZXJpYy1lcnJvcic6XG4gICAgICByZXR1cm4gbnVsbFxuICB9XG4gIGNvbnN0IF9leGhhdXN0aXZlOiBuZXZlciA9IGVycm9yXG4gIHJldHVybiBudWxsXG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLFNBQVNBLHFCQUFxQixFQUFFLEtBQUtDLFdBQVcsUUFBUSx1QkFBdUI7QUFFL0UsT0FBTyxTQUFTQyxrQkFBa0JBLENBQUNDLEtBQUssRUFBRUYsV0FBVyxDQUFDLEVBQUUsTUFBTSxDQUFDO0VBQzdELFFBQVFFLEtBQUssQ0FBQ0MsSUFBSTtJQUNoQixLQUFLLGdCQUFnQjtNQUNuQixPQUFPLEdBQUdELEtBQUssQ0FBQ0UsU0FBUyxvQkFBb0JGLEtBQUssQ0FBQ0csSUFBSSxFQUFFO0lBQzNELEtBQUssaUJBQWlCO01BQ3BCLE9BQU8sT0FBT0gsS0FBSyxDQUFDSSxRQUFRLENBQUNDLFdBQVcsQ0FBQyxDQUFDLDhCQUE4QkwsS0FBSyxDQUFDTSxNQUFNLEVBQUU7SUFDeEYsS0FBSyxh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 \ No newline at end of file diff --git a/src/commands/plugin/PluginOptionsDialog.tsx b/src/commands/plugin/PluginOptionsDialog.tsx new file mode 100644 index 0000000000000000000000000000000000000000..87e6721b70908c0d0fd48b94f7e6b994959266f1 --- /dev/null +++ b/src/commands/plugin/PluginOptionsDialog.tsx @@ -0,0 +1,357 @@ +import { c as _c } from "react/compiler-runtime"; +import figures from 'figures'; +import React, { useCallback, useState } from 'react'; +import { Dialog } from '../../components/design-system/Dialog.js'; +import { stringWidth } from '../../ink/stringWidth.js'; +// eslint-disable-next-line custom-rules/prefer-use-keybindings -- raw text input for config dialog +import { Box, Text, useInput } from '../../ink.js'; +import { useKeybinding, useKeybindings } from '../../keybindings/useKeybinding.js'; +import { isEnvTruthy } from '../../utils/envUtils.js'; +import type { PluginOptionSchema, PluginOptionValues } from '../../utils/plugins/pluginOptionsStorage.js'; + +/** + * Build the onSave payload from collected string inputs. + * + * Sensitive fields are never prepopulated in the text buffer (security), so + * by the time the user reaches the last field every sensitive field they + * stepped through contains '' in collected. To avoid silently wiping saved + * secrets on reconfigure: if a sensitive field is '' AND initialValues has + * a value for it, OMIT the key entirely. savePluginOptions only writes keys + * it receives, so omitting = keep existing. + * + * Exported for unit testing. + */ +export function buildFinalValues(fields: string[], collected: Record, configSchema: PluginOptionSchema, initialValues: PluginOptionValues | undefined): PluginOptionValues { + const finalValues: PluginOptionValues = {}; + for (const fieldKey of fields) { + const schema = configSchema[fieldKey]; + const value = collected[fieldKey] ?? ''; + if (schema?.sensitive === true && value === '' && initialValues?.[fieldKey] !== undefined) { + continue; + } + if (schema?.type === 'number') { + // Number('') returns 0, not NaN — omit blank number inputs so + // validateUserConfig's required check actually catches them. + if (value.trim() === '') continue; + const num = Number(value); + finalValues[fieldKey] = Number.isNaN(num) ? value : num; + } else if (schema?.type === 'boolean') { + finalValues[fieldKey] = isEnvTruthy(value); + } else { + finalValues[fieldKey] = value; + } + } + return finalValues; +} +type Props = { + title: string; + subtitle: string; + configSchema: PluginOptionSchema; + /** Pre-fill fields when reconfiguring. Sensitive fields are not prepopulated. */ + initialValues?: PluginOptionValues; + onSave: (config: PluginOptionValues) => void; + onCancel: () => void; +}; +export function PluginOptionsDialog(t0) { + const $ = _c(70); + const { + title, + subtitle, + configSchema, + initialValues, + onSave, + onCancel + } = t0; + let t1; + if ($[0] !== configSchema) { + t1 = Object.keys(configSchema); + $[0] = configSchema; + $[1] = t1; + } else { + t1 = $[1]; + } + const fields = t1; + let t2; + if ($[2] !== configSchema || $[3] !== initialValues) { + t2 = key => { + if (configSchema[key]?.sensitive === true) { + return ""; + } + const v = initialValues?.[key]; + return v === undefined ? "" : String(v); + }; + $[2] = configSchema; + $[3] = initialValues; + $[4] = t2; + } else { + t2 = $[4]; + } + const initialFor = t2; + const [currentFieldIndex, setCurrentFieldIndex] = useState(0); + let t3; + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { + t3 = {}; + $[5] = t3; + } else { + t3 = $[5]; + } + const [values, setValues] = useState(t3); + let t4; + if ($[6] !== fields[0] || $[7] !== initialFor) { + t4 = () => fields[0] ? initialFor(fields[0]) : ""; + $[6] = fields[0]; + $[7] = initialFor; + $[8] = t4; + } else { + t4 = $[8]; + } + const [currentInput, setCurrentInput] = useState(t4); + const currentField = fields[currentFieldIndex]; + const fieldSchema = currentField ? configSchema[currentField] : null; + let t5; + if ($[9] === Symbol.for("react.memo_cache_sentinel")) { + t5 = { + context: "Settings" + }; + $[9] = t5; + } else { + t5 = $[9]; + } + useKeybinding("confirm:no", onCancel, t5); + let t6; + if ($[10] !== currentField || $[11] !== currentFieldIndex || $[12] !== currentInput || $[13] !== fields || $[14] !== initialFor) { + t6 = () => { + if (currentFieldIndex < fields.length - 1 && currentField) { + setValues(prev => ({ + ...prev, + [currentField]: currentInput + })); + setCurrentFieldIndex(_temp); + const nextKey = fields[currentFieldIndex + 1]; + setCurrentInput(nextKey ? initialFor(nextKey) : ""); + } + }; + $[10] = currentField; + $[11] = currentFieldIndex; + $[12] = currentInput; + $[13] = fields; + $[14] = initialFor; + $[15] = t6; + } else { + t6 = $[15]; + } + const handleNextField = t6; + let t7; + if ($[16] !== configSchema || $[17] !== currentField || $[18] !== currentFieldIndex || $[19] !== currentInput || $[20] !== fields || $[21] !== initialFor || $[22] !== initialValues || $[23] !== onSave || $[24] !== values) { + t7 = () => { + if (!currentField) { + return; + } + const newValues = { + ...values, + [currentField]: currentInput + }; + if (currentFieldIndex === fields.length - 1) { + onSave(buildFinalValues(fields, newValues, configSchema, initialValues)); + } else { + setValues(newValues); + setCurrentFieldIndex(_temp2); + const nextKey_0 = fields[currentFieldIndex + 1]; + setCurrentInput(nextKey_0 ? initialFor(nextKey_0) : ""); + } + }; + $[16] = configSchema; + $[17] = currentField; + $[18] = currentFieldIndex; + $[19] = currentInput; + $[20] = fields; + $[21] = initialFor; + $[22] = initialValues; + $[23] = onSave; + $[24] = values; + $[25] = t7; + } else { + t7 = $[25]; + } + const handleConfirm = t7; + let t8; + if ($[26] !== handleConfirm || $[27] !== handleNextField) { + t8 = { + "confirm:nextField": handleNextField, + "confirm:yes": handleConfirm + }; + $[26] = handleConfirm; + $[27] = handleNextField; + $[28] = t8; + } else { + t8 = $[28]; + } + let t9; + if ($[29] === Symbol.for("react.memo_cache_sentinel")) { + t9 = { + context: "Confirmation" + }; + $[29] = t9; + } else { + t9 = $[29]; + } + useKeybindings(t8, t9); + let t10; + if ($[30] === Symbol.for("react.memo_cache_sentinel")) { + t10 = (char, key_0) => { + if (key_0.backspace || key_0.delete) { + setCurrentInput(_temp3); + return; + } + if (char && !key_0.ctrl && !key_0.meta && !key_0.tab && !key_0.return) { + setCurrentInput(prev_3 => prev_3 + char); + } + }; + $[30] = t10; + } else { + t10 = $[30]; + } + useInput(t10); + if (!fieldSchema || !currentField) { + return null; + } + const isSensitive = fieldSchema.sensitive === true; + const isRequired = fieldSchema.required === true; + let t11; + if ($[31] !== currentInput || $[32] !== isSensitive) { + t11 = isSensitive ? "*".repeat(stringWidth(currentInput)) : currentInput; + $[31] = currentInput; + $[32] = isSensitive; + $[33] = t11; + } else { + t11 = $[33]; + } + const displayValue = t11; + const t12 = fieldSchema.title || currentField; + let t13; + if ($[34] !== isRequired) { + t13 = isRequired && *; + $[34] = isRequired; + $[35] = t13; + } else { + t13 = $[35]; + } + let t14; + if ($[36] !== t12 || $[37] !== t13) { + t14 = {t12}{t13}; + $[36] = t12; + $[37] = t13; + $[38] = t14; + } else { + t14 = $[38]; + } + let t15; + if ($[39] !== fieldSchema.description) { + t15 = fieldSchema.description && {fieldSchema.description}; + $[39] = fieldSchema.description; + $[40] = t15; + } else { + t15 = $[40]; + } + let t16; + if ($[41] === Symbol.for("react.memo_cache_sentinel")) { + t16 = {figures.pointerSmall} ; + $[41] = t16; + } else { + t16 = $[41]; + } + let t17; + if ($[42] !== displayValue) { + t17 = {displayValue}; + $[42] = displayValue; + $[43] = t17; + } else { + t17 = $[43]; + } + let t18; + if ($[44] === Symbol.for("react.memo_cache_sentinel")) { + t18 = ; + $[44] = t18; + } else { + t18 = $[44]; + } + let t19; + if ($[45] !== t17) { + t19 = {t16}{t17}{t18}; + $[45] = t17; + $[46] = t19; + } else { + t19 = $[46]; + } + let t20; + if ($[47] !== t14 || $[48] !== t15 || $[49] !== t19) { + t20 = {t14}{t15}{t19}; + $[47] = t14; + $[48] = t15; + $[49] = t19; + $[50] = t20; + } else { + t20 = $[50]; + } + const t21 = currentFieldIndex + 1; + let t22; + if ($[51] !== fields.length || $[52] !== t21) { + t22 = Field {t21} of {fields.length}; + $[51] = fields.length; + $[52] = t21; + $[53] = t22; + } else { + t22 = $[53]; + } + let t23; + if ($[54] !== currentFieldIndex || $[55] !== fields.length) { + t23 = currentFieldIndex < fields.length - 1 && Tab: Next field · Enter: Save and continue; + $[54] = currentFieldIndex; + $[55] = fields.length; + $[56] = t23; + } else { + t23 = $[56]; + } + let t24; + if ($[57] !== currentFieldIndex || $[58] !== fields.length) { + t24 = currentFieldIndex === fields.length - 1 && Enter: Save configuration; + $[57] = currentFieldIndex; + $[58] = fields.length; + $[59] = t24; + } else { + t24 = $[59]; + } + let t25; + if ($[60] !== t22 || $[61] !== t23 || $[62] !== t24) { + t25 = {t22}{t23}{t24}; + $[60] = t22; + $[61] = t23; + $[62] = t24; + $[63] = t25; + } else { + t25 = $[63]; + } + let t26; + if ($[64] !== onCancel || $[65] !== subtitle || $[66] !== t20 || $[67] !== t25 || $[68] !== title) { + t26 = {t20}{t25}; + $[64] = onCancel; + $[65] = subtitle; + $[66] = t20; + $[67] = t25; + $[68] = title; + $[69] = t26; + } else { + t26 = $[69]; + } + return t26; +} +function _temp3(prev_2) { + return prev_2.slice(0, -1); +} +function _temp2(prev_1) { + return prev_1 + 1; +} +function _temp(prev_0) { + return prev_0 + 1; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwiUmVhY3QiLCJ1c2VDYWxsYmFjayIsInVzZVN0YXRlIiwiRGlhbG9nIiwic3RyaW5nV2lkdGgiLCJCb3giLCJUZXh0IiwidXNlSW5wdXQiLCJ1c2VLZXliaW5kaW5nIiwidXNlS2V5YmluZGluZ3MiLCJpc0VudlRydXRoeSIsIlBsdWdpbk9wdGlvblNjaGVtYSIsIlBsdWdpbk9wdGlvblZhbHVlcyIsImJ1aWxkRmluYWxWYWx1ZXMiLCJmaWVsZHMiLCJjb2xsZWN0ZWQiLCJSZWNvcmQiLCJjb25maWdTY2hlbWEiLCJpbml0aWFsVmFsdWVzIiwiZmluYWxWYWx1ZXMiLCJmaWVsZEtleSIsInNjaGVtYSIsInZhbHVlIiwic2Vuc2l0aXZlIiwidW5kZWZpbmVkIiwidHlwZSIsInRyaW0iLCJudW0iLCJOdW1iZXIiLCJpc05hTiIsIlByb3BzIiwidGl0bGUiLCJzdWJ0aXRsZSIsIm9uU2F2ZSIsImNvbmZpZyIsIm9uQ2FuY2VsIiwiUGx1Z2luT3B0aW9uc0RpYWxvZyIsInQwIiwiJCIsIl9jIiwidDEiLCJPYmplY3QiLCJrZXlzIiwidDIiLCJrZXkiLCJ2IiwiU3RyaW5nIiwiaW5pdGlhbEZvciIsImN1cnJlbnRGaWVsZEluZGV4Iiwic2V0Q3VycmVudEZpZWxkSW5kZXgiLCJ0MyIsIlN5bWJvbCIsImZvciIsInZhbHVlcyIsInNldFZhbHVlcyIsInQ0IiwiY3VycmVudElucHV0Iiwic2V0Q3VycmVudElucHV0IiwiY3VycmVudEZpZWxkIiwiZmllbGRTY2hlbWEiLCJ0NSIsImNvbnRleHQiLCJ0NiIsImxlbmd0aCIsInByZXYiLCJfdGVtcCIsIm5leHRLZXkiLCJoYW5kbGVOZXh0RmllbGQiLCJ0NyIsIm5ld1ZhbHVlcyIsIl90ZW1wMiIsIm5leHRLZXlfMCIsImhhbmRsZUNvbmZpcm0iLCJ0OCIsInQ5IiwidDEwIiwiY2hhciIsImtleV8wIiwiYmFja3NwYWNlIiwiZGVsZXRlIiwiX3RlbXAzIiwiY3RybCIsIm1ldGEiLCJ0YWIiLCJyZXR1cm4iLCJwcmV2XzMiLCJpc1NlbnNpdGl2ZSIsImlzUmVxdWlyZWQiLCJyZXF1aXJlZCIsInQxMSIsInJlcGVhdCIsImRpc3BsYXlWYWx1ZSIsInQxMiIsInQxMyIsInQxNCIsInQxNSIsImRlc2NyaXB0aW9uIiwidDE2IiwicG9pbnRlclNtYWxsIiwidDE3IiwidDE4IiwidDE5IiwidDIwIiwidDIxIiwidDIyIiwidDIzIiwidDI0IiwidDI1IiwidDI2IiwicHJldl8yIiwic2xpY2UiLCJwcmV2XzEiLCJwcmV2XzAiXSwic291cmNlcyI6WyJQbHVnaW5PcHRpb25zRGlhbG9nLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgZmlndXJlcyBmcm9tICdmaWd1cmVzJ1xuaW1wb3J0IFJlYWN0LCB7IHVzZUNhbGxiYWNrLCB1c2VTdGF0ZSB9IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgRGlhbG9nIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL0RpYWxvZy5qcydcbmltcG9ydCB7IHN0cmluZ1dpZHRoIH0gZnJvbSAnLi4vLi4vaW5rL3N0cmluZ1dpZHRoLmpzJ1xuLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGN1c3RvbS1ydWxlcy9wcmVmZXItdXNlLWtleWJpbmRpbmdzIC0tIHJhdyB0ZXh0IGlucHV0IGZvciBjb25maWcgZGlhbG9nXG5pbXBvcnQgeyBCb3gsIFRleHQsIHVzZUlucHV0IH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHtcbiAgdXNlS2V5YmluZGluZyxcbiAgdXNlS2V5YmluZGluZ3MsXG59IGZyb20gJy4uLy4uL2tleWJpbmRpbmdzL3VzZUtleWJpbmRpbmcuanMnXG5pbXBvcnQgeyBpc0VudlRydXRoeSB9IGZyb20gJy4uLy4uL3V0aWxzL2VudlV0aWxzLmpzJ1xuaW1wb3J0IHR5cGUge1xuICBQbHVnaW5PcHRpb25TY2hlbWEsXG4gIFBsdWdpbk9wdGlvblZhbHVlcyxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9wbHVnaW5PcHRpb25zU3RvcmFnZS5qcydcblxuLyoqXG4gKiBCdWlsZCB0aGUgb25TYXZlIHBheWxvYWQgZnJvbSBjb2xsZWN0ZWQgc3RyaW5nIGlucHV0cy5cbiAqXG4gKiBTZW5zaXRpdmUgZmllbGRzIGFyZSBuZXZlciBwcmVwb3B1bGF0ZWQgaW4gdGhlIHRleHQgYnVmZmVyIChzZWN1cml0eSksIHNvXG4gKiBieSB0aGUgdGltZSB0aGUgdXNlciByZWFjaGVzIHRoZSBsYXN0IGZpZWxkIGV2ZXJ5IHNlbnNpdGl2ZSBmaWVsZCB0aGV5XG4gKiBzdGVwcGVkIHRocm91Z2ggY29udGFpbnMgJycgaW4gY29sbGVjdGVkLiBUbyBhdm9pZCBzaWxlbnRseSB3aXBpbmcgc2F2ZWRcbiAqIHNlY3JldHMgb24gcmVjb25maWd1cmU6IGlmIGEgc2Vuc2l0aXZlIGZpZWxkIGlzICcnIEFORCBpbml0aWFsVmFsdWVzIGhhc1xuICogYSB2YWx1ZSBmb3IgaXQsIE9NSVQgdGhlIGtleSBlbnRpcmVseS4gc2F2ZVBsdWdpbk9wdGlvbnMgb25seSB3cml0ZXMga2V5c1xuICogaXQgcmVjZWl2ZXMsIHNvIG9taXR0aW5nID0ga2VlcCBleGlzdGluZy5cbiAqXG4gKiBFeHBvcnRlZCBmb3IgdW5pdCB0ZXN0aW5nLlxuICovXG5leHBvcnQgZnVuY3Rpb24gYnVpbGRGaW5hbFZhbHVlcyhcbiAgZmllbGRzOiBzdHJpbmdbXSxcbiAgY29sbGVjdGVkOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+LFxuICBjb25maWdTY2hlbWE6IFBsdWdpbk9wdGlvblNjaGVtYSxcbiAgaW5pdGlhbFZhbHVlczogUGx1Z2luT3B0aW9uVmFsdWVzIHwgdW5kZWZpbmVkLFxuKTogUGx1Z2luT3B0aW9uVmFsdWVzIHtcbiAgY29uc3QgZmluYWxWYWx1ZXM6IFBsdWdpbk9wdGlvblZhbHVlcyA9IHt9XG4gIGZvciAoY29uc3QgZmllbGRLZXkgb2YgZmllbGRzKSB7XG4gICAgY29uc3Qgc2NoZW1hID0gY29uZmlnU2NoZW1hW2ZpZWxkS2V5XVxuICAgIGNvbnN0IHZhbHVlID0gY29sbGVjdGVkW2ZpZWxkS2V5XSA/PyAnJ1xuXG4gICAgaWYgKFxuICAgICAgc2NoZW1hPy5zZW5zaXRpdmUgPT09IHRydWUgJiZcbiAgICAgIHZhbHVlID09PSAnJyAmJlxuICAgICAgaW5pdGlhbFZhbHVlcz8uW2ZpZWxkS2V5XSAhPT0gdW5kZWZpbmVkXG4gICAgKSB7XG4gICAgICBjb250aW51ZVxuICAgIH1cblxuICAgIGlmIChzY2hlbWE/LnR5cGUgPT09ICdudW1iZXInKSB7XG4gICAgICAvLyBOdW1iZXIoJycpIHJldHVybnMgMCwgbm90IE5hTiDigJQgb21pdCBibGFuayBudW1iZXIgaW5wdXRzIHNvXG4gICAgICAvLyB2YWxpZGF0ZVVzZXJDb25maWcncyByZXF1aXJlZCBjaGVjayBhY3R1YWxseSBjYXRjaGVzIHRoZW0uXG4gICAgICBpZiAodmFsdWUudHJpbSgpID09PSAnJykgY29udGludWVcbiAgICAgIGNvbnN0IG51bSA9IE51bWJlcih2YWx1ZSlcbiAgICAgIGZpbmFsVmFsdWVzW2ZpZWxkS2V5XSA9IE51bWJlci5pc05hTihudW0pID8gdmFsdWUgOiBudW1cbiAgICB9IGVsc2UgaWYgKHNjaGVtYT8udHlwZSA9PT0gJ2Jvb2xlYW4nKSB7XG4gICAgICBmaW5hbFZhbHVlc1tmaWVsZEtleV0gPSBpc0VudlRydXRoeSh2YWx1ZSlcbiAgICB9IGVsc2Uge1xuICAgICAgZmluYWxWYWx1ZXNbZmllbGRLZXldID0gdmFsdWVcbiAgICB9XG4gIH1cbiAgcmV0dXJuIGZpbmFsVmFsdWVzXG59XG5cbnR5cGUgUHJvcHMgPSB7XG4gIHRpdGxlOiBzdHJpbmdcbiAgc3VidGl0bGU6IHN0cmluZ1xuICBjb25maWdTY2hlbWE6IFBsdWdpbk9wdGlvblNjaGVtYVxuICAvKiogUHJlLWZpbGwgZmllbGRzIHdoZW4gcmVjb25maWd1cmluZy4gU2Vuc2l0aXZlIGZpZWxkcyBhcmUgbm90IHByZXBvcHVsYXRlZC4gKi9cbiAgaW5pdGlhbFZhbHVlcz86IFBsdWdpbk9wdGlvblZhbHVlc1xuICBvblNhdmU6IChjb25maWc6IFBsdWdpbk9wdGlvblZhbHVlcykgPT4gdm9pZFxuICBvbkNhbmNlbDogKCkgPT4gdm9pZFxufVxuXG5leHBvcnQgZnVuY3Rpb24gUGx1Z2luT3B0aW9uc0RpYWxvZyh7XG4gIHRpdGxlLFxuICBzdWJ0aXRsZSxcbiAgY29uZmlnU2NoZW1hLFxuICBpbml0aWFsVmFsdWVzLFxuICBvblNhdmUsXG4gIG9uQ2FuY2VsLFxufTogUHJvcHMpOiBSZWFjdC5SZWFjdE5vZGUge1xuICBjb25zdCBmaWVsZHMgPSBPYmplY3Qua2V5cyhjb25maWdTY2hlbWEpXG5cbiAgLy8gUHJlcG9wdWxhdGUgZnJvbSBpbml0aWFsVmFsdWVzIGJ1dCBza2lwIHNlbnNpdGl2ZSBmaWVsZHMg4oCUIHdlIGRvbid0XG4gIC8vIHdhbnQgdG8gZWNobyBzZWNyZXRzIGJhY2sgaW50byB0aGUgdGV4dCBidWZmZXIuXG4gIGNvbnN0IGluaXRpYWxGb3IgPSB1c2VDYWxsYmFjayhcbiAgICAoa2V5OiBzdHJpbmcpOiBzdHJpbmcgPT4ge1xuICAgICAgaWYgKGNvbmZpZ1NjaGVtYVtrZXldPy5zZW5zaXRpdmUgPT09IHRydWUpIHJldHVybiAnJ1xuICAgICAgY29uc3QgdiA9IGluaXRpYWxWYWx1ZXM/LltrZXldXG4gICAgICByZXR1cm4gdiA9PT0gdW5kZWZpbmVkID8gJycgOiBTdHJpbmcodilcbiAgICB9LFxuICAgIFtjb25maWdTY2hlbWEsIGluaXRpYWxWYWx1ZXNdLFxuICApXG5cbiAgY29uc3QgW2N1cnJlbnRGaWVsZEluZGV4LCBzZXRDdXJyZW50RmllbGRJbmRleF0gPSB1c2VTdGF0ZSgwKVxuICBjb25zdCBbdmFsdWVzLCBzZXRWYWx1ZXNdID0gdXNlU3RhdGU8UmVjb3JkPHN0cmluZywgc3RyaW5nPj4oe30pXG4gIGNvbnN0IFtjdXJyZW50SW5wdXQsIHNldEN1cnJlbnRJbnB1dF0gPSB1c2VTdGF0ZSgoKSA9PlxuICAgIGZpZWxkc1swXSA/IGluaXRpYWxGb3IoZmllbGRzWzBdKSA6ICcnLFxuICApXG5cbiAgY29uc3QgY3VycmVudEZpZWxkID0gZmllbGRzW2N1cnJlbnRGaWVsZEluZGV4XVxuICBjb25zdCBmaWVsZFNjaGVtYSA9IGN1cnJlbnRGaWVsZCA/IGNvbmZpZ1NjaGVtYVtjdXJyZW50RmllbGRdIDogbnVsbFxuXG4gIC8vIFVzZSBTZXR0aW5ncyBjb250ZXh0IHNvICduJyBrZXkgZG9lc24ndCBjYW5jZWwgKGFsbG93cyB0eXBpbmcgJ24nIGluIGlucHV0KS5cbiAgLy8gaXNDYW5jZWxBY3RpdmU9e2ZhbHNlfSBvbiBEaWFsb2cga2VlcHMgaXRzIG93biBjb25maXJtOm5vIG91dCBvZiB0aGUgd2F5LlxuICB1c2VLZXliaW5kaW5nKCdjb25maXJtOm5vJywgb25DYW5jZWwsIHsgY29udGV4dDogJ1NldHRpbmdzJyB9KVxuXG4gIC8vIFRhYiB0byBuZXh0IGZpZWxkXG4gIGNvbnN0IGhhbmRsZU5leHRGaWVsZCA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICBpZiAoY3VycmVudEZpZWxkSW5kZXggPCBmaWVsZHMubGVuZ3RoIC0gMSAmJiBjdXJyZW50RmllbGQpIHtcbiAgICAgIHNldFZhbHVlcyhwcmV2ID0+ICh7IC4uLnByZXYsIFtjdXJyZW50RmllbGRdOiBjdXJyZW50SW5wdXQgfSkpXG4gICAgICBzZXRDdXJyZW50RmllbGRJbmRleChwcmV2ID0+IHByZXYgKyAxKVxuICAgICAgY29uc3QgbmV4dEtleSA9IGZpZWxkc1tjdXJyZW50RmllbGRJbmRleCArIDFdXG4gICAgICBzZXRDdXJyZW50SW5wdXQobmV4dEtleSA/IGluaXRpYWxGb3IobmV4dEtleSkgOiAnJylcbiAgICB9XG4gIH0sIFtjdXJyZW50RmllbGRJbmRleCwgZmllbGRzLCBjdXJyZW50RmllbGQsIGN1cnJlbnRJbnB1dCwgaW5pdGlhbEZvcl0pXG5cbiAgLy8gRW50ZXIgdG8gc2F2ZSBjdXJyZW50IGZpZWxkIGFuZCBtb3ZlIHRvIG5leHQsIG9yIHNhdmUgYWxsIGlmIGxhc3RcbiAgY29uc3QgaGFuZGxlQ29uZmlybSA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICBpZiAoIWN1cnJlbnRGaWVsZCkgcmV0dXJuXG5cbiAgICBjb25zdCBuZXdWYWx1ZXMgPSB7IC4uLnZhbHVlcywgW2N1cnJlbnRGaWVsZF06IGN1cnJlbnRJbnB1dCB9XG5cbiAgICBpZiAoY3VycmVudEZpZWxkSW5kZXggPT09IGZpZWxkcy5sZW5ndGggLSAxKSB7XG4gICAgICBvblNhdmUoYnVpbGRGaW5hbFZhbHVlcyhmaWVsZHMsIG5ld1ZhbHVlcywgY29uZmlnU2NoZW1hLCBpbml0aWFsVmFsdWVzKSlcbiAgICB9IGVsc2Uge1xuICAgICAgLy8gTW92ZSB0byBuZXh0IGZpZWxkXG4gICAgICBzZXRWYWx1ZXMobmV3VmFsdWVzKVxuICAgICAgc2V0Q3VycmVudEZpZWxkSW5kZXgocHJldiA9PiBwcmV2ICsgMSlcbiAgICAgIGNvbnN0IG5leHRLZXkgPSBmaWVsZHNbY3VycmVudEZpZWxkSW5kZXggKyAxXVxuICAgICAgc2V0Q3VycmVudElucHV0KG5leHRLZXkgPyBpbml0aWFsRm9yKG5leHRLZXkpIDogJycpXG4gICAgfVxuICB9LCBbXG4gICAgY3VycmVudEZpZWxkLFxuICAgIHZhbHVlcyxcbiAgICBjdXJyZW50SW5wdXQsXG4gICAgY3VycmVudEZpZWxkSW5kZXgsXG4gICAgZmllbGRzLFxuICAgIGNvbmZpZ1NjaGVtYSxcbiAgICBvblNhdmUsXG4gICAgaW5pdGlhbEZvcixcbiAgICBpbml0aWFsVmFsdWVzLFxuICBdKVxuXG4gIHVzZUtleWJpbmRpbmdzKFxuICAgIHtcbiAgICAgICdjb25maXJtOm5leHRGaWVsZCc6IGhhbmRsZU5leHRGaWVsZCxcbiAgICAgICdjb25maXJtOnllcyc6IGhhbmRsZUNvbmZpcm0sXG4gICAgfSxcbiAgICB7IGNvbnRleHQ6ICdDb25maXJtYXRpb24nIH0sXG4gIClcblxuICAvLyBDaGFyYWN0ZXIgaW5wdXQgaGFuZGxpbmcgKGJhY2tzcGFjZSwgdHlwaW5nKVxuICB1c2VJbnB1dCgoY2hhciwga2V5KSA9PiB7XG4gICAgLy8gQmFja3NwYWNlXG4gICAgaWYgKGtleS5iYWNrc3BhY2UgfHwga2V5LmRlbGV0ZSkge1xuICAgICAgc2V0Q3VycmVudElucHV0KHByZXYgPT4gcHJldi5zbGljZSgwLCAtMSkpXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICAvLyBSZWd1bGFyIGNoYXJhY3RlciBpbnB1dFxuICAgIGlmIChjaGFyICYmICFrZXkuY3RybCAmJiAha2V5Lm1ldGEgJiYgIWtleS50YWIgJiYgIWtleS5yZXR1cm4pIHtcbiAgICAgIHNldEN1cnJlbnRJbnB1dChwcmV2ID0+IHByZXYgKyBjaGFyKVxuICAgIH1cbiAgfSlcblxuICBpZiAoIWZpZWxkU2NoZW1hIHx8ICFjdXJyZW50RmllbGQpIHtcbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgY29uc3QgaXNTZW5zaXRpdmUgPSBmaWVsZFNjaGVtYS5zZW5zaXRpdmUgPT09IHRydWVcbiAgY29uc3QgaXNSZXF1aXJlZCA9IGZpZWxkU2NoZW1hLnJlcXVpcmVkID09PSB0cnVlXG4gIGNvbnN0IGRpc3BsYXlWYWx1ZSA9IGlzU2Vuc2l0aXZlXG4gICAgPyAnKicucmVwZWF0KHN0cmluZ1dpZHRoKGN1cnJlbnRJbnB1dCkpXG4gICAgOiBjdXJyZW50SW5wdXRcblxuICByZXR1cm4gKFxuICAgIDxEaWFsb2dcbiAgICAgIHRpdGxlPXt0aXRsZX1cbiAgICAgIHN1YnRpdGxlPXtzdWJ0aXRsZX1cbiAgICAgIG9uQ2FuY2VsPXtvbkNhbmNlbH1cbiAgICAgIGlzQ2FuY2VsQWN0aXZlPXtmYWxzZX1cbiAgICA+XG4gICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgPFRleHQgYm9sZD17dHJ1ZX0+XG4gICAgICAgICAge2ZpZWxkU2NoZW1hLnRpdGxlIHx8IGN1cnJlbnRGaWVsZH1cbiAgICAgICAgICB7aXNSZXF1aXJlZCAmJiA8VGV4dCBjb2xvcj1cImVycm9yXCI+ICo8L1RleHQ+fVxuICAgICAgICA8L1RleHQ+XG4gICAgICAgIHtmaWVsZFNjaGVtYS5kZXNjcmlwdGlvbiAmJiAoXG4gICAgICAgICAgPFRleHQgZGltQ29sb3I9e3RydWV9PntmaWVsZFNjaGVtYS5kZXNjcmlwdGlvbn08L1RleHQ+XG4gICAgICAgICl9XG5cbiAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgIDxUZXh0PntmaWd1cmVzLnBvaW50ZXJTbWFsbH0gPC9UZXh0PlxuICAgICAgICAgIDxUZXh0PntkaXNwbGF5VmFsdWV9PC9UZXh0PlxuICAgICAgICAgIDxUZXh0PuKWiDwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICA8L0JveD5cblxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPXt0cnVlfT5cbiAgICAgICAgICBGaWVsZCB7Y3VycmVudEZpZWxkSW5kZXggKyAxfSBvZiB7ZmllbGRzLmxlbmd0aH1cbiAgICAgICAgPC9UZXh0PlxuICAgICAgICB7Y3VycmVudEZpZWxkSW5kZXggPCBmaWVsZHMubGVuZ3RoIC0gMSAmJiAoXG4gICAgICAgICAgPFRleHQgZGltQ29sb3I9e3RydWV9PlxuICAgICAgICAgICAgVGFiOiBOZXh0IGZpZWxkIMK3IEVudGVyOiBTYXZlIGFuZCBjb250aW51ZVxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgKX1cbiAgICAgICAge2N1cnJlbnRGaWVsZEluZGV4ID09PSBmaWVsZHMubGVuZ3RoIC0gMSAmJiAoXG4gICAgICAgICAgPFRleHQgZGltQ29sb3I9e3RydWV9PkVudGVyOiBTYXZlIGNvbmZpZ3VyYXRpb248L1RleHQ+XG4gICAgICAgICl9XG4gICAgICA8L0JveD5cbiAgICA8L0RpYWxvZz5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBT0EsT0FBTyxNQUFNLFNBQVM7QUFDN0IsT0FBT0MsS0FBSyxJQUFJQyxXQUFXLEVBQUVDLFFBQVEsUUFBUSxPQUFPO0FBQ3BELFNBQVNDLE1BQU0sUUFBUSww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 \ No newline at end of file diff --git a/src/commands/plugin/PluginOptionsFlow.tsx b/src/commands/plugin/PluginOptionsFlow.tsx new file mode 100644 index 0000000000000000000000000000000000000000..837704fac4bc4c4548753316337a54c681d894ed --- /dev/null +++ b/src/commands/plugin/PluginOptionsFlow.tsx @@ -0,0 +1,135 @@ +/** + * 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 { + 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(() => { + 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 onDone('skipped')} />; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIkxvYWRlZFBsdWdpbiIsImVycm9yTWVzc2FnZSIsImxvYWRNY3BTZXJ2ZXJVc2VyQ29uZmlnIiwic2F2ZU1jcFNlcnZlclVzZXJDb25maWciLCJnZXRVbmNvbmZpZ3VyZWRDaGFubmVscyIsIlVuY29uZmlndXJlZENoYW5uZWwiLCJsb2FkQWxsUGx1Z2lucyIsImdldFVuY29uZmlndXJlZE9wdGlvbnMiLCJsb2FkUGx1Z2luT3B0aW9ucyIsIlBsdWdpbk9wdGlvblNjaGVtYSIsIlBsdWdpbk9wdGlvblZhbHVlcyIsInNhdmVQbHVnaW5PcHRpb25zIiwiUGx1Z2luT3B0aW9uc0RpYWxvZyIsImZpbmRQbHVnaW5PcHRpb25zVGFyZ2V0IiwicGx1Z2luSWQiLCJQcm9taXNlIiwiZW5hYmxlZCIsImRpc2FibGVkIiwiZmluZCIsInAiLCJyZXBvc2l0b3J5Iiwic291cmNlIiwiQ29uZmlnU3RlcCIsImtleSIsInRpdGxlIiwic3VidGl0bGUiLCJzY2hlbWEiLCJsb2FkIiwic2F2ZSIsInZhbHVlcyIsIlByb3BzIiwicGx1Z2luIiwib25Eb25lIiwib3V0Y29tZSIsImRldGFpbCIsIlBsdWdpbk9wdGlvbnNGbG93IiwiUmVhY3ROb2RlIiwic3RlcHMiLCJ1c2VTdGF0ZSIsInJlc3VsdCIsInVuY29uZmlndXJlZCIsIk9iamVjdCIsImtleXMiLCJsZW5ndGgiLCJwdXNoIiwibmFtZSIsIm1hbmlmZXN0IiwidXNlckNvbmZpZyIsImNoYW5uZWxzIiwiY2hhbm5lbCIsInNlcnZlciIsImRpc3BsYXlOYW1lIiwiY29uZmlnU2NoZW1hIiwidW5kZWZpbmVkIiwiaW5kZXgiLCJzZXRJbmRleCIsIm9uRG9uZVJlZiIsInVzZVJlZiIsImN1cnJlbnQiLCJ1c2VFZmZlY3QiLCJoYW5kbGVTYXZlIiwiZXJyIiwibmV4dCJdLCJzb3VyY2VzIjpbIlBsdWdpbk9wdGlvbnNGbG93LnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFBvc3QtaW5zdGFsbC9wb3N0LWVuYWJsZSBjb25maWcgcHJvbXB0LlxuICpcbiAqIEdpdmVuIGEgTG9hZGVkUGx1Z2luLCBjaGVja3MgYm90aCB0aGUgdG9wLWxldmVsIG1hbmlmZXN0LnVzZXJDb25maWcgYW5kIHRoZVxuICogY2hhbm5lbC1zcGVjaWZpYyB1c2VyQ29uZmlnLiBXYWxrcyBQbHVnaW5PcHRpb25zRGlhbG9nIHRocm91Z2ggZWFjaFxuICogdW5jb25maWd1cmVkIGl0ZW0sIHNhdmluZyB2aWEgdGhlIGFwcHJvcHJpYXRlIHN0b3JhZ2UgZnVuY3Rpb24uIENhbGxzXG4gKiBvbkRvbmUoJ3NraXBwZWQnKSBpbW1lZGlhdGVseSBpZiBub3RoaW5nIG5lZWRzIGZpbGxpbmcuXG4gKi9cblxuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgdHlwZSB7IExvYWRlZFBsdWdpbiB9IGZyb20gJy4uLy4uL3R5cGVzL3BsdWdpbi5qcydcbmltcG9ydCB7IGVycm9yTWVzc2FnZSB9IGZyb20gJy4uLy4uL3V0aWxzL2Vycm9ycy5qcydcbmltcG9ydCB7XG4gIGxvYWRNY3BTZXJ2ZXJVc2VyQ29uZmlnLFxuICBzYXZlTWNwU2VydmVyVXNlckNvbmZpZyxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9tY3BiSGFuZGxlci5qcydcbmltcG9ydCB7XG4gIGdldFVuY29uZmlndXJlZENoYW5uZWxzLFxuICB0eXBlIFVuY29uZmlndXJlZENoYW5uZWwsXG59IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvbWNwUGx1Z2luSW50ZWdyYXRpb24uanMnXG5pbXBvcnQgeyBsb2FkQWxsUGx1Z2lucyB9IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvcGx1Z2luTG9hZGVyLmpzJ1xuaW1wb3J0IHtcbiAgZ2V0VW5jb25maWd1cmVkT3B0aW9ucyxcbiAgbG9hZFBsdWdpbk9wdGlvbnMsXG4gIHR5cGUgUGx1Z2luT3B0aW9uU2NoZW1hLFxuICB0eXBlIFBsdWdpbk9wdGlvblZhbHVlcyxcbiAgc2F2ZVBsdWdpbk9wdGlvbnMsXG59IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvcGx1Z2luT3B0aW9uc1N0b3JhZ2UuanMnXG5pbXBvcnQgeyBQbHVnaW5PcHRpb25zRGlhbG9nIH0gZnJvbSAnLi9QbHVnaW5PcHRpb25zRGlhbG9nLmpzJ1xuXG4vKipcbiAqIFBvc3QtaW5zdGFsbCBsb29rdXA6IHJldHVybiB0aGUgTG9hZGVkUGx1Z2luIGZvciB0aGUganVzdC1pbnN0YWxsZWRcbiAqIHBsdWdpbklkIHNvIHRoZSBjYWxsZXIgY2FuIGRpdmVydCB0byBQbHVnaW5PcHRpb25zRmxvdy4gUmV0dXJucyB1bmRlZmluZWRcbiAqIGlmIHRoZSBwbHVnaW4gc29tZWhvdyBkaWRuJ3QgbWFrZSBpdCBpbnRvIHRoZSBmcmVzaCBsb2FkIOKAlCBjYWxsZXJzIHRyZWF0XG4gKiB1bmRlZmluZWQgYXMgXCJjYXJyeSBvbiBjbG9zaW5nLlwiXG4gKlxuICogSW5zdGFsbCBzaG91bGQgaGF2ZSBjbGVhcmVkIGNhY2hlcyBhbHJlYWR5OyBsb2FkQWxsUGx1Z2lucyByZWFkcyBmcmVzaC5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGZpbmRQbHVnaW5PcHRpb25zVGFyZ2V0KFxuICBwbHVnaW5JZDogc3RyaW5nLFxuKTogUHJvbWlzZTxMb2FkZWRQbHVnaW4gfCB1bmRlZmluZWQ+IHtcbiAgY29uc3QgeyBlbmFibGVkLCBkaXNhYmxlZCB9ID0gYXdhaXQgbG9hZEFsbFBsdWdpbnMoKVxuICByZXR1cm4gWy4uLmVuYWJsZWQsIC4uLmRpc2FibGVkXS5maW5kKFxuICAgIHAgPT4gcC5yZXBvc2l0b3J5ID09PSBwbHVnaW5JZCB8fCBwLnNvdXJjZSA9PT0gcGx1Z2luSWQsXG4gIClcbn1cblxuLyoqXG4gKiBBIHNpbmdsZSBkaWFsb2cgc3RlcCBpbiB0aGUgd2Fsay4gVG9wLWxldmVsIG9wdGlvbnMgYW5kIGNoYW5uZWxzIGJvdGhcbiAqIGNvbGxhcHNlIHRvIHRoaXMgc2hhcGUg4oCUIHRoZSBvbmx5IGRpZmZlcmVuY2UgaXMgd2hpY2ggc2F2ZSBmdW5jdGlvbiBydW5zLlxuICovXG50eXBlIENvbmZpZ1N0ZXAgPSB7XG4gIGtleTogc3RyaW5nXG4gIHRpdGxlOiBzdHJpbmdcbiAgc3VidGl0bGU6IHN0cmluZ1xuICBzY2hlbWE6IFBsdWdpbk9wdGlvblNjaGVtYVxuICAvKiogUmV0dXJucyBhbnkgYWxyZWFkeS1zYXZlZCB2YWx1ZXMgc28gUGx1Z2luT3B0aW9uc0RpYWxvZyBjYW4gcHJlLWZpbGwgYW5kXG4gICAqICBza2lwIHVuY2hhbmdlZCBzZW5zaXRpdmUgZmllbGRzIG9uIHJlY29uZmlndXJlLiAqL1xuICBsb2FkOiAoKSA9PiBQbHVnaW5PcHRpb25WYWx1ZXMgfCB1bmRlZmluZWRcbiAgc2F2ZTogKHZhbHVlczogUGx1Z2luT3B0aW9uVmFsdWVzKSA9PiB2b2lkXG59XG5cbnR5cGUgUHJvcHMgPSB7XG4gIHBsdWdpbjogTG9hZGVkUGx1Z2luXG4gIC8qKiBgbmFtZUBtYXJrZXRwbGFjZWAg4oCUIHRoZSBzYXZlUGx1Z2luT3B0aW9ucyAvIHNhdmVNY3BTZXJ2ZXJVc2VyQ29uZmlnIGtleS4gKi9cbiAgcGx1Z2luSWQ6IHN0cmluZ1xuICAvKipcbiAgICogYGNvbmZpZ3VyZWRgID0gdXNlciBmaWxsZWQgYWxsIGZpZWxkcy4gYHNraXBwZWRgID0gbm90aGluZyBuZWVkZWRcbiAgICogY29uZmlndXJpbmcsIG9yIHVzZXIgaGl0IGNhbmNlbC4gYGVycm9yYCA9IHNhdmUgdGhyZXcuXG4gICAqL1xuICBvbkRvbmU6IChvdXRjb21lOiAnY29uZmlndXJlZCcgfCAnc2tpcHBlZCcgfCAnZXJyb3InLCBkZXRhaWw/OiBzdHJpbmcpID0+IHZvaWRcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFBsdWdpbk9wdGlvbnNGbG93KHtcbiAgcGx1Z2luLFxuICBwbHVnaW5JZCxcbiAgb25Eb25lLFxufTogUHJvcHMpOiBSZWFjdC5SZWFjdE5vZGUge1xuICAvLyBCdWlsZCB0aGUgc3RlcCBsaXN0IG9uY2UgYXQgbW91bnQuIFJlLWNhbGxpbmcgYWZ0ZXIgYSBzYXZlIHdvdWxkIGRyb3AgdGhlXG4gIC8vIGl0ZW0gd2UganVzdCBjb25maWd1cmVkLlxuICBjb25zdCBbc3RlcHNdID0gUmVhY3QudXNlU3RhdGU8Q29uZmlnU3RlcFtdPigoKSA9PiB7XG4gICAgY29uc3QgcmVzdWx0OiBDb25maWdTdGVwW10gPSBbXVxuXG4gICAgLy8gVG9wLWxldmVsIG1hbmlmZXN0LnVzZXJDb25maWdcbiAgICBjb25zdCB1bmNvbmZpZ3VyZWQgPSBnZXRVbmNvbmZpZ3VyZWRPcHRpb25zKHBsdWdpbilcbiAgICBpZiAoT2JqZWN0LmtleXModW5jb25maWd1cmVkKS5sZW5ndGggPiAwKSB7XG4gICAgICByZXN1bHQucHVzaCh7XG4gICAgICAgIGtleTogJ3RvcC1sZXZlbCcsXG4gICAgICAgIHRpdGxlOiBgQ29uZmlndXJlICR7cGx1Z2luLm5hbWV9YCxcbiAgICAgICAgc3VidGl0bGU6ICdQbHVnaW4gb3B0aW9ucycsXG4gICAgICAgIHNjaGVtYTogdW5jb25maWd1cmVkLFxuICAgICAgICBsb2FkOiAoKSA9PiBsb2FkUGx1Z2luT3B0aW9ucyhwbHVnaW5JZCksXG4gICAgICAgIHNhdmU6IHZhbHVlcyA9PlxuICAgICAgICAgIHNhdmVQbHVnaW5PcHRpb25zKHBsdWdpbklkLCB2YWx1ZXMsIHBsdWdpbi5tYW5pZmVzdC51c2VyQ29uZmlnISksXG4gICAgICB9KVxuICAgIH1cblxuICAgIC8vIFBlci1jaGFubmVsIHVzZXJDb25maWcgKGFzc2lzdGFudC1tb2RlIGNoYW5uZWxzKVxuICAgIGNvbnN0IGNoYW5uZWxzOiBVbmNvbmZpZ3VyZWRDaGFubmVsW10gPSBnZXRVbmNvbmZpZ3VyZWRDaGFubmVscyhwbHVnaW4pXG4gICAgZm9yIChjb25zdCBjaGFubmVsIG9mIGNoYW5uZWxzKSB7XG4gICAgICByZXN1bHQucHVzaCh7XG4gICAgICAgIGtleTogYGNoYW5uZWw6JHtjaGFubmVsLnNlcnZlcn1gLFxuICAgICAgICB0aXRsZTogYENvbmZpZ3VyZSAke2NoYW5uZWwuZGlzcGxheU5hbWV9YCxcbiAgICAgICAgc3VidGl0bGU6IGBQbHVnaW46ICR7cGx1Z2luLm5hbWV9YCxcbiAgICAgICAgc2NoZW1hOiBjaGFubmVsLmNvbmZpZ1NjaGVtYSxcbiAgICAgICAgbG9hZDogKCkgPT5cbiAgICAgICAgICBsb2FkTWNwU2VydmVyVXNlckNvbmZpZyhwbHVnaW5JZCwgY2hhbm5lbC5zZXJ2ZXIpID8/IHVuZGVmaW5lZCxcbiAgICAgICAgc2F2ZTogdmFsdWVzID0+XG4gICAgICAgICAgc2F2ZU1jcFNlcnZlclVzZXJDb25maWcoXG4gICAgICAgICAgICBwbHVnaW5JZCxcbiAgICAgICAgICAgIGNoYW5uZWwuc2VydmVyLFxuICAgICAgICAgICAgdmFsdWVzLFxuICAgICAgICAgICAgY2hhbm5lbC5jb25maWdTY2hlbWEsXG4gICAgICAgICAgKSxcbiAgICAgIH0pXG4gICAgfVxuXG4gICAgcmV0dXJuIHJlc3VsdFxuICB9KVxuXG4gIGNvbnN0IFtpbmRleCwgc2V0SW5kZXhdID0gUmVhY3QudXNlU3RhdGUoMClcblxuICAvLyBMYXRlc3QtcmVmOiBsZXRzIHRoZSBlZmZlY3QgY2xvc2Ugb3ZlciB0aGUgY3VycmVudCBvbkRvbmUgd2l0aG91dFxuICAvLyByZS1ydW5uaW5nIHdoZW4gdGhlIHBhcmVudCByZS1yZW5kZXJzLlxuICBjb25zdCBvbkRvbmVSZWYgPSBSZWFjdC51c2VSZWYob25Eb25lKVxuICBvbkRvbmVSZWYuY3VycmVudCA9IG9uRG9uZVxuXG4gIC8vIE5vdGhpbmcgdG8gY29uZmlndXJlIOKGkiB0ZWxsIHRoZSBjYWxsZXIgYW5kIHJlbmRlciBub3RoaW5nLiBFZmZlY3QsXG4gIC8vIG5vdCBpbmxpbmUgY2FsbDogY2FsbGluZyBzZXRTdGF0ZSBpbiB0aGUgcGFyZW50IGR1cmluZyBvdXIgcmVuZGVyXG4gIC8vIGlzIGEgUmVhY3QgcnVsZXMtb2YtaG9va3MgdmlvbGF0aW9uLlxuICBSZWFjdC51c2VFZmZlY3QoKCkgPT4ge1xuICAgIGlmIChzdGVwcy5sZW5ndGggPT09IDApIHtcbiAgICAgIG9uRG9uZVJlZi5jdXJyZW50KCdza2lwcGVkJylcbiAgICB9XG4gIH0sIFtzdGVwcy5sZW5ndGhdKVxuXG4gIGlmIChzdGVwcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgY29uc3QgY3VycmVudCA9IHN0ZXBzW2luZGV4XSFcblxuICBmdW5jdGlvbiBoYW5kbGVTYXZlKHZhbHVlczogUGx1Z2luT3B0aW9uVmFsdWVzKTogdm9pZCB7XG4gICAgdHJ5IHtcbiAgICAgIGN1cnJlbnQuc2F2ZSh2YWx1ZXMpXG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICBvbkRvbmUoJ2Vycm9yJywgZXJyb3JNZXNzYWdlKGVycikpXG4gICAgICByZXR1cm5cbiAgICB9XG4gICAgY29uc3QgbmV4dCA9IGluZGV4ICsgMVxuICAgIGlmIChuZXh0IDwgc3RlcHMubGVuZ3RoKSB7XG4gICAgICBzZXRJbmRleChuZXh0KVxuICAgIH0gZWxzZSB7XG4gICAgICBvbkRvbmUoJ2NvbmZpZ3VyZWQnKVxuICAgIH1cbiAgfVxuXG4gIC8vIGtleSBmb3JjZXMgYSByZW1vdW50IHdoZW4gYWR2YW5jaW5nIHRvIHRoZSBuZXh0IHN0ZXAg4oCUIFJlYWN0IHdvdWxkXG4gIC8vIG90aGVyd2lzZSByZXVzZSB0aGUgaW5zdGFuY2UgYW5kIGNhcnJ5IFBsdWdpbk9wdGlvbnNEaWFsb2cnc1xuICAvLyBpbnRlcm5hbCB1c2VTdGF0ZSAoZmllbGQgaW5kZXgsIHR5cGVkIHZhbHVlcykgb3Zlci5cbiAgcmV0dXJuIChcbiAgICA8UGx1Z2luT3B0aW9uc0RpYWxvZ1xuICAgICAga2V5PXtjdXJyZW50LmtleX1cbiAgICAgIHRpdGxlPXtjdXJyZW50LnRpdGxlfVxuICAgICAgc3VidGl0bGU9e2N1cnJlbnQuc3VidGl0bGV9XG4gICAgICBjb25maWdTY2hlbWE9e2N1cnJlbnQuc2NoZW1hfVxuICAgICAgaW5pdGlhbFZhbHVlcz17Y3VycmVudC5sb2FkKCl9XG4gICAgICBvblNhdmU9e2hhbmRsZVNhdmV9XG4gICAgICBvbkNhbmNlbD17KCkgPT4gb25Eb25lKCdza2lwcGVkJyl9XG4gICAgLz5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiQUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsY0FBY0MsWUFBWSxRQUFRLHVCQUF1QjtBQUN6RCxTQUFTQyxZQUFZLFFBQVEsdUJBQXVCO0FBQ3BELFNBQ0VDLHVCQUF1QixFQUN2QkMsdUJBQXVCLFFBQ2xCLG9DQUFvQztBQUMzQyxTQUNFQyx1QkFBdUIsRUFDdkIsS0FBS0MsbUJBQW1CLFFBQ25CLDZDQUE2QztBQUNwRCxTQUFTQyxjQUFjLFFBQVEscUNBQXFDO0FBQ3BFLFNBQ0VDLHNCQUFzQixFQUN0QkMsaUJBQWlCLEVBQ2pCLEtBQUtDLGtCQUFrQixFQUN2QixLQUFLQyxrQkFBa0IsRUFDdkJDLGlCQUFpQixRQUNaLDZDQUE2QztBQUNwRCxTQUFTQyxtQkFBbUIsUUFBUSwwQkFBMEI7O0FBRTlEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxPQUFPLGVBQWVDLHVCQUF1QkEsQ0FDM0NDLFFBQVEsRUFBRSxNQUFNLENBQ2pCLEVBQUVDLE9BQU8sQ0FBQ2YsWUFBWSxHQUFHLFNBQVMsQ0FBQyxDQUFDO0VBQ25DLE1BQU07SUFBRWdCLE9BQU87SUFBRUM7RUFBUyxDQUFDLEdBQUcsTUFBTVgsY0FBYyxDQUFDLENBQUM7RUFDcEQsT0FBTyxDQUFDLEdBQUdVLE9BQU8sRUFBRSxHQUFHQyxRQUFRLENBQUMsQ0FBQ0MsSUFBSSxDQUNuQ0MsQ0FBQyxJQUFJQSxDQUFDLENBQUNDLFVBQVUsS0FBS04sUUFBUSxJQUFJSyxDQUFDLENBQUNFLE1BQU0sS0FBS1AsUUFDakQsQ0FBQztBQUNIOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsS0FBS1EsVUFBVSxHQUFHO0VBQ2hCQyxHQUFHLEVBQUUsTUFBTTtFQUNYQyxLQUFLLEVB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TyxHQUFHMUIsTUFBTTs7RUFFMUI7RUFDQTtFQUNBO0VBQ0FqQyxLQUFLLENBQUM0RCxTQUFTLENBQUMsTUFBTTtJQUNwQixJQUFJdEIsS0FBSyxDQUFDTSxNQUFNLEtBQUssQ0FBQyxFQUFFO01BQ3RCYSxTQUFTLENBQUNFLE9BQU8sQ0FBQyxTQUFTLENBQUM7SUFDOUI7RUFDRixDQUFDLEVBQUUsQ0FBQ3JCLEtBQUssQ0FBQ00sTUFBTSxDQUFDLENBQUM7RUFFbEIsSUFBSU4sS0FBSyxDQUFDTSxNQUFNLEtBQUssQ0FBQyxFQUFFO0lBQ3RCLE9BQU8sSUFBSTtFQUNiO0VBRUEsTUFBTWUsT0FBTyxHQUFHckIsS0FBSyxDQUFDaUIsS0FBSyxDQUFDLENBQUM7RUFFN0IsU0FBU00sVUFBVUEsQ0FBQy9CLFFBQU0sRUFBRW5CLGtCQUFrQixDQUFDLEVBQUUsSUFBSSxDQUFDO0lBQ3BELElBQUk7TUFDRmdELE9BQU8sQ0FBQzlCLElBQUksQ0FBQ0MsUUFBTSxDQUFDO0lBQ3RCLENBQUMsQ0FBQyxPQUFPZ0MsR0FBRyxFQUFFO01BQ1o3QixNQUFNLENBQUMsT0FBTyxFQUFFL0IsWUFBWSxDQUFDNEQsR0FBRyxDQUFDLENBQUM7TUFDbEM7SUFDRjtJQUNBLE1BQU1DLElBQUksR0FBR1IsS0FBSyxHQUFHLENBQUM7SUFDdEIsSUFBSVEsSUFBSSxHQUFHekIsS0FBSyxDQUFDTSxNQUFNLEVBQUU7TUFDdkJZLFFBQVEsQ0FBQ08sSUFBSSxDQUFDO0lBQ2hCLENBQUMsTUFBTTtNQUNMOUIsTUFBTSxDQUFDLFlBQVksQ0FBQztJQUN0QjtFQUNGOztFQUVBO0VBQ0E7RUFDQTtFQUNBLE9BQ0UsQ0FBQyxtQkFBbUIsQ0FDbEIsR0FBRyxDQUFDLENBQUMwQixPQUFPLENBQUNuQyxHQUFHLENBQUMsQ0FDakIsS0FBSyxDQUFDLENBQUNtQyxPQUFPLENBQUNsQyxLQUFLLENBQUMsQ0FDckIsUUFBUSxDQUFDLENBQUNrQyxPQUFPLENBQUNqQyxRQUFRLENBQUMsQ0FDM0IsWUFBWSxDQUFDLENBQUNpQyxPQUFPLENBQUNoQyxNQUFNLENBQUMsQ0FDN0IsYUFBYSxDQUFDLENBQUNnQyxPQUFPLENBQUMvQixJQUFJLENBQUMsQ0FBQyxDQUFDLENBQzlCLE1BQU0sQ0FBQyxDQUFDaUMsVUFBVSxDQUFDLENBQ25CLFFBQVEsQ0FBQyxDQUFDLE1BQU01QixNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsR0FDbEM7QUFFTiIsImlnbm9yZUxpc3QiOltdfQ== \ No newline at end of file diff --git a/src/commands/plugin/PluginSettings.tsx b/src/commands/plugin/PluginSettings.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f325180c25300b5e01cfebd96ce8647839f1bdf5 --- /dev/null +++ b/src/commands/plugin/PluginSettings.tsx @@ -0,0 +1,1072 @@ +import { c as _c } from "react/compiler-runtime"; +import figures from 'figures'; +import * as React from 'react'; +import { useCallback, useEffect, useState } from 'react'; +import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js'; +import { Byline } from '../../components/design-system/Byline.js'; +import { Pane } from '../../components/design-system/Pane.js'; +import { Tab, Tabs } from '../../components/design-system/Tabs.js'; +import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js'; +import { Box, Text } from '../../ink.js'; +import { useKeybinding, useKeybindings } from '../../keybindings/useKeybinding.js'; +import { useAppState, useSetAppState } from '../../state/AppState.js'; +import type { PluginError } from '../../types/plugin.js'; +import { errorMessage } from '../../utils/errors.js'; +import { clearAllCaches } from '../../utils/plugins/cacheUtils.js'; +import { loadMarketplacesWithGracefulDegradation } from '../../utils/plugins/marketplaceHelpers.js'; +import { loadKnownMarketplacesConfig, removeMarketplaceSource } from '../../utils/plugins/marketplaceManager.js'; +import { getPluginEditableScopes } from '../../utils/plugins/pluginStartupCheck.js'; +import type { EditableSettingSource } from '../../utils/settings/constants.js'; +import { getSettingsForSource, updateSettingsForSource } from '../../utils/settings/settings.js'; +import { AddMarketplace } from './AddMarketplace.js'; +import { BrowseMarketplace } from './BrowseMarketplace.js'; +import { DiscoverPlugins } from './DiscoverPlugins.js'; +import { ManageMarketplaces } from './ManageMarketplaces.js'; +import { ManagePlugins } from './ManagePlugins.js'; +import { formatErrorMessage, getErrorGuidance } from './PluginErrors.js'; +import { type ParsedCommand, parsePluginArgs } from './parseArgs.js'; +import type { PluginSettingsProps, ViewState } from './types.js'; +import { ValidatePlugin } from './ValidatePlugin.js'; +type TabId = 'discover' | 'installed' | 'marketplaces' | 'errors'; +function MarketplaceList(t0) { + const $ = _c(4); + const { + onComplete + } = t0; + let t1; + let t2; + if ($[0] !== onComplete) { + t1 = () => { + const loadList = async function loadList() { + ; + try { + const config = await loadKnownMarketplacesConfig(); + const names = Object.keys(config); + if (names.length === 0) { + onComplete("No marketplaces configured"); + } else { + onComplete(`Configured marketplaces:\n${names.map(_temp).join("\n")}`); + } + } catch (t3) { + const err = t3; + onComplete(`Error loading marketplaces: ${errorMessage(err)}`); + } + }; + loadList(); + }; + t2 = [onComplete]; + $[0] = onComplete; + $[1] = t1; + $[2] = t2; + } else { + t1 = $[1]; + t2 = $[2]; + } + useEffect(t1, t2); + let t3; + if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + t3 = Loading marketplaces...; + $[3] = t3; + } else { + t3 = $[3]; + } + return t3; +} +function _temp(n) { + return ` • ${n}`; +} +function McpRedirectBanner() { + return null; +} +type ErrorRowAction = { + kind: 'navigate'; + tab: TabId; + viewState: ViewState; +} | { + kind: 'remove-extra-marketplace'; + name: string; + sources: Array<{ + source: EditableSettingSource; + scope: string; + }>; +} | { + kind: 'remove-installed-marketplace'; + name: string; +} | { + kind: 'managed-only'; + name: string; +} | { + kind: 'none'; +}; +type ErrorRow = { + label: string; + message: string; + guidance?: string | null; + action: ErrorRowAction; + scope?: string; +}; + +/** + * Determine which settings sources define an extraKnownMarketplace entry. + * Returns the editable sources (user/project/local) and whether policy also has it. + */ +function getExtraMarketplaceSourceInfo(name: string): { + editableSources: Array<{ + source: EditableSettingSource; + scope: string; + }>; + isInPolicy: boolean; +} { + const editableSources: Array<{ + source: EditableSettingSource; + scope: string; + }> = []; + const sourcesToCheck = [{ + source: 'userSettings' as const, + scope: 'user' + }, { + source: 'projectSettings' as const, + scope: 'project' + }, { + source: 'localSettings' as const, + scope: 'local' + }]; + for (const { + source, + scope + } of sourcesToCheck) { + const settings = getSettingsForSource(source); + if (settings?.extraKnownMarketplaces?.[name]) { + editableSources.push({ + source, + scope + }); + } + } + const policySettings = getSettingsForSource('policySettings'); + const isInPolicy = Boolean(policySettings?.extraKnownMarketplaces?.[name]); + return { + editableSources, + isInPolicy + }; +} +function buildMarketplaceAction(name: string): ErrorRowAction { + const { + editableSources, + isInPolicy + } = getExtraMarketplaceSourceInfo(name); + if (editableSources.length > 0) { + return { + kind: 'remove-extra-marketplace', + name, + sources: editableSources + }; + } + if (isInPolicy) { + return { + kind: 'managed-only', + name + }; + } + + // Marketplace is in known_marketplaces.json but not in extraKnownMarketplaces + // (e.g. previously installed manually) — route to ManageMarketplaces + return { + kind: 'navigate', + tab: 'marketplaces', + viewState: { + type: 'manage-marketplaces', + targetMarketplace: name, + action: 'remove' + } + }; +} +function buildPluginAction(pluginName: string): ErrorRowAction { + return { + kind: 'navigate', + tab: 'installed', + viewState: { + type: 'manage-plugins', + targetPlugin: pluginName, + action: 'uninstall' + } + }; +} +const TRANSIENT_ERROR_TYPES = new Set(['git-auth-failed', 'git-timeout', 'network-error']); +function isTransientError(error: PluginError): boolean { + return TRANSIENT_ERROR_TYPES.has(error.type); +} + +/** + * Extract the plugin name from a PluginError, checking explicit fields first, + * then falling back to the source field (format: "pluginName@marketplace"). + */ +function getPluginNameFromError(error: PluginError): string | undefined { + if ('pluginId' in error && error.pluginId) return error.pluginId; + if ('plugin' in error && error.plugin) return error.plugin; + // Fallback: source often contains "pluginName@marketplace" + if (error.source.includes('@')) return error.source.split('@')[0]; + return undefined; +} +function buildErrorRows(failedMarketplaces: Array<{ + name: string; + error?: string; +}>, extraMarketplaceErrors: PluginError[], pluginLoadingErrors: PluginError[], otherErrors: PluginError[], brokenInstalledMarketplaces: Array<{ + name: string; + error: string; +}>, transientErrors: PluginError[], pluginScopes: Map): ErrorRow[] { + const rows: ErrorRow[] = []; + + // --- Transient errors at the top (restart to retry) --- + for (const error of transientErrors) { + const pluginName = 'pluginId' in error ? error.pluginId : 'plugin' in error ? error.plugin : undefined; + rows.push({ + label: pluginName ?? error.source, + message: formatErrorMessage(error), + guidance: 'Restart to retry loading plugins', + action: { + kind: 'none' + } + }); + } + + // --- Marketplace errors --- + // Track shown marketplace names to avoid duplicates across sources + const shownMarketplaceNames = new Set(); + for (const m of failedMarketplaces) { + shownMarketplaceNames.add(m.name); + const action = buildMarketplaceAction(m.name); + const sourceInfo = getExtraMarketplaceSourceInfo(m.name); + const scope = sourceInfo.isInPolicy ? 'managed' : sourceInfo.editableSources[0]?.scope; + rows.push({ + label: m.name, + message: m.error ?? 'Installation failed', + guidance: action.kind === 'managed-only' ? 'Managed by your organization — contact your admin' : undefined, + action, + scope + }); + } + for (const e of extraMarketplaceErrors) { + const marketplace = 'marketplace' in e ? e.marketplace : e.source; + if (shownMarketplaceNames.has(marketplace)) continue; + shownMarketplaceNames.add(marketplace); + const action = buildMarketplaceAction(marketplace); + const sourceInfo = getExtraMarketplaceSourceInfo(marketplace); + const scope = sourceInfo.isInPolicy ? 'managed' : sourceInfo.editableSources[0]?.scope; + rows.push({ + label: marketplace, + message: formatErrorMessage(e), + guidance: action.kind === 'managed-only' ? 'Managed by your organization — contact your admin' : getErrorGuidance(e), + action, + scope + }); + } + + // Installed marketplaces that fail to load data (from known_marketplaces.json) + for (const m of brokenInstalledMarketplaces) { + if (shownMarketplaceNames.has(m.name)) continue; + shownMarketplaceNames.add(m.name); + rows.push({ + label: m.name, + message: m.error, + action: { + kind: 'remove-installed-marketplace', + name: m.name + } + }); + } + + // --- Plugin errors --- + const shownPluginNames = new Set(); + for (const error of pluginLoadingErrors) { + const pluginName = getPluginNameFromError(error); + if (pluginName && shownPluginNames.has(pluginName)) continue; + if (pluginName) shownPluginNames.add(pluginName); + const marketplace = 'marketplace' in error ? error.marketplace : undefined; + // Try pluginId@marketplace format first, then just pluginName + const scope = pluginName ? pluginScopes.get(error.source) ?? pluginScopes.get(pluginName) : undefined; + rows.push({ + label: pluginName ? marketplace ? `${pluginName} @ ${marketplace}` : pluginName : error.source, + message: formatErrorMessage(error), + guidance: getErrorGuidance(error), + action: pluginName ? buildPluginAction(pluginName) : { + kind: 'none' + }, + scope + }); + } + + // --- Other errors (non-marketplace, non-plugin-specific) --- + for (const error of otherErrors) { + rows.push({ + label: error.source, + message: formatErrorMessage(error), + guidance: getErrorGuidance(error), + action: { + kind: 'none' + } + }); + } + return rows; +} + +/** + * Remove a marketplace from extraKnownMarketplaces in the given settings sources, + * and also remove any associated enabled plugins. + */ +function removeExtraMarketplace(name: string, sources: Array<{ + source: EditableSettingSource; +}>): void { + for (const { + source + } of sources) { + const settings = getSettingsForSource(source); + if (!settings) continue; + const updates: Record = {}; + + // Remove from extraKnownMarketplaces + if (settings.extraKnownMarketplaces?.[name]) { + updates.extraKnownMarketplaces = { + ...settings.extraKnownMarketplaces, + [name]: undefined + }; + } + + // Remove associated enabled plugins (format: "plugin@marketplace") + if (settings.enabledPlugins) { + const suffix = `@${name}`; + let removedPlugins = false; + const updatedPlugins = { + ...settings.enabledPlugins + }; + for (const pluginId in updatedPlugins) { + if (pluginId.endsWith(suffix)) { + updatedPlugins[pluginId] = undefined; + removedPlugins = true; + } + } + if (removedPlugins) { + updates.enabledPlugins = updatedPlugins; + } + } + if (Object.keys(updates).length > 0) { + updateSettingsForSource(source, updates); + } + } +} +function ErrorsTabContent(t0) { + const $ = _c(26); + const { + setViewState, + setActiveTab, + markPluginsChanged + } = t0; + const errors = useAppState(_temp2); + const installationStatus = useAppState(_temp3); + const setAppState = useSetAppState(); + const [selectedIndex, setSelectedIndex] = useState(0); + const [actionMessage, setActionMessage] = useState(null); + let t1; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t1 = []; + $[0] = t1; + } else { + t1 = $[0]; + } + const [marketplaceLoadFailures, setMarketplaceLoadFailures] = useState(t1); + let t2; + let t3; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t2 = () => { + (async () => { + try { + const config = await loadKnownMarketplacesConfig(); + const { + failures + } = await loadMarketplacesWithGracefulDegradation(config); + setMarketplaceLoadFailures(failures); + } catch {} + })(); + }; + t3 = []; + $[1] = t2; + $[2] = t3; + } else { + t2 = $[1]; + t3 = $[2]; + } + useEffect(t2, t3); + const failedMarketplaces = installationStatus.marketplaces.filter(_temp4); + const failedMarketplaceNames = new Set(failedMarketplaces.map(_temp5)); + const transientErrors = errors.filter(isTransientError); + const extraMarketplaceErrors = errors.filter(e => (e.type === "marketplace-not-found" || e.type === "marketplace-load-failed" || e.type === "marketplace-blocked-by-policy") && !failedMarketplaceNames.has(e.marketplace)); + const pluginLoadingErrors = errors.filter(_temp6); + const otherErrors = errors.filter(_temp7); + const pluginScopes = getPluginEditableScopes(); + const rows = buildErrorRows(failedMarketplaces, extraMarketplaceErrors, pluginLoadingErrors, otherErrors, marketplaceLoadFailures, transientErrors, pluginScopes); + let t4; + if ($[3] !== setViewState) { + t4 = () => { + setViewState({ + type: "menu" + }); + }; + $[3] = setViewState; + $[4] = t4; + } else { + t4 = $[4]; + } + let t5; + if ($[5] === Symbol.for("react.memo_cache_sentinel")) { + t5 = { + context: "Confirmation" + }; + $[5] = t5; + } else { + t5 = $[5]; + } + useKeybinding("confirm:no", t4, t5); + const handleSelect = () => { + const row = rows[selectedIndex]; + if (!row) { + return; + } + const { + action + } = row; + bb77: switch (action.kind) { + case "navigate": + { + setActiveTab(action.tab); + setViewState(action.viewState); + break bb77; + } + case "remove-extra-marketplace": + { + const scopes = action.sources.map(_temp8).join(", "); + removeExtraMarketplace(action.name, action.sources); + clearAllCaches(); + setAppState(prev_0 => ({ + ...prev_0, + plugins: { + ...prev_0.plugins, + errors: prev_0.plugins.errors.filter(e_2 => !("marketplace" in e_2 && e_2.marketplace === action.name)), + installationStatus: { + ...prev_0.plugins.installationStatus, + marketplaces: prev_0.plugins.installationStatus.marketplaces.filter(m_1 => m_1.name !== action.name) + } + } + })); + setActionMessage(`${figures.tick} Removed "${action.name}" from ${scopes} settings`); + markPluginsChanged(); + break bb77; + } + case "remove-installed-marketplace": + { + (async () => { + ; + try { + await removeMarketplaceSource(action.name); + clearAllCaches(); + setMarketplaceLoadFailures(prev => prev.filter(f => f.name !== action.name)); + setActionMessage(`${figures.tick} Removed marketplace "${action.name}"`); + markPluginsChanged(); + } catch (t6) { + const err = t6; + setActionMessage(`Failed to remove "${action.name}": ${err instanceof Error ? err.message : String(err)}`); + } + })(); + break bb77; + } + case "managed-only": + { + break bb77; + } + case "none": + } + }; + let t7; + if ($[6] === Symbol.for("react.memo_cache_sentinel")) { + t7 = () => setSelectedIndex(_temp9); + $[6] = t7; + } else { + t7 = $[6]; + } + const t8 = rows.length > 0; + let t9; + if ($[7] !== t8) { + t9 = { + context: "Select", + isActive: t8 + }; + $[7] = t8; + $[8] = t9; + } else { + t9 = $[8]; + } + useKeybindings({ + "select:previous": t7, + "select:next": () => setSelectedIndex(prev_2 => Math.min(rows.length - 1, prev_2 + 1)), + "select:accept": handleSelect + }, t9); + const clampedIndex = Math.min(selectedIndex, Math.max(0, rows.length - 1)); + if (clampedIndex !== selectedIndex) { + setSelectedIndex(clampedIndex); + } + const selectedAction = rows[clampedIndex]?.action; + const hasAction = selectedAction && selectedAction.kind !== "none" && selectedAction.kind !== "managed-only"; + if (rows.length === 0) { + let t10; + if ($[9] === Symbol.for("react.memo_cache_sentinel")) { + t10 = No plugin errors; + $[9] = t10; + } else { + t10 = $[9]; + } + let t11; + if ($[10] === Symbol.for("react.memo_cache_sentinel")) { + t11 = {t10}; + $[10] = t11; + } else { + t11 = $[10]; + } + return t11; + } + const T0 = Box; + const t10 = "column"; + let t11; + if ($[11] !== clampedIndex) { + t11 = (row_0, idx) => { + const isSelected = idx === clampedIndex; + return {isSelected ? figures.pointer : figures.cross}{" "}{row_0.label}{row_0.scope && ({row_0.scope})}{row_0.message}{row_0.guidance && {row_0.guidance}}; + }; + $[11] = clampedIndex; + $[12] = t11; + } else { + t11 = $[12]; + } + const t12 = rows.map(t11); + let t13; + if ($[13] !== actionMessage) { + t13 = actionMessage && {actionMessage}; + $[13] = actionMessage; + $[14] = t13; + } else { + t13 = $[14]; + } + let t14; + if ($[15] === Symbol.for("react.memo_cache_sentinel")) { + t14 = ; + $[15] = t14; + } else { + t14 = $[15]; + } + let t15; + if ($[16] !== hasAction) { + t15 = hasAction && ; + $[16] = hasAction; + $[17] = t15; + } else { + t15 = $[17]; + } + let t16; + if ($[18] === Symbol.for("react.memo_cache_sentinel")) { + t16 = ; + $[18] = t16; + } else { + t16 = $[18]; + } + let t17; + if ($[19] !== t15) { + t17 = {t14}{t15}{t16}; + $[19] = t15; + $[20] = t17; + } else { + t17 = $[20]; + } + let t18; + if ($[21] !== T0 || $[22] !== t12 || $[23] !== t13 || $[24] !== t17) { + t18 = {t12}{t13}{t17}; + $[21] = T0; + $[22] = t12; + $[23] = t13; + $[24] = t17; + $[25] = t18; + } else { + t18 = $[25]; + } + return t18; +} +function _temp9(prev_1) { + return Math.max(0, prev_1 - 1); +} +function _temp8(s_1) { + return s_1.scope; +} +function _temp7(e_1) { + if (isTransientError(e_1)) { + return false; + } + if (e_1.type === "marketplace-not-found" || e_1.type === "marketplace-load-failed" || e_1.type === "marketplace-blocked-by-policy") { + return false; + } + return getPluginNameFromError(e_1) === undefined; +} +function _temp6(e_0) { + if (isTransientError(e_0)) { + return false; + } + if (e_0.type === "marketplace-not-found" || e_0.type === "marketplace-load-failed" || e_0.type === "marketplace-blocked-by-policy") { + return false; + } + return getPluginNameFromError(e_0) !== undefined; +} +function _temp5(m_0) { + return m_0.name; +} +function _temp4(m) { + return m.status === "failed"; +} +function _temp3(s_0) { + return s_0.plugins.installationStatus; +} +function _temp2(s) { + return s.plugins.errors; +} +function getInitialViewState(parsedCommand: ParsedCommand): ViewState { + switch (parsedCommand.type) { + case 'help': + return { + type: 'help' + }; + case 'validate': + return { + type: 'validate', + path: parsedCommand.path + }; + case 'install': + if (parsedCommand.marketplace) { + return { + type: 'browse-marketplace', + targetMarketplace: parsedCommand.marketplace, + targetPlugin: parsedCommand.plugin + }; + } + if (parsedCommand.plugin) { + return { + type: 'discover-plugins', + targetPlugin: parsedCommand.plugin + }; + } + return { + type: 'discover-plugins' + }; + case 'manage': + return { + type: 'manage-plugins' + }; + case 'uninstall': + return { + type: 'manage-plugins', + targetPlugin: parsedCommand.plugin, + action: 'uninstall' + }; + case 'enable': + return { + type: 'manage-plugins', + targetPlugin: parsedCommand.plugin, + action: 'enable' + }; + case 'disable': + return { + type: 'manage-plugins', + targetPlugin: parsedCommand.plugin, + action: 'disable' + }; + case 'marketplace': + if (parsedCommand.action === 'list') { + return { + type: 'marketplace-list' + }; + } + if (parsedCommand.action === 'add') { + return { + type: 'add-marketplace', + initialValue: parsedCommand.target + }; + } + if (parsedCommand.action === 'remove') { + return { + type: 'manage-marketplaces', + targetMarketplace: parsedCommand.target, + action: 'remove' + }; + } + if (parsedCommand.action === 'update') { + return { + type: 'manage-marketplaces', + targetMarketplace: parsedCommand.target, + action: 'update' + }; + } + return { + type: 'marketplace-menu' + }; + case 'menu': + default: + // Default to discover view showing all plugins + return { + type: 'discover-plugins' + }; + } +} +function getInitialTab(viewState: ViewState): TabId { + if (viewState.type === 'manage-plugins') return 'installed'; + if (viewState.type === 'manage-marketplaces') return 'marketplaces'; + return 'discover'; +} +export function PluginSettings(t0) { + const $ = _c(75); + const { + onComplete, + args, + showMcpRedirectMessage + } = t0; + let parsedCommand; + let t1; + if ($[0] !== args) { + parsedCommand = parsePluginArgs(args); + t1 = getInitialViewState(parsedCommand); + $[0] = args; + $[1] = parsedCommand; + $[2] = t1; + } else { + parsedCommand = $[1]; + t1 = $[2]; + } + const initialViewState = t1; + const [viewState, setViewState] = useState(initialViewState); + let t2; + if ($[3] !== initialViewState) { + t2 = getInitialTab(initialViewState); + $[3] = initialViewState; + $[4] = t2; + } else { + t2 = $[4]; + } + const [activeTab, setActiveTab] = useState(t2); + const [inputValue, setInputValue] = useState(viewState.type === "add-marketplace" ? viewState.initialValue || "" : ""); + const [cursorOffset, setCursorOffset] = useState(0); + const [error, setError] = useState(null); + const [result, setResult] = useState(null); + const [childSearchActive, setChildSearchActive] = useState(false); + const setAppState = useSetAppState(); + const pluginErrorCount = useAppState(_temp0); + const errorsTabTitle = pluginErrorCount > 0 ? `Errors (${pluginErrorCount})` : "Errors"; + const exitState = useExitOnCtrlCDWithKeybindings(); + const cliMode = parsedCommand.type === "marketplace" && parsedCommand.action === "add" && parsedCommand.target !== undefined; + let t3; + if ($[5] !== setAppState) { + t3 = () => { + setAppState(_temp1); + }; + $[5] = setAppState; + $[6] = t3; + } else { + t3 = $[6]; + } + const markPluginsChanged = t3; + let t4; + if ($[7] === Symbol.for("react.memo_cache_sentinel")) { + t4 = tabId => { + const tab = tabId as TabId; + setActiveTab(tab); + setError(null); + bb37: switch (tab) { + case "discover": + { + setViewState({ + type: "discover-plugins" + }); + break bb37; + } + case "installed": + { + setViewState({ + type: "manage-plugins" + }); + break bb37; + } + case "marketplaces": + { + setViewState({ + type: "manage-marketplaces" + }); + break bb37; + } + case "errors": + } + }; + $[7] = t4; + } else { + t4 = $[7]; + } + const handleTabChange = t4; + let t5; + let t6; + if ($[8] !== onComplete || $[9] !== result || $[10] !== viewState.type) { + t5 = () => { + if (viewState.type === "menu" && !result) { + onComplete(); + } + }; + t6 = [viewState.type, result, onComplete]; + $[8] = onComplete; + $[9] = result; + $[10] = viewState.type; + $[11] = t5; + $[12] = t6; + } else { + t5 = $[11]; + t6 = $[12]; + } + useEffect(t5, t6); + let t7; + let t8; + if ($[13] !== activeTab || $[14] !== viewState.type) { + t7 = () => { + if (viewState.type === "browse-marketplace" && activeTab !== "discover") { + setActiveTab("discover"); + } + }; + t8 = [viewState.type, activeTab]; + $[13] = activeTab; + $[14] = viewState.type; + $[15] = t7; + $[16] = t8; + } else { + t7 = $[15]; + t8 = $[16]; + } + useEffect(t7, t8); + let t9; + if ($[17] === Symbol.for("react.memo_cache_sentinel")) { + t9 = () => { + setActiveTab("marketplaces"); + setViewState({ + type: "manage-marketplaces" + }); + setInputValue(""); + setError(null); + }; + $[17] = t9; + } else { + t9 = $[17]; + } + const handleAddMarketplaceEscape = t9; + const t10 = viewState.type === "add-marketplace"; + let t11; + if ($[18] !== t10) { + t11 = { + context: "Settings", + isActive: t10 + }; + $[18] = t10; + $[19] = t11; + } else { + t11 = $[19]; + } + useKeybinding("confirm:no", handleAddMarketplaceEscape, t11); + let t12; + let t13; + if ($[20] !== onComplete || $[21] !== result) { + t12 = () => { + if (result) { + onComplete(result); + } + }; + t13 = [result, onComplete]; + $[20] = onComplete; + $[21] = result; + $[22] = t12; + $[23] = t13; + } else { + t12 = $[22]; + t13 = $[23]; + } + useEffect(t12, t13); + let t14; + let t15; + if ($[24] !== onComplete || $[25] !== viewState.type) { + t14 = () => { + if (viewState.type === "help") { + onComplete(); + } + }; + t15 = [viewState.type, onComplete]; + $[24] = onComplete; + $[25] = viewState.type; + $[26] = t14; + $[27] = t15; + } else { + t14 = $[26]; + t15 = $[27]; + } + useEffect(t14, t15); + if (viewState.type === "help") { + let t16; + if ($[28] === Symbol.for("react.memo_cache_sentinel")) { + t16 = Plugin Command Usage: Installation: /plugin install - Browse and install plugins{" "}{"/plugin install - Install from specific marketplace"}{" /plugin install - Install specific plugin"}{" "}{"/plugin install @ - Install plugin from marketplace"} Management: /plugin manage - Manage installed plugins{" /plugin enable - Enable a plugin"}{" /plugin disable - Disable a plugin"}{" /plugin uninstall - Uninstall a plugin"} Marketplaces: /plugin marketplace - Marketplace management menu /plugin marketplace add - Add a marketplace{" "}{"/plugin marketplace add - Add marketplace directly"} /plugin marketplace update - Update marketplaces{" "}{"/plugin marketplace update - Update specific marketplace"} /plugin marketplace remove - Remove a marketplace{" "}{"/plugin marketplace remove - Remove specific marketplace"} /plugin marketplace list - List all marketplaces Validation:{" "}{"/plugin validate - Validate a manifest file or directory"} Other: /plugin - Main plugin menu /plugin help - Show this help /plugins - Alias for /plugin; + $[28] = t16; + } else { + t16 = $[28]; + } + return t16; + } + if (viewState.type === "validate") { + let t16; + if ($[29] !== onComplete || $[30] !== viewState.path) { + t16 = ; + $[29] = onComplete; + $[30] = viewState.path; + $[31] = t16; + } else { + t16 = $[31]; + } + return t16; + } + if (viewState.type === "marketplace-menu") { + setViewState({ + type: "menu" + }); + return null; + } + if (viewState.type === "marketplace-list") { + let t16; + if ($[32] !== onComplete) { + t16 = ; + $[32] = onComplete; + $[33] = t16; + } else { + t16 = $[33]; + } + return t16; + } + if (viewState.type === "add-marketplace") { + let t16; + if ($[34] !== cliMode || $[35] !== cursorOffset || $[36] !== error || $[37] !== inputValue || $[38] !== markPluginsChanged || $[39] !== result) { + t16 = ; + $[34] = cliMode; + $[35] = cursorOffset; + $[36] = error; + $[37] = inputValue; + $[38] = markPluginsChanged; + $[39] = result; + $[40] = t16; + } else { + t16 = $[40]; + } + return t16; + } + let t16; + if ($[41] !== activeTab || $[42] !== showMcpRedirectMessage) { + t16 = showMcpRedirectMessage && activeTab === "installed" ? : undefined; + $[41] = activeTab; + $[42] = showMcpRedirectMessage; + $[43] = t16; + } else { + t16 = $[43]; + } + let t17; + if ($[44] !== error || $[45] !== markPluginsChanged || $[46] !== result || $[47] !== viewState.targetMarketplace || $[48] !== viewState.targetPlugin || $[49] !== viewState.type) { + t17 = {viewState.type === "browse-marketplace" ? : }; + $[44] = error; + $[45] = markPluginsChanged; + $[46] = result; + $[47] = viewState.targetMarketplace; + $[48] = viewState.targetPlugin; + $[49] = viewState.type; + $[50] = t17; + } else { + t17 = $[50]; + } + const t18 = viewState.type === "manage-plugins" ? viewState.targetPlugin : undefined; + const t19 = viewState.type === "manage-plugins" ? viewState.targetMarketplace : undefined; + const t20 = viewState.type === "manage-plugins" ? viewState.action : undefined; + let t21; + if ($[51] !== markPluginsChanged || $[52] !== t18 || $[53] !== t19 || $[54] !== t20) { + t21 = ; + $[51] = markPluginsChanged; + $[52] = t18; + $[53] = t19; + $[54] = t20; + $[55] = t21; + } else { + t21 = $[55]; + } + const t22 = viewState.type === "manage-marketplaces" ? viewState.targetMarketplace : undefined; + const t23 = viewState.type === "manage-marketplaces" ? viewState.action : undefined; + let t24; + if ($[56] !== error || $[57] !== exitState || $[58] !== markPluginsChanged || $[59] !== t22 || $[60] !== t23) { + t24 = ; + $[56] = error; + $[57] = exitState; + $[58] = markPluginsChanged; + $[59] = t22; + $[60] = t23; + $[61] = t24; + } else { + t24 = $[61]; + } + let t25; + if ($[62] !== markPluginsChanged) { + t25 = ; + $[62] = markPluginsChanged; + $[63] = t25; + } else { + t25 = $[63]; + } + let t26; + if ($[64] !== errorsTabTitle || $[65] !== t25) { + t26 = {t25}; + $[64] = errorsTabTitle; + $[65] = t25; + $[66] = t26; + } else { + t26 = $[66]; + } + let t27; + if ($[67] !== activeTab || $[68] !== childSearchActive || $[69] !== t16 || $[70] !== t17 || $[71] !== t21 || $[72] !== t24 || $[73] !== t26) { + t27 = {t17}{t21}{t24}{t26}; + $[67] = activeTab; + $[68] = childSearchActive; + $[69] = t16; + $[70] = t17; + $[71] = t21; + $[72] = t24; + $[73] = t26; + $[74] = t27; + } else { + t27 = $[74]; + } + return t27; +} +function _temp1(prev) { + return prev.plugins.needsRefresh ? prev : { + ...prev, + plugins: { + ...prev.plugins, + needsRefresh: true + } + }; +} +function _temp0(s) { + let count = s.plugins.errors.length; + for (const m of s.plugins.installationStatus.marketplaces) { + if (m.status === "failed") { + count++; + } + } + return count; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwiUmVhY3QiLCJ1c2VDYWxsYmFjayIsInVzZUVmZmVjdCIsInVzZVN0YXRlIiwiQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50IiwiQnlsaW5lIiwiUGFuZSIsIlRhYiIsIlRhYnMiLCJ1c2VFeGl0T25DdHJsQ0RXaXRoS2V5YmluZGluZ3MiLCJCb3giLCJUZXh0IiwidXNlS2V5YmluZGluZyIsInVzZUtleWJpbmRpbmdzIiwidXNlQXBwU3RhdGUiLCJ1c2VTZXRBcHBTdGF0ZSIsIlBsdWdpbkVycm9yIiwiZXJyb3JNZXNzYWdlIiwiY2xlYXJBbGxDYWNoZXMiLCJsb2FkTWFya2V0cGxhY2VzV2l0aEdyYWNlZnVsRGVncmFkYXRpb24iLCJsb2FkS25vd25NYXJrZXRwbGFjZXNDb25maWciLCJyZW1vdmVNYXJrZXRwbGFjZVNvdXJjZSIsImdldFBsdWdpbkVkaXRhYmxlU2NvcGVzIiwiRWRpdGFibGVTZXR0aW5nU291cmNlIiwiZ2V0U2V0dGluZ3NGb3JTb3VyY2UiLCJ1cGRhdGVTZXR0aW5nc0ZvclNvdXJjZSIsIkFkZE1hcmtldHBsYWNlIiwiQnJvd3NlTWFya2V0cGxhY2UiLCJEaXNjb3ZlclBsdWdpbnMiLCJNYW5hZ2VNYXJrZXRwbGFjZXMiLCJNYW5hZ2VQbHVnaW5zIiwiZm9ybWF0RXJyb3JNZXNzYWdlIiwiZ2V0RXJyb3JHdWlkYW5jZSIsIlBhcnNlZENvbW1hbmQiLCJwYXJzZVBsdWdpbkFyZ3MiLCJQbHVnaW5TZXR0aW5nc1Byb3BzIiwiVmlld1N0YXRlIiwiVmFsaWRhdGVQbHVnaW4iLCJUYWJJZCIsIk1hcmtldHBsYWNlTGlzdCIsInQwIiwiJCIsIl9jIiwib25Db21wbGV0ZSIsInQxIiwidDIiLCJsb2FkTGlzdCIsImNvbmZpZyIsIm5hbWVzIiwiT2JqZWN0Iiwia2V5cyIsImxlbmd0aCIsIm1hcCIsIl90ZW1wIiwiam9pbiIsInQzIiwiZXJyIiwiU3ltYm9sIiwiZm9yIiwibiIsIk1jcFJlZGlyZWN0QmFubmVyIiwiRXJyb3JSb3dBY3Rpb24iLCJraW5kIiwidGFiIiwidmlld1N0YXRlIiwibmFtZSIsInNvdXJjZXMiLCJBcnJheSIsInNvdXJjZSIsInNjb3BlIiwiRXJyb3JSb3ciLCJsYWJlbCIsIm1lc3NhZ2UiLCJndWlkYW5jZSIsImFjdGlvbiIsImdldEV4dHJhTWFya2V0cGxhY2VTb3VyY2VJbmZvIiwiZWRpdGFibGVTb3VyY2VzIiwiaXNJblBvbGljeSIsInNvdXJjZXNUb0NoZWNrIiwiY29uc3QiLCJzZXR0aW5ncyIsImV4dHJhS25vd25NYXJrZXRwbGFjZXMiLCJwdXNoIiwicG9saWN5U2V0dGluZ3MiLCJCb29sZWFuIiwiYnVpbGRNYXJrZXRwbGFjZUFjdGlvbiIsInR5cGUiLCJ0YXJnZXRNYXJrZXRwbGFjZSIsImJ1aWxkUGx1Z2luQWN0aW9uIiwicGx1Z2luTmFtZSIsInRhcmdldFBsdWdpbiIsIlRSQU5TSUVOVF9FUlJPUl9UWVBFUyIsIlNldCIsImlzVHJhbnNpZW50RXJyb3IiLCJlcnJvciIsImhhcyIsImdldFBsdWdpbk5hbWVGcm9tRXJyb3IiLCJwbHVnaW5JZCIsInBsdWdpbiIsImluY2x1ZGVzIiwic3BsaXQiLCJ1bmRlZmluZWQiLCJidWlsZEVycm9yUm93cyIsImZhaWxlZE1hcmtldHBsYWNlcyIsImV4dHJhTWFya2V0cGxhY2VFcnJvcnMiLCJwbHVnaW5Mb2FkaW5nRXJyb3JzIiwib3RoZXJFcnJvcnMiLCJicm9rZW5JbnN0YWxsZWRNYXJrZXRwbGFjZXMiLCJ0cmFuc2llbnRFcnJvcnMiLCJwbHVnaW5TY29wZXMiLCJNYXAiLCJyb3dzIiwic2hvd25NYXJrZXRwbGFjZU5hbWVzIiwibSIsImFkZCIsInNvdXJjZUluZm8iLCJlIiwibWFya2V0cGxhY2UiLCJzaG93blBsdWdpbk5hbWVzIiwiZ2V0IiwicmVtb3ZlRXh0cmFNYXJrZXRwbGFjZSIsInVwZGF0ZXMiLCJSZWNvcmQiLCJlbmFibGVkUGx1Z2lucyIsInN1ZmZpeCIsInJlbW92ZWRQbHVnaW5zIiwidXBkYXRlZFBsdWdpbnMiLCJlbmRzV2l0aCIsIkVycm9yc1RhYkNvbnRlbnQiLCJzZXRWaWV3U3RhdGUiLCJzZXRBY3RpdmVUYWIiLCJtYXJrUGx1Z2luc0NoYW5nZWQiLCJlcnJvcnMiLCJfdGVtcDIiLCJpbnN0YWxsYXRpb25TdGF0dXMiLCJfdGVtcDMiLCJzZXRBcHBTdGF0ZSIsInNlbGVjdGVkSW5kZXgiLCJzZXRTZWxlY3RlZEluZGV4IiwiYWN0aW9uTWVzc2FnZSIsInNldEFjdGlvbk1lc3NhZ2UiLCJtYXJrZXRwbGFjZUxvYWRGYWlsdXJlcyIsInNldE1hcmtldHBsYWNlTG9hZEZhaWx1cmVzIiwiZmFpbHVyZXMiLCJtYXJrZXRwbGFjZXMiLCJmaWx0ZXIiLCJfdGVtcDQiLCJmYWlsZWRNYXJrZXRwbGFjZU5hbWVzIiwiX3RlbXA1IiwiX3RlbXA2IiwiX3RlbXA3IiwidDQiLCJ0NSIsImNvbnRleHQiLCJoYW5kbGVTZWxlY3QiLCJyb3ciLCJiYjc3Iiwic2NvcGVzIiwiX3RlbXA4IiwicHJldl8wIiwicHJldiIsInBsdWdpbnMiLCJlXzIiLCJtXzEiLCJ0aWNrIiwiZiIsInQ2IiwiRXJyb3IiLCJTdHJpbmciLCJ0NyIsIl90ZW1wOSIsInQ4IiwidDkiLCJpc0FjdGl2ZSIsInNlbGVjdDpuZXh0IiwicHJldl8yIiwiTWF0aCIsIm1pbiIsImNsYW1wZWRJbmRleCIsIm1heCIsInNlbGVjdGVkQWN0aW9uIiwiaGFzQWN0aW9uIiwidDEwIiwidDExIiwiVDAiLCJyb3dfMCIsImlkeCIsImlzU2VsZWN0ZWQiLCJwb2ludGVyIiwiY3Jvc3MiLCJ0MTIiLCJ0MTMiLCJ0MTQiLCJ0MTUiLCJ0MTYiLCJ0MTciLCJ0MTgiLCJwcmV2XzEiLCJzXzEiLCJzIiwiZV8xIiwiZV8wIiwibV8wIiwic3RhdHVzIiwic18wIiwiZ2V0SW5pdGlhbFZpZXdTdGF0ZSIsInBhcnNlZENvbW1hbmQiLCJwYXRoIiwiaW5pdGlhbFZhbHVlIiwidGFyZ2V0IiwiZ2V0SW5pdGlhbFRhYiIsIlBsdWdpblNldHRpbmdzIiwiYXJncyIsInNob3dNY3BSZWRpcmVjdE1lc3NhZ2UiLCJpbml0aWFsVmlld1N0YXRlIiwiYWN0aXZlVGFiIiwiaW5wdXRWYWx1ZSIsInNldElucHV0VmFsdWUiLCJjdXJzb3JPZmZzZXQiLCJzZXRDdXJzb3JPZmZzZXQiLCJzZXRFcnJvciIsInJlc3VsdCIsInNldFJlc3VsdCIsImNoaWxkU2VhcmNoQWN0aXZlIiwic2V0Q2hpbGRTZWFyY2hBY3RpdmUiLCJwbHVnaW5FcnJvckNvdW50IiwiX3RlbXAwIiwiZXJyb3JzVGFiVGl0bGUiLCJleGl0U3RhdGUiLCJjbGlNb2RlIiwiX3RlbXAxIiwidGFiSWQiLCJiYjM3IiwiaGFuZGxlVGFiQ2hhbmdlIiwiaGFuZGxlQWRkTWFya2V0cGxhY2VFc2NhcGUiLCJ0MTkiLCJ0MjAiLCJ0MjEiLCJ0MjIiLCJ0MjMiLCJ0MjQiLCJ0MjUiLCJ0MjYiLCJ0MjciLCJuZWVkc1JlZnJlc2giLCJjb3VudCJdLCJzb3VyY2VzIjpbIlBsdWdpblNldHRpbmdzLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgZmlndXJlcyBmcm9tICdmaWd1cmVzJ1xuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyB1c2VDYWxsYmFjaywgdXNlRWZmZWN0LCB1c2VTdGF0ZSB9IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9Db25maWd1cmFibGVTaG9ydGN1dEhpbnQuanMnXG5pbXBvcnQgeyBCeWxpbmUgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL2Rlc2lnbi1zeXN0ZW0vQnlsaW5lLmpzJ1xuaW1wb3J0IHsgUGFuZSB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvZGVzaWduLXN5c3RlbS9QYW5lLmpzJ1xuaW1wb3J0IHsgVGFiLCBUYWJzIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL1RhYnMuanMnXG5pbXBvcnQgeyB1c2VFeGl0T25DdHJsQ0RXaXRoS2V5YmluZGluZ3MgfSBmcm9tICcuLi8uLi9ob29rcy91c2VFeGl0T25DdHJsQ0RXaXRoS2V5YmluZGluZ3MuanMnXG5pbXBvcnQgeyBCb3gsIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQge1xuICB1c2VLZXliaW5kaW5nLFxuICB1c2VLZXliaW5kaW5ncyxcbn0gZnJvbSAnLi4vLi4va2V5YmluZGluZ3MvdXNlS2V5YmluZGluZy5qcydcbmltcG9ydCB7IHVzZUFwcFN0YXRlLCB1c2VTZXRBcHBTdGF0ZSB9IGZyb20gJy4uLy4uL3N0YXRlL0FwcFN0YXRlLmpzJ1xuaW1wb3J0IHR5cGUgeyBQbHVnaW5FcnJvciB9IGZyb20gJy4uLy4uL3R5cGVzL3BsdWdpbi5qcydcbmltcG9ydCB7IGVycm9yTWVzc2FnZSB9IGZyb20gJy4uLy4uL3V0aWxzL2Vycm9ycy5qcydcbmltcG9ydCB7IGNsZWFyQWxsQ2FjaGVzIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9jYWNoZVV0aWxzLmpzJ1xuaW1wb3J0IHsgbG9hZE1hcmtldHBsYWNlc1dpdGhHcmFjZWZ1bERlZ3JhZGF0aW9uIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9tYXJrZXRwbGFjZUhlbHBlcnMuanMnXG5pbXBvcnQge1xuICBsb2FkS25vd25NYXJrZXRwbGFjZXNDb25maWcsXG4gIHJlbW92ZU1hcmtldHBsYWNlU291cmNlLFxufSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL21hcmtldHBsYWNlTWFuYWdlci5qcydcbmltcG9ydCB7IGdldFBsdWdpbkVkaXRhYmxlU2NvcGVzIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9wbHVnaW5TdGFydHVwQ2hlY2suanMnXG5pbXBvcnQgdHlwZSB7IEVkaXRhYmxlU2V0dGluZ1NvdXJjZSB9IGZyb20gJy4uLy4uL3V0aWxzL3NldHRpbmdzL2NvbnN0YW50cy5qcydcbmltcG9ydCB7XG4gIGdldFNldHRpbmdzRm9yU291cmNlLFxuICB1cGRhdGVTZXR0aW5nc0ZvclNvdXJjZSxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvc2V0dGluZ3Mvc2V0dGluZ3MuanMnXG5pbXBvcnQgeyBBZGRNYXJrZXRwbGFjZSB9IGZyb20gJy4vQWRkTWFya2V0cGxhY2UuanMnXG5pbXBvcnQgeyBCcm93c2VNYXJrZXRwbGFjZSB9IGZyb20gJy4vQnJvd3NlTWFya2V0cGxhY2UuanMnXG5pbXBvcnQgeyBEaXNjb3ZlclBsdWdpbnMgfSBmcm9tICcuL0Rpc2NvdmVyUGx1Z2lucy5qcydcbmltcG9ydCB7IE1hbmFnZU1hcmtldHBsYWNlcyB9IGZyb20gJy4vTWFuYWdlTWFya2V0cGxhY2VzLmpzJ1xuaW1wb3J0IHsgTWFuYWdlUGx1Z2lucyB9IGZyb20gJy4vTWFuYWdlUGx1Z2lucy5qcydcbmltcG9ydCB7IGZvcm1hdEVycm9yTWVzc2FnZSwgZ2V0RXJyb3JHdWlkYW5jZSB9IGZyb20gJy4vUGx1Z2luRXJyb3JzLmpzJ1xuaW1wb3J0IHsgdHlwZSBQYXJzZWRDb21tYW5kLCBwYXJzZVBsdWdpbkFyZ3MgfSBmcm9tICcuL3BhcnNlQXJncy5qcydcbmltcG9ydCB0eXBlIHsgUGx1Z2luU2V0dGluZ3NQcm9wcywgVmlld1N0YXRlIH0gZnJvbSAnLi90eXBlcy5qcydcbmltcG9ydCB7IFZhbGlkYXRlUGx1Z2luIH0gZnJvbSAnLi9WYWxpZGF0ZVBsdWdpbi5qcydcblxudHlwZSBUYWJJZCA9ICdkaXNjb3ZlcicgfCAnaW5zdGFsbGVkJyB8ICdtYXJrZXRwbGFjZXMnIHwgJ2Vycm9ycydcblxuZnVuY3Rpb24gTWFya2V0cGxhY2VMaXN0KHtcbiAgb25Db21wbGV0ZSxcbn06IHtcbiAgb25Db21wbGV0ZTogKHJlc3VsdD86IHN0cmluZykgPT4gdm9pZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgYXN5bmMgZnVuY3Rpb24gbG9hZExpc3QoKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCBjb25maWcgPSBhd2FpdCBsb2FkS25vd25NYXJrZXRwbGFjZXNDb25maWcoKVxuICAgICAgICBjb25zdCBuYW1lcyA9IE9iamVjdC5rZXlzKGNvbmZpZylcblxuICAgICAgICBpZiAobmFtZXMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgb25Db21wbGV0ZSgnTm8gbWFya2V0cGxhY2VzIGNvbmZpZ3VyZWQnKVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIG9uQ29tcGxldGUoXG4gICAgICAgICAgICBgQ29uZmlndXJlZCBtYXJrZXRwbGFjZXM6XFxuJHtuYW1lcy5tYXAobiA9PiBgICDigKIgJHtufWApLmpvaW4oJ1xcbicpfWAsXG4gICAgICAgICAgKVxuICAgICAgICB9XG4gICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgb25Db21wbGV0ZShgRXJyb3IgbG9hZGluZyBtYXJrZXRwbGFjZXM6ICR7ZXJyb3JNZXNzYWdlKGVycil9YClcbiAgICAgIH1cbiAgICB9XG5cbiAgICB2b2lkIGxvYWRMaXN0KClcbiAgfSwgW29uQ29tcGxldGVdKVxuXG4gIHJldHVybiA8VGV4dD5Mb2FkaW5nIG1hcmtldHBsYWNlcy4uLjwvVGV4dD5cbn1cblxuZnVuY3Rpb24gTWNwUmVkaXJlY3RCYW5uZXIoKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgaWYgKFwiZXh0ZXJuYWxcIiAhPT0gJ2FudCcpIHtcbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgcmV0dXJuIChcbiAgICA8Qm94XG4gICAgICBmbGV4RGlyZWN0aW9uPVwicm93XCJcbiAgICAgIGFsaWduSXRlbXM9XCJmbGV4LXN0YXJ0XCJcbiAgICAgIHBhZGRpbmdMZWZ0PXsxfVxuICAgICAgbWFyZ2luVG9wPXsxfVxuICAgICAgYm9yZGVyTGVmdFxuICAgICAgYm9yZGVyUmlnaHQ9e2ZhbHNlfVxuICAgICAgYm9yZGVyVG9wPXtmYWxzZX1cbiAgICAgIGJvcmRlckJvdHRvbT17ZmFsc2V9XG4gICAgICBib3JkZXJDb2xvcj1cInBlcm1pc3Npb25cIlxuICAgICAgYm9yZGVyU3R5bGU9XCJzaW5nbGVcIlxuICAgID5cbiAgICAgIDxCb3ggZmxleFNocmluaz17MH0+XG4gICAgICAgIDxUZXh0IGJvbGQgaXRhbGljIGNvbG9yPVwicGVybWlzc2lvblwiPlxuICAgICAgICAgIGl7JyAnfVxuICAgICAgICA8L1RleHQ+XG4gICAgICA8L0JveD5cbiAgICAgIDxUZXh0PlxuICAgICAgICBbQU5ULU9OTFldIE1DUCBzZXJ2ZXJzIGFyZSBub3cgbWFuYWdlZCBpbiAvcGx1Z2lucy4gVXNlIC9tY3Agbm8tcmVkaXJlY3RcbiAgICAgICAgdG8gdGVzdCBvbGQgVUlcbiAgICAgIDwvVGV4dD5cbiAgICA8L0JveD5cbiAgKVxufVxuXG50eXBlIEVycm9yUm93QWN0aW9uID1cbiAgfCB7IGtpbmQ6ICduYXZpZ2F0ZSc7IHRhYjogVGFiSWQ7IHZpZXdTdGF0ZTogVmlld1N0YXRlIH1cbiAgfCB7XG4gICAgICBraW5kOiAncmVtb3ZlLWV4dHJhLW1hcmtldHBsYWNlJ1xuICAgICAgbmFtZTogc3RyaW5nXG4gICAgICBzb3VyY2VzOiBBcnJheTx7IHNvdXJjZTogRWRpdGFibGVTZXR0aW5nU291cmNlOyBzY29wZTogc3RyaW5nIH0+XG4gICAgfVxuICB8IHsga2luZDogJ3JlbW92ZS1pbnN0YWxsZWQtbWFya2V0cGxhY2UnOyBuYW1lOiBzdHJpbmcgfVxuICB8IHsga2luZDogJ21hbmFnZWQtb25seSc7IG5hbWU6IHN0cmluZyB9XG4gIHwgeyBraW5kOiAnbm9uZScgfVxuXG50eXBlIEVycm9yUm93ID0ge1xuICBsYWJlbDogc3RyaW5nXG4gIG1lc3NhZ2U6IHN0cmluZ1xuICBndWlkYW5jZT86IHN0cmluZyB8IG51bGxcbiAgYWN0aW9uOiBFcnJvclJvd0FjdGlvblxuICBzY29wZT86IHN0cmluZ1xufVxuXG4vKipcbiAqIERldGVybWluZSB3aGljaCBzZXR0aW5ncyBzb3VyY2VzIGRlZmluZSBhbiBleHRyYUtub3duTWFya2V0cGxhY2UgZW50cnkuXG4gKiBSZXR1cm5zIHRoZSBlZGl0YWJsZSBzb3VyY2VzICh1c2VyL3Byb2plY3QvbG9jYWwpIGFuZCB3aGV0aGVyIHBvbGljeSBhbHNvIGhhcyBpdC5cbiAqL1xuZnVuY3Rpb24gZ2V0RXh0cmFNYXJrZXRwbGFjZVNvdXJjZUluZm8obmFtZTogc3RyaW5nKToge1xuICBlZGl0YWJsZVNvdXJjZXM6IEFycmF5PHsgc291cmNlOiBFZGl0YWJsZVNldHRpbmdTb3VyY2U7IHNjb3BlOiBzdHJpbmcgfT5cbiAgaXNJblBvbGljeTogYm9vbGVhblxufSB7XG4gIGNvbnN0IGVkaXRhYmxlU291cmNlczogQXJyYXk8e1xuICAgIHNvdXJjZTogRWRpdGFibGVTZXR0aW5nU291cmNlXG4gICAgc2NvcGU6IHN0cmluZ1xuICB9PiA9IFtdXG5cbiAgY29uc3Qgc291cmNlc1RvQ2hlY2sgPSBbXG4gICAgeyBzb3VyY2U6ICd1c2VyU2V0dGluZ3MnIGFzIGNvbnN0LCBzY29wZTogJ3VzZXInIH0sXG4gICAgeyBzb3VyY2U6ICdwcm9qZWN0U2V0dGluZ3MnIGFzIGNvbnN0LCBzY29wZTogJ3Byb2plY3QnIH0sXG4gICAgeyBzb3VyY2U6ICdsb2NhbFNldHRpbmdzJyBhcyBjb25zdCwgc2NvcGU6ICdsb2NhbCcgfSxcbiAgXVxuXG4gIGZvciAoY29uc3QgeyBzb3VyY2UsIHNjb3BlIH0gb2Ygc291cmNlc1RvQ2hlY2spIHtcbiAgICBjb25zdCBzZXR0aW5ncyA9IGdldFNldHRpbmdzRm9yU291cmNlKHNvdXJjZSlcbiAgICBpZiAoc2V0dGluZ3M/LmV4dHJhS25vd25NYXJrZXRwbGFjZXM/LltuYW1lXSkge1xuICAgICAgZWRpdGFibGVTb3VyY2VzLnB1c2goeyBzb3VyY2UsIHNjb3BlIH0pXG4gICAgfVxuICB9XG5cbiAgY29uc3QgcG9saWN5U2V0dGluZ3MgPSBnZXRTZXR0aW5nc0ZvclNvdXJjZSgncG9saWN5U2V0dGluZ3MnKVxuICBjb25zdCBpc0luUG9saWN5ID0gQm9vbGVhbihwb2xpY3lTZXR0aW5ncz8uZXh0cmFLbm93bk1hcmtldHBsYWNlcz8uW25hbWVdKVxuXG4gIHJldHVybiB7IGVkaXRhYmxlU291cmNlcywgaXNJblBvbGljeSB9XG59XG5cbmZ1bmN0aW9uIGJ1aWxkTWFya2V0cGxhY2VBY3Rpb24obmFtZTogc3RyaW5nKTogRXJyb3JSb3dBY3Rpb24ge1xuICBjb25zdCB7IGVkaXRhYmxlU291cmNlcywgaXNJblBvbGljeSB9ID0gZ2V0RXh0cmFNYXJrZXRwbGFjZVNvdXJjZUluZm8obmFtZSlcblxuICBpZiAoZWRpdGFibGVTb3VyY2VzLmxlbmd0aCA+IDApIHtcbiAgICByZXR1cm4ge1xuICAgICAga2luZDogJ3JlbW92ZS1leHRyYS1tYXJrZXRwbGFjZScsXG4gICAgICBuYW1lLFxuICAgICAgc291cmNlczogZWRpdGFibGVTb3VyY2VzLFxuICAgIH1cbiAgfVxuXG4gIGlmIChpc0luUG9saWN5KSB7XG4gICAgcmV0dXJuIHsga2luZDogJ21hbmFnZWQtb25seScsIG5hbWUgfVxuICB9XG5cbiAgLy8gTWFya2V0cGxhY2UgaXMgaW4ga25vd25fbWFya2V0cGxhY2VzLmpzb24gYnV0IG5vdCBpbiBleHRyYUtub3duTWFya2V0cGxhY2VzXG4gIC8vIChlLmcuIHByZXZpb3VzbHkgaW5zdGFsbGVkIG1hbnVhbGx5KSDigJQgcm91dGUgdG8gTWFuYWdlTWFya2V0cGxhY2VzXG4gIHJldHVybiB7XG4gICAga2luZDogJ25hdmlnYXRlJyxcbiAgICB0YWI6ICdtYXJrZXRwbGFjZXMnLFxuICAgIHZpZXdTdGF0ZToge1xuICAgICAgdHlwZTogJ21hbmFnZS1tYXJrZXRwbGFjZXMnLFxuICAgICAgdGFyZ2V0TWFya2V0cGxhY2U6IG5hbWUsXG4gICAgICBhY3Rpb246ICdyZW1vdmUnLFxuICAgIH0sXG4gIH1cbn1cblxuZnVuY3Rpb24gYnVpbGRQbHVnaW5BY3Rpb24ocGx1Z2luTmFtZTogc3RyaW5nKTogRXJyb3JSb3dBY3Rpb24ge1xuICByZXR1cm4ge1xuICAgIGtpbmQ6ICduYXZpZ2F0ZScsXG4gICAgdGFiOiAnaW5zdGFsbGVkJyxcbiAgICB2aWV3U3RhdGU6IHtcbiAgICAgIHR5cGU6ICdtYW5hZ2UtcGx1Z2lucycsXG4gICAgICB0YXJnZXRQbHVnaW46IHBsdWdpbk5hbWUsXG4gICAgICBhY3Rpb246ICd1bmluc3RhbGwnLFxuICAgIH0sXG4gIH1cbn1cblxuY29uc3QgVFJBTlNJRU5UX0VSUk9SX1RZUEVTID0gbmV3IFNldChbXG4gICdnaXQtYXV0aC1mYWlsZWQnLFxuICAnZ2l0LXRpbWVvdXQnLFxuICAnbmV0d29yay1lcnJvcicsXG5dKVxuXG5mdW5jdGlvbiBpc1RyYW5zaWVudEVycm9yKGVycm9yOiBQbHVnaW5FcnJvcik6IGJvb2xlYW4ge1xuICByZXR1cm4gVFJBTlNJRU5UX0VSUk9SX1RZUEVTLmhhcyhlcnJvci50eXBlKVxufVxuXG4vKipcbiAqIEV4dHJhY3QgdGhlIHBsdWdpbiBuYW1lIGZyb20gYSBQbHVnaW5FcnJvciwgY2hlY2tpbmcgZXhwbGljaXQgZmllbGRzIGZpcnN0LFxuICogdGhlbiBmYWxsaW5nIGJhY2sgdG8gdGhlIHNvdXJjZSBmaWVsZCAoZm9ybWF0OiBcInBsdWdpbk5hbWVAbWFya2V0cGxhY2VcIikuXG4gKi9cbmZ1bmN0aW9uIGdldFBsdWdpbk5hbWVGcm9tRXJyb3IoZXJyb3I6IFBsdWdpbkVycm9yKTogc3RyaW5nIHwgdW5kZWZpbmVkIHtcbiAgaWYgKCdwbHVnaW5JZCcgaW4gZXJyb3IgJiYgZXJyb3IucGx1Z2luSWQpIHJldHVybiBlcnJvci5wbHVnaW5JZFxuICBpZiAoJ3BsdWdpbicgaW4gZXJyb3IgJiYgZXJyb3IucGx1Z2luKSByZXR1cm4gZXJyb3IucGx1Z2luXG4gIC8vIEZhbGxiYWNrOiBzb3VyY2Ugb2Z0ZW4gY29udGFpbnMgXCJwbHVnaW5OYW1lQG1hcmtldHBsYWNlXCJcbiAgaWYgKGVycm9yLnNvdXJjZS5pbmNsdWRlcygnQCcpKSByZXR1cm4gZXJyb3Iuc291cmNlLnNwbGl0KCdAJylbMF1cbiAgcmV0dXJuIHVuZGVmaW5lZFxufVxuXG5mdW5jdGlvbiBidWlsZEVycm9yUm93cyhcbiAgZmFpbGVkTWFya2V0cGxhY2VzOiBBcnJheTx7IG5hbWU6IHN0cmluZzsgZXJyb3I/OiBzdHJpbmcgfT4sXG4gIGV4dHJhTWFya2V0cGxhY2VFcnJvcnM6IFBsdWdpbkVycm9yW10sXG4gIHBsdWdpbkxvYWRpbmdFcnJvcnM6IFBsdWdpbkVycm9yW10sXG4gIG90aGVyRXJyb3JzOiBQbHVnaW5FcnJvcltdLFxuICBicm9rZW5JbnN0YWxsZWRNYXJrZXRwbGFjZXM6IEFycmF5PHsgbmFtZTogc3RyaW5nOyBlcnJvcjogc3RyaW5nIH0+LFxuICB0cmFuc2llbnRFcnJvcnM6IFBsdWdpbkVycm9yW10sXG4gIHBsdWdpblNjb3BlczogTWFwPHN0cmluZywgc3RyaW5nPixcbik6IEVycm9yUm93W10ge1xuICBjb25zdCByb3dzOiBFcnJvclJvd1tdID0gW11cblxuICAvLyAtLS0gVHJhbnNpZW50IGVycm9ycyBhdCB0aGUgdG9wIChyZXN0YXJ0IHRvIHJldHJ5KSAtLS1cbiAgZm9yIChjb25zdCBlcnJvciBvZiB0cmFuc2llbnRFcnJvcnMpIHtcbiAgICBjb25zdCBwbHVnaW5OYW1lID1cbiAgICAgICdwbHVnaW5JZCcgaW4gZXJyb3JcbiAgICAgICAgPyBlcnJvci5wbHVnaW5JZFxuICAgICAgICA6ICdwbHVnaW4nIGluIGVycm9yXG4gICAgICAgICAgPyBlcnJvci5wbHVnaW5cbiAgICAgICAgICA6IHVuZGVmaW5lZFxuICAgIHJvd3MucHVzaCh7XG4gICAgICBsYWJlbDogcGx1Z2luTmFtZSA/PyBlcnJvci5zb3VyY2UsXG4gICAgICBtZXNzYWdlOiBmb3JtYXRFcnJvck1lc3NhZ2UoZXJyb3IpLFxuICAgICAgZ3VpZGFuY2U6ICdSZXN0YXJ0IHRvIHJldHJ5IGxvYWRpbmcgcGx1Z2lucycsXG4gICAgICBhY3Rpb246IHsga2luZDogJ25vbmUnIH0sXG4gICAgfSlcbiAgfVxuXG4gIC8vIC0tLSBNYXJrZXRwbGFjZSBlcnJvcnMgLS0tXG4gIC8vIFRyYWNrIHNob3duIG1hcmtldHBsYWNlIG5hbWVzIHRvIGF2b2lkIGR1cGxpY2F0ZXMgYWNyb3NzIHNvdXJjZXNcbiAgY29uc3Qgc2hvd25NYXJrZXRwbGFjZU5hbWVzID0gbmV3IFNldDxzdHJpbmc+KClcblxuICBmb3IgKGNvbnN0IG0gb2YgZmFpbGVkTWFya2V0cGxhY2VzKSB7XG4gICAgc2hvd25NYXJrZXRwbGFjZU5hbWVzLmFkZChtLm5hbWUpXG4gICAgY29uc3QgYWN0aW9uID0gYnVpbGRNYXJrZXRwbGFjZUFjdGlvbihtLm5hbWUpXG4gICAgY29uc3Qgc291cmNlSW5mbyA9IGdldEV4dHJhTWFya2V0cGxhY2VTb3VyY2VJbmZvKG0ubmFtZSlcbiAgICBjb25zdCBzY29wZSA9IHNvdXJjZUluZm8uaXNJblBvbGljeVxuICAgICAgPyAnbWFuYWdlZCdcbiAgICAgIDogc291cmNlSW5mby5lZGl0YWJsZVNvdXJjZXNbMF0/LnNjb3BlXG4gICAgcm93cy5wdXNoKHtcbiAgICAgIGxhYmVsOiBtLm5hbWUsXG4gICAgICBtZXNzYWdlOiBtLmVycm9yID8/ICdJbnN0YWxsYXRpb24gZmFpbGVkJyxcbiAgICAgIGd1aWRhbmNlOlxuICAgICAgICBhY3Rpb24ua2luZCA9PT0gJ21hbmFnZWQtb25seSdcbiAgICAgICAgICA/ICdNYW5hZ2VkIGJ5IHlvdXIgb3JnYW5pemF0aW9uIOKAlCBjb250YWN0IHlvdXIgYWRtaW4nXG4gICAgICAgICAgOiB1bmRlZmluZWQsXG4gICAgICBhY3Rpb24sXG4gICAgICBzY29wZSxcbiAgICB9KVxuICB9XG5cbiAgZm9yIChjb25zdCBlIG9mIGV4dHJhTWFya2V0cGxhY2VFcnJvcnMpIHtcbiAgICBjb25zdCBtYXJrZXRwbGFjZSA9ICdtYXJrZXRwbGFjZScgaW4gZSA/IGUubWFya2V0cGxhY2UgOiBlLnNvdXJjZVxuICAgIGlmIChzaG93bk1hcmtldHBsYWNlTmFtZXMuaGFzKG1hcmtldHBsYWNlKSkgY29udGludWVcbiAgICBzaG93bk1hcmtldHBsYWNlTmFtZXMuYWRkKG1hcmtldHBsYWNlKVxuICAgIGNvbnN0IGFjdGlvbiA9IGJ1aWxkTWFya2V0cGxhY2VBY3Rpb24obWFya2V0cGxhY2UpXG4gICAgY29uc3Qgc291cmNlSW5mbyA9IGdldEV4dHJhTWFya2V0cGxhY2VTb3VyY2VJbmZvKG1hcmtldHBsYWNlKVxuICAgIGNvbnN0IHNjb3BlID0gc291cmNlSW5mby5pc0luUG9saWN5XG4gICAgICA/ICdtYW5hZ2VkJ1xuICAgICAgOiBzb3VyY2VJbmZvLmVkaXRhYmxlU291cmNlc1swXT8uc2NvcGVcbiAgICByb3dzLnB1c2goe1xuICAgICAgbGFiZWw6IG1hcmtldHBsYWNlLFxuICAgICAgbWVzc2FnZTogZm9ybWF0RXJyb3JNZXNzYWdlKGUpLFxuICAgICAgZ3VpZGFuY2U6XG4gICAgICAgIGFjdGlvbi5raW5kID09PSAnbWFuYWdlZC1vbmx5J1xuICAgICAgICAgID8gJ01hbmFnZWQgYnkgeW91ciBvcmdhbml6YXRpb24g4oCUIGNvbnRhY3QgeW91ciBhZG1pbidcbiAgICAgICAgICA6IGdldEVycm9yR3VpZGFuY2UoZSksXG4gICAgICBhY3Rpb24sXG4gICAgICBzY29wZSxcbiAgICB9KVxuICB9XG5cbiAgLy8gSW5zdGFsbGVkIG1hcmtldHBsYWNlcyB0aGF0IGZhaWwgdG8gbG9hZCBkYXRhIChmcm9tIGtub3duX21hcmtldHBsYWNlcy5qc29uKVxuICBmb3IgKGNvbnN0IG0gb2YgYnJva2VuSW5zdGFsbGVkTWFya2V0cGxhY2VzKSB7XG4gICAgaWYgKHNob3duTWFya2V0cGxhY2VOYW1lcy5oYXMobS5uYW1lKSkgY29udGludWVcbiAgICBzaG93bk1hcmtldHBsYWNlTmFtZXMuYWRkKG0ubmFtZSlcbiAgICByb3dzLnB1c2goe1xuICAgICAgbGFiZWw6IG0ubmFtZSxcbiAgICAgIG1lc3NhZ2U6IG0uZXJyb3IsXG4gICAgICBhY3Rpb246IHsga2luZDogJ3JlbW92ZS1pbnN0YWxsZWQtbWFya2V0cGxhY2UnLCBuYW1lOiBtLm5hbWUgfSxcbiAgICB9KVxuICB9XG5cbiAgLy8gLS0tIFBsdWdpbiBlcnJvcnMgLS0tXG4gIGNvbnN0IHNob3duUGx1Z2luTmFtZXMgPSBuZXcgU2V0PHN0cmluZz4oKVxuICBmb3IgKGNvbnN0IGVycm9yIG9mIHBsdWdpbkxvYWRpbmdFcnJvcnMpIHtcbiAgICBjb25zdCBwbHVnaW5OYW1lID0gZ2V0UGx1Z2luTmFtZUZyb21FcnJvcihlcnJvcilcbiAgICBpZiAocGx1Z2luTmFtZSAmJiBzaG93blBsdWdpbk5hbWVzLmhhcyhwbHVnaW5OYW1lKSkgY29udGludWVcbiAgICBpZiAocGx1Z2luTmFtZSkgc2hvd25QbHVnaW5OYW1lcy5hZGQocGx1Z2luTmFtZSlcblxuICAgIGNvbnN0IG1hcmtldHBsYWNlID0gJ21hcmtldHBsYWNlJyBpbiBlcnJvciA/IGVycm9yLm1hcmtldHBsYWNlIDogdW5kZWZpbmVkXG4gICAgLy8gVHJ5IHBsdWdpbklkQG1hcmtldHBsYWNlIGZvcm1hdCBmaXJzdCwgdGhlbiBqdXN0IHBsdWdpbk5hbWVcbiAgICBjb25zdCBzY29wZSA9IHBsdWdpbk5hbWVcbiAgICAgID8gKHBsdWdpblNjb3Blcy5nZXQoZXJyb3Iuc291cmNlKSA/PyBwbHVnaW5TY29wZXMuZ2V0KHBsdWdpbk5hbWUpKVxuICAgICAgOiB1bmRlZmluZWRcbiAgICByb3dzLnB1c2goe1xuICAgICAgbGFiZWw6IHBsdWdpbk5hbWVcbiAgICAgICAgPyBtYXJrZXRwbGFjZVxuICAgICAgICAgID8gYCR7cGx1Z2luTmFtZX0gQCAke21hcmtldHBsYWNlfWBcbiAgICAgICAgICA6IHBsdWdpbk5hbWVcbiAgICAgICAgOiBlcnJvci5zb3VyY2UsXG4gICAgICBtZXNzYWdlOiBmb3JtYXRFcnJvck1lc3NhZ2UoZXJyb3IpLFxuICAgICAgZ3VpZGFuY2U6IGdldEVycm9yR3VpZGFuY2UoZXJyb3IpLFxuICAgICAgYWN0aW9uOiBwbHVnaW5OYW1lID8gYnVpbGRQbHVnaW5BY3Rpb24ocGx1Z2luTmFtZSkgOiB7IGtpbmQ6ICdub25lJyB9LFxuICAgICAgc2NvcGUsXG4gICAgfSlcbiAgfVxuXG4gIC8vIC0tLSBPdGhlciBlcnJvcnMgKG5vbi1tYXJrZXRwbGFjZSwgbm9uLXBsdWdpbi1zcGVjaWZpYykgLS0tXG4gIGZvciAoY29uc3QgZXJyb3Igb2Ygb3RoZXJFcnJvcnMpIHtcbiAgICByb3dzLnB1c2goe1xuICAgICAgbGFiZWw6IGVycm9yLnNvdXJjZSxcbiAgICAgIG1lc3NhZ2U6IGZvcm1hdEVycm9yTWVzc2FnZShlcnJvciksXG4gICAgICBndWlkYW5jZTogZ2V0RXJyb3JHdWlkYW5jZShlcnJvciksXG4gICAgICBhY3Rpb246IHsga2luZDogJ25vbmUnIH0sXG4gICAgfSlcbiAgfVxuXG4gIHJldHVybiByb3dzXG59XG5cbi8qKlxuICogUmVtb3ZlIGEgbWFya2V0cGxhY2UgZnJvbSBleHRyYUtub3duTWFya2V0cGxhY2VzIGluIHRoZSBnaXZlbiBzZXR0aW5ncyBzb3VyY2VzLFxuICogYW5kIGFsc28gcmVtb3ZlIGFueSBhc3NvY2lhdGVkIGVuYWJsZWQgcGx1Z2lucy5cbiAqL1xuZnVuY3Rpb24gcmVtb3ZlRXh0cmFNYXJrZXRwbGFjZShcbiAgbmFtZTogc3RyaW5nLFxuICBzb3VyY2VzOiBBcnJheTx7IHNvdXJjZTogRWRpdGFibGVTZXR0aW5nU291cmNlIH0+LFxuKTogdm9pZCB7XG4gIGZvciAoY29uc3QgeyBzb3VyY2UgfSBvZiBzb3VyY2VzKSB7XG4gICAgY29uc3Qgc2V0dGluZ3MgPSBnZXRTZXR0aW5nc0ZvclNvdXJjZShzb3VyY2UpXG4gICAgaWYgKCFzZXR0aW5ncykgY29udGludWVcblxuICAgIGNvbnN0IHVwZGF0ZXM6IFJlY29yZDxzdHJpbmcsIHVua25vd24+ID0ge31cblxuICAgIC8vIFJlbW92ZSBmcm9tIGV4dHJhS25vd25NYXJrZXRwbGFjZXNcbiAgICBpZiAoc2V0dGluZ3MuZXh0cmFLbm93bk1hcmtldHBsYWNlcz8uW25hbWVdKSB7XG4gICAgICB1cGRhdGVzLmV4dHJhS25vd25NYXJrZXRwbGFjZXMgPSB7XG4gICAgICAgIC4uLnNldHRpbmdzLmV4dHJhS25vd25NYXJrZXRwbGFjZXMsXG4gICAgICAgIFtuYW1lXTogdW5kZWZpbmVkLFxuICAgICAgfVxuICAgIH1cblxuICAgIC8vIFJlbW92ZSBhc3NvY2lhdGVkIGVuYWJsZWQgcGx1Z2lucyAoZm9ybWF0OiBcInBsdWdpbkBtYXJrZXRwbGFjZVwiKVxuICAgIGlmIChzZXR0aW5ncy5lbmFibGVkUGx1Z2lucykge1xuICAgICAgY29uc3Qgc3VmZml4ID0gYEAke25hbWV9YFxuICAgICAgbGV0IHJlbW92ZWRQbHVnaW5zID0gZmFsc2VcbiAgICAgIGNvbnN0IHVwZGF0ZWRQbHVnaW5zID0geyAuLi5zZXR0aW5ncy5lbmFibGVkUGx1Z2lucyB9XG4gICAgICBmb3IgKGNvbnN0IHBsdWdpbklkIGluIHVwZGF0ZWRQbHVnaW5zKSB7XG4gICAgICAgIGlmIChwbHVnaW5JZC5lbmRzV2l0aChzdWZmaXgpKSB7XG4gICAgICAgICAgdXBkYXRlZFBsdWdpbnNbcGx1Z2luSWRdID0gdW5kZWZpbmVkXG4gICAgICAgICAgcmVtb3ZlZFBsdWdpbnMgPSB0cnVlXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmIChyZW1vdmVkUGx1Z2lucykge1xuICAgICAgICB1cGRhdGVzLmVuYWJsZWRQbHVnaW5zID0gdXBkYXRlZFBsdWdpbnNcbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoT2JqZWN0LmtleXModXBkYXRlcykubGVuZ3RoID4gMCkge1xuICAgICAgdXBkYXRlU2V0dGluZ3NGb3JTb3VyY2Uoc291cmNlLCB1cGRhdGVzKVxuICAgIH1cbiAgfVxufVxuXG5mdW5jdGlvbiBFcnJvcnNUYWJDb250ZW50KHtcbiAgc2V0Vmlld1N0YXRlLFxuICBzZXRBY3RpdmVUYWIsXG4gIG1hcmtQbHVnaW5zQ2hhbmdlZCxcbn06IHtcbiAgc2V0Vmlld1N0YXRlOiAoc3RhdGU6IFZpZXdTdGF0ZSkgPT4gdm9pZFxuICBzZXRBY3RpdmVUYWI6ICh0YWI6IFRhYklkKSA9PiB2b2lkXG4gIG1hcmtQbHVnaW5zQ2hhbmdlZDogKCkgPT4gdm9pZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IGVycm9ycyA9IHVzZUFwcFN0YXRlKHMgPT4gcy5wbHVnaW5zLmVycm9ycylcbiAgY29uc3QgaW5zdGFsbGF0aW9uU3RhdHVzID0gdXNlQXBwU3RhdGUocyA9PiBzLnBsdWdpbnMuaW5zdGFsbGF0aW9uU3RhdHVzKVxuICBjb25zdCBzZXRBcHBTdGF0ZSA9IHVzZVNldEFwcFN0YXRlKClcbiAgY29uc3QgW3NlbGVjdGVkSW5kZXgsIHNldFNlbGVjdGVkSW5kZXhdID0gdXNlU3RhdGUoMClcbiAgY29uc3QgW2FjdGlvbk1lc3NhZ2UsIHNldEFjdGlvbk1lc3NhZ2VdID0gdXNlU3RhdGU8c3RyaW5nIHwgbnVsbD4obnVsbClcbiAgY29uc3QgW21hcmtldHBsYWNlTG9hZEZhaWx1cmVzLCBzZXRNYXJrZXRwbGFjZUxvYWRGYWlsdXJlc10gPSB1c2VTdGF0ZTxcbiAgICBBcnJheTx7IG5hbWU6IHN0cmluZzsgZXJyb3I6IHN0cmluZyB9PlxuICA+KFtdKVxuXG4gIC8vIERldGVjdCBtYXJrZXRwbGFjZXMgdGhhdCBhcmUgaW5zdGFsbGVkIGJ1dCBmYWlsIHRvIGxvYWQgdGhlaXIgZGF0YVxuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIHZvaWQgKGFzeW5jICgpID0+IHtcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGNvbmZpZyA9IGF3YWl0IGxvYWRLbm93bk1hcmtldHBsYWNlc0NvbmZpZygpXG4gICAgICAgIGNvbnN0IHsgZmFpbHVyZXMgfSA9XG4gICAgICAgICAgYXdhaXQgbG9hZE1hcmtldHBsYWNlc1dpdGhHcmFjZWZ1bERlZ3JhZGF0aW9uKGNvbmZpZylcbiAgICAgICAgc2V0TWFya2V0cGxhY2VMb2FkRmFpbHVyZXMoZmFpbHVyZXMpXG4gICAgICB9IGNhdGNoIHtcbiAgICAgICAgLy8gSWdub3JlIOKAlCBpZiB3ZSBjYW4ndCBsb2FkIGNvbmZpZywgb3RoZXIgdGFicyBoYW5kbGUgaXRcbiAgICAgIH1cbiAgICB9KSgpXG4gIH0sIFtdKVxuXG4gIGNvbnN0IGZhaWxlZE1hcmtldHBsYWNlcyA9IGluc3RhbGxhdGlvblN0YXR1cy5tYXJrZXRwbGFjZXMuZmlsdGVyKFxuICAgIG0gPT4gbS5zdGF0dXMgPT09ICdmYWlsZWQnLFxuICApXG4gIGNvbnN0IGZhaWxlZE1hcmtldHBsYWNlTmFtZXMgPSBuZXcgU2V0KGZhaWxlZE1hcmtldHBsYWNlcy5tYXAobSA9PiBtLm5hbWUpKVxuXG4gIC8vIFRyYW5zaWVudCBlcnJvcnMgKGdpdC9uZXR3b3JrKSDigJQgc2hvdyBhdCB0b3Agd2l0aCBcInJlc3RhcnQgdG8gcmV0cnlcIlxuICBjb25zdCB0cmFuc2llbnRFcnJvcnMgPSBlcnJvcnMuZmlsdGVyKGlzVHJhbnNpZW50RXJyb3IpXG5cbiAgLy8gTWFya2V0cGxhY2UtcmVsYXRlZCBsb2FkaW5nIGVycm9ycyBub3QgYWxyZWFkeSBjb3ZlcmVkIGJ5IGluc3RhbGwgZmFpbHVyZXNcbiAgY29uc3QgZXh0cmFNYXJrZXRwbGFjZUVycm9ycyA9IGVycm9ycy5maWx0ZXIoXG4gICAgZSA9PlxuICAgICAgKGUudHlwZSA9PT0gJ21hcmtldHBsYWNlLW5vdC1mb3VuZCcgfHxcbiAgICAgICAgZS50eXBlID09PSAnbWFya2V0cGxhY2UtbG9hZC1mYWlsZWQnIHx8XG4gICAgICAgIGUudHlwZSA9PT0gJ21hcmtldHBsYWNlLWJsb2NrZWQtYnktcG9saWN5JykgJiZcbiAgICAgICFmYWlsZWRNYXJrZXRwbGFjZU5hbWVzLmhhcyhlLm1hcmtldHBsYWNlKSxcbiAgKVxuXG4gIC8vIFBsdWdpbi1zcGVjaWZpYyBsb2FkaW5nIGVycm9yc1xuICBjb25zdCBwbHVnaW5Mb2FkaW5nRXJyb3JzID0gZXJyb3JzLmZpbHRlcihlID0+IHtcbiAgICBpZiAoaXNUcmFuc2llbnRFcnJvcihlKSkgcmV0dXJuIGZhbHNlXG4gICAgaWYgKFxuICAgICAgZS50eXBlID09PSAnbWFya2V0cGxhY2Utbm90LWZvdW5kJyB8fFxuICAgICAgZS50eXBlID09PSAnbWFya2V0cGxhY2UtbG9hZC1mYWlsZWQnIHx8XG4gICAgICBlLnR5cGUgPT09ICdtYXJrZXRwbGFjZS1ibG9ja2VkLWJ5LXBvbGljeSdcbiAgICApIHtcbiAgICAgIHJldHVybiBmYWxzZVxuICAgIH1cbiAgICByZXR1cm4gZ2V0UGx1Z2luTmFtZUZyb21FcnJvcihlKSAhPT0gdW5kZWZpbmVkXG4gIH0pXG5cbiAgLy8gUmVtYWluaW5nIGVycm9ycyB3aXRoIG5vIHBsdWdpbiBhc3NvY2lhdGlvblxuICBjb25zdCBvdGhlckVycm9ycyA9IGVycm9ycy5maWx0ZXIoZSA9PiB7XG4gICAgaWYgKGlzVHJhbnNpZW50RXJyb3IoZSkpIHJldHVybiBmYWxzZVxuICAgIGlmIChcbiAgICAgIGUudHlwZSA9PT0gJ21hcmtldHBsYWNlLW5vdC1mb3VuZCcgfHxcbiAgICAgIGUudHlwZSA9PT0gJ21hcmtldHBsYWNlLWxvYWQtZmFpbGVkJyB8fFxuICAgICAgZS50eXBlID09PSAnbWFya2V0cGxhY2UtYmxvY2tlZC1ieS1wb2xpY3knXG4gICAgKSB7XG4gICAgICByZXR1cm4gZmFsc2VcbiAgICB9XG4gICAgcmV0dXJuIGdldFBsdWdpbk5hbWVGcm9tRXJyb3IoZSkgPT09IHVuZGVmaW5lZFxuICB9KVxuXG4gIGNvbnN0IHBsdWdpblNjb3BlcyA9IGdldFBsdWdpbkVkaXRhYmxlU2NvcGVzKClcbiAgY29uc3Qgcm93cyA9IGJ1aWxkRXJyb3JSb3dzKFxuICAgIGZhaWxlZE1hcmtldHBsYWNlcyxcbiAgICBleHRyYU1hcmtldHBsYWNlRXJyb3JzLFxuICAgIHBsdWdpbkxvYWRpbmdFcnJvcnMsXG4gICAgb3RoZXJFcnJvcnMsXG4gICAgbWFya2V0cGxhY2VMb2FkRmFpbHVyZXMsXG4gICAgdHJhbnNpZW50RXJyb3JzLFxuICAgIHBsdWdpblNjb3BlcyxcbiAgKVxuXG4gIC8vIEhhbmRsZSBlc2NhcGUgdG8gZXhpdCB0aGUgcGx1Z2luIG1lbnVcbiAgdXNlS2V5YmluZGluZyhcbiAgICAnY29uZmlybTpubycsXG4gICAgKCkgPT4ge1xuICAgICAgc2V0Vmlld1N0YXRlKHsgdHlwZTogJ21lbnUnIH0pXG4gICAgfSxcbiAgICB7IGNvbnRleHQ6ICdDb25maXJtYXRpb24nIH0sXG4gIClcblxuICBjb25zdCBoYW5kbGVTZWxlY3QgPSAoKSA9PiB7XG4gICAgY29uc3Qgcm93ID0gcm93c1tzZWxlY3RlZEluZGV4XVxuICAgIGlmICghcm93KSByZXR1cm5cbiAgICBjb25zdCB7IGFjdGlvbiB9ID0gcm93XG4gICAgc3dpdGNoIChhY3Rpb24ua2luZCkge1xuICAgICAgY2FzZSAnbmF2aWdhdGUnOlxuICAgICAgICBzZXRBY3RpdmVUYWIoYWN0aW9uLnRhYilcbiAgICAgICAgc2V0Vmlld1N0YXRlKGFjdGlvbi52aWV3U3RhdGUpXG4gICAgICAgIGJyZWFrXG4gICAgICBjYXNlICdyZW1vdmUtZXh0cmEtbWFya2V0cGxhY2UnOiB7XG4gICAgICAgIGNvbnN0IHNjb3BlcyA9IGFjdGlvbi5zb3VyY2VzLm1hcChzID0+IHMuc2NvcGUpLmpvaW4oJywgJylcbiAgICAgICAgcmVtb3ZlRXh0cmFNYXJrZXRwbGFjZShhY3Rpb24ubmFtZSwgYWN0aW9uLnNvdXJjZXMpXG4gICAgICAgIGNsZWFyQWxsQ2FjaGVzKClcbiAgICAgICAgLy8gU3luY2hyb25vdXNseSBjbGVhciBhbGwgc3RhbGUgc3RhdGUgZm9yIHRoaXMgbWFya2V0cGxhY2Ugc28gdGhlIFVJXG4gICAgICAgIC8vIHVwZGF0ZXMgZ2xpdGNoLWZyZWUuIG1hcmtQbHVnaW5zQ2hhbmdlZCBvbmx5IHNldHMgbmVlZHNSZWZyZXNoIOKAlFxuICAgICAgICAvLyBpdCBkb2VzIG5vdCByZWZyZXNoIHBsdWdpbnMuZXJyb3JzLCBzbyB0aGlzIGlzIHRoZSBhdXRob3JpdGF0aXZlXG4gICAgICAgIC8vIGNsZWFudXAgdW50aWwgdGhlIHVzZXIgcnVucyAvcmVsb2FkLXBsdWdpbnMuXG4gICAgICAgIHNldEFwcFN0YXRlKHByZXYgPT4gKHtcbiAgICAgICAgICAuLi5wcmV2LFxuICAgICAgICAgIHBsdWdpbnM6IHtcbiAgICAgICAgICAgIC4uLnByZXYucGx1Z2lucyxcbiAgICAgICAgICAgIGVycm9yczogcHJldi5wbHVnaW5zLmVycm9ycy5maWx0ZXIoXG4gICAgICAgICAgICAgIGUgPT4gISgnbWFya2V0cGxhY2UnIGluIGUgJiYgZS5tYXJrZXRwbGFjZSA9PT0gYWN0aW9uLm5hbWUpLFxuICAgICAgICAgICAgKSxcbiAgICAgICAgICAgIGluc3RhbGxhdGlvblN0YXR1czoge1xuICAgICAgICAgICAgICAuLi5wcmV2LnBsdWdpbnMuaW5zdGFsbGF0aW9uU3RhdHVzLFxuICAgICAgICAgICAgICBtYXJrZXRwbGFjZXM6IHByZXYucGx1Z2lucy5pbnN0YWxsYXRpb25TdGF0dXMubWFya2V0cGxhY2VzLmZpbHRlcihcbiAgICAgICAgICAgICAgICBtID0+IG0ubmFtZSAhPT0gYWN0aW9uLm5hbWUsXG4gICAgICAgICAgICAgICksXG4gICAgICAgICAgICB9LFxuICAgICAgICAgIH0sXG4gICAgICAgIH0pKVxuICAgICAgICBzZXRBY3Rpb25NZXNzYWdlKFxuICAgICAgICAgIGAke2ZpZ3VyZXMudGlja30gUmVtb3ZlZCBcIiR7YWN0aW9uLm5hbWV9XCIgZnJvbSAke3Njb3Blc30gc2V0dGluZ3NgLFxuICAgICAgICApXG4gICAgICAgIG1hcmtQbHVnaW5zQ2hhbmdlZCgpXG4gICAgICAgIGJyZWFrXG4gICAgICB9XG4gICAgICBjYXNlICdyZW1vdmUtaW5zdGFsbGVkLW1hcmtldHBsYWNlJzoge1xuICAgICAgICB2b2lkIChhc3luYyAoKSA9PiB7XG4gICAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGF3YWl0IHJlbW92ZU1hcmtldHBsYWNlU291cmNlKGFjdGlvbi5uYW1lKVxuICAgICAgICAgICAgY2xlYXJBbGxDYWNoZXMoKVxuICAgICAgICAgICAgc2V0TWFya2V0cGxhY2VMb2FkRmFpbHVyZXMocHJldiA9PlxuICAgICAgICAgICAgICBwcmV2LmZpbHRlcihmID0+IGYubmFtZSAhPT0gYWN0aW9uLm5hbWUpLFxuICAgICAgICAgICAgKVxuICAgICAgICAgICAgc2V0QWN0aW9uTWVzc2FnZShcbiAgICAgICAgICAgICAgYCR7ZmlndXJlcy50aWNrfSBSZW1vdmVkIG1hcmtldHBsYWNlIFwiJHthY3Rpb24ubmFtZX1cImAsXG4gICAgICAgICAgICApXG4gICAgICAgICAgICBtYXJrUGx1Z2luc0NoYW5nZWQoKVxuICAgICAgICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgICAgICAgc2V0QWN0aW9uTWVzc2FnZShcbiAgICAgICAgICAgICAgYEZhaWxlZCB0byByZW1vdmUgXCIke2FjdGlvbi5uYW1lfVwiOiAke2VyciBpbnN0YW5jZW9mIEVycm9yID8gZXJyLm1lc3NhZ2UgOiBTdHJpbmcoZXJyKX1gLFxuICAgICAgICAgICAgKVxuICAgICAgICAgIH1cbiAgICAgICAgfSkoKVxuICAgICAgICBicmVha1xuICAgICAgfVxuICAgICAgY2FzZSAnbWFuYWdlZC1vbmx5JzpcbiAgICAgICAgLy8gTm8gYWN0aW9uIGF2YWlsYWJsZSDigJQgZ3VpZGFuY2UgdGV4dCBhbHJlYWR5IHNob3duXG4gICAgICAgIGJyZWFrXG4gICAgICBjYXNlICdub25lJzpcbiAgICAgICAgYnJlYWtcbiAgICB9XG4gIH1cblxuICB1c2VLZXliaW5kaW5ncyhcbiAgICB7XG4gICAgICAnc2VsZWN0OnByZXZpb3VzJzogKCkgPT4gc2V0U2VsZWN0ZWRJbmRleChwcmV2ID0+IE1hdGgubWF4KDAsIHByZXYgLSAxKSksXG4gICAgICAnc2VsZWN0Om5leHQnOiAoKSA9PlxuICAgICAgICBzZXRTZWxlY3RlZEluZGV4KHByZXYgPT4gTWF0aC5taW4ocm93cy5sZW5ndGggLSAxLCBwcmV2ICsgMSkpLFxuICAgICAgJ3NlbGVjdDphY2NlcHQnOiBoYW5kbGVTZWxlY3QsXG4gICAgfSxcbiAgICB7IGNvbnRleHQ6ICdTZWxlY3QnLCBpc0FjdGl2ZTogcm93cy5sZW5ndGggPiAwIH0sXG4gIClcblxuICAvLyBDbGFtcCBzZWxlY3RlZEluZGV4IHdoZW4gcm93cyBzaHJpbmsgKGUuZy4gYWZ0ZXIgcmVtb3ZhbClcbiAgY29uc3QgY2xhbXBlZEluZGV4ID0gTWF0aC5taW4oc2VsZWN0ZWRJbmRleCwgTWF0aC5tYXgoMCwgcm93cy5sZW5ndGggLSAxKSlcbiAgaWYgKGNsYW1wZWRJbmRleCAhPT0gc2VsZWN0ZWRJbmRleCkge1xuICAgIHNldFNlbGVjdGVkSW5kZXgoY2xhbXBlZEluZGV4KVxuICB9XG5cbiAgY29uc3Qgc2VsZWN0ZWRBY3Rpb24gPSByb3dzW2NsYW1wZWRJbmRleF0/LmFjdGlvblxuICBjb25zdCBoYXNBY3Rpb24gPVxuICAgIHNlbGVjdGVkQWN0aW9uICYmXG4gICAgc2VsZWN0ZWRBY3Rpb24ua2luZCAhPT0gJ25vbmUnICYmXG4gICAgc2VsZWN0ZWRBY3Rpb24ua2luZCAhPT0gJ21hbmFnZWQtb25seSdcblxuICBpZiAocm93cy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gKFxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICAgIDxCb3ggbWFyZ2luTGVmdD17MX0+XG4gICAgICAgICAgPFRleHQgZGltQ29sb3I+Tm8gcGx1Z2luIGVycm9yczwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvciBpdGFsaWM+XG4gICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgIGFjdGlvbj1cImNvbmZpcm06bm9cIlxuICAgICAgICAgICAgICBjb250ZXh0PVwiQ29uZmlybWF0aW9uXCJcbiAgICAgICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cImJhY2tcIlxuICAgICAgICAgICAgLz5cbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgcmV0dXJuIChcbiAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgIHtyb3dzLm1hcCgocm93LCBpZHgpID0+IHtcbiAgICAgICAgY29uc3QgaXNTZWxlY3RlZCA9IGlkeCA9PT0gY2xhbXBlZEluZGV4XG4gICAgICAgIHJldHVybiAoXG4gICAgICAgICAgPEJveCBrZXk9e2lkeH0gbWFyZ2luTGVmdD17MX0gZmxleERpcmVjdGlvbj1cImNvbHVtblwiIG1hcmdpbkJvdHRvbT17MX0+XG4gICAgICAgICAgICA8VGV4dD5cbiAgICAgICAgICAgICAgPFRleHQgY29sb3I9e2lzU2VsZWN0ZWQgPyAnc3VnZ2VzdGlvbicgOiAnZXJyb3InfT5cbiAgICAgICAgICAgICAgICB7aXNTZWxlY3RlZCA/IGZpZ3VyZXMucG9pbnRlciA6IGZpZ3VyZXMuY3Jvc3N9eycgJ31cbiAgICAgICAgICAgICAgPC9UZXh0PlxuICAgICAgICAgICAgICA8VGV4dCBib2xkPXtpc1NlbGVjdGVkfT57cm93LmxhYmVsfTwvVGV4dD5cbiAgICAgICAgICAgICAge3Jvdy5zY29wZSAmJiA8VGV4dCBkaW1Db2xvcj4gKHtyb3cuc2NvcGV9KTwvVGV4dD59XG4gICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICA8Qm94IG1hcmdpbkxlZnQ9ezN9PlxuICAgICAgICAgICAgICA8VGV4dCBjb2xvcj1cImVycm9yXCI+e3Jvdy5tZXNzYWdlfTwvVGV4dD5cbiAgICAgICAgICAgIDwvQm94PlxuICAgICAgICAgICAge3Jvdy5ndWlkYW5jZSAmJiAoXG4gICAgICAgICAgICAgIDxCb3ggbWFyZ2luTGVmdD17M30+XG4gICAgICAgICAgICAgICAgPFRleHQgZGltQ29sb3IgaXRhbGljPlxuICAgICAgICAgICAgICAgICAge3Jvdy5ndWlkYW5jZX1cbiAgICAgICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgICAgIDwvQm94PlxuICAgICAgICAgICAgKX1cbiAgICAgICAgICA8L0JveD5cbiAgICAgICAgKVxuICAgICAgfSl9XG5cbiAgICAgIHthY3Rpb25NZXNzYWdlICYmIChcbiAgICAgICAgPEJveCBtYXJnaW5Ub3A9ezF9IG1hcmdpbkxlZnQ9ezF9PlxuICAgICAgICAgIDxUZXh0IGNvbG9yPVwiY2xhdWRlXCI+e2FjdGlvbk1lc3NhZ2V9PC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICl9XG5cbiAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgPFRleHQgZGltQ29sb3IgaXRhbGljPlxuICAgICAgICAgIDxCeWxpbmU+XG4gICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgIGFjdGlvbj1cInNlbGVjdDpwcmV2aW91c1wiXG4gICAgICAgICAgICAgIGNvbnRleHQ9XCJTZWxlY3RcIlxuICAgICAgICAgICAgICBmYWxsYmFjaz1cIuKGkVwiXG4gICAgICAgICAgICAgIGRlc2NyaXB0aW9uPVwibmF2aWdhdGVcIlxuICAgICAgICAgICAgLz5cbiAgICAgICAgICAgIHtoYXNBY3Rpb24gJiYgKFxuICAgICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgICAgYWN0aW9uPVwic2VsZWN0OmFjY2VwdFwiXG4gICAgICAgICAgICAgICAgY29udGV4dD1cIlNlbGVjdFwiXG4gICAgICAgICAgICAgICAgZmFsbGJhY2s9XCJFbnRlclwiXG4gICAgICAgICAgICAgICAgZGVzY3JpcHRpb249XCJyZXNvbHZlXCJcbiAgICAgICAgICAgICAgLz5cbiAgICAgICAgICAgICl9XG4gICAgICAgICAgICA8Q29uZmlndXJhYmxlU2hvcnRjdXRIaW50XG4gICAgICAgICAgICAgIGFjdGlvbj1cImNvbmZpcm06bm9cIlxuICAgICAgICAgICAgICBjb250ZXh0PVwiQ29uZmlybWF0aW9uXCJcbiAgICAgICAgICAgICAgZmFsbGJhY2s9XCJFc2NcIlxuICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cImJhY2tcIlxuICAgICAgICAgICAgLz5cbiAgICAgICAgICA8L0J5bGluZT5cbiAgICAgICAgPC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgPC9Cb3g+XG4gIClcbn1cblxuZnVuY3Rpb24gZ2V0SW5pdGlhbFZpZXdTdGF0ZShwYXJzZWRDb21tYW5kOiBQYXJzZWRDb21tYW5kKTogVmlld1N0YXRlIHtcbiAgc3dpdGNoIChwYXJzZWRDb21tYW5kLnR5cGUpIHtcbiAgICBjYXNlICdoZWxwJzpcbiAgICAgIHJldHVybiB7IHR5cGU6ICdoZWxwJyB9XG4gICAgY2FzZSAndmFsaWRhdGUnOlxuICAgICAgcmV0dXJuIHsgdHlwZTogJ3ZhbGlkYXRlJywgcGF0aDogcGFyc2VkQ29tbWFuZC5wYXRoIH1cbiAgICBjYXNlICdpbnN0YWxsJzpcbiAgICAgIGlmIChwYXJzZWRDb21tYW5kLm1hcmtldHBsYWNlKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgdHlwZTogJ2Jyb3dzZS1tYXJrZXRwbGFjZScsXG4gICAgICAgICAgdGFyZ2V0TWFya2V0cGxhY2U6IHBhcnNlZENvbW1hbmQubWFya2V0cGxhY2UsXG4gICAgICAgICAgdGFyZ2V0UGx1Z2luOiBwYXJzZWRDb21tYW5kLnBsdWdpbixcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgaWYgKHBhcnNlZENvbW1hbmQucGx1Z2luKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgdHlwZTogJ2Rpc2NvdmVyLXBsdWdpbnMnLFxuICAgICAgICAgIHRhcmdldFBsdWdpbjogcGFyc2VkQ29tbWFuZC5wbHVnaW4sXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiB7IHR5cGU6ICdkaXNjb3Zlci1wbHVnaW5zJyB9XG4gICAgY2FzZSAnbWFuYWdlJzpcbiAgICAgIHJldHVybiB7IHR5cGU6ICdtYW5hZ2UtcGx1Z2lucycgfVxuICAgIGNhc2UgJ3VuaW5zdGFsbCc6XG4gICAgICByZXR1cm4ge1xuICAgICAgICB0eXBlOiAnbWFuYWdlLXBsdWdpbnMnLFxuICAgICAgICB0YXJnZXRQbHVnaW46IHBhcnNlZENvbW1hbmQucGx1Z2luLFxuICAgICAgICBhY3Rpb246ICd1bmluc3RhbGwnLFxuICAgICAgfVxuICAgIGNhc2UgJ2VuYWJsZSc6XG4gICAgICByZXR1cm4ge1xuICAgICAgICB0eXBlOiAnbWFuYWdlLXBsdWdpbnMnLFxuICAgICAgICB0YXJnZXRQbHVnaW46IHBhcnNlZENvbW1hbmQucGx1Z2luLFxuICAgICAgICBhY3Rpb246ICdlbmFibGUnLFxuICAgICAgfVxuICAgIGNhc2UgJ2Rpc2FibGUnOlxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgdHlwZTogJ21hbmFnZS1wbHVnaW5zJyxcbiAgICAgICAgdGFyZ2V0UGx1Z2luOiBwYXJzZWRDb21tYW5kLnBsdWdpbixcbiAgICAgICAgYWN0aW9uOiAnZGlzYWJsZScsXG4gICAgICB9XG4gICAgY2FzZSAnbWFya2V0cGxhY2UnOlxuICAgICAgaWYgKHBhcnNlZENvbW1hbmQuYWN0aW9uID09PSAnbGlzdCcpIHtcbiAgICAgICAgcmV0dXJuIHsgdHlwZTogJ21hcmtldHBsYWNlLWxpc3QnIH1cbiAgICAgIH1cbiAgICAgIGlmIChwYXJzZWRDb21tYW5kLmFjdGlvbiA9PT0gJ2FkZCcpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICB0eXBlOiAnYWRkLW1hcmtldHBsYWNlJyxcbiAgICAgICAgICBpbml0aWFsVmFsdWU6IHBhcnNlZENvbW1hbmQudGFyZ2V0LFxuICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZiAocGFyc2VkQ29tbWFuZC5hY3Rpb24gPT09ICdyZW1vdmUnKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgdHlwZTogJ21hbmFnZS1tYXJrZXRwbGFjZXMnLFxuICAgICAgICAgIHRhcmdldE1hcmtldHBsYWNlOiBwYXJzZWRDb21tYW5kLnRhcmdldCxcbiAgICAgICAgICBhY3Rpb246ICdyZW1vdmUnLFxuICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZiAocGFyc2VkQ29tbWFuZC5hY3Rpb24gPT09ICd1cGRhdGUnKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgdHlwZTogJ21hbmFnZS1tYXJrZXRwbGFjZXMnLFxuICAgICAgICAgIHRhcmdldE1hcmtldHBsYWNlOiBwYXJzZWRDb21tYW5kLnRhcmdldCxcbiAgICAgICAgICBhY3Rpb246ICd1cGRhdGUnLFxuICAgICAgICB9XG4gICAgICB9XG4gICAgICByZXR1cm4geyB0eXBlOiAnbWFya2V0cGxhY2UtbWVudScgfVxuICAgIGNhc2UgJ21lbnUnOlxuICAgIGRlZmF1bHQ6XG4gICAgICAvLyBEZWZhdWx0IHRvIGRpc2NvdmVyIHZpZXcgc2hvd2luZyBhbGwgcGx1Z2luc1xuICAgICAgcmV0dXJuIHsgdHlwZTogJ2Rpc2NvdmVyLXBsdWdpbnMnIH1cbiAgfVxufVxuXG5mdW5jdGlvbiBnZXRJbml0aWFsVGFiKHZpZXdTdGF0ZTogVmlld1N0YXRlKTogVGFiSWQge1xuICBpZiAodmlld1N0YXRlLnR5cGUgPT09ICdtYW5hZ2UtcGx1Z2lucycpIHJldHVybiAnaW5zdGFsbGVkJ1xuICBpZiAodmlld1N0YXRlLnR5cGUgPT09ICdtYW5hZ2UtbWFya2V0cGxhY2VzJykgcmV0dXJuICdtYXJrZXRwbGFjZXMnXG4gIHJldHVybiAnZGlzY292ZXInXG59XG5cbmV4cG9ydCBmdW5jdGlvbiBQbHVnaW5TZXR0aW5ncyh7XG4gIG9uQ29tcGxldGUsXG4gIGFyZ3MsXG4gIHNob3dNY3BSZWRpcmVjdE1lc3NhZ2UsXG59OiBQbHVnaW5TZXR0aW5nc1Byb3BzKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgY29uc3QgcGFyc2VkQ29tbWFuZCA9IHBhcnNlUGx1Z2luQXJncyhhcmdzKVxuICBjb25zdCBpbml0aWFsVmlld1N0YXRlID0gZ2V0SW5pdGlhbFZpZXdTdGF0ZShwYXJzZWRDb21tYW5kKVxuICBjb25zdCBbdmlld1N0YXRlLCBzZXRWaWV3U3RhdGVdID0gdXNlU3RhdGU8Vmlld1N0YXRlPihpbml0aWFsVmlld1N0YXRlKVxuICBjb25zdCBbYWN0aXZlVGFiLCBzZXRBY3RpdmVUYWJdID0gdXNlU3RhdGU8VGFiSWQ+KFxuICAgIGdldEluaXRpYWxUYWIoaW5pdGlhbFZpZXdTdGF0ZSksXG4gIClcbiAgY29uc3QgW2lucHV0VmFsdWUsIHNldElucHV0VmFsdWVdID0gdXNlU3RhdGUoXG4gICAgdmlld1N0YXRlLnR5cGUgPT09ICdhZGQtbWFya2V0cGxhY2UnID8gdmlld1N0YXRlLmluaXRpYWxWYWx1ZSB8fCAnJyA6ICcnLFxuICApXG4gIGNvbnN0IFtjdXJzb3JPZmZzZXQsIHNldEN1cnNvck9mZnNldF0gPSB1c2VTdGF0ZSgwKVxuICBjb25zdCBbZXJyb3IsIHNldEVycm9yXSA9IHVzZVN0YXRlPHN0cmluZyB8IG51bGw+KG51bGwpXG4gIGNvbnN0IFtyZXN1bHQsIHNldFJlc3VsdF0gPSB1c2VTdGF0ZTxzdHJpbmcgfCBudWxsPihudWxsKVxuICBjb25zdCBbY2hpbGRTZWFyY2hBY3RpdmUsIHNldENoaWxkU2VhcmNoQWN0aXZlXSA9IHVzZVN0YXRlKGZhbHNlKVxuICBjb25zdCBzZXRBcHBTdGF0ZSA9IHVzZVNldEFwcFN0YXRlKClcblxuICAvLyBFcnJvciBjb3VudCBmb3IgdGhlIEVycm9ycyB0YWIgYmFkZ2Ug4oCUIGNvdW50cyBsb2FkZXIgZXJyb3JzICsgYmFja2dyb3VuZFxuICAvLyBtYXJrZXRwbGFjZSBpbnN0YWxsIGZhaWx1cmVzLiBEb2VzIE5PVCBjb3VudCBtYXJrZXRwbGFjZS1vbi1kaXNrIGxvYWRcbiAgLy8gZmFpbHVyZXMgKHRob3NlIHJlcXVpcmUgSS9PIGFuZCBhcmUgZGlzY292ZXJlZCBsYXppbHkgd2hlbiB0aGUgdGFiIG9wZW5zKS5cbiAgLy8gTWF5IHNsaWdodGx5IG92ZXJjb3VudCB2cy4gZGlzcGxheWVkIHJvd3Mgd2hlbiBhIG1hcmtldHBsYWNlIGhhcyBib3RoIGFcbiAgLy8gbG9hZGVyIGVycm9yIGFuZCBhIGZhaWxlZCBpbnN0YWxsIHN0YXR1cyAoYnVpbGRFcnJvclJvd3MgZGVkdXBsaWNhdGVzKS5cbiAgY29uc3QgcGx1Z2luRXJyb3JDb3VudCA9IHVzZUFwcFN0YXRlKHMgPT4ge1xuICAgIGxldCBjb3VudCA9IHMucGx1Z2lucy5lcnJvcnMubGVuZ3RoXG4gICAgZm9yIChjb25zdCBtIG9mIHMucGx1Z2lucy5pbnN0YWxsYXRpb25TdGF0dXMubWFya2V0cGxhY2VzKSB7XG4gICAgICBpZiAobS5zdGF0dXMgPT09ICdmYWlsZWQnKSBjb3VudCsrXG4gICAgfVxuICAgIHJldHVybiBjb3VudFxuICB9KVxuICBjb25zdCBlcnJvcnNUYWJUaXRsZSA9XG4gICAgcGx1Z2luRXJyb3JDb3VudCA+IDAgPyBgRXJyb3JzICgke3BsdWdpbkVycm9yQ291bnR9KWAgOiAnRXJyb3JzJ1xuXG4gIGNvbnN0IGV4aXRTdGF0ZSA9IHVzZUV4aXRPbkN0cmxDRFdpdGhLZXliaW5kaW5ncygpXG5cbiAgLyoqXG4gICAqIENMSSBtb2RlIGlzIGFjdGl2ZSB3aGVuIHRoZSB1c2VyIHByb3ZpZGVzIGEgY29tcGxldGUgY29tbWFuZCB3aXRoIGFsbCByZXF1aXJlZCBhcmd1bWVudHMuXG4gICAqIEluIHRoaXMgbW9kZSwgdGhlIG9wZXJhdGlvbiBleGVjdXRlcyBpbW1lZGlhdGVseSB3aXRob3V0IGludGVyYWN0aXZlIHByb21wdHMuXG4gICAqIEludGVyYWN0aXZlIG1vZGUgaXMgdXNlZCB3aGVuIGFyZ3VtZW50cyBhcmUgbWlzc2luZywgYWxsb3dpbmcgdGhlIHVzZXIgdG8gaW5wdXQgdGhlbS5cbiAgICovXG4gIGNvbnN0IGNsaU1vZGUgPVxuICAgIHBhcnNlZENvbW1hbmQudHlwZSA9PT0gJ21hcmtldHBsYWNlJyAmJlxuICAgIHBhcnNlZENvbW1hbmQuYWN0aW9uID09PSAnYWRkJyAmJlxuICAgIHBhcnNlZENvbW1hbmQudGFyZ2V0ICE9PSB1bmRlZmluZWRcblxuICAvLyBTaWduYWwgdGhhdCBwbHVnaW4gc3RhdGUgaGFzIGNoYW5nZWQgb24gZGlzayAoTGF5ZXIgMikgYW5kIGFjdGl2ZVxuICAvLyBjb21wb25lbnRzIChMYXllciAzKSBhcmUgc3RhbGUuIFVzZXIgcnVucyAvcmVsb2FkLXBsdWdpbnMgdG8gYXBwbHkuXG4gIC8vIFByZXZpb3VzbHkgdGhpcyB3YXMgdXBkYXRlUGx1Z2luU3RhdGUoKSB3aGljaCBkaWQgYSBwYXJ0aWFsIHJlZnJlc2hcbiAgLy8gKGNvbW1hbmRzIG9ubHkg4oCUIGFnZW50cy9ob29rcy9NQ1Agd2VyZSBzaWxlbnRseSBza2lwcGVkKS4gTm93IGFsbFxuICAvLyBMYXllci0zIHJlZnJlc2ggZmxvd3MgdGhyb3VnaCB0aGUgdW5pZmllZCByZWZyZXNoQWN0aXZlUGx1Z2lucygpXG4gIC8vIHByaW1pdGl2ZSB2aWEgL3JlbG9hZC1wbHVnaW5zLCBnaXZpbmcgb25lIGNvbnNpc3RlbnQgbWVudGFsIG1vZGVsOlxuICAvLyBwbHVnaW4gY2hhbmdlcyByZXF1aXJlIC9yZWxvYWQtcGx1Z2lucy5cbiAgY29uc3QgbWFya1BsdWdpbnNDaGFuZ2VkID0gdXNlQ2FsbGJhY2soKCkgPT4ge1xuICAgIHNldEFwcFN0YXRlKHByZXYgPT5cbiAgICAgIHByZXYucGx1Z2lucy5uZWVkc1JlZnJlc2hcbiAgICAgICAgPyBwcmV2XG4gICAgICAgIDogeyAuLi5wcmV2LCBwbHVnaW5zOiB7IC4uLnByZXYucGx1Z2lucywgbmVlZHNSZWZyZXNoOiB0cnVlIH0gfSxcbiAgICApXG4gIH0sIFtzZXRBcHBTdGF0ZV0pXG5cbiAgLy8gSGFuZGxlIHRhYiBzd2l0Y2hpbmcgKGNhbGxlZCBieSBUYWJzIGNvbXBvbmVudClcbiAgY29uc3QgaGFuZGxlVGFiQ2hhbmdlID0gdXNlQ2FsbGJhY2soKHRhYklkOiBzdHJpbmcpID0+IHtcbiAgICBjb25zdCB0YWIgPSB0YWJJZCBhcyBUYWJJZFxuICAgIHNldEFjdGl2ZVRhYih0YWIpXG4gICAgc2V0RXJyb3IobnVsbClcbiAgICBzd2l0Y2ggKHRhYikge1xuICAgICAgY2FzZSAnZGlzY292ZXInOlxuICAgICAgICBzZXRWaWV3U3RhdGUoeyB0eXBlOiAnZGlzY292ZXItcGx1Z2lucycgfSlcbiAgICAgICAgYnJlYWtcbiAgICAgIGNhc2UgJ2luc3RhbGxlZCc6XG4gICAgICAgIHNldFZpZXdTdGF0ZSh7IHR5cGU6ICdtYW5hZ2UtcGx1Z2lucycgfSlcbiAgICAgICAgYnJlYWtcbiAgICAgIGNhc2UgJ21hcmtldHBsYWNlcyc6XG4gICAgICAgIHNldFZpZXdTdGF0ZSh7IHR5cGU6ICdtYW5hZ2UtbWFya2V0cGxhY2VzJyB9KVxuICAgICAgICBicmVha1xuICAgICAgY2FzZSAnZXJyb3JzJzpcbiAgICAgICAgLy8gTm8gdmlld1N0YXRlIGNoYW5nZSBuZWVkZWQg4oCUIEVycm9yc1RhYkNvbnRlbnQgcmVuZGVycyBpbnNpZGUgPFRhYiBpZD1cImVycm9yc1wiPlxuICAgICAgICBicmVha1xuICAgIH1cbiAgfSwgW10pXG5cbiAgLy8gSGFuZGxlIGV4aXRpbmcgd2hlbiBjaGlsZCBjb21wb25lbnRzIHNldCB2aWV3U3RhdGUgdG8gJ21lbnUnLlxuICAvLyBDaGlsZCBjb21wb25lbnRzIHR5cGljYWxseSBzZXQgQk9USCBzZXRSZXN1bHQobXNnKSBhbmQgc2V0UGFyZW50Vmlld1N0YXRlXG4gIC8vICh7dHlwZTonbWVudSd9KSDigJQgYm90aCBlZmZlY3RzIGZpcmUgb24gdGhlIHNhbWUgcmVuZGVyLiBPbmx5IGNsb3NlIHZpYSB0aGlzXG4gIC8vIHBhdGggd2hlbiB0aGVyZSdzIG5vIHJlc3VsdCwgb3RoZXJ3aXNlIHRoZSByZXN1bHQgZWZmZWN0IChiZWxvdykgaGFuZGxlc1xuICAvLyB0aGUgY2xvc2UgQU5EIGRlbGl2ZXJzIHRoZSBtZXNzYWdlIHRvIHRoZSB0cmFuc2NyaXB0LlxuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIGlmICh2aWV3U3RhdGUudHlwZSA9PT0gJ21lbnUnICYmICFyZXN1bHQpIHtcbiAgICAgIG9uQ29tcGxldGUoKVxuICAgIH1cbiAgfSwgW3ZpZXdTdGF0ZS50eXBlLCByZXN1bHQsIG9uQ29tcGxldGVdKVxuXG4gIC8vIFN5bmMgYWN0aXZlVGFiIHdoZW4gdmlld1N0YXRlIGNoYW5nZXMgdG8gYSBkaWZmZXJlbnQgdGFiJ3MgY29udGVudFxuICAvLyBUaGlzIGhhbmRsZXMgY2FzZXMgbGlrZSBBZGRNYXJrZXRwbGFjZSBuYXZpZ2F0aW5nIHRvIGJyb3dzZS1tYXJrZXRwbGFjZVxuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIGlmICh2aWV3U3RhdGUudHlwZSA9PT0gJ2Jyb3dzZS1tYXJrZXRwbGFjZScgJiYgYWN0aXZlVGFiICE9PSAnZGlzY292ZXInKSB7XG4gICAgICBzZXRBY3RpdmVUYWIoJ2Rpc2NvdmVyJylcbiAgICB9XG4gIH0sIFt2aWV3U3RhdGUudHlwZSwgYWN0aXZlVGFiXSlcblxuICAvLyBIYW5kbGUgZXNjYXBlIGtleSBmb3IgYWRkLW1hcmtldHBsYWNlIG1vZGUgb25seVxuICAvLyBPdGhlciB0YWJiZWQgdmlld3MgaGFuZGxlIGVzY2FwZSBpbiB0aGVpciBvd24gY29tcG9uZW50c1xuICBjb25zdCBoYW5kbGVBZGRNYXJrZXRwbGFjZUVzY2FwZSA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICBzZXRBY3RpdmVUYWIoJ21hcmtldHBsYWNlcycpXG4gICAgc2V0Vmlld1N0YXRlKHsgdHlwZTogJ21hbmFnZS1tYXJrZXRwbGFjZXMnIH0pXG4gICAgc2V0SW5wdXRWYWx1ZSgnJylcbiAgICBzZXRFcnJvcihudWxsKVxuICB9LCBbXSlcblxuICB1c2VLZXliaW5kaW5nKCdjb25maXJtOm5vJywgaGFuZGxlQWRkTWFya2V0cGxhY2VFc2NhcGUsIHtcbiAgICBjb250ZXh0OiAnU2V0dGluZ3MnLFxuICAgIGlzQWN0aXZlOiB2aWV3U3RhdGUudHlwZSA9PT0gJ2FkZC1tYXJrZXRwbGFjZScsXG4gIH0pXG5cbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICBpZiAocmVzdWx0KSB7XG4gICAgICBvbkNvbXBsZXRlKHJlc3VsdClcbiAgICB9XG4gIH0sIFtyZXN1bHQsIG9uQ29tcGxldGVdKVxuXG4gIC8vIEhhbmRsZSBoZWxwIHZpZXcgY29tcGxldGlvblxuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIGlmICh2aWV3U3RhdGUudHlwZSA9PT0gJ2hlbHAnKSB7XG4gICAgICBvbkNvbXBsZXRlKClcbiAgICB9XG4gIH0sIFt2aWV3U3RhdGUudHlwZSwgb25Db21wbGV0ZV0pXG5cbiAgLy8gUmVuZGVyIGRpZmZlcmVudCB2aWV3cyBiYXNlZCBvbiBzdGF0ZVxuICBpZiAodmlld1N0YXRlLnR5cGUgPT09ICdoZWxwJykge1xuICAgIHJldHVybiAoXG4gICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgPFRleHQgYm9sZD5QbHVnaW4gQ29tbWFuZCBVc2FnZTo8L1RleHQ+XG4gICAgICAgIDxUZXh0PiA8L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPkluc3RhbGxhdGlvbjo8L1RleHQ+XG4gICAgICAgIDxUZXh0PiAvcGx1Z2luIGluc3RhbGwgLSBCcm93c2UgYW5kIGluc3RhbGwgcGx1Z2luczwvVGV4dD5cbiAgICAgICAgPFRleHQ+XG4gICAgICAgICAgeycgJ31cbiAgICAgICAgICAvcGx1Z2luIGluc3RhbGwgJmx0O21hcmtldHBsYWNlJmd0OyAtIEluc3RhbGwgZnJvbSBzcGVjaWZpY1xuICAgICAgICAgIG1hcmtldHBsYWNlXG4gICAgICAgIDwvVGV4dD5cbiAgICAgICAgPFRleHQ+IC9wbHVnaW4gaW5zdGFsbCAmbHQ7cGx1Z2luJmd0OyAtIEluc3RhbGwgc3BlY2lmaWMgcGx1Z2luPC9UZXh0PlxuICAgICAgICA8VGV4dD5cbiAgICAgICAgICB7JyAnfVxuICAgICAgICAgIC9wbHVnaW4gaW5zdGFsbCAmbHQ7cGx1Z2luJmd0O0AmbHQ7bWFya2V0Jmd0OyAtIEluc3RhbGwgcGx1Z2luIGZyb21cbiAgICAgICAgICBtYXJrZXRwbGFjZVxuICAgICAgICA8L1RleHQ+XG4gICAgICAgIDxUZXh0PiA8L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPk1hbmFnZW1lbnQ6PC9UZXh0PlxuICAgICAgICA8VGV4dD4gL3BsdWdpbiBtYW5hZ2UgLSBNYW5hZ2UgaW5zdGFsbGVkIHBsdWdpbnM8L1RleHQ+XG4gICAgICAgIDxUZXh0PiAvcGx1Z2luIGVuYWJsZSAmbHQ7cGx1Z2luJmd0OyAtIEVuYWJsZSBhIHBsdWdpbjwvVGV4dD5cbiAgICAgICAgPFRleHQ+IC9wbHVnaW4gZGlzYWJsZSAmbHQ7cGx1Z2luJmd0OyAtIERpc2FibGUgYSBwbHVnaW48L1RleHQ+XG4gICAgICAgIDxUZXh0PiAvcGx1Z2luIHVuaW5zdGFsbCAmbHQ7cGx1Z2luJmd0OyAtIFVuaW5zdGFsbCBhIHBsdWdpbjwvVGV4dD5cbiAgICAgICAgPFRleHQ+IDwvVGV4dD5cbiAgICAgICAgPFRleHQgZGltQ29sb3I+TWFya2V0cGxhY2VzOjwvVGV4dD5cbiAgICAgICAgPFRleHQ+IC9wbHVnaW4gbWFya2V0cGxhY2UgLSBNYXJrZXRwbGFjZSBtYW5hZ2VtZW50IG1lbnU8L1RleHQ+XG4gICAgICAgIDxUZXh0PiAvcGx1Z2luIG1hcmtldHBsYWNlIGFkZCAtIEFkZCBhIG1hcmtldHBsYWNlPC9UZXh0PlxuICAgICAgICA8VGV4dD5cbiAgICAgICAgICB7JyAnfVxuICAgICAgICAgIC9wbHVnaW4gbWFya2V0cGxhY2UgYWRkICZsdDtwYXRoL3VybCZndDsgLSBBZGQgbWFya2V0cGxhY2UgZGlyZWN0bHlcbiAgICAgICAgPC9UZXh0PlxuICAgICAgICA8VGV4dD4gL3BsdWdpbiBtYXJrZXRwbGFjZSB1cGRhdGUgLSBVcGRhdGUgbWFya2V0cGxhY2VzPC9UZXh0PlxuICAgICAgICA8VGV4dD5cbiAgICAgICAgICB7JyAnfVxuICAgICAgICAgIC9wbHVnaW4gbWFya2V0cGxhY2UgdXBkYXRlICZsdDtuYW1lJmd0OyAtIFVwZGF0ZSBzcGVjaWZpYyBtYXJrZXRwbGFjZVxuICAgICAgICA8L1RleHQ+XG4gICAgICAgIDxUZXh0PiAvcGx1Z2luIG1hcmtldHBsYWNlIHJlbW92ZSAtIFJlbW92ZSBhIG1hcmtldHBsYWNlPC9UZXh0PlxuICAgICAgICA8VGV4dD5cbiAgICAgICAgICB7JyAnfVxuICAgICAgICAgIC9wbHVnaW4gbWFya2V0cGxhY2UgcmVtb3ZlICZsdDtuYW1lJmd0OyAtIFJlbW92ZSBzcGVjaWZpYyBtYXJrZXRwbGFjZVxuICAgICAgICA8L1RleHQ+XG4gICAgICAgIDxUZXh0PiAvcGx1Z2luIG1hcmtldHBsYWNlIGxpc3QgLSBMaXN0IGFsbCBtYXJrZXRwbGFjZXM8L1RleHQ+XG4gICAgICAgIDxUZXh0PiA8L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPlZhbGlkYXRpb246PC9UZXh0PlxuICAgICAgICA8VGV4dD5cbiAgICAgICAgICB7JyAnfVxuICAgICAgICAgIC9wbHVnaW4gdmFsaWRhdGUgJmx0O3BhdGgmZ3Q7IC0gVmFsaWRhdGUgYSBtYW5pZmVzdCBmaWxlIG9yIGRpcmVjdG9yeVxuICAgICAgICA8L1RleHQ+XG4gICAgICAgIDxUZXh0PiA8L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPk90aGVyOjwvVGV4dD5cbiAgICAgICAgPFRleHQ+IC9wbHVnaW4gLSBNYWluIHBsdWdpbiBtZW51PC9UZXh0PlxuICAgICAgICA8VGV4dD4gL3BsdWdpbiBoZWxwIC0gU2hvdyB0aGlzIGhlbHA8L1RleHQ+XG4gICAgICAgIDxUZXh0PiAvcGx1Z2lucyAtIEFsaWFzIGZvciAvcGx1Z2luPC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgaWYgKHZpZXdTdGF0ZS50eXBlID09PSAndmFsaWRhdGUnKSB7XG4gICAgcmV0dXJuIDxWYWxpZGF0ZVBsdWdpbiBvbkNvbXBsZXRlPXtvbkNvbXBsZXRlfSBwYXRoPXt2aWV3U3RhdGUucGF0aH0gLz5cbiAgfVxuXG4gIGlmICh2aWV3U3RhdGUudHlwZSA9PT0gJ21hcmtldHBsYWNlLW1lbnUnKSB7XG4gICAgLy8gU2hvdyBhIHNpbXBsZSBtZW51IGZvciBtYXJrZXRwbGFjZSBvcGVyYXRpb25zXG4gICAgc2V0Vmlld1N0YXRlKHsgdHlwZTogJ21lbnUnIH0pXG4gICAgcmV0dXJuIG51bGxcbiAgfVxuXG4gIGlmICh2aWV3U3RhdGUudHlwZSA9PT0gJ21hcmtldHBsYWNlLWxpc3QnKSB7XG4gICAgcmV0dXJuIDxNYXJrZXRwbGFjZUxpc3Qgb25Db21wbGV0ZT17b25Db21wbGV0ZX0gLz5cbiAgfVxuXG4gIGlmICh2aWV3U3RhdGUudHlwZSA9PT0gJ2FkZC1tYXJrZXRwbGFjZScpIHtcbiAgICByZXR1cm4gKFxuICAgICAgPEFkZE1hcmtldHBsYWNlXG4gICAgICAgIGlucHV0VmFsdWU9e2lucHV0VmFsdWV9XG4gICAgICAgIHNldElucHV0VmFsdWU9e3NldElucHV0VmFsdWV9XG4gICAgICAgIGN1cnNvck9mZnNldD17Y3Vyc29yT2Zmc2V0fVxuICAgICAgICBzZXRDdXJzb3JPZmZzZXQ9e3NldEN1cnNvck9mZnNldH1cbiAgICAgICAgZXJyb3I9e2Vycm9yfVxuICAgICAgICBzZXRFcnJvcj17c2V0RXJyb3J9XG4gICAgICAgIHJlc3VsdD17cmVzdWx0fVxuICAgICAgICBzZXRSZXN1bHQ9e3NldFJlc3VsdH1cbiAgICAgICAgc2V0Vmlld1N0YXRlPXtzZXRWaWV3U3RhdGV9XG4gICAgICAgIG9uQWRkQ29tcGxldGU9e21hcmtQbHVnaW5zQ2hhbmdlZH1cbiAgICAgICAgY2xpTW9kZT17Y2xpTW9kZX1cbiAgICAgIC8+XG4gICAgKVxuICB9XG4gIC8vIFJlbmRlciB0YWJiZWQgaW50ZXJmYWNlIHVzaW5nIHRoZSBkZXNpZ24gc3lzdGVtIFRhYnMgY29tcG9uZW50XG4gIHJldHVybiAoXG4gICAgPFBhbmUgY29sb3I9XCJzdWdnZXN0aW9uXCI+XG4gICAgICA8VGFic1xuICAgICAgICB0aXRsZT1cIlBsdWdpbnNcIlxuICAgICAgICBzZWxlY3RlZFRhYj17YWN0aXZlVGFifVxuICAgICAgICBvblRhYkNoYW5nZT17aGFuZGxlVGFiQ2hhbmdlfVxuICAgICAgICBjb2xvcj1cInN1Z2dlc3Rpb25cIlxuICAgICAgICBkaXNhYmxlTmF2aWdhdGlvbj17Y2hpbGRTZWFyY2hBY3RpdmV9XG4gICAgICAgIGJhbm5lcj17XG4gICAgICAgICAgc2hvd01jcFJlZGlyZWN0TWVzc2FnZSAmJiBhY3RpdmVUYWIgPT09ICdpbnN0YWxsZWQnID8gKFxuICAgICAgICAgICAgPE1jcFJlZGlyZWN0QmFubmVyIC8+XG4gICAgICAgICAgKSA6IHVuZGVmaW5lZFxuICAgICAgICB9XG4gICAgICA+XG4gICAgICAgIDxUYWIgaWQ9XCJkaXNjb3ZlclwiIHRpdGxlPVwiRGlzY292ZXJcIj5cbiAgICAgICAgICB7dmlld1N0YXRlLnR5cGUgPT09ICdicm93c2UtbWFya2V0cGxhY2UnID8gKFxuICAgICAgICAgICAgPEJyb3dzZU1hcmtldHBsYWNlXG4gICAgICAgICAgICAgIGVycm9yPXtlcnJvcn1cbiAgICAgICAgICAgICAgc2V0RXJyb3I9e3NldEVycm9yfVxuICAgICAgICAgICAgICByZXN1bHQ9e3Jlc3VsdH1cbiAgICAgICAgICAgICAgc2V0UmVzdWx0PXtzZXRSZXN1bHR9XG4gICAgICAgICAgICAgIHNldFZpZXdTdGF0ZT17c2V0Vmlld1N0YXRlfVxuICAgICAgICAgICAgICBvbkluc3RhbGxDb21wbGV0ZT17bWFya1BsdWdpbnNDaGFuZ2VkfVxuICAgICAgICAgICAgICB0YXJnZXRNYXJrZXRwbGFjZT17dmlld1N0YXRlLnRhcmdldE1hcmtldHBsYWNlfVxuICAgICAgICAgICAgICB0YXJnZXRQbHVnaW49e3ZpZXdTdGF0ZS50YXJnZXRQbHVnaW59XG4gICAgICAgICAgICAvPlxuICAgICAgICAgICkgOiAoXG4gICAgICAgICAgICA8RGlzY292ZXJQbHVnaW5zXG4gICAgICAgICAgICAgIGVycm9yPXtlcnJvcn1cbiAgICAgICAgICAgICAgc2V0RXJyb3I9e3NldEVycm9yfVxuICAgICAgICAgICAgICByZXN1bHQ9e3Jlc3VsdH1cbiAgICAgICAgICAgICAgc2V0UmVzdWx0PXtzZXRSZXN1bHR9XG4gICAgICAgICAgICAgIHNldFZpZXdTdGF0ZT17c2V0Vmlld1N0YXRlfVxuICAgICAgICAgICAgICBvbkluc3RhbGxDb21wbGV0ZT17bWFya1BsdWdpbnNDaGFuZ2VkfVxuICAgICAgICAgICAgICBvblNlYXJjaE1vZGVDaGFuZ2U9e3NldENoaWxkU2VhcmNoQWN0aXZlfVxuICAgICAgICAgICAgICB0YXJnZXRQbHVnaW49e1xuICAgICAgICAgICAgICAgIHZpZXdTdGF0ZS50eXBlID09PSAnZGlzY292ZXItcGx1Z2lucydcbiAgICAgICAgICAgICAgICAgID8gdmlld1N0YXRlLnRhcmdldFBsdWdpblxuICAgICAgICAgICAgICAgICAgOiB1bmRlZmluZWRcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgLz5cbiAgICAgICAgICApfVxuICAgICAgICA8L1RhYj5cbiAgICAgICAgPFRhYiBpZD1cImluc3RhbGxlZFwiIHRpdGxlPVwiSW5zdGFsbGVkXCI+XG4gICAgICAgICAgPE1hbmFnZVBsdWdpbnNcbiAgICAgICAgICAgIHNldFZpZXdTdGF0ZT17c2V0Vmlld1N0YXRlfVxuICAgICAgICAgICAgc2V0UmVzdWx0PXtzZXRSZXN1bHR9XG4gICAgICAgICAgICBvbk1hbmFnZUNvbXBsZXRlPXttYXJrUGx1Z2luc0NoYW5nZWR9XG4gICAgICAgICAgICBvblNlYXJjaE1vZGVDaGFuZ2U9e3NldENoaWxkU2VhcmNoQWN0aXZlfVxuICAgICAgICAgICAgdGFyZ2V0UGx1Z2luPXtcbiAgICAgICAgICAgICAgdmlld1N0YXRlLnR5cGUgPT09ICdtYW5hZ2UtcGx1Z2lucydcbiAgICAgICAgICAgICAgICA/IHZpZXdTdGF0ZS50YXJnZXRQbHVnaW5cbiAgICAgICAgICAgICAgICA6IHVuZGVmaW5lZFxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGFyZ2V0TWFya2V0cGxhY2U9e1xuICAgICAgICAgICAgICB2aWV3U3RhdGUudHlwZSA9PT0gJ21hbmFnZS1wbHVnaW5zJ1xuICAgICAgICAgICAgICAgID8gdmlld1N0YXRlLnRhcmdldE1hcmtldHBsYWNlXG4gICAgICAgICAgICAgICAgOiB1bmRlZmluZWRcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGFjdGlvbj17XG4gICAgICAgICAgICAgIHZpZXdTdGF0ZS50eXBlID09PSAnbWFuYWdlLXBsdWdpbnMnID8gdmlld1N0YXRlLmFjdGlvbiA6IHVuZGVmaW5lZFxuICAgICAgICAgICAgfVxuICAgICAgICAgIC8+XG4gICAgICAgIDwvVGFiPlxuICAgICAgICA8VGFiIGlkPVwibWFya2V0cGxhY2VzXCIgdGl0bGU9XCJNYXJrZXRwbGFjZXNcIj5cbiAgICAgICAgICA8TWFuYWdlTWFya2V0cGxhY2VzXG4gICAgICAgICAgICBzZXRWaWV3U3RhdGU9e3NldFZpZXdTdGF0ZX1cbiAgICAgICAgICAgIGVycm9yPXtlcnJvcn1cbiAgICAgICAgICAgIHNldEVycm9yPXtzZXRFcnJvcn1cbiAgICAgICAgICAgIHNldFJlc3VsdD17c2V0UmVzdWx0fVxuICAgICAgICAgICAgZXhpdFN0YXRlPXtleGl0U3RhdGV9XG4gICAgICAgICAgICBvbk1hbmFnZUNvbXBsZXRlPXttYXJrUGx1Z2luc0NoYW5nZWR9XG4gICAgICAgICAgICB0YXJnZXRNYXJrZXRwbGFjZT17XG4gICAgICAgICAgICAgIHZpZXdTdGF0ZS50eXBlID09PSAnbWFuYWdlLW1hcmtldHBsYWNlcydcbiAgICAgICAgICAgICAgICA/IHZpZXdTdGF0ZS50YXJnZXRNYXJrZXRwbGFjZVxuICAgICAgICAgICAgICAgIDogdW5kZWZpbmVkXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBhY3Rpb249e1xuICAgICAgICAgICAgICB2aWV3U3RhdGUudHlwZSA9PT0gJ21hbmFnZS1tYXJrZXRwbGFjZXMnXG4gICAgICAgICAgICAgICAgPyB2aWV3U3RhdGUuYWN0aW9uXG4gICAgICAgICAgICAgICAgOiB1bmRlZmluZWRcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAvPlxuICAgICAgICA8L1RhYj5cbiAgICAgICAgPFRhYiBpZD1cImVycm9yc1wiIHRpdGxlPXtlcnJvcnNUYWJUaXRsZX0+XG4gICAgICAgICAgPEVycm9yc1RhYkNvbnRlbnRcbiAgICAgICAgICAgIHNldFZpZXdTdGF0ZT17c2V0Vmlld1N0YXRlfVxuICAgICAgICAgICAgc2V0QWN0aXZlVGFiPXtzZXRBY3RpdmVUYWJ9XG4gICAgICAgICAgICBtYXJrUGx1Z2luc0NoYW5nZWQ9e21hcmtQbHVnaW5zQ2hhbmdlZH1cbiAgICAgICAgICAvPlxuICAgICAgICA8L1RhYj5cbiAgICAgIDwvVGFicz5cbiAgICA8L1BhbmU+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU9BLE9BQU8sTUFBTSxTQUFTO0FBQzdCLE9BQU8sS0FBS0MsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0MsV0FBVyxFQUFFQyxTQUFTLEVBQUVDLFFBQVEsUUFBUSxPQUFPO0FBQ3hELFNBQVNDLHdCQUF3QixRQUFRLDhDQUE4QztBQUN2RixTQUFTQyxNQUFNLFFBQVEsMENBQTBDO0FBQ2pFLFNBQVNDLElBQUksUUFBUSx3Q0FBd0M7QUFDN0QsU0FBU0MsR0FBRyxFQUFFQyxJQUFJLFFBQVEsd0NBQXdDO0FBQ2xFLFNBQVNDLDhCQUE4QixRQUFRLCtDQUErQztBQUM5RixTQUFTQyxHQUFHLEVBQUVDLElBQUksUUFBUSxjQUFjO0FBQ3hDLFNBQ0VDLGFBQWEsRUFDYkMsY0FBYyxRQUNULG9DQUFvQztBQUMzQyxTQUFTQyxXQUFXLEVBQUVDLGNBQWMsUUFBUSx5QkFBeUI7QUFDckUsY0FBY0MsV0FBVyxRQUFRLHVCQUF1QjtBQUN4RCxTQUFTQyxZQUFZLFFBQVEsdUJBQXVCO0FBQ3BELFNBQVNDLGNBQWMsUUFBUSxtQ0FBbUM7QUFDbEUsU0FBU0MsdUNBQXVDLFFBQVEsMkNBQTJDO0FBQ25HLFNBQ0VDLDJCQUEyQixFQUMzQkMsdUJBQXVCLFFBQ2xCLDJDQUEyQztBQUNsRCxTQUFTQyx1QkFBdUIsUUFBUSwyQ0FBMkM7QUFDbkYsY0FBY0MscUJBQXFCLFFBQVEsbUNBQW1DO0FBQzlFLFNBQ0VDLG9CQUFvQixFQUNwQkMsdUJBQXVCLFFBQ2xCLGtDQUFrQztBQUN6QyxTQUFTQyxjQUFjLFFBQVEscUJBQXFCO0FB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 \ No newline at end of file diff --git a/src/commands/plugin/PluginTrustWarning.tsx b/src/commands/plugin/PluginTrustWarning.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a0321dc47b5616c584cdd02e8661618355b3ee87 --- /dev/null +++ b/src/commands/plugin/PluginTrustWarning.tsx @@ -0,0 +1,32 @@ +import { c as _c } from "react/compiler-runtime"; +import figures from 'figures'; +import * as React from 'react'; +import { Box, Text } from '../../ink.js'; +import { getPluginTrustMessage } from '../../utils/plugins/marketplaceHelpers.js'; +export function PluginTrustWarning() { + const $ = _c(3); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = getPluginTrustMessage(); + $[0] = t0; + } else { + t0 = $[0]; + } + const customMessage = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = {figures.warning} ; + $[1] = t1; + } else { + t1 = $[1]; + } + let t2; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t2 = {t1}Make sure you trust a plugin before installing, updating, or using it. Anthropic does not control what MCP servers, files, or other software are included in plugins and cannot verify that they will work as intended or that they won't change. See each plugin's homepage for more information.{customMessage ? ` ${customMessage}` : ""}; + $[2] = t2; + } else { + t2 = $[2]; + } + return t2; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwiUmVhY3QiLCJCb3giLCJUZXh0IiwiZ2V0UGx1Z2luVHJ1c3RNZXNzYWdlIiwiUGx1Z2luVHJ1c3RXYXJuaW5nIiwiJCIsIl9jIiwidDAiLCJTeW1ib2wiLCJmb3IiLCJjdXN0b21NZXNzYWdlIiwidDEiLCJ3YXJuaW5nIiwidDIiXSwic291cmNlcyI6WyJQbHVnaW5UcnVzdFdhcm5pbmcudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBmaWd1cmVzIGZyb20gJ2ZpZ3VyZXMnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IEJveCwgVGV4dCB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB7IGdldFBsdWdpblRydXN0TWVzc2FnZSB9IGZyb20gJy4uLy4uL3V0aWxzL3BsdWdpbnMvbWFya2V0cGxhY2VIZWxwZXJzLmpzJ1xuXG5leHBvcnQgZnVuY3Rpb24gUGx1Z2luVHJ1c3RXYXJuaW5nKCk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IGN1c3RvbU1lc3NhZ2UgPSBnZXRQbHVnaW5UcnVzdE1lc3NhZ2UoKVxuICByZXR1cm4gKFxuICAgIDxCb3ggbWFyZ2luQm90dG9tPXsxfT5cbiAgICAgIDxUZXh0IGNvbG9yPVwiY2xhdWRlXCI+e2ZpZ3VyZXMud2FybmluZ30gPC9UZXh0PlxuICAgICAgPFRleHQgZGltQ29sb3IgaXRhbGljPlxuICAgICAgICBNYWtlIHN1cmUgeW91IHRydXN0IGEgcGx1Z2luIGJlZm9yZSBpbnN0YWxsaW5nLCB1cGRhdGluZywgb3IgdXNpbmcgaXQuXG4gICAgICAgIEFudGhyb3BpYyBkb2VzIG5vdCBjb250cm9sIHdoYXQgTUNQIHNlcnZlcnMsIGZpbGVzLCBvciBvdGhlciBzb2Z0d2FyZVxuICAgICAgICBhcmUgaW5jbHVkZWQgaW4gcGx1Z2lucyBhbmQgY2Fubm90IHZlcmlmeSB0aGF0IHRoZXkgd2lsbCB3b3JrIGFzXG4gICAgICAgIGludGVuZGVkIG9yIHRoYXQgdGhleSB3b24mYXBvczt0IGNoYW5nZS4gU2VlIGVhY2ggcGx1Z2luJmFwb3M7cyBob21lcGFnZVxuICAgICAgICBmb3IgbW9yZSBpbmZvcm1hdGlvbi57Y3VzdG9tTWVzc2FnZSA/IGAgJHtjdXN0b21NZXNzYWdlfWAgOiAnJ31cbiAgICAgIDwvVGV4dD5cbiAgICA8L0JveD5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBT0EsT0FBTyxNQUFNLFNBQVM7QUFDN0IsT0FBTyxLQUFLQyxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxHQUFHLEVBQUVDLElBQUksUUFBUSxjQUFjO0FBQ3hDLFNBQVNDLHFCQUFxQixRQUFRLDJDQUEyQztBQUVqRixPQUFPLFNBQUFDLG1CQUFBO0VBQUEsTUFBQUMsQ0FBQSxHQUFBQyxFQUFBO0VBQUEsSUFBQUMsRUFBQTtFQUFBLElBQUFGLENBQUEsUUFBQUcsTUFBQSxDQUFBQyxHQUFBO0lBQ2lCRixFQUFBLEdBQUFKLHFCQUFxQixDQUFDLENBQUM7SUFBQUUsQ0FBQSxNQUFBRSxFQUFBO0VBQUE7SUFBQUEsRUFBQSxHQUFBRixDQUFBO0VBQUE7RUFBN0MsTUFBQUssYUFBQSxHQUFzQkgsRUFBdUI7RUFBQSxJQUFBSSxFQUFBO0VBQUEsSUFBQU4sQ0FBQSxRQUFBRyxNQUFBLENBQUFDLEdBQUE7SUFHekNFLEVBQUEsSUFBQyxJQUFJLENBQU8sS0FBUSxDQUFSLFFBQVEsQ0FBRSxDQUFBWixPQUFPLENBQUFhLE9BQU8sQ0FBRSxDQUFDLEVBQXRDLElBQUksQ0FBeUM7SUFBQVAsQ0FBQSxNQUFBTSxFQUFBO0VBQUE7SUFBQUEsRUFBQSxHQUFBTixDQUFBO0VBQUE7RUFBQSxJQUFBUSxFQUFBO0VBQUEsSUFBQVIsQ0FBQSxRQUFBRyxNQUFBLENBQUFDLEdBQUE7SUFEaERJLEVBQUEsSUFBQyxHQUFHLENBQWUsWUFBQyxDQUFELEdBQUMsQ0FDbEIsQ0FBQUYsRUFBNkMsQ0FDN0MsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFSLEtBQU8sQ0FBQyxDQUFDLE1BQU0sQ0FBTixLQUFLLENBQUMsQ0FBQyxrU0FLRSxDQUFBRCxhQUFhLEdBQWIsSUFBb0JBLGFBQWEsRUFBTyxHQUF4QyxFQUF1QyxDQUMvRCxFQU5DLElBQUksQ0FPUCxFQVRDLEdBQUcsQ0FTRTtJQUFBTCxDQUFBLE1BQUFRLEVBQUE7RUFBQTtJQUFBQSxFQUFBLEdBQUFSLENBQUE7RUFBQTtFQUFBLE9BVE5RLEVBU007QUFBQSIsImlnbm9yZUxpc3QiOltdfQ== \ No newline at end of file diff --git a/src/commands/plugin/UnifiedInstalledCell.tsx b/src/commands/plugin/UnifiedInstalledCell.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cc4cbcbd962a42177b98b90121221456ebfeea6a --- /dev/null +++ b/src/commands/plugin/UnifiedInstalledCell.tsx @@ -0,0 +1,565 @@ +import { c as _c } from "react/compiler-runtime"; +import figures from 'figures'; +import * as React from 'react'; +import { Box, color, Text, useTheme } from '../../ink.js'; +import { plural } from '../../utils/stringUtils.js'; +import type { UnifiedInstalledItem } from './unifiedTypes.js'; +type Props = { + item: UnifiedInstalledItem; + isSelected: boolean; +}; +export function UnifiedInstalledCell(t0) { + const $ = _c(142); + const { + item, + isSelected + } = t0; + const [theme] = useTheme(); + if (item.type === "plugin") { + let statusIcon; + let statusText; + if (item.pendingToggle) { + let t1; + if ($[0] !== theme) { + t1 = color("suggestion", theme)(figures.arrowRight); + $[0] = theme; + $[1] = t1; + } else { + t1 = $[1]; + } + statusIcon = t1; + statusText = item.pendingToggle === "will-enable" ? "will enable" : "will disable"; + } else { + if (item.errorCount > 0) { + let t1; + if ($[2] !== theme) { + t1 = color("error", theme)(figures.cross); + $[2] = theme; + $[3] = t1; + } else { + t1 = $[3]; + } + statusIcon = t1; + const t2 = item.errorCount; + let t3; + if ($[4] !== item.errorCount) { + t3 = plural(item.errorCount, "error"); + $[4] = item.errorCount; + $[5] = t3; + } else { + t3 = $[5]; + } + statusText = `${t2} ${t3}`; + } else { + if (!item.isEnabled) { + let t1; + if ($[6] !== theme) { + t1 = color("inactive", theme)(figures.radioOff); + $[6] = theme; + $[7] = t1; + } else { + t1 = $[7]; + } + statusIcon = t1; + statusText = "disabled"; + } else { + let t1; + if ($[8] !== theme) { + t1 = color("success", theme)(figures.tick); + $[8] = theme; + $[9] = t1; + } else { + t1 = $[9]; + } + statusIcon = t1; + statusText = "enabled"; + } + } + } + const t1 = isSelected ? "suggestion" : undefined; + const t2 = isSelected ? `${figures.pointer} ` : " "; + let t3; + if ($[10] !== t1 || $[11] !== t2) { + t3 = {t2}; + $[10] = t1; + $[11] = t2; + $[12] = t3; + } else { + t3 = $[12]; + } + const t4 = isSelected ? "suggestion" : undefined; + let t5; + if ($[13] !== item.name || $[14] !== t4) { + t5 = {item.name}; + $[13] = item.name; + $[14] = t4; + $[15] = t5; + } else { + t5 = $[15]; + } + const t6 = !isSelected; + let t7; + if ($[16] === Symbol.for("react.memo_cache_sentinel")) { + t7 = Plugin; + $[16] = t7; + } else { + t7 = $[16]; + } + let t8; + if ($[17] !== t6) { + t8 = {" "}{t7}; + $[17] = t6; + $[18] = t8; + } else { + t8 = $[18]; + } + let t9; + if ($[19] !== item.marketplace) { + t9 = · {item.marketplace}; + $[19] = item.marketplace; + $[20] = t9; + } else { + t9 = $[20]; + } + const t10 = !isSelected; + let t11; + if ($[21] !== statusIcon || $[22] !== t10) { + t11 = · {statusIcon} ; + $[21] = statusIcon; + $[22] = t10; + $[23] = t11; + } else { + t11 = $[23]; + } + const t12 = !isSelected; + let t13; + if ($[24] !== statusText || $[25] !== t12) { + t13 = {statusText}; + $[24] = statusText; + $[25] = t12; + $[26] = t13; + } else { + t13 = $[26]; + } + let t14; + if ($[27] !== t11 || $[28] !== t13 || $[29] !== t3 || $[30] !== t5 || $[31] !== t8 || $[32] !== t9) { + t14 = {t3}{t5}{t8}{t9}{t11}{t13}; + $[27] = t11; + $[28] = t13; + $[29] = t3; + $[30] = t5; + $[31] = t8; + $[32] = t9; + $[33] = t14; + } else { + t14 = $[33]; + } + return t14; + } + if (item.type === "flagged-plugin") { + let t1; + if ($[34] !== theme) { + t1 = color("warning", theme)(figures.warning); + $[34] = theme; + $[35] = t1; + } else { + t1 = $[35]; + } + const statusIcon_0 = t1; + const t2 = isSelected ? "suggestion" : undefined; + const t3 = isSelected ? `${figures.pointer} ` : " "; + let t4; + if ($[36] !== t2 || $[37] !== t3) { + t4 = {t3}; + $[36] = t2; + $[37] = t3; + $[38] = t4; + } else { + t4 = $[38]; + } + const t5 = isSelected ? "suggestion" : undefined; + let t6; + if ($[39] !== item.name || $[40] !== t5) { + t6 = {item.name}; + $[39] = item.name; + $[40] = t5; + $[41] = t6; + } else { + t6 = $[41]; + } + const t7 = !isSelected; + let t8; + if ($[42] === Symbol.for("react.memo_cache_sentinel")) { + t8 = Plugin; + $[42] = t8; + } else { + t8 = $[42]; + } + let t9; + if ($[43] !== t7) { + t9 = {" "}{t8}; + $[43] = t7; + $[44] = t9; + } else { + t9 = $[44]; + } + let t10; + if ($[45] !== item.marketplace) { + t10 = · {item.marketplace}; + $[45] = item.marketplace; + $[46] = t10; + } else { + t10 = $[46]; + } + const t11 = !isSelected; + let t12; + if ($[47] !== statusIcon_0 || $[48] !== t11) { + t12 = · {statusIcon_0} ; + $[47] = statusIcon_0; + $[48] = t11; + $[49] = t12; + } else { + t12 = $[49]; + } + const t13 = !isSelected; + let t14; + if ($[50] !== t13) { + t14 = removed; + $[50] = t13; + $[51] = t14; + } else { + t14 = $[51]; + } + let t15; + if ($[52] !== t10 || $[53] !== t12 || $[54] !== t14 || $[55] !== t4 || $[56] !== t6 || $[57] !== t9) { + t15 = {t4}{t6}{t9}{t10}{t12}{t14}; + $[52] = t10; + $[53] = t12; + $[54] = t14; + $[55] = t4; + $[56] = t6; + $[57] = t9; + $[58] = t15; + } else { + t15 = $[58]; + } + return t15; + } + if (item.type === "failed-plugin") { + let t1; + if ($[59] !== theme) { + t1 = color("error", theme)(figures.cross); + $[59] = theme; + $[60] = t1; + } else { + t1 = $[60]; + } + const statusIcon_1 = t1; + const t2 = item.errorCount; + let t3; + if ($[61] !== item.errorCount) { + t3 = plural(item.errorCount, "error"); + $[61] = item.errorCount; + $[62] = t3; + } else { + t3 = $[62]; + } + const statusText_0 = `failed to load · ${t2} ${t3}`; + const t4 = isSelected ? "suggestion" : undefined; + const t5 = isSelected ? `${figures.pointer} ` : " "; + let t6; + if ($[63] !== t4 || $[64] !== t5) { + t6 = {t5}; + $[63] = t4; + $[64] = t5; + $[65] = t6; + } else { + t6 = $[65]; + } + const t7 = isSelected ? "suggestion" : undefined; + let t8; + if ($[66] !== item.name || $[67] !== t7) { + t8 = {item.name}; + $[66] = item.name; + $[67] = t7; + $[68] = t8; + } else { + t8 = $[68]; + } + const t9 = !isSelected; + let t10; + if ($[69] === Symbol.for("react.memo_cache_sentinel")) { + t10 = Plugin; + $[69] = t10; + } else { + t10 = $[69]; + } + let t11; + if ($[70] !== t9) { + t11 = {" "}{t10}; + $[70] = t9; + $[71] = t11; + } else { + t11 = $[71]; + } + let t12; + if ($[72] !== item.marketplace) { + t12 = · {item.marketplace}; + $[72] = item.marketplace; + $[73] = t12; + } else { + t12 = $[73]; + } + const t13 = !isSelected; + let t14; + if ($[74] !== statusIcon_1 || $[75] !== t13) { + t14 = · {statusIcon_1} ; + $[74] = statusIcon_1; + $[75] = t13; + $[76] = t14; + } else { + t14 = $[76]; + } + const t15 = !isSelected; + let t16; + if ($[77] !== statusText_0 || $[78] !== t15) { + t16 = {statusText_0}; + $[77] = statusText_0; + $[78] = t15; + $[79] = t16; + } else { + t16 = $[79]; + } + let t17; + if ($[80] !== t11 || $[81] !== t12 || $[82] !== t14 || $[83] !== t16 || $[84] !== t6 || $[85] !== t8) { + t17 = {t6}{t8}{t11}{t12}{t14}{t16}; + $[80] = t11; + $[81] = t12; + $[82] = t14; + $[83] = t16; + $[84] = t6; + $[85] = t8; + $[86] = t17; + } else { + t17 = $[86]; + } + return t17; + } + let statusIcon_2; + let statusText_1; + if (item.status === "connected") { + let t1; + if ($[87] !== theme) { + t1 = color("success", theme)(figures.tick); + $[87] = theme; + $[88] = t1; + } else { + t1 = $[88]; + } + statusIcon_2 = t1; + statusText_1 = "connected"; + } else { + if (item.status === "disabled") { + let t1; + if ($[89] !== theme) { + t1 = color("inactive", theme)(figures.radioOff); + $[89] = theme; + $[90] = t1; + } else { + t1 = $[90]; + } + statusIcon_2 = t1; + statusText_1 = "disabled"; + } else { + if (item.status === "pending") { + let t1; + if ($[91] !== theme) { + t1 = color("inactive", theme)(figures.radioOff); + $[91] = theme; + $[92] = t1; + } else { + t1 = $[92]; + } + statusIcon_2 = t1; + statusText_1 = "connecting\u2026"; + } else { + if (item.status === "needs-auth") { + let t1; + if ($[93] !== theme) { + t1 = color("warning", theme)(figures.triangleUpOutline); + $[93] = theme; + $[94] = t1; + } else { + t1 = $[94]; + } + statusIcon_2 = t1; + statusText_1 = "Enter to auth"; + } else { + let t1; + if ($[95] !== theme) { + t1 = color("error", theme)(figures.cross); + $[95] = theme; + $[96] = t1; + } else { + t1 = $[96]; + } + statusIcon_2 = t1; + statusText_1 = "failed"; + } + } + } + } + if (item.indented) { + const t1 = isSelected ? "suggestion" : undefined; + const t2 = isSelected ? `${figures.pointer} ` : " "; + let t3; + if ($[97] !== t1 || $[98] !== t2) { + t3 = {t2}; + $[97] = t1; + $[98] = t2; + $[99] = t3; + } else { + t3 = $[99]; + } + const t4 = !isSelected; + let t5; + if ($[100] !== t4) { + t5 = ; + $[100] = t4; + $[101] = t5; + } else { + t5 = $[101]; + } + const t6 = isSelected ? "suggestion" : undefined; + let t7; + if ($[102] !== item.name || $[103] !== t6) { + t7 = {item.name}; + $[102] = item.name; + $[103] = t6; + $[104] = t7; + } else { + t7 = $[104]; + } + const t8 = !isSelected; + let t9; + if ($[105] === Symbol.for("react.memo_cache_sentinel")) { + t9 = MCP; + $[105] = t9; + } else { + t9 = $[105]; + } + let t10; + if ($[106] !== t8) { + t10 = {" "}{t9}; + $[106] = t8; + $[107] = t10; + } else { + t10 = $[107]; + } + const t11 = !isSelected; + let t12; + if ($[108] !== statusIcon_2 || $[109] !== t11) { + t12 = · {statusIcon_2} ; + $[108] = statusIcon_2; + $[109] = t11; + $[110] = t12; + } else { + t12 = $[110]; + } + const t13 = !isSelected; + let t14; + if ($[111] !== statusText_1 || $[112] !== t13) { + t14 = {statusText_1}; + $[111] = statusText_1; + $[112] = t13; + $[113] = t14; + } else { + t14 = $[113]; + } + let t15; + if ($[114] !== t10 || $[115] !== t12 || $[116] !== t14 || $[117] !== t3 || $[118] !== t5 || $[119] !== t7) { + t15 = {t3}{t5}{t7}{t10}{t12}{t14}; + $[114] = t10; + $[115] = t12; + $[116] = t14; + $[117] = t3; + $[118] = t5; + $[119] = t7; + $[120] = t15; + } else { + t15 = $[120]; + } + return t15; + } + const t1 = isSelected ? "suggestion" : undefined; + const t2 = isSelected ? `${figures.pointer} ` : " "; + let t3; + if ($[121] !== t1 || $[122] !== t2) { + t3 = {t2}; + $[121] = t1; + $[122] = t2; + $[123] = t3; + } else { + t3 = $[123]; + } + const t4 = isSelected ? "suggestion" : undefined; + let t5; + if ($[124] !== item.name || $[125] !== t4) { + t5 = {item.name}; + $[124] = item.name; + $[125] = t4; + $[126] = t5; + } else { + t5 = $[126]; + } + const t6 = !isSelected; + let t7; + if ($[127] === Symbol.for("react.memo_cache_sentinel")) { + t7 = MCP; + $[127] = t7; + } else { + t7 = $[127]; + } + let t8; + if ($[128] !== t6) { + t8 = {" "}{t7}; + $[128] = t6; + $[129] = t8; + } else { + t8 = $[129]; + } + const t9 = !isSelected; + let t10; + if ($[130] !== statusIcon_2 || $[131] !== t9) { + t10 = · {statusIcon_2} ; + $[130] = statusIcon_2; + $[131] = t9; + $[132] = t10; + } else { + t10 = $[132]; + } + const t11 = !isSelected; + let t12; + if ($[133] !== statusText_1 || $[134] !== t11) { + t12 = {statusText_1}; + $[133] = statusText_1; + $[134] = t11; + $[135] = t12; + } else { + t12 = $[135]; + } + let t13; + if ($[136] !== t10 || $[137] !== t12 || $[138] !== t3 || $[139] !== t5 || $[140] !== t8) { + t13 = {t3}{t5}{t8}{t10}{t12}; + $[136] = t10; + $[137] = t12; + $[138] = t3; + $[139] = t5; + $[140] = t8; + $[141] = t13; + } else { + t13 = $[141]; + } + return t13; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwiUmVhY3QiLCJCb3giLCJjb2xvciIsIlRleHQiLCJ1c2VUaGVtZSIsInBsdXJhbCIsIlVuaWZpZWRJbnN0YWxsZWRJdGVtIiwiUHJvcHMiLCJpdGVtIiwiaXNTZWxlY3RlZCIsIlVuaWZpZWRJbnN0YWxsZWRDZWxsIiwidDAiLCIkIiwiX2MiLCJ0aGVtZSIsInR5cGUiLCJzdGF0dXNJY29uIiwic3RhdHVzVGV4dCIsInBlbmRpbmdUb2dnbGUiLCJ0MSIsImFycm93UmlnaHQiLCJlcnJvckNvdW50IiwiY3Jvc3MiLCJ0MiIsInQzIiwiaXNFbmFibGVkIiwicmFkaW9PZmYiLCJ0aWNrIiwidW5kZWZpbmVkIiwicG9pbnRlciIsInQ0IiwidDUiLCJuYW1lIiwidDYiLCJ0NyIsIlN5bWJvbCIsImZvciIsInQ4IiwidDkiLCJtYXJrZXRwbGFjZSIsInQxMCIsInQxMSIsInQxMiIsInQxMyIsInQxNCIsIndhcm5pbmciLCJzdGF0dXNJY29uXzAiLCJ0MTUiLCJzdGF0dXNJY29uXzEiLCJzdGF0dXNUZXh0XzAiLCJ0MTYiLCJ0MTciLCJzdGF0dXMiLCJ0cmlhbmdsZVVwT3V0bGluZSIsImluZGVudGVkIiwic3RhdHVzSWNvbl8yIiwic3RhdHVzVGV4dF8xIl0sInNvdXJjZXMiOlsiVW5pZmllZEluc3RhbGxlZENlbGwudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBmaWd1cmVzIGZyb20gJ2ZpZ3VyZXMnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IEJveCwgY29sb3IsIFRleHQsIHVzZVRoZW1lIH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHsgcGx1cmFsIH0gZnJvbSAnLi4vLi4vdXRpbHMvc3RyaW5nVXRpbHMuanMnXG5pbXBvcnQgdHlwZSB7IFVuaWZpZWRJbnN0YWxsZWRJdGVtIH0gZnJvbSAnLi91bmlmaWVkVHlwZXMuanMnXG5cbnR5cGUgUHJvcHMgPSB7XG4gIGl0ZW06IFVuaWZpZWRJbnN0YWxsZWRJdGVtXG4gIGlzU2VsZWN0ZWQ6IGJvb2xlYW5cbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFVuaWZpZWRJbnN0YWxsZWRDZWxsKHtcbiAgaXRlbSxcbiAgaXNTZWxlY3RlZCxcbn06IFByb3BzKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgY29uc3QgW3RoZW1lXSA9IHVzZVRoZW1lKClcblxuICBpZiAoaXRlbS50eXBlID09PSAncGx1Z2luJykge1xuICAgIC8vIFN0YXR1cyBpY29uIGFuZCB0ZXh0XG4gICAgbGV0IHN0YXR1c0ljb246IHN0cmluZ1xuICAgIGxldCBzdGF0dXNUZXh0OiBzdHJpbmdcblxuICAgIC8vIFNob3cgcGVuZGluZyB0b2dnbGUgc3RhdHVzIGlmIHNldCwgb3RoZXJ3aXNlIHNob3cgY3VycmVudCBzdGF0dXNcbiAgICBpZiAoaXRlbS5wZW5kaW5nVG9nZ2xlKSB7XG4gICAgICBzdGF0dXNJY29uID0gY29sb3IoJ3N1Z2dlc3Rpb24nLCB0aGVtZSkoZmlndXJlcy5hcnJvd1JpZ2h0KVxuICAgICAgc3RhdHVzVGV4dCA9XG4gICAgICAgIGl0ZW0ucGVuZGluZ1RvZ2dsZSA9PT0gJ3dpbGwtZW5hYmxlJyA/ICd3aWxsIGVuYWJsZScgOiAnd2lsbCBkaXNhYmxlJ1xuICAgIH0gZWxzZSBpZiAoaXRlbS5lcnJvckNvdW50ID4gMCkge1xuICAgICAgc3RhdHVzSWNvbiA9IGNvbG9yKCdlcnJvcicsIHRoZW1lKShmaWd1cmVzLmNyb3NzKVxuICAgICAgc3RhdHVzVGV4dCA9IGAke2l0ZW0uZXJyb3JDb3VudH0gJHtwbHVyYWwoaXRlbS5lcnJvckNvdW50LCAnZXJyb3InKX1gXG4gICAgfSBlbHNlIGlmICghaXRlbS5pc0VuYWJsZWQpIHtcbiAgICAgIHN0YXR1c0ljb24gPSBjb2xvcignaW5hY3RpdmUnLCB0aGVtZSkoZmlndXJlcy5yYWRpb09mZilcbiAgICAgIHN0YXR1c1RleHQgPSAnZGlzYWJsZWQnXG4gICAgfSBlbHNlIHtcbiAgICAgIHN0YXR1c0ljb24gPSBjb2xvcignc3VjY2VzcycsIHRoZW1lKShmaWd1cmVzLnRpY2spXG4gICAgICBzdGF0dXNUZXh0ID0gJ2VuYWJsZWQnXG4gICAgfVxuXG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3g+XG4gICAgICAgIDxUZXh0IGNvbG9yPXtpc1NlbGVjdGVkID8gJ3N1Z2dlc3Rpb24nIDogdW5kZWZpbmVkfT5cbiAgICAgICAgICB7aXNTZWxlY3RlZCA/IGAke2ZpZ3VyZXMucG9pbnRlcn0gYCA6ICcgICd9XG4gICAgICAgIDwvVGV4dD5cbiAgICAgICAgPFRleHQgY29sb3I9e2lzU2VsZWN0ZWQgPyAnc3VnZ2VzdGlvbicgOiB1bmRlZmluZWR9PntpdGVtLm5hbWV9PC9UZXh0PlxuICAgICAgICA8VGV4dCBkaW1Db2xvcj17IWlzU2VsZWN0ZWR9PlxuICAgICAgICAgIHsnICd9XG4gICAgICAgICAgPFRleHQgYmFja2dyb3VuZENvbG9yPVwidXNlck1lc3NhZ2VCYWNrZ3JvdW5kXCI+UGx1Z2luPC9UZXh0PlxuICAgICAgICA8L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPiDCtyB7aXRlbS5tYXJrZXRwbGFjZX08L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPXshaXNTZWxlY3RlZH0+IMK3IHtzdGF0dXNJY29ufSA8L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPXshaXNTZWxlY3RlZH0+e3N0YXR1c1RleHR9PC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgaWYgKGl0ZW0udHlwZSA9PT0gJ2ZsYWdnZWQtcGx1Z2luJykge1xuICAgIGNvbnN0IHN0YXR1c0ljb24gPSBjb2xvcignd2FybmluZycsIHRoZW1lKShmaWd1cmVzLndhcm5pbmcpXG5cbiAgICByZXR1cm4gKFxuICAgICAgPEJveD5cbiAgICAgICAgPFRleHQgY29sb3I9e2lzU2VsZWN0ZWQgPyAnc3VnZ2VzdGlvbicgOiB1bmRlZmluZWR9PlxuICAgICAgICAgIHtpc1NlbGVjdGVkID8gYCR7ZmlndXJlcy5wb2ludGVyfSBgIDogJyAgJ31cbiAgICAgICAgPC9UZXh0PlxuICAgICAgICA8VGV4dCBjb2xvcj17aXNTZWxlY3RlZCA/ICdzdWdnZXN0aW9uJyA6IHVuZGVmaW5lZH0+e2l0ZW0ubmFtZX08L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPXshaXNTZWxlY3RlZH0+XG4gICAgICAgICAgeycgJ31cbiAgICAgICAgICA8VGV4dCBiYWNrZ3JvdW5kQ29sb3I9XCJ1c2VyTWVzc2FnZUJhY2tncm91bmRcIj5QbHVnaW48L1RleHQ+XG4gICAgICAgIDwvVGV4dD5cbiAgICAgICAgPFRleHQgZGltQ29sb3I+IMK3IHtpdGVtLm1hcmtldHBsYWNlfTwvVGV4dD5cbiAgICAgICAgPFRleHQgZGltQ29sb3I9eyFpc1NlbGVjdGVkfT4gwrcge3N0YXR1c0ljb259IDwvVGV4dD5cbiAgICAgICAgPFRleHQgZGltQ29sb3I9eyFpc1NlbGVjdGVkfT5yZW1vdmVkPC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgaWYgKGl0ZW0udHlwZSA9PT0gJ2ZhaWxlZC1wbHVnaW4nKSB7XG4gICAgY29uc3Qgc3RhdHVzSWNvbiA9IGNvbG9yKCdlcnJvcicsIHRoZW1lKShmaWd1cmVzLmNyb3NzKVxuICAgIGNvbnN0IHN0YXR1c1RleHQgPSBgZmFpbGVkIHRvIGxvYWQgwrcgJHtpdGVtLmVycm9yQ291bnR9ICR7cGx1cmFsKGl0ZW0uZXJyb3JDb3VudCwgJ2Vycm9yJyl9YFxuXG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3g+XG4gICAgICAgIDxUZXh0IGNvbG9yPXtpc1NlbGVjdGVkID8gJ3N1Z2dlc3Rpb24nIDogdW5kZWZpbmVkfT5cbiAgICAgICAgICB7aXNTZWxlY3RlZCA/IGAke2ZpZ3VyZXMucG9pbnRlcn0gYCA6ICcgICd9XG4gICAgICAgIDwvVGV4dD5cbiAgICAgICAgPFRleHQgY29sb3I9e2lzU2VsZWN0ZWQgPyAnc3VnZ2VzdGlvbicgOiB1bmRlZmluZWR9PntpdGVtLm5hbWV9PC9UZXh0PlxuICAgICAgICA8VGV4dCBkaW1Db2xvcj17IWlzU2VsZWN0ZWR9PlxuICAgICAgICAgIHsnICd9XG4gICAgICAgICAgPFRleHQgYmFja2dyb3VuZENvbG9yPVwidXNlck1lc3NhZ2VCYWNrZ3JvdW5kXCI+UGx1Z2luPC9UZXh0PlxuICAgICAgICA8L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPiDCtyB7aXRlbS5tYXJrZXRwbGFjZX08L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPXshaXNTZWxlY3RlZH0+IMK3IHtzdGF0dXNJY29ufSA8L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPXshaXNTZWxlY3RlZH0+e3N0YXR1c1RleHR9PC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgLy8gTUNQIHNlcnZlclxuICBsZXQgc3RhdHVzSWNvbjogc3RyaW5nXG4gIGxldCBzdGF0dXNUZXh0OiBzdHJpbmdcblxuICBpZiAoaXRlbS5zdGF0dXMgPT09ICdjb25uZWN0ZWQnKSB7XG4gICAgc3RhdHVzSWNvbiA9IGNvbG9yKCdzdWNjZXNzJywgdGhlbWUpKGZpZ3VyZXMudGljaylcbiAgICBzdGF0dXNUZXh0ID0gJ2Nvbm5lY3RlZCdcbiAgfSBlbHNlIGlmIChpdGVtLnN0YXR1cyA9PT0gJ2Rpc2FibGVkJykge1xuICAgIHN0YXR1c0ljb24gPSBjb2xvcignaW5hY3RpdmUnLCB0aGVtZSkoZmlndXJlcy5yYWRpb09mZilcbiAgICBzdGF0dXNUZXh0ID0gJ2Rpc2FibGVkJ1xuICB9IGVsc2UgaWYgKGl0ZW0uc3RhdHVzID09PSAncGVuZGluZycpIHtcbiAgICBzdGF0dXNJY29uID0gY29sb3IoJ2luYWN0aXZlJywgdGhlbWUpKGZpZ3VyZXMucmFkaW9PZmYpXG4gICAgc3RhdHVzVGV4dCA9ICdjb25uZWN0aW5n4oCmJ1xuICB9IGVsc2UgaWYgKGl0ZW0uc3RhdHVzID09PSAnbmVlZHMtYXV0aCcpIHtcbiAgICBzdGF0dXNJY29uID0gY29sb3IoJ3dhcm5pbmcnLCB0aGVtZSkoZmlndXJlcy50cmlhbmdsZVVwT3V0bGluZSlcbiAgICBzdGF0dXNUZXh0ID0gJ0VudGVyIHRvIGF1dGgnXG4gIH0gZWxzZSB7XG4gICAgc3RhdHVzSWNvbiA9IGNvbG9yKCdlcnJvcicsIHRoZW1lKShmaWd1cmVzLmNyb3NzKVxuICAgIHN0YXR1c1RleHQgPSAnZmFpbGVkJ1xuICB9XG5cbiAgLy8gSW5kZW50ZWQgTUNQcyAoY2hpbGQgb2YgYSBwbHVnaW4pXG4gIGlmIChpdGVtLmluZGVudGVkKSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3g+XG4gICAgICAgIDxUZXh0IGNvbG9yPXtpc1NlbGVjdGVkID8gJ3N1Z2dlc3Rpb24nIDogdW5kZWZpbmVkfT5cbiAgICAgICAgICB7aXNTZWxlY3RlZCA/IGAke2ZpZ3VyZXMucG9pbnRlcn0gYCA6ICcgICd9XG4gICAgICAgIDwvVGV4dD5cbiAgICAgICAgPFRleHQgZGltQ29sb3I9eyFpc1NlbGVjdGVkfT7ilJQgPC9UZXh0PlxuICAgICAgICA8VGV4dCBjb2xvcj17aXNTZWxlY3RlZCA/ICdzdWdnZXN0aW9uJyA6IHVuZGVmaW5lZH0+e2l0ZW0ubmFtZX08L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPXshaXNTZWxlY3RlZH0+XG4gICAgICAgICAgeycgJ31cbiAgICAgICAgICA8VGV4dCBiYWNrZ3JvdW5kQ29sb3I9XCJ1c2VyTWVzc2FnZUJhY2tncm91bmRcIj5NQ1A8L1RleHQ+XG4gICAgICAgIDwvVGV4dD5cbiAgICAgICAgPFRleHQgZGltQ29sb3I9eyFpc1NlbGVjdGVkfT4gwrcge3N0YXR1c0ljb259IDwvVGV4dD5cbiAgICAgICAgPFRleHQgZGltQ29sb3I9eyFpc1NlbGVjdGVkfT57c3RhdHVzVGV4dH08L1RleHQ+XG4gICAgICA8L0JveD5cbiAgICApXG4gIH1cblxuICByZXR1cm4gKFxuICAgIDxCb3g+XG4gICAgICA8VGV4dCBjb2xvcj17aXNTZWxlY3RlZCA/ICdzdWdnZXN0aW9uJyA6IHVuZGVmaW5lZH0+XG4gICAgICAgIHtpc1NlbGVjdGVkID8gYCR7ZmlndXJlcy5wb2ludGVyfSBgIDogJyAgJ31cbiAgICAgIDwvVGV4dD5cbiAgICAgIDxUZXh0IGNvbG9yPXtpc1NlbGVjdGVkID8gJ3N1Z2dlc3Rpb24nIDogdW5kZWZpbmVkfT57aXRlbS5uYW1lfTwvVGV4dD5cbiAgICAgIDxUZXh0IGRpbUNvbG9yPXshaXNTZWxlY3RlZH0+XG4gICAgICAgIHsnICd9XG4gICAgICAgIDxUZXh0IGJhY2tncm91bmRDb2xvcj1cInVzZXJNZXNzYWdlQmFja2dyb3VuZFwiPk1DUDwvVGV4dD5cbiAgICAgIDwvVGV4dD5cbiAgICAgIDxUZXh0IGRpbUNvbG9yPXshaXNTZWxlY3RlZH0+IMK3IHtzdGF0dXNJY29ufSA8L1RleHQ+XG4gICAgICA8VGV4dCBkaW1Db2xvcj17IWlzU2VsZWN0ZWR9PntzdGF0dXNUZXh0fTwvVGV4dD5cbiAgICA8L0JveD5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBT0EsT0FBTyxNQUFNLFNBQVM7QUFDN0IsT0FBTyxLQUFLQyxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxHQUFHLEVBQUVDLEtBQUssRUFBRUMsSUFBSSxFQUFFQyxRQUFRLFFBQVEsY0FBYztBQUN6RCxTQUFTQyxNQUFNLFFBQVEsNEJBQTRCO0FBQ25ELGNBQWNDLG9CQUFvQixRQUFRLG1CQUFtQjtBQUU3RCxLQUFLQyxLQUFLLEdBQUc7RUFDWEMsSUFBSSxFQUFFRixvQkFBb0I7RUFDMUJHLFVBQVUsRUFBRSxPQUFPO0FBQ3JCLENBQUM7QUFFRCxPQUFPLFNBQUFDLHFCQUFBQyxFQUFBO0VBQUEsTUFBQUMsQ0FBQSxHQUFBQyxFQUFBO0VBQThCO0lBQUFMLElBQUE7SUFBQUM7RUFBQSxJQUFBRSxFQUc3QjtFQUNOLE9BQUFHLEtBQUEsSUFBZ0JWLFFBQVEsQ0FBQyxDQUFDO0VBRTFCLElBQUlJLElBQUksQ0FBQU8sSUFBSyxLQUFLLFFBQVE7SUFFcEJDLEdBQUEsQ0FBQUEsVUFBQTtJQUNBQyxHQUFBLENBQUFBLFVBQUE7SUFH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LEVBQUEsR0FBQXpCLENBQUE7RUFBQTtFQUNTLE1BQUEwQixFQUFBLElBQUM3QixVQUFVO0VBQUEsSUFBQStCLEdBQUE7RUFBQSxJQUFBNUIsQ0FBQSxVQUFBMkMsWUFBQSxJQUFBM0MsQ0FBQSxVQUFBMEIsRUFBQTtJQUEzQkUsR0FBQSxJQUFDLElBQUksQ0FBVyxRQUFXLENBQVgsQ0FBQUYsRUFBVSxDQUFDLENBQUUsR0FBSXRCLGFBQVMsQ0FBRSxDQUFDLEVBQTVDLElBQUksQ0FBK0M7SUFBQUosQ0FBQSxRQUFBMkMsWUFBQTtJQUFBM0MsQ0FBQSxRQUFBMEIsRUFBQTtJQUFBMUIsQ0FBQSxRQUFBNEIsR0FBQTtFQUFBO0lBQUFBLEdBQUEsR0FBQTVCLENBQUE7RUFBQTtFQUNwQyxNQUFBNkIsR0FBQSxJQUFDaEMsVUFBVTtFQUFBLElBQUFpQyxHQUFBO0VBQUEsSUFBQTlCLENBQUEsVUFBQTRDLFlBQUEsSUFBQTVDLENBQUEsVUFBQTZCLEdBQUE7SUFBM0JDLEdBQUEsSUFBQyxJQUFJLENBQVcsUUFBVyxDQUFYLENBQUFELEdBQVUsQ0FBQyxDQUFHeEIsYUFBUyxDQUFFLEVBQXhDLElBQUksQ0FBMkM7SUFBQUwsQ0FBQSxRQUFBNEMsWUFBQTtJQUFBNUMsQ0FBQSxRQUFBNkIsR0FBQTtJQUFBN0IsQ0FBQSxRQUFBOEIsR0FBQTtFQUFBO0lBQUFBLEdBQUEsR0FBQTlCLENBQUE7RUFBQTtFQUFBLElBQUErQixHQUFBO0VBQUEsSUFBQS9CLENBQUEsVUFBQTRCLEdBQUEsSUFBQTVCLENBQUEsVUFBQThCLEdBQUEsSUFBQTlCLENBQUEsVUFBQVksRUFBQSxJQUFBWixDQUFBLFVBQUFtQixFQUFBLElBQUFuQixDQUFBLFVBQUF5QixFQUFBO0lBVmxETSxHQUFBLElBQUMsR0FBRyxDQUNGLENBQUFuQixFQUVNLENBQ04sQ0FBQU8sRUFBcUUsQ0FDckUsQ0FBQU0sRUFHTSxDQUNOLENBQUFHLEdBQW1ELENBQ25ELENBQUFFLEdBQStDLENBQ2pELEVBWEMsR0FBRyxDQVdFO0lBQUE5QixDQUFBLFFBQUE0QixHQUFBO0lBQUE1QixDQUFBLFFBQUE4QixHQUFBO0lBQUE5QixDQUFBLFFBQUFZLEVBQUE7SUFBQVosQ0FBQSxRQUFBbUIsRUFBQTtJQUFBbkIsQ0FBQSxRQUFBeUIsRUFBQTtJQUFBekIsQ0FBQSxRQUFBK0IsR0FBQTtFQUFBO0lBQUFBLEdBQUEsR0FBQS9CLENBQUE7RUFBQTtFQUFBLE9BWE4rQixHQVdNO0FBQUEiLCJpZ25vcmVMaXN0IjpbXX0= \ No newline at end of file diff --git a/src/commands/plugin/ValidatePlugin.tsx b/src/commands/plugin/ValidatePlugin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..084f5dc675951764bb00ef3749d869e825049cc1 --- /dev/null +++ b/src/commands/plugin/ValidatePlugin.tsx @@ -0,0 +1,98 @@ +import { c as _c } from "react/compiler-runtime"; +import figures from 'figures'; +import * as React from 'react'; +import { useEffect } from 'react'; +import { Box, Text } from '../../ink.js'; +import { errorMessage } from '../../utils/errors.js'; +import { logError } from '../../utils/log.js'; +import { validateManifest } from '../../utils/plugins/validatePlugin.js'; +import { plural } from '../../utils/stringUtils.js'; +type Props = { + onComplete: (result?: string) => void; + path?: string; +}; +export function ValidatePlugin(t0) { + const $ = _c(5); + const { + onComplete, + path + } = t0; + let t1; + let t2; + if ($[0] !== onComplete || $[1] !== path) { + t1 = () => { + const runValidation = async function runValidation() { + if (!path) { + onComplete("Usage: /plugin validate \n\nValidate a plugin or marketplace manifest file or directory.\n\nExamples:\n /plugin validate .claude-plugin/plugin.json\n /plugin validate /path/to/plugin-directory\n /plugin validate .\n\nWhen given a directory, automatically validates .claude-plugin/marketplace.json\nor .claude-plugin/plugin.json (prefers marketplace if both exist).\n\nOr from the command line:\n claude plugin validate "); + return; + } + ; + try { + const result = await validateManifest(path); + let output = ""; + output = output + `Validating ${result.fileType} manifest: ${result.filePath}\n\n`; + output; + if (result.errors.length > 0) { + output = output + `${figures.cross} Found ${result.errors.length} ${plural(result.errors.length, "error")}:\n\n`; + output; + result.errors.forEach(error_0 => { + output = output + ` ${figures.pointer} ${error_0.path}: ${error_0.message}\n`; + output; + }); + output = output + "\n"; + output; + } + if (result.warnings.length > 0) { + output = output + `${figures.warning} Found ${result.warnings.length} ${plural(result.warnings.length, "warning")}:\n\n`; + output; + result.warnings.forEach(warning => { + output = output + ` ${figures.pointer} ${warning.path}: ${warning.message}\n`; + output; + }); + output = output + "\n"; + output; + } + if (result.success) { + if (result.warnings.length > 0) { + output = output + `${figures.tick} Validation passed with warnings\n`; + output; + } else { + output = output + `${figures.tick} Validation passed\n`; + output; + } + process.exitCode = 0; + } else { + output = output + `${figures.cross} Validation failed\n`; + output; + process.exitCode = 1; + } + onComplete(output); + } catch (t3) { + const error = t3; + process.exitCode = 2; + logError(error); + onComplete(`${figures.cross} Unexpected error during validation: ${errorMessage(error)}`); + } + }; + runValidation(); + }; + t2 = [onComplete, path]; + $[0] = onComplete; + $[1] = path; + $[2] = t1; + $[3] = t2; + } else { + t1 = $[2]; + t2 = $[3]; + } + useEffect(t1, t2); + let t3; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t3 = Running validation...; + $[4] = t3; + } else { + t3 = $[4]; + } + return t3; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwiUmVhY3QiLCJ1c2VFZmZlY3QiLCJCb3giLCJUZXh0IiwiZXJyb3JNZXNzYWdlIiwibG9nRXJyb3IiLCJ2YWxpZGF0ZU1hbmlmZXN0IiwicGx1cmFsIiwiUHJvcHMiLCJvbkNvbXBsZXRlIiwicmVzdWx0IiwicGF0aCIsIlZhbGlkYXRlUGx1Z2luIiwidDAiLCIkIiwiX2MiLCJ0MSIsInQyIiwicnVuVmFsaWRhdGlvbiIsIm91dHB1dCIsImZpbGVUeXBlIiwiZmlsZVBhdGgiLCJlcnJvcnMiLCJsZW5ndGgiLCJjcm9zcyIsImZvckVhY2giLCJlcnJvcl8wIiwicG9pbnRlciIsImVycm9yIiwibWVzc2FnZSIsIndhcm5pbmdzIiwid2FybmluZyIsInN1Y2Nlc3MiLCJ0aWNrIiwicHJvY2VzcyIsImV4aXRDb2RlIiwidDMiLCJTeW1ib2wiLCJmb3IiXSwic291cmNlcyI6WyJWYWxpZGF0ZVBsdWdpbi50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IGZpZ3VyZXMgZnJvbSAnZmlndXJlcydcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgdXNlRWZmZWN0IH0gZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBCb3gsIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgeyBlcnJvck1lc3NhZ2UgfSBmcm9tICcuLi8uLi91dGlscy9lcnJvcnMuanMnXG5pbXBvcnQgeyBsb2dFcnJvciB9IGZyb20gJy4uLy4uL3V0aWxzL2xvZy5qcydcbmltcG9ydCB7IHZhbGlkYXRlTWFuaWZlc3QgfSBmcm9tICcuLi8uLi91dGlscy9wbHVnaW5zL3ZhbGlkYXRlUGx1Z2luLmpzJ1xuaW1wb3J0IHsgcGx1cmFsIH0gZnJvbSAnLi4vLi4vdXRpbHMvc3RyaW5nVXRpbHMuanMnXG5cbnR5cGUgUHJvcHMgPSB7XG4gIG9uQ29tcGxldGU6IChyZXN1bHQ/OiBzdHJpbmcpID0+IHZvaWRcbiAgcGF0aD86IHN0cmluZ1xufVxuXG5leHBvcnQgZnVuY3Rpb24gVmFsaWRhdGVQbHVnaW4oeyBvbkNvbXBsZXRlLCBwYXRoIH06IFByb3BzKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgdXNlRWZmZWN0KCgpID0+IHtcbiAgICBhc3luYyBmdW5jdGlvbiBydW5WYWxpZGF0aW9uKCkge1xuICAgICAgLy8gSWYgbm8gcGF0aCBwcm92aWRlZCwgc2hvdyB1c2FnZVxuICAgICAgaWYgKCFwYXRoKSB7XG4gICAgICAgIG9uQ29tcGxldGUoXG4gICAgICAgICAgJ1VzYWdlOiAvcGx1Z2luIHZhbGlkYXRlIDxwYXRoPlxcblxcbicgK1xuICAgICAgICAgICAgJ1ZhbGlkYXRlIGEgcGx1Z2luIG9yIG1hcmtldHBsYWNlIG1hbmlmZXN0IGZpbGUgb3IgZGlyZWN0b3J5LlxcblxcbicgK1xuICAgICAgICAgICAgJ0V4YW1wbGVzOlxcbicgK1xuICAgICAgICAgICAgJyAgL3BsdWdpbiB2YWxpZGF0ZSAuY2xhdWRlLXBsdWdpbi9wbHVnaW4uanNvblxcbicgK1xuICAgICAgICAgICAgJyAgL3BsdWdpbiB2YWxpZGF0ZSAvcGF0aC90by9wbHVnaW4tZGlyZWN0b3J5XFxuJyArXG4gICAgICAgICAgICAnICAvcGx1Z2luIHZhbGlkYXRlIC5cXG5cXG4nICtcbiAgICAgICAgICAgICdXaGVuIGdpdmVuIGEgZGlyZWN0b3J5LCBhdXRvbWF0aWNhbGx5IHZhbGlkYXRlcyAuY2xhdWRlLXBsdWdpbi9tYXJrZXRwbGFjZS5qc29uXFxuJyArXG4gICAgICAgICAgICAnb3IgLmNsYXVkZS1wbHVnaW4vcGx1Z2luLmpzb24gKHByZWZlcnMgbWFya2V0cGxhY2UgaWYgYm90aCBleGlzdCkuXFxuXFxuJyArXG4gICAgICAgICAgICAnT3IgZnJvbSB0aGUgY29tbWFuZCBsaW5lOlxcbicgK1xuICAgICAgICAgICAgJyAgY2xhdWRlIHBsdWdpbiB2YWxpZGF0ZSA8cGF0aD4nLFxuICAgICAgICApXG4gICAgICAgIHJldHVyblxuICAgICAgfVxuXG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCByZXN1bHQgPSBhd2FpdCB2YWxpZGF0ZU1hbmlmZXN0KHBhdGgpXG5cbiAgICAgICAgbGV0IG91dHB1dCA9ICcnXG5cbiAgICAgICAgLy8gQWRkIGhlYWRlclxuICAgICAgICBvdXRwdXQgKz0gYFZhbGlkYXRpbmcgJHtyZXN1bHQuZmlsZVR5cGV9IG1hbmlmZXN0OiAke3Jlc3VsdC5maWxlUGF0aH1cXG5cXG5gXG5cbiAgICAgICAgLy8gU2hvdyBlcnJvcnNcbiAgICAgICAgaWYgKHJlc3VsdC5lcnJvcnMubGVuZ3RoID4gMCkge1xuICAgICAgICAgIG91dHB1dCArPSBgJHtmaWd1cmVzLmNyb3NzfSBGb3VuZCAke3Jlc3VsdC5lcnJvcnMubGVuZ3RofSAke3BsdXJhbChyZXN1bHQuZXJyb3JzLmxlbmd0aCwgJ2Vycm9yJyl9OlxcblxcbmBcblxuICAgICAgICAgIHJlc3VsdC5lcnJvcnMuZm9yRWFjaChlcnJvciA9PiB7XG4gICAgICAgICAgICBvdXRwdXQgKz0gYCAgJHtmaWd1cmVzLnBvaW50ZXJ9ICR7ZXJyb3IucGF0aH06ICR7ZXJyb3IubWVzc2FnZX1cXG5gXG4gICAgICAgICAgfSlcblxuICAgICAgICAgIG91dHB1dCArPSAnXFxuJ1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gU2hvdyB3YXJuaW5nc1xuICAgICAgICBpZiAocmVzdWx0Lndhcm5pbmdzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICBvdXRwdXQgKz0gYCR7ZmlndXJlcy53YXJuaW5nfSBGb3VuZCAke3Jlc3VsdC53YXJuaW5ncy5sZW5ndGh9ICR7cGx1cmFsKHJlc3VsdC53YXJuaW5ncy5sZW5ndGgsICd3YXJuaW5nJyl9OlxcblxcbmBcblxuICAgICAgICAgIHJlc3VsdC53YXJuaW5ncy5mb3JFYWNoKHdhcm5pbmcgPT4ge1xuICAgICAgICAgICAgb3V0cHV0ICs9IGAgICR7ZmlndXJlcy5wb2ludGVyfSAke3dhcm5pbmcucGF0aH06ICR7d2FybmluZy5tZXNzYWdlfVxcbmBcbiAgICAgICAgICB9KVxuXG4gICAgICAgICAgb3V0cHV0ICs9ICdcXG4nXG4gICAgICAgIH1cblxuICAgICAgICAvLyBTaG93IHN1Y2Nlc3Mgb3IgZmFpbHVyZVxuICAgICAgICBpZiAocmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgICAgICBpZiAocmVzdWx0Lndhcm5pbmdzLmxlbmd0aCA+IDApIHtcbiAgICAgICAgICAgIG91dHB1dCArPSBgJHtmaWd1cmVzLnRpY2t9IFZhbGlkYXRpb24gcGFzc2VkIHdpdGggd2FybmluZ3NcXG5gXG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIG91dHB1dCArPSBgJHtmaWd1cmVzLnRpY2t9IFZhbGlkYXRpb24gcGFzc2VkXFxuYFxuICAgICAgICAgIH1cblxuICAgICAgICAgIC8vIEV4aXQgd2l0aCBjb2RlIDAgKHN1Y2Nlc3MpXG4gICAgICAgICAgcHJvY2Vzcy5leGl0Q29kZSA9IDBcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBvdXRwdXQgKz0gYCR7ZmlndXJlcy5jcm9zc30gVmFsaWRhdGlvbiBmYWlsZWRcXG5gXG5cbiAgICAgICAgICAvLyBFeGl0IHdpdGggY29kZSAxICh2YWxpZGF0aW9uIGZhaWx1cmUpXG4gICAgICAgICAgcHJvY2Vzcy5leGl0Q29kZSA9IDFcbiAgICAgICAgfVxuXG4gICAgICAgIG9uQ29tcGxldGUob3V0cHV0KVxuICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgLy8gRXhpdCB3aXRoIGNvZGUgMiAodW5leHBlY3RlZCBlcnJvcilcbiAgICAgICAgcHJvY2Vzcy5leGl0Q29kZSA9IDJcblxuICAgICAgICBsb2dFcnJvcihlcnJvcilcblxuICAgICAgICBvbkNvbXBsZXRlKFxuICAgICAgICAgIGAke2ZpZ3VyZXMuY3Jvc3N9IFVuZXhwZWN0ZWQgZXJyb3IgZHVyaW5nIHZhbGlkYXRpb246ICR7ZXJyb3JNZXNzYWdlKGVycm9yKX1gLFxuICAgICAgICApXG4gICAgICB9XG4gICAgfVxuXG4gICAgdm9pZCBydW5WYWxpZGF0aW9uKClcbiAgfSwgW29uQ29tcGxldGUsIHBhdGhdKVxuXG4gIHJldHVybiAoXG4gICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCI+XG4gICAgICA8VGV4dD5SdW5uaW5nIHZhbGlkYXRpb24uLi48L1RleHQ+XG4gICAgPC9Cb3g+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU9BLE9BQU8sTUFBTSxTQUFTO0FBQzdCLE9BQU8sS0FBS0MsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0MsU0FBUyxRQUFRLE9BQU87QUFDakMsU0FBU0MsR0FBRyxFQUFFQyxJQUFJLFFBQVEsY0FBYztBQUN4QyxTQUFTQyxZQUFZLFFBQVEsdUJBQXVCO0FBQ3BELFNBQVNDLFFBQVEsUUFBUSxvQkFBb0I7QUFDN0MsU0FBU0MsZ0JBQWdCLFFBQVEsdUNBQXVDO0FBQ3hFLFNBQVNDLE1BQU0sUUFBUSw0QkFBNEI7QUFFbkQsS0FBS0MsS0FBSyxHQUFHO0VBQ1hDLFVBQVUsRUFBRSxDQUFDQyxNQUFlLENBQVIsRUFBRSxNQUFNLEVBQUUsR0FBRyxJQUFJO0VBQ3JDQyxJQUFJLENBQUMsRUFBRSxNQUFNO0FBQ2YsQ0FBQztBQUVELE9BQU8sU0FBQUMsZUFBQUMsRUFBQTtFQUFBLE1BQUFDLENBQUEsR0FBQUMsRUFBQTtFQUF3QjtJQUFBTixVQUFBO0lBQUFFO0VBQUEsSUFBQUUsRUFBMkI7RUFBQSxJQUFBRyxFQUFBO0VBQUEsSUFBQUMsRUFBQTtFQUFBLElBQUFILENBQUEsUUFBQUwsVUFBQSxJQUFBSyxDQUFBLFFBQUFILElBQUE7SUFDOUNLLEVBQUEsR0FBQUEsQ0FBQTtNQUNSLE1BQUFFLGFBQUEsa0JBQUFBLGNBQUE7UUFFRSxJQUFJLENBQUNQLElBQUk7VUFDUEYsVUFBVSxDQUNSLHFiQVVGLENBQUM7VUFBQTtRQUFBO1FBRUY7UUFFRDtVQUNFLE1BQUFDLE1BQUEsR0FBZSxNQUFNSixnQkFBZ0IsQ0FBQ0ssSUFBSSxDQUFDO1VBRTNDLElBQUFRLE1BQUEsR0FBYSxFQUFFO1VBR2ZBLE1BQUEsR0FBQUEsTUFBTSxHQUFJLGNBQWNULE1BQU0sQ0FBQVUsUUFBUyxjQUFjVixNQUFNLENBQUFXLFFBQVMsTUFBTTtVQUExRUYsTUFBMEU7VUFHMUUsSUFBSVQsTUFBTSxDQUFBWSxNQUFPLENBQUFDLE1BQU8sR0FBRyxDQUFDO1lBQzFCSixNQUFBLEdBQUFBLE1BQU0sR0FBSSxHQUFHcEIsT0FBTyxDQUFBeUIsS0FBTSxVQUFVZCxNQUFNLENBQUFZLE1BQU8sQ0FBQUMsTUFBTyxJQUFJaEIsTUFBTSxDQUFDRyxNQUFNLENBQUFZLE1BQU8sQ0FBQUMsTUFBTyxFQUFFLE9BQU8sQ0FBQyxPQUFPO1lBQXhHSixNQUF3RztZQUV4R1QsTUFBTSxDQUFBWSxNQUFPLENBQUFHLE9BQVEsQ0FBQ0MsT0FBQTtjQUNwQlAsTUFBQSxHQUFBQSxNQUFNLEdBQUksS0FBS3BCLE9BQU8sQ0FBQTRCLE9BQVEsSUFBSUMsT0FBSyxDQUFBakIsSUFBSyxLQUFLaUIsT0FBSyxDQUFBQyxPQUFRLElBQUk7Y0FBbEVWLE1BQWtFO1lBQUEsQ0FDbkUsQ0FBQztZQUVGQSxNQUFBLEdBQUFBLE1BQU0sR0FBSSxJQUFJO1lBQWRBLE1BQWM7VUFBQTtVQUloQixJQUFJVCxNQUFNLENBQUFvQixRQUFTLENBQUFQLE1BQU8sR0FBRyxDQUFDO1lBQzVCSixNQUFBLEdBQUFBLE1BQU0sR0FBSSxHQUFHcEIsT0FBTyxDQUFBZ0MsT0FBUSxVQUFVckIsTUFBTSxDQUFBb0IsUUFBUyxDQUFBUCxNQUFPLElBQUloQixNQUFNLENBQUNHLE1BQU0sQ0FBQW9CLFFBQVMsQ0FBQVAsTUFBTyxFQUFFLFNBQVMsQ0FBQyxPQUFPO1lBQWhISixNQUFnSDtZQUVoSFQsTUFBTSxDQUFBb0IsUUFBUyxDQUFBTCxPQUFRLENBQUNNLE9BQUE7Y0FDdEJaLE1BQUEsR0FBQUEsTUFBTSxHQUFJLEtBQUtwQixPQUFPLENBQUE0QixPQUFRLElBQUlJLE9BQU8sQ0FBQXBCLElBQUssS0FBS29CLE9BQU8sQ0FBQUYsT0FBUSxJQUFJO2NBQXRFVixNQUFzRTtZQUFBLENBQ3ZFLENBQUM7WUFFRkEsTUFBQSxHQUFBQSxNQUFNLEdBQUksSUFBSTtZQUFkQSxNQUFjO1VBQUE7VUFJaEIsSUFBSVQsTUFBTSxDQUFBc0IsT0FBUTtZQUNoQixJQUFJdEIsTUFBTSxDQUFBb0IsUUFBUyxDQUFBUCxNQUFPLEdBQUcsQ0FBQztjQUM1QkosTUFBQSxHQUFBQSxNQUFNLEdBQUksR0FBR3BCLE9BQU8sQ0FBQWtDLElBQUssb0NBQW9DO2NBQTdEZCxNQUE2RDtZQUFBO2NBRTdEQSxNQUFBLEdBQUFBLE1BQU0sR0FBSSxHQUFHcEIsT0FBTyxDQUFBa0MsSUFBSyxzQkFBc0I7Y0FBL0NkLE1BQStDO1lBQUE7WUFJakRlLE9BQU8sQ0FBQUMsUUFBQSxHQUFZLENBQUg7VUFBQTtZQUVoQmhCLE1BQUEsR0FBQUEsTUFBTSxHQUFJLEdBQUdwQixPQUFPLENBQUF5QixLQUFNLHNCQUFzQjtZQUFoREwsTUFBZ0Q7WUFHaERlLE9BQU8sQ0FBQUMsUUFBQSxHQUFZLENBQUg7VUFBQTtVQUdsQjFCLFVBQVUsQ0FBQ1UsTUFBTSxDQUFDO1FBQUEsU0FBQWlCLEVBQUE7VUFDWFIsS0FBQSxDQUFBQSxLQUFBLENBQUFBLENBQUEsQ0FBQUEsRUFBSztVQUVaTSxPQUFPLENBQUFDLFFBQUEsR0FBWSxDQUFIO1VBRWhCOUIsUUFBUSxDQUFDdUIsS0FBSyxDQUFDO1VBRWZuQixVQUFVLENBQ1IsR0FBR1YsT0FBTyxDQUFBeUIsS0FBTSx3Q0FBd0NwQixZQUFZLENBQUN3QixLQUFLLENBQUMsRUFDN0UsQ0FBQztRQUFBO01BQ0YsQ0FDRjtNQUVJVixhQUFhLENBQUMsQ0FBQztJQUFBLENBQ3JCO0lBQUVELEVBQUEsSUFBQ1IsVUFBVSxFQUFFRSxJQUFJLENBQUM7SUFBQUcsQ0FBQSxNQUFBTCxVQUFBO0lBQUFLLENBQUEsTUFBQUgsSUFBQTtJQUFBRyxDQUFBLE1BQUFFLEVBQUE7SUFBQUYsQ0FBQSxNQUFBRyxFQUFBO0VBQUE7SUFBQUQsRUFBQSxHQUFBRixDQUFBO0lBQUFHLEVBQUEsR0FBQUgsQ0FBQTtFQUFBO0VBaEZyQmIsU0FBUyxDQUFDZSxFQWdGVCxFQUFFQyxFQUFrQixDQUFDO0VBQUEsSUFBQW1CLEVBQUE7RUFBQSxJQUFBdEIsQ0FBQSxRQUFBdUIsTUFBQSxDQUFBQyxHQUFBO0lBR3BCRixFQUFBLElBQUMsR0FBRyxDQUFlLGFBQVEsQ0FBUixRQUFRLENBQ3pCLENBQUMsSUFBSSxDQUFDLHFCQUFxQixFQUExQixJQUFJLENBQ1AsRUFGQyxHQUFHLENBRUU7SUFBQXRCLENBQUEsTUFBQXNCLEVBQUE7RUFBQTtJQUFBQSxFQUFBLEdBQUF0QixDQUFBO0VBQUE7RUFBQSxPQUZOc0IsRUFFTTtBQUFBIiwiaWdub3JlTGlzdCI6W119 \ No newline at end of file diff --git a/src/commands/plugin/index.tsx b/src/commands/plugin/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..02dad87d730024db53c76032d872fd6b62e22f94 --- /dev/null +++ b/src/commands/plugin/index.tsx @@ -0,0 +1,11 @@ +import type { Command } from '../../commands.js'; +const plugin = { + type: 'local-jsx', + name: 'plugin', + aliases: ['plugins', 'marketplace'], + description: 'Manage Claude Code plugins', + immediate: true, + load: () => import('./plugin.js') +} satisfies Command; +export default plugin; +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJDb21tYW5kIiwicGx1Z2luIiwidHlwZSIsIm5hbWUiLCJhbGlhc2VzIiwiZGVzY3JpcHRpb24iLCJpbW1lZGlhdGUiLCJsb2FkIl0sInNvdXJjZXMiOlsiaW5kZXgudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB0eXBlIHsgQ29tbWFuZCB9IGZyb20gJy4uLy4uL2NvbW1hbmRzLmpzJ1xuXG5jb25zdCBwbHVnaW4gPSB7XG4gIHR5cGU6ICdsb2NhbC1qc3gnLFxuICBuYW1lOiAncGx1Z2luJyxcbiAgYWxpYXNlczogWydwbHVnaW5zJywgJ21hcmtldHBsYWNlJ10sXG4gIGRlc2NyaXB0aW9uOiAnTWFuYWdlIENsYXVkZSBDb2RlIHBsdWdpbnMnLFxuICBpbW1lZGlhdGU6IHRydWUsXG4gIGxvYWQ6ICgpID0+IGltcG9ydCgnLi9wbHVnaW4uanMnKSxcbn0gc2F0aXNmaWVzIENvbW1hbmRcblxuZXhwb3J0IGRlZmF1bHQgcGx1Z2luXG4iXSwibWFwcGluZ3MiOiJBQUFBLGNBQWNBLE9BQU8sUUFBUSxtQkFBbUI7QUFFaEQsTUFBTUMsTUFBTSxHQUFHO0VBQ2JDLElBQUksRUFBRSxXQUFXO0VBQ2pCQyxJQUFJLEVBQUUsUUFBUTtFQUNkQyxPQUFPLEVBQUUsQ0FBQyxTQUFTLEVBQUUsYUFBYSxDQUFDO0VBQ25DQyxXQUFXLEVBQUUsNEJBQTRCO0VBQ3pDQyxTQUFTLEVBQUUsSUFBSTtFQUNmQyxJQUFJLEVBQUVBLENBQUEsS0FBTSxNQUFNLENBQUMsYUFBYTtBQUNsQyxDQUFDLFdBQVdQLE9BQU87QUFFbkIsZUFBZUMsTUFBTSIsImlnbm9yZUxpc3QiOltdfQ== \ No newline at end of file diff --git a/src/commands/plugin/parseArgs.ts b/src/commands/plugin/parseArgs.ts new file mode 100644 index 0000000000000000000000000000000000000000..a2c36e4b95da8a11814c7804b45827a202e57561 --- /dev/null +++ b/src/commands/plugin/parseArgs.ts @@ -0,0 +1,103 @@ +// Parse plugin subcommand arguments into structured commands +export type ParsedCommand = + | { type: 'menu' } + | { type: 'help' } + | { type: 'install'; marketplace?: string; plugin?: string } + | { type: 'manage' } + | { type: 'uninstall'; plugin?: string } + | { type: 'enable'; plugin?: string } + | { type: 'disable'; plugin?: string } + | { type: 'validate'; path?: string } + | { + type: 'marketplace' + action?: 'add' | 'remove' | 'update' | 'list' + target?: string + } + +export function parsePluginArgs(args?: string): ParsedCommand { + if (!args) { + return { type: 'menu' } + } + + const parts = args.trim().split(/\s+/) + const command = parts[0]?.toLowerCase() + + switch (command) { + case 'help': + case '--help': + case '-h': + return { type: 'help' } + + case 'install': + case 'i': { + const target = parts[1] + if (!target) { + return { type: 'install' } + } + + // Check if it's in format plugin@marketplace + if (target.includes('@')) { + const [plugin, marketplace] = target.split('@') + return { type: 'install', plugin, marketplace } + } + + // Check if the target looks like a marketplace (URL or path) + const isMarketplace = + target.startsWith('http://') || + target.startsWith('https://') || + target.startsWith('file://') || + target.includes('/') || + target.includes('\\') + + if (isMarketplace) { + // This is a marketplace URL/path, no plugin specified + return { type: 'install', marketplace: target } + } + + // Otherwise treat it as a plugin name + return { type: 'install', plugin: target } + } + + case 'manage': + return { type: 'manage' } + + case 'uninstall': + return { type: 'uninstall', plugin: parts[1] } + + case 'enable': + return { type: 'enable', plugin: parts[1] } + + case 'disable': + return { type: 'disable', plugin: parts[1] } + + case 'validate': { + const target = parts.slice(1).join(' ').trim() + return { type: 'validate', path: target || undefined } + } + + case 'marketplace': + case 'market': { + const action = parts[1]?.toLowerCase() + const target = parts.slice(2).join(' ') + + switch (action) { + case 'add': + return { type: 'marketplace', action: 'add', target } + case 'remove': + case 'rm': + return { type: 'marketplace', action: 'remove', target } + case 'update': + return { type: 'marketplace', action: 'update', target } + case 'list': + return { type: 'marketplace', action: 'list' } + default: + // No action specified, show marketplace menu + return { type: 'marketplace' } + } + } + + default: + // Unknown command, show menu + return { type: 'menu' } + } +} diff --git a/src/commands/plugin/plugin.tsx b/src/commands/plugin/plugin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f079f49a0f2187e6fcc41ac9cd46eefe94356e7c --- /dev/null +++ b/src/commands/plugin/plugin.tsx @@ -0,0 +1,7 @@ +import * as React from 'react'; +import type { LocalJSXCommandOnDone } from '../../types/command.js'; +import { PluginSettings } from './PluginSettings.js'; +export async function call(onDone: LocalJSXCommandOnDone, _context: unknown, args?: string): Promise { + return ; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIkxvY2FsSlNYQ29tbWFuZE9uRG9uZSIsIlBsdWdpblNldHRpbmdzIiwiY2FsbCIsIm9uRG9uZSIsIl9jb250ZXh0IiwiYXJncyIsIlByb21pc2UiLCJSZWFjdE5vZGUiXSwic291cmNlcyI6WyJwbHVnaW4udHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHR5cGUgeyBMb2NhbEpTWENvbW1hbmRPbkRvbmUgfSBmcm9tICcuLi8uLi90eXBlcy9jb21tYW5kLmpzJ1xuaW1wb3J0IHsgUGx1Z2luU2V0dGluZ3MgfSBmcm9tICcuL1BsdWdpblNldHRpbmdzLmpzJ1xuXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gY2FsbChcbiAgb25Eb25lOiBMb2NhbEpTWENvbW1hbmRPbkRvbmUsXG4gIF9jb250ZXh0OiB1bmtub3duLFxuICBhcmdzPzogc3RyaW5nLFxuKTogUHJvbWlzZTxSZWFjdC5SZWFjdE5vZGU+IHtcbiAgcmV0dXJuIDxQbHVnaW5TZXR0aW5ncyBvbkNvbXBsZXRlPXtvbkRvbmV9IGFyZ3M9e2FyZ3N9IC8+XG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsY0FBY0MscUJBQXFCLFFBQVEsd0JBQXdCO0FBQ25FLFNBQVNDLGNBQWMsUUFBUSxxQkFBcUI7QUFFcEQsT0FBTyxlQUFlQyxJQUFJQSxDQUN4QkMsTUFBTSxFQUFFSCxxQkFBcUIsRUFDN0JJLFFBQVEsRUFBRSxPQUFPLEVBQ2pCQyxJQUFhLENBQVIsRUFBRSxNQUFNLENBQ2QsRUFBRUMsT0FBTyxDQUFDUCxLQUFLLENBQUNRLFNBQVMsQ0FBQyxDQUFDO0VBQzFCLE9BQU8sQ0FBQyxjQUFjLENBQUMsVUFBVSxDQUFDLENBQUNKLE1BQU0sQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDRSxJQUFJLENBQUMsR0FBRztBQUMzRCIsImlnbm9yZUxpc3QiOltdfQ== \ No newline at end of file diff --git a/src/commands/plugin/pluginDetailsHelpers.tsx b/src/commands/plugin/pluginDetailsHelpers.tsx new file mode 100644 index 0000000000000000000000000000000000000000..fbd365a9c77da136c51f886b8a332037dac35144 --- /dev/null +++ b/src/commands/plugin/pluginDetailsHelpers.tsx @@ -0,0 +1,117 @@ +import { c as _c } from "react/compiler-runtime"; +/** + * Shared helper functions and types for plugin details views + * + * Used by both DiscoverPlugins and BrowseMarketplace components. + */ + +import * as React from 'react'; +import { ConfigurableShortcutHint } from '../../components/ConfigurableShortcutHint.js'; +import { Byline } from '../../components/design-system/Byline.js'; +import { Box, Text } from '../../ink.js'; +import type { PluginMarketplaceEntry } from '../../utils/plugins/schemas.js'; + +/** + * Represents a plugin available for installation from a marketplace + */ +export type InstallablePlugin = { + entry: PluginMarketplaceEntry; + marketplaceName: string; + pluginId: string; + isInstalled: boolean; +}; + +/** + * Menu option for plugin details view + */ +export type PluginDetailsMenuOption = { + label: string; + action: string; +}; + +/** + * Extract GitHub repo info from a plugin's source + */ +export function extractGitHubRepo(plugin: InstallablePlugin): string | null { + const isGitHub = plugin.entry.source && typeof plugin.entry.source === 'object' && 'source' in plugin.entry.source && plugin.entry.source.source === 'github'; + if (isGitHub && typeof plugin.entry.source === 'object' && 'repo' in plugin.entry.source) { + return plugin.entry.source.repo; + } + return null; +} + +/** + * Build menu options for plugin details view with scoped installation options + */ +export function buildPluginDetailsMenuOptions(hasHomepage: string | undefined, githubRepo: string | null): PluginDetailsMenuOption[] { + const options: PluginDetailsMenuOption[] = [{ + label: 'Install for you (user scope)', + action: 'install-user' + }, { + label: 'Install for all collaborators on this repository (project scope)', + action: 'install-project' + }, { + label: 'Install for you, in this repo only (local scope)', + action: 'install-local' + }]; + if (hasHomepage) { + options.push({ + label: 'Open homepage', + action: 'homepage' + }); + } + if (githubRepo) { + options.push({ + label: 'View on GitHub', + action: 'github' + }); + } + options.push({ + label: 'Back to plugin list', + action: 'back' + }); + return options; +} + +/** + * Key hint component for plugin selection screens + */ +export function PluginSelectionKeyHint(t0) { + const $ = _c(7); + const { + hasSelection + } = t0; + let t1; + if ($[0] !== hasSelection) { + t1 = hasSelection && ; + $[0] = hasSelection; + $[1] = t1; + } else { + t1 = $[1]; + } + let t2; + let t3; + let t4; + if ($[2] === Symbol.for("react.memo_cache_sentinel")) { + t2 = ; + t3 = ; + t4 = ; + $[2] = t2; + $[3] = t3; + $[4] = t4; + } else { + t2 = $[2]; + t3 = $[3]; + t4 = $[4]; + } + let t5; + if ($[5] !== t1) { + t5 = {t1}{t2}{t3}{t4}; + $[5] = t1; + $[6] = t5; + } else { + t5 = $[6]; + } + return t5; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIkNvbmZpZ3VyYWJsZVNob3J0Y3V0SGludCIsIkJ5bGluZSIsIkJveCIsIlRleHQiLCJQbHVnaW5NYXJrZXRwbGFjZUVudHJ5IiwiSW5zdGFsbGFibGVQbHVnaW4iLCJlbnRyeSIsIm1hcmtldHBsYWNlTmFtZSIsInBsdWdpbklkIiwiaXNJbnN0YWxsZWQiLCJQbHVnaW5EZXRhaWxzTWVudU9wdGlvbiIsImxhYmVsIiwiYWN0aW9uIiwiZXh0cmFjdEdpdEh1YlJlcG8iLCJwbHVnaW4iLCJpc0dpdEh1YiIsInNvdXJjZSIsInJlcG8iLCJidWlsZFBsdWdpbkRldGFpbHNNZW51T3B0aW9ucyIsImhhc0hvbWVwYWdlIiwiZ2l0aHViUmVwbyIsIm9wdGlvbnMiLCJwdXNoIiwiUGx1Z2luU2VsZWN0aW9uS2V5SGludCIsInQwIiwiJCIsIl9jIiwiaGFzU2VsZWN0aW9uIiwidDEiLCJ0MiIsInQzIiwidDQiLCJTeW1ib2wiLCJmb3IiLCJ0NSJdLCJzb3VyY2VzIjpbInBsdWdpbkRldGFpbHNIZWxwZXJzLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIFNoYXJlZCBoZWxwZXIgZnVuY3Rpb25zIGFuZCB0eXBlcyBmb3IgcGx1Z2luIGRldGFpbHMgdmlld3NcbiAqXG4gKiBVc2VkIGJ5IGJvdGggRGlzY292ZXJQbHVnaW5zIGFuZCBCcm93c2VNYXJrZXRwbGFjZSBjb21wb25lbnRzLlxuICovXG5cbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgQ29uZmlndXJhYmxlU2hvcnRjdXRIaW50IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9Db25maWd1cmFibGVTaG9ydGN1dEhpbnQuanMnXG5pbXBvcnQgeyBCeWxpbmUgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL2Rlc2lnbi1zeXN0ZW0vQnlsaW5lLmpzJ1xuaW1wb3J0IHsgQm94LCBUZXh0IH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHR5cGUgeyBQbHVnaW5NYXJrZXRwbGFjZUVudHJ5IH0gZnJvbSAnLi4vLi4vdXRpbHMvcGx1Z2lucy9zY2hlbWFzLmpzJ1xuXG4vKipcbiAqIFJlcHJlc2VudHMgYSBwbHVnaW4gYXZhaWxhYmxlIGZvciBpbnN0YWxsYXRpb24gZnJvbSBhIG1hcmtldHBsYWNlXG4gKi9cbmV4cG9ydCB0eXBlIEluc3RhbGxhYmxlUGx1Z2luID0ge1xuICBlbnRyeTogUGx1Z2luTWFya2V0cGxhY2VFbnRyeVxuICBtYXJrZXRwbGFjZU5hbWU6IHN0cmluZ1xuICBwbHVnaW5JZDogc3RyaW5nXG4gIGlzSW5zdGFsbGVkOiBib29sZWFuXG59XG5cbi8qKlxuICogTWVudSBvcHRpb24gZm9yIHBsdWdpbiBkZXRhaWxzIHZpZXdcbiAqL1xuZXhwb3J0IHR5cGUgUGx1Z2luRGV0YWlsc01lbnVPcHRpb24gPSB7XG4gIGxhYmVsOiBzdHJpbmdcbiAgYWN0aW9uOiBzdHJpbmdcbn1cblxuLyoqXG4gKiBFeHRyYWN0IEdpdEh1YiByZXBvIGluZm8gZnJvbSBhIHBsdWdpbidzIHNvdXJjZVxuICovXG5leHBvcnQgZnVuY3Rpb24gZXh0cmFjdEdpdEh1YlJlcG8ocGx1Z2luOiBJbnN0YWxsYWJsZVBsdWdpbik6IHN0cmluZyB8IG51bGwge1xuICBjb25zdCBpc0dpdEh1YiA9XG4gICAgcGx1Z2luLmVudHJ5LnNvdXJjZSAmJlxuICAgIHR5cGVvZiBwbHVnaW4uZW50cnkuc291cmNlID09PSAnb2JqZWN0JyAmJlxuICAgICdzb3VyY2UnIGluIHBsdWdpbi5lbnRyeS5zb3VyY2UgJiZcbiAgICBwbHVnaW4uZW50cnkuc291cmNlLnNvdXJjZSA9PT0gJ2dpdGh1YidcblxuICBpZiAoXG4gICAgaXNHaXRIdWIgJiZcbiAgICB0eXBlb2YgcGx1Z2luLmVudHJ5LnNvdXJjZSA9PT0gJ29iamVjdCcgJiZcbiAgICAncmVwbycgaW4gcGx1Z2luLmVudHJ5LnNvdXJjZVxuICApIHtcbiAgICByZXR1cm4gcGx1Z2luLmVudHJ5LnNvdXJjZS5yZXBvXG4gIH1cblxuICByZXR1cm4gbnVsbFxufVxuXG4vKipcbiAqIEJ1aWxkIG1lbnUgb3B0aW9ucyBmb3IgcGx1Z2luIGRldGFpbHMgdmlldyB3aXRoIHNjb3BlZCBpbnN0YWxsYXRpb24gb3B0aW9uc1xuICovXG5leHBvcnQgZnVuY3Rpb24gYnVpbGRQbHVnaW5EZXRhaWxzTWVudU9wdGlvbnMoXG4gIGhhc0hvbWVwYWdlOiBzdHJpbmcgfCB1bmRlZmluZWQsXG4gIGdpdGh1YlJlcG86IHN0cmluZyB8IG51bGwsXG4pOiBQbHVnaW5EZXRhaWxzTWVudU9wdGlvbltdIHtcbiAgY29uc3Qgb3B0aW9uczogUGx1Z2luRGV0YWlsc01lbnVPcHRpb25bXSA9IFtcbiAgICB7IGxhYmVsOiAnSW5zdGFsbCBmb3IgeW91ICh1c2VyIHNjb3BlKScsIGFjdGlvbjogJ2luc3RhbGwtdXNlcicgfSxcbiAgICB7XG4gICAgICBsYWJlbDogJ0luc3RhbGwgZm9yIGFsbCBjb2xsYWJvcmF0b3JzIG9uIHRoaXMgcmVwb3NpdG9yeSAocHJvamVjdCBzY29wZSknLFxuICAgICAgYWN0aW9uOiAnaW5zdGFsbC1wcm9qZWN0JyxcbiAgICB9LFxuICAgIHtcbiAgICAgIGxhYmVsOiAnSW5zdGFsbCBmb3IgeW91LCBpbiB0aGlzIHJlcG8gb25seSAobG9jYWwgc2NvcGUpJyxcbiAgICAgIGFjdGlvbjogJ2luc3RhbGwtbG9jYWwnLFxuICAgIH0sXG4gIF1cbiAgaWYgKGhhc0hvbWVwYWdlKSB7XG4gICAgb3B0aW9ucy5wdXNoKHsgbGFiZWw6ICdPcGVuIGhvbWVwYWdlJywgYWN0aW9uOiAnaG9tZXBhZ2UnIH0pXG4gIH1cbiAgaWYgKGdpdGh1YlJlcG8pIHtcbiAgICBvcHRpb25zLnB1c2goeyBsYWJlbDogJ1ZpZXcgb24gR2l0SHViJywgYWN0aW9uOiAnZ2l0aHViJyB9KVxuICB9XG4gIG9wdGlvbnMucHVzaCh7IGxhYmVsOiAnQmFjayB0byBwbHVnaW4gbGlzdCcsIGFjdGlvbjogJ2JhY2snIH0pXG4gIHJldHVybiBvcHRpb25zXG59XG5cbi8qKlxuICogS2V5IGhpbnQgY29tcG9uZW50IGZvciBwbHVnaW4gc2VsZWN0aW9uIHNjcmVlbnNcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIFBsdWdpblNlbGVjdGlvbktleUhpbnQoe1xuICBoYXNTZWxlY3Rpb24sXG59OiB7XG4gIGhhc1NlbGVjdGlvbjogYm9vbGVhblxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIHJldHVybiAoXG4gICAgPEJveCBtYXJnaW5Ub3A9ezF9PlxuICAgICAgPFRleHQgZGltQ29sb3IgaXRhbGljPlxuICAgICAgICA8QnlsaW5lPlxuICAgICAgICAgIHtoYXNTZWxlY3Rpb24gJiYgKFxuICAgICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgICBhY3Rpb249XCJwbHVnaW46aW5zdGFsbFwiXG4gICAgICAgICAgICAgIGNvbnRleHQ9XCJQbHVnaW5cIlxuICAgICAgICAgICAgICBmYWxsYmFjaz1cImlcIlxuICAgICAgICAgICAgICBkZXNjcmlwdGlvbj1cImluc3RhbGxcIlxuICAgICAgICAgICAgICBib2xkXG4gICAgICAgICAgICAvPlxuICAgICAgICAgICl9XG4gICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgYWN0aW9uPVwicGx1Z2luOnRvZ2dsZVwiXG4gICAgICAgICAgICBjb250ZXh0PVwiUGx1Z2luXCJcbiAgICAgICAgICAgIGZhbGxiYWNrPVwiU3BhY2VcIlxuICAgICAgICAgICAgZGVzY3JpcHRpb249XCJ0b2dnbGVcIlxuICAgICAgICAgIC8+XG4gICAgICAgICAgPENvbmZpZ3VyYWJsZVNob3J0Y3V0SGludFxuICAgICAgICAgICAgYWN0aW9uPVwic2VsZWN0OmFjY2VwdFwiXG4gICAgICAgICAgICBjb250ZXh0PVwiU2VsZWN0XCJcbiAgICAgICAgICAgIGZhbGxiYWNrPVwiRW50ZXJcIlxuICAgICAgICAgICAgZGVzY3JpcHRpb249XCJkZXRhaWxzXCJcbiAgICAgICAgICAvPlxuICAgICAgICAgIDxDb25maWd1cmFibGVTaG9ydGN1dEhpbnRcbiAgICAgICAgICAgIGFjdGlvbj1cImNvbmZpcm06bm9cIlxuICAgICAgICAgICAgY29udGV4dD1cIkNvbmZpcm1hdGlvblwiXG4gICAgICAgICAgICBmYWxsYmFjaz1cIkVzY1wiXG4gICAgICAgICAgICBkZXNjcmlwdGlvbj1cImJhY2tcIlxuICAgICAgICAgIC8+XG4gICAgICAgIDwvQnlsaW5lPlxuICAgICAgPC9UZXh0PlxuICAgIDwvQm94PlxuICApXG59XG4iXSwibWFwcGluZ3MiOiI7QUFBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0Msd0JBQXdCLFFBQVEsOENBQThDO0FBQ3ZGLFNBQVNDLE1BQU0sUUFBUSwwQ0FBMEM7QUFDakUsU0FBU0MsR0FBRyxFQUFFQyxJQUFJLFFBQVEsY0FBYztBQUN4QyxjQUFjQyxzQkFBc0IsUUFBUSxnQ0FBZ0M7O0FBRTVFO0FBQ0E7QUFDQTtBQUNBLE9BQU8sS0FBS0MsaUJBQWlCLEdBQUc7RUFDOUJDLEtBQUssRUFBRUYsc0JBQXNCO0VBQzdCRyxlQUFlLEVBQUUsTUFBTTtFQUN2QkMsUUFBUSxFQUFFLE1BQU07RUFDaEJDLFdBQVcsRUFBRSxPQUFPO0FBQ3RCLENBQUM7O0FBRUQ7QUFDQTtBQUNBO0FBQ0EsT0FBTyxLQUFLQyx1QkFBdUIsR0FBRztFQUNwQ0MsS0FBSyxFQUFFLE1BQU07RUFDYkMsTUFBTSxFQUFFLE1BQU07QUFDaEIsQ0FBQzs7QUFFRDtBQUNBO0FBQ0E7QUFDQSxPQUFPLFNBQVNDLGlCQUFpQkEsQ0FBQ0MsTUFBTSxFQUFFVCxpQkFBaUIsQ0FBQyxFQUFFLE1BQU0sR0FBRyxJQUFJLENBQUM7RUFDMUUsTUFBTVUsUUFBUSxHQUNaRCxNQUFNLENBQUNSLEtBQUssQ0FBQ1UsTUFBTSxJQUNuQixPQUFPRixNQUFNLENBQUNSLEtBQUssQ0FBQ1UsTUFBTSxLQUFLLFFBQVEsSUFDdkMsUUFBUSxJQUFJRixNQUFN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 \ No newline at end of file diff --git a/src/commands/plugin/usePagination.ts b/src/commands/plugin/usePagination.ts new file mode 100644 index 0000000000000000000000000000000000000000..8c1f4afaacbcc5b8ba42699c568e42ec9c303958 --- /dev/null +++ b/src/commands/plugin/usePagination.ts @@ -0,0 +1,171 @@ +import { useCallback, useMemo, useRef } from 'react' + +const DEFAULT_MAX_VISIBLE = 5 + +type UsePaginationOptions = { + totalItems: number + maxVisible?: number + selectedIndex?: number +} + +type UsePaginationResult = { + // For backwards compatibility with page-based terminology + currentPage: number + totalPages: number + startIndex: number + endIndex: number + needsPagination: boolean + pageSize: number + // Get visible slice of items + getVisibleItems: (items: T[]) => T[] + // Convert visible index to actual index + toActualIndex: (visibleIndex: number) => number + // Check if actual index is visible + isOnCurrentPage: (actualIndex: number) => boolean + // Navigation (kept for API compatibility) + goToPage: (page: number) => void + nextPage: () => void + prevPage: () => void + // Handle selection - just updates the index, scrolling is automatic + handleSelectionChange: ( + newIndex: number, + setSelectedIndex: (index: number) => void, + ) => void + // Page navigation - returns false for continuous scrolling (not needed) + handlePageNavigation: ( + direction: 'left' | 'right', + setSelectedIndex: (index: number) => void, + ) => boolean + // Scroll position info for UI display + scrollPosition: { + current: number + total: number + canScrollUp: boolean + canScrollDown: boolean + } +} + +export function usePagination({ + totalItems, + maxVisible = DEFAULT_MAX_VISIBLE, + selectedIndex = 0, +}: UsePaginationOptions): UsePaginationResult { + const needsPagination = totalItems > maxVisible + + // Use a ref to track the previous scroll offset for smooth scrolling + const scrollOffsetRef = useRef(0) + + // Compute the scroll offset based on selectedIndex + // This ensures the selected item is always visible + const scrollOffset = useMemo(() => { + if (!needsPagination) return 0 + + const prevOffset = scrollOffsetRef.current + + // If selected item is above the visible window, scroll up + if (selectedIndex < prevOffset) { + scrollOffsetRef.current = selectedIndex + return selectedIndex + } + + // If selected item is below the visible window, scroll down + if (selectedIndex >= prevOffset + maxVisible) { + const newOffset = selectedIndex - maxVisible + 1 + scrollOffsetRef.current = newOffset + return newOffset + } + + // Selected item is within visible window, keep current offset + // But ensure offset is still valid + const maxOffset = Math.max(0, totalItems - maxVisible) + const clampedOffset = Math.min(prevOffset, maxOffset) + scrollOffsetRef.current = clampedOffset + return clampedOffset + }, [selectedIndex, maxVisible, needsPagination, totalItems]) + + const startIndex = scrollOffset + const endIndex = Math.min(scrollOffset + maxVisible, totalItems) + + const getVisibleItems = useCallback( + (items: T[]): T[] => { + if (!needsPagination) return items + return items.slice(startIndex, endIndex) + }, + [needsPagination, startIndex, endIndex], + ) + + const toActualIndex = useCallback( + (visibleIndex: number): number => { + return startIndex + visibleIndex + }, + [startIndex], + ) + + const isOnCurrentPage = useCallback( + (actualIndex: number): boolean => { + return actualIndex >= startIndex && actualIndex < endIndex + }, + [startIndex, endIndex], + ) + + // These are mostly no-ops for continuous scrolling but kept for API compatibility + const goToPage = useCallback((_page: number) => { + // No-op - scrolling is controlled by selectedIndex + }, []) + + const nextPage = useCallback(() => { + // No-op - scrolling is controlled by selectedIndex + }, []) + + const prevPage = useCallback(() => { + // No-op - scrolling is controlled by selectedIndex + }, []) + + // Simple selection handler - just updates the index + // Scrolling happens automatically via the useMemo above + const handleSelectionChange = useCallback( + (newIndex: number, setSelectedIndex: (index: number) => void) => { + const clampedIndex = Math.max(0, Math.min(newIndex, totalItems - 1)) + setSelectedIndex(clampedIndex) + }, + [totalItems], + ) + + // Page navigation - disabled for continuous scrolling + const handlePageNavigation = useCallback( + ( + _direction: 'left' | 'right', + _setSelectedIndex: (index: number) => void, + ): boolean => { + return false + }, + [], + ) + + // Calculate page-like values for backwards compatibility + const totalPages = Math.max(1, Math.ceil(totalItems / maxVisible)) + const currentPage = Math.floor(scrollOffset / maxVisible) + + return { + currentPage, + totalPages, + startIndex, + endIndex, + needsPagination, + pageSize: maxVisible, + getVisibleItems, + toActualIndex, + isOnCurrentPage, + goToPage, + nextPage, + prevPage, + handleSelectionChange, + handlePageNavigation, + scrollPosition: { + current: selectedIndex + 1, + total: totalItems, + canScrollUp: scrollOffset > 0, + canScrollDown: scrollOffset + maxVisible < totalItems, + }, + } +} diff --git a/src/commands/pr_comments/index.ts b/src/commands/pr_comments/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..df8969fe324085a2388d80ce3ac611028d35dd77 --- /dev/null +++ b/src/commands/pr_comments/index.ts @@ -0,0 +1,50 @@ +import { createMovedToPluginCommand } from '../createMovedToPluginCommand.js' + +export default createMovedToPluginCommand({ + name: 'pr-comments', + description: 'Get comments from a GitHub pull request', + progressMessage: 'fetching PR comments', + pluginName: 'pr-comments', + pluginCommand: 'pr-comments', + async getPromptWhileMarketplaceIsPrivate(args) { + return [ + { + type: 'text', + text: `You are an AI assistant integrated into a git-based version control system. Your task is to fetch and display comments from a GitHub pull request. + +Follow these steps: + +1. Use \`gh pr view --json number,headRepository\` to get the PR number and repository info +2. Use \`gh api /repos/{owner}/{repo}/issues/{number}/comments\` to get PR-level comments +3. Use \`gh api /repos/{owner}/{repo}/pulls/{number}/comments\` to get review comments. Pay particular attention to the following fields: \`body\`, \`diff_hunk\`, \`path\`, \`line\`, etc. If the comment references some code, consider fetching it using eg \`gh api /repos/{owner}/{repo}/contents/{path}?ref={branch} | jq .content -r | base64 -d\` +4. Parse and format all comments in a readable way +5. Return ONLY the formatted comments, with no additional text + +Format the comments as: + +## Comments + +[For each comment thread:] +- @author file.ts#line: + \`\`\`diff + [diff_hunk from the API response] + \`\`\` + > quoted comment text + + [any replies indented] + +If there are no comments, return "No comments found." + +Remember: +1. Only show the actual comments, no explanatory text +2. Include both PR-level and code review comments +3. Preserve the threading/nesting of comment replies +4. Show the file and line number context for code review comments +5. Use jq to parse the JSON responses from the GitHub API + +${args ? 'Additional user input: ' + args : ''} +`, + }, + ] + }, +}) diff --git a/src/commands/privacy-settings/privacy-settings.tsx b/src/commands/privacy-settings/privacy-settings.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4e4acf67c2cda14bd9c5825f15891a4cb9f59ed7 --- /dev/null +++ b/src/commands/privacy-settings/privacy-settings.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; +import { type GroveDecision, GroveDialog, PrivacySettingsDialog } from '../../components/grove/Grove.js'; +import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from '../../services/analytics/index.js'; +import { getGroveNoticeConfig, getGroveSettings, isQualifiedForGrove } from '../../services/api/grove.js'; +import type { LocalJSXCommandOnDone } from '../../types/command.js'; +const FALLBACK_MESSAGE = 'Review and manage your privacy settings at https://claude.ai/settings/data-privacy-controls'; +export async function call(onDone: LocalJSXCommandOnDone): Promise { + const qualified = await isQualifiedForGrove(); + if (!qualified) { + onDone(FALLBACK_MESSAGE); + return null; + } + const [settingsResult, configResult] = await Promise.all([getGroveSettings(), getGroveNoticeConfig()]); + // Hide dialog on API failure (after retry) + if (!settingsResult.success) { + onDone(FALLBACK_MESSAGE); + return null; + } + const settings = settingsResult.data; + const config = configResult.success ? configResult.data : null; + async function onDoneWithDecision(decision: GroveDecision) { + if (decision === 'escape' || decision === 'defer') { + onDone('Privacy settings dialog dismissed', { + display: 'system' + }); + return; + } + await onDoneWithSettingsCheck(); + } + async function onDoneWithSettingsCheck() { + const updatedSettingsResult = await getGroveSettings(); + if (!updatedSettingsResult.success) { + onDone('Unable to retrieve updated privacy settings', { + display: 'system' + }); + return; + } + const updatedSettings = updatedSettingsResult.data; + const groveStatus = updatedSettings.grove_enabled ? 'true' : 'false'; + onDone(`"Help improve Claude" set to ${groveStatus}.`); + if (settings.grove_enabled !== null && settings.grove_enabled !== updatedSettings.grove_enabled) { + logEvent('tengu_grove_policy_toggled', { + state: updatedSettings.grove_enabled as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + location: 'settings' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS + }); + } + } + + // Show privacy settings directly if the user has already accepted the + // terms. + if (settings.grove_enabled !== null) { + return ; + } + + // Show the GroveDialog for users who haven't accepted terms yet + return ; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIkdyb3ZlRGVjaXNpb24iLCJHcm92ZURpYWxvZyIsIlByaXZhY3lTZXR0aW5nc0RpYWxvZyIsIkFuYWx5dGljc01ldGFkYXRhX0lfVkVSSUZJRURfVEhJU19JU19OT1RfQ09ERV9PUl9GSUxFUEFUSFMiLCJsb2dFdmVudCIsImdldEdyb3ZlTm90aWNlQ29uZmlnIiwiZ2V0R3JvdmVTZXR0aW5ncyIsImlzUXVhbGlmaWVkRm9yR3JvdmUiLCJMb2NhbEpTWENvbW1hbmRPbkRvbmUiLCJGQUxMQkFDS19NRVNTQUdFIiwiY2FsbCIsIm9uRG9uZSIsIlByb21pc2UiLCJSZWFjdE5vZGUiLCJxdWFsaWZpZWQiLCJzZXR0aW5nc1Jlc3VsdCIsImNvbmZpZ1Jlc3VsdCIsImFsbCIsInN1Y2Nlc3MiLCJzZXR0aW5ncyIsImRhdGEiLCJjb25maWciLCJvbkRvbmVXaXRoRGVjaXNpb24iLCJkZWNpc2lvbiIsImRpc3BsYXkiLCJvbkRvbmVXaXRoU2V0dGluZ3NDaGVjayIsInVwZGF0ZWRTZXR0aW5nc1Jlc3VsdCIsInVwZGF0ZWRTZXR0aW5ncyIsImdyb3ZlU3RhdHVzIiwiZ3JvdmVfZW5hYmxlZCIsInN0YXRlIiwibG9jYXRpb24iLCJkb21haW5fZXhjbHVkZWQiXSwic291cmNlcyI6WyJwcml2YWN5LXNldHRpbmdzLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7XG4gIHR5cGUgR3JvdmVEZWNpc2lvbixcbiAgR3JvdmVEaWFsb2csXG4gIFByaXZhY3lTZXR0aW5nc0RpYWxvZyxcbn0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9ncm92ZS9Hcm92ZS5qcydcbmltcG9ydCB7XG4gIHR5cGUgQW5hbHl0aWNzTWV0YWRhdGFfSV9WRVJJRklFRF9USElTX0lTX05PVF9DT0RFX09SX0ZJTEVQQVRIUyxcbiAgbG9nRXZlbnQsXG59IGZyb20gJy4uLy4uL3NlcnZpY2VzL2FuYWx5dGljcy9pbmRleC5qcydcbmltcG9ydCB7XG4gIGdldEdyb3ZlTm90aWNlQ29uZmlnLFxuICBnZXRHcm92ZVNldHRpbmdzLFxuICBpc1F1YWxpZmllZEZvckdyb3ZlLFxufSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9hcGkvZ3JvdmUuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5cbmNvbnN0IEZBTExCQUNLX01FU1NBR0UgPVxuICAnUmV2aWV3IGFuZCBtYW5hZ2UgeW91ciBwcml2YWN5IHNldHRpbmdzIGF0IGh0dHBzOi8vY2xhdWRlLmFpL3NldHRpbmdzL2RhdGEtcHJpdmFjeS1jb250cm9scydcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNhbGwoXG4gIG9uRG9uZTogTG9jYWxKU1hDb21tYW5kT25Eb25lLFxuKTogUHJvbWlzZTxSZWFjdC5SZWFjdE5vZGUgfCBudWxsPiB7XG4gIGNvbnN0IHF1YWxpZmllZCA9IGF3YWl0IGlzUXVhbGlmaWVkRm9yR3JvdmUoKVxuICBpZiAoIXF1YWxpZmllZCkge1xuICAgIG9uRG9uZShGQUxMQkFDS19NRVNTQUdFKVxuICAgIHJldHVybiBudWxsXG4gIH1cblxuICBjb25zdCBbc2V0dGluZ3NSZXN1bHQsIGNvbmZpZ1Jlc3VsdF0gPSBhd2FpdCBQcm9taXNlLmFsbChbXG4gICAgZ2V0R3JvdmVTZXR0aW5ncygpLFxuICAgIGdldEdyb3ZlTm90aWNlQ29uZmlnKCksXG4gIF0pXG4gIC8vIEhpZGUgZGlhbG9nIG9uIEFQSSBmYWlsdXJlIChhZnRlciByZXRyeSlcbiAgaWYgKCFzZXR0aW5nc1Jlc3VsdC5zdWNjZXNzKSB7XG4gICAgb25Eb25lKEZBTExCQUNLX01FU1NBR0UpXG4gICAgcmV0dXJuIG51bGxcbiAgfVxuICBjb25zdCBzZXR0aW5ncyA9IHNldHRpbmdzUmVzdWx0LmRhdGFcbiAgY29uc3QgY29uZmlnID0gY29uZmlnUmVzdWx0LnN1Y2Nlc3MgPyBjb25maWdSZXN1bHQuZGF0YSA6IG51bGxcblxuICBhc3luYyBmdW5jdGlvbiBvbkRvbmVXaXRoRGVjaXNpb24oZGVjaXNpb246IEdyb3ZlRGVjaXNpb24pIHtcbiAgICBpZiAoZGVjaXNpb24gPT09ICdlc2NhcGUnIHx8IGRlY2lzaW9uID09PSAnZGVmZXInKSB7XG4gICAgICBvbkRvbmUoJ1ByaXZhY3kgc2V0dGluZ3MgZGlhbG9nIGRpc21pc3NlZCcsIHtcbiAgICAgICAgZGlzcGxheTogJ3N5c3RlbScsXG4gICAgICB9KVxuICAgICAgcmV0dXJuXG4gICAgfVxuICAgIGF3YWl0IG9uRG9uZVdpdGhTZXR0aW5nc0NoZWNrKClcbiAgfVxuXG4gIGFzeW5jIGZ1bmN0aW9uIG9uRG9uZVdpdGhTZXR0aW5nc0NoZWNrKCkge1xuICAgIGNvbnN0IHVwZGF0ZWRTZXR0aW5nc1Jlc3VsdCA9IGF3YWl0IGdldEdyb3ZlU2V0dGluZ3MoKVxuICAgIGlmICghdXBkYXRlZFNldHRpbmdzUmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgIG9uRG9uZSgnVW5hYmxlIHRvIHJldHJpZXZlIHVwZGF0ZWQgcHJpdmFjeSBzZXR0aW5ncycsIHtcbiAgICAgICAgZGlzcGxheTogJ3N5c3RlbScsXG4gICAgICB9KVxuICAgICAgcmV0dXJuXG4gICAgfVxuICAgIGNvbnN0IHVwZGF0ZWRTZXR0aW5ncyA9IHVwZGF0ZWRTZXR0aW5nc1Jlc3VsdC5kYXRhXG4gICAgY29uc3QgZ3JvdmVTdGF0dXMgPSB1cGRhdGVkU2V0dGluZ3MuZ3JvdmVfZW5hYmxlZCA/ICd0cnVlJyA6ICdmYWxzZSdcbiAgICBvbkRvbmUoYFwiSGVscCBpbXByb3ZlIENsYXVkZVwiIHNldCB0byAke2dyb3ZlU3RhdHVzfS5gKVxuICAgIGlmIChcbiAgICAgIHNldHRpbmdzLmdyb3ZlX2VuYWJsZWQgIT09IG51bGwgJiZcbiAgICAgIHNldHRpbmdzLmdyb3ZlX2VuYWJsZWQgIT09IHVwZGF0ZWRTZXR0aW5ncy5ncm92ZV9lbmFibGVkXG4gICAgKSB7XG4gICAgICBsb2dFdmVudCgndGVuZ3VfZ3JvdmVfcG9saWN5X3RvZ2dsZWQnLCB7XG4gICAgICAgIHN0YXRlOlxuICAgICAgICAgIHVwZGF0ZWRTZXR0aW5ncy5ncm92ZV9lbmFibGVkIGFzIEFuYWx5dGljc01ldGFkYXRhX0lfVkVSSUZJRURfVEhJU19JU19OT1RfQ09ERV9PUl9GSUxFUEFUSFMsXG4gICAgICAgIGxvY2F0aW9uOlxuICAgICAgICAgICdzZXR0aW5ncycgYXMgQW5hbHl0aWNzTWV0YWRhdGFfSV9WRVJJRklFRF9USElTX0lTX05PVF9DT0RFX09SX0ZJTEVQQVRIUyxcbiAgICAgIH0pXG4gICAgfVxuICB9XG5cbiAgLy8gU2hvdyBwcml2YWN5IHNldHRpbmdzIGRpcmVjdGx5IGlmIHRoZSB1c2VyIGhhcyBhbHJlYWR5IGFjY2VwdGVkIHRoZVxuICAvLyB0ZXJtcy5cbiAgaWYgKHNldHRpbmdzLmdyb3ZlX2VuYWJsZWQgIT09IG51bGwpIHtcbiAgICByZXR1cm4gKFxuICAgICAgPFByaXZhY3lTZXR0aW5nc0RpYWxvZ1xuICAgICAgICBzZXR0aW5ncz17c2V0dGluZ3N9XG4gICAgICAgIGRvbWFpbkV4Y2x1ZGVkPXtjb25maWc/LmRvbWFpbl9leGNsdWRlZH1cbiAgICAgICAgb25Eb25lPXtvbkRvbmVXaXRoU2V0dGluZ3NDaGVja31cbiAgICAgID48L1ByaXZhY3lTZXR0aW5nc0RpYWxvZz5cbiAgICApXG4gIH1cblxuICAvLyBTaG93IHRoZSBHcm92ZURpYWxvZyBmb3IgdXNlcnMgd2hvIGhhdmVuJ3QgYWNjZXB0ZWQgdGVybXMgeWV0XG4gIHJldHVybiAoXG4gICAgPEdyb3ZlRGlhbG9nXG4gICAgICBzaG93SWZBbHJlYWR5Vmlld2VkPXt0cnVlfVxuICAgICAgb25Eb25lPXtvbkRvbmVXaXRoRGVjaXNpb259XG4gICAgICBsb2NhdGlvbj17J3NldHRpbmdzJ31cbiAgICAvPlxuICApXG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FDRSxLQUFLQyxhQUFhLEVBQ2xCQyxXQUFX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 \ No newline at end of file diff --git a/src/commands/rate-limit-options/index.ts b/src/commands/rate-limit-options/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..671b440a14c1012fd368806cd1dc90093494d4b4 --- /dev/null +++ b/src/commands/rate-limit-options/index.ts @@ -0,0 +1,19 @@ +import type { Command } from '../../commands.js' +import { isClaudeAISubscriber } from '../../utils/auth.js' + +const rateLimitOptions = { + type: 'local-jsx', + name: 'rate-limit-options', + description: 'Show options when rate limit is reached', + isEnabled: () => { + if (!isClaudeAISubscriber()) { + return false + } + + return true + }, + isHidden: true, // Hidden from help - only used internally + load: () => import('./rate-limit-options.js'), +} satisfies Command + +export default rateLimitOptions diff --git a/src/commands/rate-limit-options/rate-limit-options.tsx b/src/commands/rate-limit-options/rate-limit-options.tsx new file mode 100644 index 0000000000000000000000000000000000000000..296ca4b8ef077d019bbf814b4c56b04bcabb0537 --- /dev/null +++ b/src/commands/rate-limit-options/rate-limit-options.tsx @@ -0,0 +1,210 @@ +import { c as _c } from "react/compiler-runtime"; +import React, { useMemo, useState } from 'react'; +import type { CommandResultDisplay, LocalJSXCommandContext } from '../../commands.js'; +import { type OptionWithDescription, Select } from '../../components/CustomSelect/select.js'; +import { Dialog } from '../../components/design-system/Dialog.js'; +import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js'; +import { logEvent } from '../../services/analytics/index.js'; +import { useClaudeAiLimits } from '../../services/claudeAiLimitsHook.js'; +import type { ToolUseContext } from '../../Tool.js'; +import type { LocalJSXCommandOnDone } from '../../types/command.js'; +import { getOauthAccountInfo, getRateLimitTier, getSubscriptionType } from '../../utils/auth.js'; +import { hasClaudeAiBillingAccess } from '../../utils/billing.js'; +import { call as extraUsageCall } from '../extra-usage/extra-usage.js'; +import { extraUsage } from '../extra-usage/index.js'; +import upgrade from '../upgrade/index.js'; +import { call as upgradeCall } from '../upgrade/upgrade.js'; +type RateLimitOptionsMenuOptionType = 'upgrade' | 'extra-usage' | 'cancel'; +type RateLimitOptionsMenuProps = { + onDone: (result?: string, options?: { + display?: CommandResultDisplay | undefined; + } | undefined) => void; + context: ToolUseContext & LocalJSXCommandContext; +}; +function RateLimitOptionsMenu(t0) { + const $ = _c(25); + const { + onDone, + context + } = t0; + const [subCommandJSX, setSubCommandJSX] = useState(null); + const claudeAiLimits = useClaudeAiLimits(); + let t1; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t1 = getSubscriptionType(); + $[0] = t1; + } else { + t1 = $[0]; + } + const subscriptionType = t1; + let t2; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t2 = getRateLimitTier(); + $[1] = t2; + } else { + t2 = $[1]; + } + const rateLimitTier = t2; + const hasExtraUsageEnabled = getOauthAccountInfo()?.hasExtraUsageEnabled === true; + const isMax = subscriptionType === "max"; + const isMax20x = isMax && rateLimitTier === "default_claude_max_20x"; + const isTeamOrEnterprise = subscriptionType === "team" || subscriptionType === "enterprise"; + const buyFirst = getFeatureValue_CACHED_MAY_BE_STALE("tengu_jade_anvil_4", false); + let t3; + bb0: { + let actionOptions; + if ($[2] !== claudeAiLimits.overageDisabledReason || $[3] !== claudeAiLimits.overageStatus) { + actionOptions = []; + if (extraUsage.isEnabled()) { + const hasBillingAccess = hasClaudeAiBillingAccess(); + const needsToRequestFromAdmin = isTeamOrEnterprise && !hasBillingAccess; + const isOrgSpendCapDepleted = claudeAiLimits.overageDisabledReason === "out_of_credits" || claudeAiLimits.overageDisabledReason === "org_level_disabled_until" || claudeAiLimits.overageDisabledReason === "org_service_zero_credit_limit"; + if (needsToRequestFromAdmin && isOrgSpendCapDepleted) {} else { + const isOverageState = claudeAiLimits.overageStatus === "rejected" || claudeAiLimits.overageStatus === "allowed_warning"; + let label; + if (needsToRequestFromAdmin) { + label = isOverageState ? "Request more" : "Request extra usage"; + } else { + label = hasExtraUsageEnabled ? "Add funds to continue with extra usage" : "Switch to extra usage"; + } + let t4; + if ($[5] !== label) { + t4 = { + label, + value: "extra-usage" + }; + $[5] = label; + $[6] = t4; + } else { + t4 = $[6]; + } + actionOptions.push(t4); + } + } + if (!isMax20x && !isTeamOrEnterprise && upgrade.isEnabled()) { + let t4; + if ($[7] === Symbol.for("react.memo_cache_sentinel")) { + t4 = { + label: "Upgrade your plan", + value: "upgrade" + }; + $[7] = t4; + } else { + t4 = $[7]; + } + actionOptions.push(t4); + } + $[2] = claudeAiLimits.overageDisabledReason; + $[3] = claudeAiLimits.overageStatus; + $[4] = actionOptions; + } else { + actionOptions = $[4]; + } + let t4; + if ($[8] === Symbol.for("react.memo_cache_sentinel")) { + t4 = { + label: "Stop and wait for limit to reset", + value: "cancel" + }; + $[8] = t4; + } else { + t4 = $[8]; + } + const cancelOption = t4; + if (buyFirst) { + let t5; + if ($[9] !== actionOptions) { + t5 = [...actionOptions, cancelOption]; + $[9] = actionOptions; + $[10] = t5; + } else { + t5 = $[10]; + } + t3 = t5; + break bb0; + } + let t5; + if ($[11] !== actionOptions) { + t5 = [cancelOption, ...actionOptions]; + $[11] = actionOptions; + $[12] = t5; + } else { + t5 = $[12]; + } + t3 = t5; + } + const options = t3; + let t4; + if ($[13] !== onDone) { + t4 = function handleCancel() { + logEvent("tengu_rate_limit_options_menu_cancel", {}); + onDone(undefined, { + display: "skip" + }); + }; + $[13] = onDone; + $[14] = t4; + } else { + t4 = $[14]; + } + const handleCancel = t4; + let t5; + if ($[15] !== context || $[16] !== handleCancel || $[17] !== onDone) { + t5 = function handleSelect(value) { + if (value === "upgrade") { + logEvent("tengu_rate_limit_options_menu_select_upgrade", {}); + upgradeCall(onDone, context).then(jsx => { + if (jsx) { + setSubCommandJSX(jsx); + } + }); + } else { + if (value === "extra-usage") { + logEvent("tengu_rate_limit_options_menu_select_extra_usage", {}); + extraUsageCall(onDone, context).then(jsx_0 => { + if (jsx_0) { + setSubCommandJSX(jsx_0); + } + }); + } else { + if (value === "cancel") { + handleCancel(); + } + } + } + }; + $[15] = context; + $[16] = handleCancel; + $[17] = onDone; + $[18] = t5; + } else { + t5 = $[18]; + } + const handleSelect = t5; + if (subCommandJSX) { + return subCommandJSX; + } + let t6; + if ($[19] !== handleSelect || $[20] !== options) { + t6 = { + if (value === 'send') { + void handleConfirm(token); + } else { + handleCancel(); + } + }} onCancel={handleCancel} /> + ; +} +export async function call(onDone: LocalJSXCommandOnDone): Promise { + return ; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJleGVjYSIsIlJlYWN0IiwidXNlRWZmZWN0IiwidXNlU3RhdGUiLCJTZWxlY3QiLCJEaWFsb2ciLCJMb2FkaW5nU3RhdGUiLCJCb3giLCJUZXh0IiwibG9nRXZlbnQiLCJBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTIiwiU2FmZVN0cmluZyIsIkxvY2FsSlNYQ29tbWFuZE9uRG9uZSIsIm9wZW5Ccm93c2VyIiwiZ2V0R2hBdXRoU3RhdHVzIiwiY3JlYXRlRGVmYXVsdEVudmlyb25tZW50IiwiZ2V0Q29kZVdlYlVybCIsIkltcG9ydFRva2VuRXJyb3IiLCJpbXBvcnRHaXRodWJUb2tlbiIsImlzU2lnbmVkSW4iLCJSZWRhY3RlZEdpdGh1YlRva2VuIiwiQ2hlY2tSZXN1bHQiLCJzdGF0dXMiLCJ0b2tlbiIsImNoZWNrTG9naW5TdGF0ZSIsIlByb21pc2UiLCJnaFN0YXR1cyIsInN0ZG91dCIsInN0ZGVyciIsInRpbWVvdXQiLCJyZWplY3QiLCJ0cmltbWVkIiwidHJpbSIsImVycm9yTWVzc2FnZSIsImVyciIsImNvZGVVcmwiLCJraW5kIiwiU3RlcCIsIm5hbWUiLCJXZWIiLCJvbkRvbmUiLCJzdGVwIiwic2V0U3RlcCIsInRoZW4iLCJyZXN1bHQiLCJ1cmwiLCJoYW5kbGVDYW5jZWwiLCJoYW5kbGVDb25maXJtIiwib2siLCJlcnJvcl9raW5kIiwiZXJyb3IiLCJnaXRodWJfdXNlcm5hbWUiLCJsYWJlbCIsInZhbHVlIiwiY2FsbCIsIlJlYWN0Tm9kZSJdLCJzb3VyY2VzIjpbInJlbW90ZS1zZXR1cC50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgZXhlY2EgfSBmcm9tICdleGVjYSdcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgdXNlRWZmZWN0LCB1c2VTdGF0ZSB9IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgU2VsZWN0IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9DdXN0b21TZWxlY3QvaW5kZXguanMnXG5pbXBvcnQgeyBEaWFsb2cgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL2Rlc2lnbi1zeXN0ZW0vRGlhbG9nLmpzJ1xuaW1wb3J0IHsgTG9hZGluZ1N0YXRlIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL0xvYWRpbmdTdGF0ZS5qcydcbmltcG9ydCB7IEJveCwgVGV4dCB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB7XG4gIGxvZ0V2ZW50LFxuICB0eXBlIEFuYWx5dGljc01ldGFkYXRhX0lfVkVSSUZJRURfVEhJU19JU19OT1RfQ09ERV9PUl9GSUxFUEFUSFMgYXMgU2FmZVN0cmluZyxcbn0gZnJvbSAnLi4vLi4vc2VydmljZXMvYW5hbHl0aWNzL2luZGV4LmpzJ1xuaW1wb3J0IHR5cGUgeyBMb2NhbEpTWENvbW1hbmRPbkRvbmUgfSBmcm9tICcuLi8uLi90eXBlcy9jb21tYW5kLmpzJ1xuaW1wb3J0IHsgb3BlbkJyb3dzZXIgfSBmcm9tICcuLi8uLi91dGlscy9icm93c2VyLmpzJ1xuaW1wb3J0IHsgZ2V0R2hBdXRoU3RhdHVzIH0gZnJvbSAnLi4vLi4vdXRpbHMvZ2l0aHViL2doQXV0aFN0YXR1cy5qcydcbmltcG9ydCB7XG4gIGNyZWF0ZURlZmF1bHRFbnZpcm9ubWVudCxcbiAgZ2V0Q29kZVdlYlVybCxcbiAgdHlwZSBJbXBvcnRUb2tlbkVycm9yLFxuICBpbXBvcnRHaXRodWJUb2tlbixcbiAgaXNTaWduZWRJbixcbiAgUmVkYWN0ZWRHaXRodWJUb2tlbixcbn0gZnJvbSAnLi9hcGkuanMnXG5cbnR5cGUgQ2hlY2tSZXN1bHQgPVxuICB8IHsgc3RhdHVzOiAnbm90X3NpZ25lZF9pbicgfVxuICB8IHsgc3RhdHVzOiAnaGFzX2doX3Rva2VuJzsgdG9rZW46IFJlZGFjdGVkR2l0aHViVG9rZW4gfVxuICB8IHsgc3RhdHVzOiAnZ2hfbm90X2luc3RhbGxlZCcgfVxuICB8IHsgc3RhdHVzOiAnZ2hfbm90X2F1dGhlbnRpY2F0ZWQnIH1cblxuYXN5bmMgZnVuY3Rpb24gY2hlY2tMb2dpblN0YXRlKCk6IFByb21pc2U8Q2hlY2tSZXN1bHQ+IHtcbiAgaWYgKCEoYXdhaXQgaXNTaWduZWRJbigpKSkge1xuICAgIHJldHVybiB7IHN0YXR1czogJ25vdF9zaWduZWRfaW4nIH1cbiAgfVxuXG4gIGNvbnN0IGdoU3RhdHVzID0gYXdhaXQgZ2V0R2hBdXRoU3RhdHVzKClcbiAgaWYgKGdoU3RhdHVzID09PSAnbm90X2luc3RhbGxlZCcpIHtcbiAgICByZXR1cm4geyBzdGF0dXM6ICdnaF9ub3RfaW5zdGFsbGVkJyB9XG4gIH1cbiAgaWYgKGdoU3RhdHVzID09PSAnbm90X2F1dGhlbnRpY2F0ZWQnKSB7XG4gICAgcmV0dXJuIHsgc3RhdHVzOiAnZ2hfbm90X2F1dGhlbnRpY2F0ZWQnIH1cbiAgfVxuXG4gIC8vIGdoU3RhdHVzID09PSAnYXV0aGVudGljYXRlZCcuIGdldEdoQXV0aFN0YXR1cyBzcGF3bnMgd2l0aCBzdGRvdXQ6J2lnbm9yZSdcbiAgLy8gKHRlbGVtZXRyeS1zYWZlKTsgc3Bhd24gb25jZSBtb3JlIHdpdGggc3Rkb3V0OidwaXBlJyB0byByZWFkIHRoZSB0b2tlbi5cbiAgY29uc3QgeyBzdGRvdXQgfSA9IGF3YWl0IGV4ZWNhKCdnaCcsIFsnYXV0aCcsICd0b2tlbiddLCB7XG4gICAgc3Rkb3V0OiAncGlwZScsXG4gICAgc3RkZXJyOiAnaWdub3JlJyxcbiAgICB0aW1lb3V0OiA1MDAwLFxuICAgIHJlamVjdDogZmFsc2UsXG4gIH0pXG4gIGNvbnN0IHRyaW1tZWQgPSBzdGRvdXQudHJpbSgpXG4gIGlmICghdHJpbW1lZCkge1xuICAgIHJldHVybiB7IHN0YXR1czogJ2doX25vdF9hdXRoZW50aWNhdGVkJyB9XG4gIH1cbiAgcmV0dXJuIHsgc3RhdHVzOiAnaGFzX2doX3Rva2VuJywgdG9rZW46IG5ldyBSZWRhY3RlZEdpdGh1YlRva2VuKHRyaW1tZWQpIH1cbn1cblxuZnVuY3Rpb24gZXJyb3JNZXNzYWdlKGVycjogSW1wb3J0VG9rZW5FcnJvciwgY29kZVVybDogc3RyaW5nKTogc3RyaW5nIHtcbiAgc3dpdGNoIChlcnIua2luZCkge1xuICAgIGNhc2UgJ25vdF9zaWduZWRfaW4nOlxuICAgICAgcmV0dXJuIGBMb2dpbiBmYWlsZWQuIFBsZWFzZSB2aXNpdCAke2NvZGVVcmx9IGFuZCBsb2dpbiB1c2luZyB0aGUgR2l0SHViIEFwcGBcbiAgICBjYXNlICdpbnZhbGlkX3Rva2VuJzpcbiAgICAgIHJldHVybiAnR2l0SHViIHJlamVjdGVkIHRoYXQgdG9rZW4uIFJ1biBgZ2ggYXV0aCBsb2dpbmAgYW5kIHRyeSBhZ2Fpbi4nXG4gICAgY2FzZSAnc2VydmVyJzpcbiAgICAgIHJldHVybiBgU2VydmVyIGVycm9yICgke2Vyci5zdGF0dXN9KS4gVHJ5IGFnYWluIGluIGEgbW9tZW50LmBcbiAgICBjYXNlICduZXR3b3JrJzpcbiAgICAgIHJldHVybiBcIkNvdWxkbid0IHJlYWNoIHRoZSBzZXJ2ZXIuIENoZWNrIHlvdXIgY29ubmVjdGlvbi5cIlxuICB9XG59XG5cbnR5cGUgU3RlcCA9XG4gIHwgeyBuYW1lOiAnY2hlY2tpbmcnIH1cbiAgfCB7IG5hbWU6ICdjb25maXJtJzsgdG9rZW46IFJlZGFjdGVkR2l0aHViVG9rZW4gfVxuICB8IHsgbmFtZTogJ3VwbG9hZGluZycgfVxuXG5mdW5jdGlvbiBXZWIoeyBvbkRvbmUgfTogeyBvbkRvbmU6IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9KSB7XG4gIGNvbnN0IFtzdGVwLCBzZXRTdGVwXSA9IHVzZVN0YXRlPFN0ZXA+KHsgbmFtZTogJ2NoZWNraW5nJyB9KVxuXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgbG9nRXZlbnQoJ3Rlbmd1X3JlbW90ZV9zZXR1cF9zdGFydGVkJywge30pXG4gICAgdm9pZCBjaGVja0xvZ2luU3RhdGUoKS50aGVuKGFzeW5jIHJlc3VsdCA9PiB7XG4gICAgICBzd2l0Y2ggKHJlc3VsdC5zdGF0dXMpIHtcbiAgICAgICAgY2FzZSAnbm90X3NpZ25lZF9pbic6XG4gICAgICAgICAgbG9nRXZlbnQoJ3Rlbmd1X3JlbW90ZV9zZXR1cF9yZXN1bHQnLCB7XG4gICAgICAgICAgICByZXN1bHQ6ICdub3Rfc2lnbmVkX2luJyBhcyBTYWZlU3RyaW5nLFxuICAgICAgICAgIH0pXG4gICAgICAgICAgb25Eb25lKCdOb3Qgc2lnbmVkIGluIHRvIENsYXVkZS4gUnVuIC9sb2dpbiBmaXJzdC4nKVxuICAgICAgICAgIHJldHVyblxuICAgICAgICBjYXNlICdnaF9ub3RfaW5zdGFsbGVkJzpcbiAgICAgICAgY2FzZSAnZ2hfbm90X2F1dGhlbnRpY2F0ZWQnOiB7XG4gICAgICAgICAgY29uc3QgdXJsID0gYCR7Z2V0Q29kZVdlYlVybCgpfS9vbmJvYXJkaW5nP3N0ZXA9YWx0LWF1dGhgXG4gICAgICAgICAgYXdhaXQgb3BlbkJyb3dzZXIodXJsKVxuICAgICAgICAgIGxvZ0V2ZW50KCd0ZW5ndV9yZW1vdGVfc2V0dXBfcmVzdWx0Jywge1xuICAgICAgICAgICAgcmVzdWx0OiByZXN1bHQuc3RhdHVzIGFzIFNhZmVTdHJpbmcsXG4gICAgICAgICAgfSlcbiAgICAgICAgICBvbkRvbmUoXG4gICAgICAgICAgICByZXN1bHQuc3RhdHVzID09PSAnZ2hfbm90X2luc3RhbGxlZCdcbiAgICAgICAgICAgICAgPyBgR2l0SHViIENMSSBub3QgZm91bmQuIEluc3RhbGwgaXQgdmlhIGh0dHBzOi8vY2xpLmdpdGh1Yi5jb20vLCB0aGVuIHJ1biBcXGBnaCBhdXRoIGxvZ2luXFxgLCBvciBjb25uZWN0IEdpdEh1YiBvbiB0aGUgd2ViOiAke3VybH1gXG4gICAgICAgICAgICAgIDogYEdpdEh1YiBDTEkgbm90IGF1dGhlbnRpY2F0ZWQuIFJ1biBcXGBnaCBhdXRoIGxvZ2luXFxgIGFuZCB0cnkgYWdhaW4sIG9yIGNvbm5lY3QgR2l0SHViIG9uIHRoZSB3ZWI6ICR7dXJsfWAsXG4gICAgICAgICAgKVxuICAgICAgICAgIHJldHVyblxuICAgICAgICB9XG4gICAgICAgIGNhc2UgJ2hhc19naF90b2tlbic6XG4gICAgICAgICAgc2V0U3RlcCh7IG5hbWU6ICdjb25maXJtJywgdG9rZW46IHJlc3VsdC50b2tlbiB9KVxuICAgICAgfVxuICAgIH0pXG4gICAgLy8gb25Eb25lIGlzIHN0YWJsZSBhY3Jvc3MgcmVuZGVyczsgaW50ZW50aW9uYWxseSBub3QgaW4gZGVwcy5cbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgcmVhY3QtaG9va3MvZXhoYXVzdGl2ZS1kZXBzXG4gIH0sIFtdKVxuXG4gIGNvbnN0IGhhbmRsZUNhbmNlbCA9ICgpID0+IHtcbiAgICBsb2dFdmVudCgndGVuZ3VfcmVtb3RlX3NldHVwX3Jlc3VsdCcsIHtcbiAgICAgIHJlc3VsdDogJ2NhbmNlbGxlZCcgYXMgU2FmZVN0cmluZyxcbiAgICB9KVxuICAgIG9uRG9uZSgpXG4gIH1cblxuICBjb25zdCBoYW5kbGVDb25maXJtID0gYXN5bmMgKHRva2VuOiBSZWRhY3RlZEdpdGh1YlRva2VuKSA9PiB7XG4gICAgc2V0U3RlcCh7IG5hbWU6ICd1cGxvYWRpbmcnIH0pXG5cbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBpbXBvcnRHaXRodWJUb2tlbih0b2tlbilcbiAgICBpZiAoIXJlc3VsdC5vaykge1xuICAgICAgbG9nRXZlbnQoJ3Rlbmd1X3JlbW90ZV9zZXR1cF9yZXN1bHQnLCB7XG4gICAgICAgIHJlc3VsdDogJ2ltcG9ydF9mYWlsZWQnIGFzIFNhZmVTdHJpbmcsXG4gICAgICAgIGVycm9yX2tpbmQ6IHJlc3VsdC5lcnJvci5raW5kIGFzIFNhZmVTdHJpbmcsXG4gICAgICB9KVxuICAgICAgb25Eb25lKGVycm9yTWVzc2FnZShyZXN1bHQuZXJyb3IsIGdldENvZGVXZWJVcmwoKSkpXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICAvLyBUb2tlbiBpbXBvcnQgc3VjY2VlZGVkLiBFbnZpcm9ubWVudCBjcmVhdGlvbiBpcyBiZXN0LWVmZm9ydCDigJQgaWYgaXRcbiAgICAvLyBmYWlscywgdGhlIHdlYiBzdGF0ZSBtYWNoaW5lIHJvdXRlcyB0byBlbnYtc2V0dXAgb24gbGFuZGluZywgd2hpY2ggaXNcbiAgICAvLyBvbmUgZXh0cmEgY2xpY2sgYnV0IHN0aWxsIGJldHRlciB0aGFuIHRoZSBPQXV0aCBkYW5jZS5cbiAgICBhd2FpdCBjcmVhdGVEZWZhdWx0RW52aXJvbm1lbnQoKVxuXG4gICAgY29uc3QgdXJsID0gZ2V0Q29kZVdlYlVybCgpXG4gICAgYXdhaXQgb3BlbkJyb3dzZXIodXJsKVxuXG4gICAgbG9nRXZlbnQoJ3Rlbmd1X3JlbW90ZV9zZXR1cF9yZXN1bHQnLCB7XG4gICAgICByZXN1bHQ6ICdzdWNjZXNzJyBhcyBTYWZlU3RyaW5nLFxuICAgIH0pXG4gICAgb25Eb25lKGBDb25uZWN0ZWQgYXMgJHtyZXN1bHQucmVzdWx0LmdpdGh1Yl91c2VybmFtZX0uIE9wZW5lZCAke3VybH1gKVxuICB9XG5cbiAgaWYgKHN0ZXAubmFtZSA9PT0gJ2NoZWNraW5nJykge1xuICAgIHJldHVybiA8TG9hZGluZ1N0YXRlIG1lc3NhZ2U9XCJDaGVja2luZyBsb2dpbiBzdGF0dXPigKZcIiAvPlxuICB9XG5cbiAgaWYgKHN0ZXAubmFtZSA9PT0gJ3VwbG9hZGluZycpIHtcbiAgICByZXR1cm4gPExvYWRpbmdTdGF0ZSBtZXNzYWdlPVwiQ29ubmVjdGluZyBHaXRIdWIgdG8gQ2xhdWRl4oCmXCIgLz5cbiAgfVxuXG4gIGNvbnN0IHRva2VuID0gc3RlcC50b2tlblxuICByZXR1cm4gKFxuICAgIDxEaWFsb2dcbiAgICAgIHRpdGxlPVwiQ29ubmVjdCBDbGF1ZGUgb24gdGhlIHdlYiB0byBHaXRIdWI/XCJcbiAgICAgIG9uQ2FuY2VsPXtoYW5kbGVDYW5jZWx9XG4gICAgICBoaWRlSW5wdXRHdWlkZVxuICAgID5cbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8VGV4dD5cbiAgICAgICAgICBDbGF1ZGUgb24gdGhlIHdlYiByZXF1aXJlcyBjb25uZWN0aW5nIHRvIHlvdXIgR2l0SHViIGFjY291bnQgdG8gY2xvbmVcbiAgICAgICAgICBhbmQgcHVzaCBjb2RlIG9uIHlvdXIgYmVoYWxmLlxuICAgICAgICA8L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgIFlvdXIgbG9jYWwgY3JlZGVudGlhbHMgYXJlIHVzZWQgdG8gYXV0aGVudGljYXRlIHdpdGggR2l0SHViXG4gICAgICAgIDwvVGV4dD5cbiAgICAgIDwvQm94PlxuICAgICAgPFNlbGVjdFxuICAgICAgICBvcHRpb25zPXtbXG4gICAgICAgICAgeyBsYWJlbDogJ0NvbnRpbnVlJywgdmFsdWU6ICdzZW5kJyB9LFxuICAgICAgICAgIHsgbGFiZWw6ICdDYW5jZWwnLCB2YWx1ZTogJ2NhbmNlbCcgfSxcbiAgICAgICAgXX1cbiAgICAgICAgb25DaGFuZ2U9e3ZhbHVlID0+IHtcbiAgICAgICAgICBpZiAodmFsdWUgPT09ICdzZW5kJykge1xuICAgICAgICAgICAgdm9pZCBoYW5kbGVDb25maXJtKHRva2VuKVxuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBoYW5kbGVDYW5jZWwoKVxuICAgICAgICAgIH1cbiAgICAgICAgfX1cbiAgICAgICAgb25DYW5jZWw9e2hhbmRsZUNhbmNlbH1cbiAgICAgIC8+XG4gICAgPC9EaWFsb2c+XG4gIClcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNhbGwoXG4gIG9uRG9uZTogTG9jYWxKU1hDb21tYW5kT25Eb25lLFxuKTogUHJvbWlzZTxSZWFjdC5SZWFjdE5vZGU+IHtcbiAgcmV0dXJuIDxXZWIgb25Eb25lPXtvbkRvbmV9IC8+XG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLFNBQVNBLEtBQUssUUFBUSxPQUFPO0FBQzdCLE9BQU8sS0FBS0MsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0MsU0FBUyxFQUFFQyxRQUFRLFFBQVEsT0FBTztBQUMzQyxTQUFTQyxNQUFNLFFBQVEsd0NBQXdDO0FBQy9ELFNBQVNDLE1BQU0sUUFBUSwwQ0FBMEM7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 \ No newline at end of file diff --git a/src/commands/rename/generateSessionName.ts b/src/commands/rename/generateSessionName.ts new file mode 100644 index 0000000000000000000000000000000000000000..2ebd78610f7a44273a1861ed2589450895166eef --- /dev/null +++ b/src/commands/rename/generateSessionName.ts @@ -0,0 +1,67 @@ +import { queryHaiku } from '../../services/api/claude.js' +import type { Message } from '../../types/message.js' +import { logForDebugging } from '../../utils/debug.js' +import { errorMessage } from '../../utils/errors.js' +import { safeParseJSON } from '../../utils/json.js' +import { extractTextContent } from '../../utils/messages.js' +import { extractConversationText } from '../../utils/sessionTitle.js' +import { asSystemPrompt } from '../../utils/systemPromptType.js' + +export async function generateSessionName( + messages: Message[], + signal: AbortSignal, +): Promise { + const conversationText = extractConversationText(messages) + if (!conversationText) { + return null + } + + try { + const result = await queryHaiku({ + systemPrompt: asSystemPrompt([ + 'Generate a short kebab-case name (2-4 words) that captures the main topic of this conversation. Use lowercase words separated by hyphens. Examples: "fix-login-bug", "add-auth-feature", "refactor-api-client", "debug-test-failures". Return JSON with a "name" field.', + ]), + userPrompt: conversationText, + outputFormat: { + type: 'json_schema', + schema: { + type: 'object', + properties: { + name: { type: 'string' }, + }, + required: ['name'], + additionalProperties: false, + }, + }, + signal, + options: { + querySource: 'rename_generate_name', + agents: [], + isNonInteractiveSession: false, + hasAppendSystemPrompt: false, + mcpTools: [], + }, + }) + + const content = extractTextContent(result.message.content) + + const response = safeParseJSON(content) + if ( + response && + typeof response === 'object' && + 'name' in response && + typeof (response as { name: unknown }).name === 'string' + ) { + return (response as { name: string }).name + } + return null + } catch (error) { + // Haiku timeout/rate-limit/network are expected operational failures — + // logForDebugging, not logError. Called automatically on every 3rd bridge + // message (initReplBridge.ts), so errors here would flood the error file. + logForDebugging(`generateSessionName failed: ${errorMessage(error)}`, { + level: 'error', + }) + return null + } +} diff --git a/src/commands/rename/index.ts b/src/commands/rename/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..1de08254b8d95323f4a7e00d6333b1a035e9997c --- /dev/null +++ b/src/commands/rename/index.ts @@ -0,0 +1,12 @@ +import type { Command } from '../../commands.js' + +const rename = { + type: 'local-jsx', + name: 'rename', + description: 'Rename the current conversation', + immediate: true, + argumentHint: '[name]', + load: () => import('./rename.js'), +} satisfies Command + +export default rename diff --git a/src/commands/rename/rename.ts b/src/commands/rename/rename.ts new file mode 100644 index 0000000000000000000000000000000000000000..014d7ebd41f24dd939afe13c75b05bee3f342671 --- /dev/null +++ b/src/commands/rename/rename.ts @@ -0,0 +1,87 @@ +import type { UUID } from 'crypto' +import { getSessionId } from '../../bootstrap/state.js' +import { + getBridgeBaseUrlOverride, + getBridgeTokenOverride, +} from '../../bridge/bridgeConfig.js' +import type { ToolUseContext } from '../../Tool.js' +import type { + LocalJSXCommandContext, + LocalJSXCommandOnDone, +} from '../../types/command.js' +import { getMessagesAfterCompactBoundary } from '../../utils/messages.js' +import { + getTranscriptPath, + saveAgentName, + saveCustomTitle, +} from '../../utils/sessionStorage.js' +import { isTeammate } from '../../utils/teammate.js' +import { generateSessionName } from './generateSessionName.js' + +export async function call( + onDone: LocalJSXCommandOnDone, + context: ToolUseContext & LocalJSXCommandContext, + args: string, +): Promise { + // Prevent teammates from renaming - their names are set by team leader + if (isTeammate()) { + onDone( + 'Cannot rename: This session is a swarm teammate. Teammate names are set by the team leader.', + { display: 'system' }, + ) + return null + } + + let newName: string + if (!args || args.trim() === '') { + const generated = await generateSessionName( + getMessagesAfterCompactBoundary(context.messages), + context.abortController.signal, + ) + if (!generated) { + onDone( + 'Could not generate a name: no conversation context yet. Usage: /rename ', + { display: 'system' }, + ) + return null + } + newName = generated + } else { + newName = args.trim() + } + + const sessionId = getSessionId() as UUID + const fullPath = getTranscriptPath() + + // Always save the custom title (session name) + await saveCustomTitle(sessionId, newName, fullPath) + + // Sync title to bridge session on claude.ai/code (best-effort, non-blocking). + // v2 env-less bridge stores cse_* in replBridgeSessionId — + // updateBridgeSessionTitle retags internally for the compat endpoint. + const appState = context.getAppState() + const bridgeSessionId = appState.replBridgeSessionId + if (bridgeSessionId) { + const tokenOverride = getBridgeTokenOverride() + void import('../../bridge/createSession.js').then( + ({ updateBridgeSessionTitle }) => + updateBridgeSessionTitle(bridgeSessionId, newName, { + baseUrl: getBridgeBaseUrlOverride(), + getAccessToken: tokenOverride ? () => tokenOverride : undefined, + }).catch(() => {}), + ) + } + + // Also persist as the session's agent name for prompt-bar display + await saveAgentName(sessionId, newName, fullPath) + context.setAppState(prev => ({ + ...prev, + standaloneAgentContext: { + ...prev.standaloneAgentContext, + name: newName, + }, + })) + + onDone(`Session renamed to: ${newName}`, { display: 'system' }) + return null +} diff --git a/src/commands/resume/resume.tsx b/src/commands/resume/resume.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8226c441ade2777f0298fb83b6a511331981f914 --- /dev/null +++ b/src/commands/resume/resume.tsx @@ -0,0 +1,275 @@ +import { c as _c } from "react/compiler-runtime"; +import chalk from 'chalk'; +import type { UUID } from 'crypto'; +import figures from 'figures'; +import * as React from 'react'; +import { getOriginalCwd, getSessionId } from '../../bootstrap/state.js'; +import type { CommandResultDisplay, ResumeEntrypoint } from '../../commands.js'; +import { LogSelector } from '../../components/LogSelector.js'; +import { MessageResponse } from '../../components/MessageResponse.js'; +import { Spinner } from '../../components/Spinner.js'; +import { useIsInsideModal } from '../../context/modalContext.js'; +import { useTerminalSize } from '../../hooks/useTerminalSize.js'; +import { setClipboard } from '../../ink/termio/osc.js'; +import { Box, Text } from '../../ink.js'; +import type { LocalJSXCommandCall } from '../../types/command.js'; +import type { LogOption } from '../../types/logs.js'; +import { agenticSessionSearch } from '../../utils/agenticSessionSearch.js'; +import { checkCrossProjectResume } from '../../utils/crossProjectResume.js'; +import { getWorktreePaths } from '../../utils/getWorktreePaths.js'; +import { logError } from '../../utils/log.js'; +import { getLastSessionLog, getSessionIdFromLog, isCustomTitleEnabled, isLiteLog, loadAllProjectsMessageLogs, loadFullLog, loadSameRepoMessageLogs, searchSessionsByCustomTitle } from '../../utils/sessionStorage.js'; +import { validateUuid } from '../../utils/uuid.js'; +type ResumeResult = { + resultType: 'sessionNotFound'; + arg: string; +} | { + resultType: 'multipleMatches'; + arg: string; + count: number; +}; +function resumeHelpMessage(result: ResumeResult): string { + switch (result.resultType) { + case 'sessionNotFound': + return `Session ${chalk.bold(result.arg)} was not found.`; + case 'multipleMatches': + return `Found ${result.count} sessions matching ${chalk.bold(result.arg)}. Please use /resume to pick a specific session.`; + } +} +function ResumeError(t0) { + const $ = _c(10); + const { + message, + args, + onDone + } = t0; + let t1; + let t2; + if ($[0] !== onDone) { + t1 = () => { + const timer = setTimeout(onDone, 0); + return () => clearTimeout(timer); + }; + t2 = [onDone]; + $[0] = onDone; + $[1] = t1; + $[2] = t2; + } else { + t1 = $[1]; + t2 = $[2]; + } + React.useEffect(t1, t2); + let t3; + if ($[3] !== args) { + t3 = {figures.pointer} /resume {args}; + $[3] = args; + $[4] = t3; + } else { + t3 = $[4]; + } + let t4; + if ($[5] !== message) { + t4 = {message}; + $[5] = message; + $[6] = t4; + } else { + t4 = $[6]; + } + let t5; + if ($[7] !== t3 || $[8] !== t4) { + t5 = {t3}{t4}; + $[7] = t3; + $[8] = t4; + $[9] = t5; + } else { + t5 = $[9]; + } + return t5; +} +function ResumeCommand({ + onDone, + onResume +}: { + onDone: (result?: string, options?: { + display?: CommandResultDisplay; + }) => void; + onResume: (sessionId: UUID, log: LogOption, entrypoint: ResumeEntrypoint) => Promise; +}): React.ReactNode { + const [logs, setLogs] = React.useState([]); + const [worktreePaths, setWorktreePaths] = React.useState([]); + const [loading, setLoading] = React.useState(true); + const [resuming, setResuming] = React.useState(false); + const [showAllProjects, setShowAllProjects] = React.useState(false); + const { + rows + } = useTerminalSize(); + const insideModal = useIsInsideModal(); + const loadLogs = React.useCallback(async (allProjects: boolean, paths: string[]) => { + setLoading(true); + try { + const allLogs = allProjects ? await loadAllProjectsMessageLogs() : await loadSameRepoMessageLogs(paths); + const resumable = filterResumableSessions(allLogs, getSessionId()); + if (resumable.length === 0) { + onDone('No conversations found to resume'); + return; + } + setLogs(resumable); + } catch (_err) { + onDone('Failed to load conversations'); + } finally { + setLoading(false); + } + }, [onDone]); + React.useEffect(() => { + async function init() { + const paths_0 = await getWorktreePaths(getOriginalCwd()); + setWorktreePaths(paths_0); + void loadLogs(false, paths_0); + } + void init(); + }, [loadLogs]); + const handleToggleAllProjects = React.useCallback(() => { + const newValue = !showAllProjects; + setShowAllProjects(newValue); + void loadLogs(newValue, worktreePaths); + }, [showAllProjects, loadLogs, worktreePaths]); + async function handleSelect(log: LogOption) { + const sessionId = validateUuid(getSessionIdFromLog(log)); + if (!sessionId) { + onDone('Failed to resume conversation'); + return; + } + + // Load full messages for lite logs + const fullLog = isLiteLog(log) ? await loadFullLog(log) : log; + + // Check if this conversation is from a different directory + const crossProjectCheck = checkCrossProjectResume(fullLog, showAllProjects, worktreePaths); + if (crossProjectCheck.isCrossProject) { + if (crossProjectCheck.isSameRepoWorktree) { + // Same repo worktree - can resume directly + setResuming(true); + void onResume(sessionId, fullLog, 'slash_command_picker'); + return; + } + + // Different project - show command instead of resuming + const raw = await setClipboard(crossProjectCheck.command); + if (raw) process.stdout.write(raw); + + // Format the output message + const message = ['', 'This conversation is from a different directory.', '', 'To resume, run:', ` ${crossProjectCheck.command}`, '', '(Command copied to clipboard)', ''].join('\n'); + onDone(message, { + display: 'user' + }); + return; + } + + // Same directory - proceed with resume + setResuming(true); + void onResume(sessionId, fullLog, 'slash_command_picker'); + } + function handleCancel() { + onDone('Resume cancelled', { + display: 'system' + }); + } + if (loading) { + return + + Loading conversations… + ; + } + if (resuming) { + return + + Resuming conversation… + ; + } + return loadLogs(showAllProjects, worktreePaths)} showAllProjects={showAllProjects} onToggleAllProjects={handleToggleAllProjects} onAgenticSearch={agenticSessionSearch} />; +} +export function filterResumableSessions(logs: LogOption[], currentSessionId: string): LogOption[] { + return logs.filter(l => !l.isSidechain && getSessionIdFromLog(l) !== currentSessionId); +} +export const call: LocalJSXCommandCall = async (onDone, context, args) => { + const onResume = async (sessionId: UUID, log: LogOption, entrypoint: ResumeEntrypoint) => { + try { + await context.resume?.(sessionId, log, entrypoint); + onDone(undefined, { + display: 'skip' + }); + } catch (error) { + logError(error as Error); + onDone(`Failed to resume: ${(error as Error).message}`); + } + }; + const arg = args?.trim(); + + // No argument provided - show picker + if (!arg) { + return ; + } + + // Load logs to search (includes same-repo worktrees) + const worktreePaths = await getWorktreePaths(getOriginalCwd()); + const logs = await loadSameRepoMessageLogs(worktreePaths); + if (logs.length === 0) { + const message = 'No conversations found to resume.'; + return onDone(message)} />; + } + + // First, check if arg is a valid UUID + const maybeSessionId = validateUuid(arg); + if (maybeSessionId) { + const matchingLogs = logs.filter(l => getSessionIdFromLog(l) === maybeSessionId).sort((a, b) => b.modified.getTime() - a.modified.getTime()); + if (matchingLogs.length > 0) { + const log = matchingLogs[0]!; + const fullLog = isLiteLog(log) ? await loadFullLog(log) : log; + void onResume(maybeSessionId, fullLog, 'slash_command_session_id'); + return null; + } + + // Enriched logs didn't find it — try direct file lookup. This handles + // sessions filtered out by enrichLogs (e.g., first message >16KB makes + // firstPrompt extraction fail, causing the session to be dropped). + const directLog = await getLastSessionLog(maybeSessionId); + if (directLog) { + void onResume(maybeSessionId, directLog, 'slash_command_session_id'); + return null; + } + } + + // Next, try exact custom title match (only if feature is enabled) + if (isCustomTitleEnabled()) { + const titleMatches = await searchSessionsByCustomTitle(arg, { + exact: true + }); + if (titleMatches.length === 1) { + const log = titleMatches[0]!; + const sessionId = getSessionIdFromLog(log); + if (sessionId) { + const fullLog = isLiteLog(log) ? await loadFullLog(log) : log; + void onResume(sessionId, fullLog, 'slash_command_title'); + return null; + } + } + + // Multiple matches - show error + if (titleMatches.length > 1) { + const message = resumeHelpMessage({ + resultType: 'multipleMatches', + arg, + count: titleMatches.length + }); + return onDone(message)} />; + } + } + + // No match found - show error + const message = resumeHelpMessage({ + resultType: 'sessionNotFound', + arg + }); + return onDone(message)} />; +}; +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJjaGFsayIsIlVVSUQiLCJmaWd1cmVzIiwiUmVhY3QiLCJnZXRPcmlnaW5hbEN3ZCIsImdldFNlc3Npb25JZCIsIkNvbW1hbmRSZXN1bHREaXNwbGF5IiwiUmVzdW1lRW50cnlwb2ludCIsIkxvZ1NlbGVjdG9yIiwiTWVzc2FnZVJlc3BvbnNlIiwiU3Bpbm5lciIsInVzZUlzSW5zaWRlTW9kYWwiLCJ1c2VUZXJtaW5hbFNpemUiLCJzZXRDbGlwYm9hcmQiLCJCb3giLCJUZXh0IiwiTG9jYWxKU1hDb21tYW5kQ2FsbCIsIkxvZ09wdGlvbiIsImFnZW50aWNTZXNzaW9uU2VhcmNoIiwiY2hlY2tDcm9zc1Byb2plY3RSZXN1bWUiLCJnZXRXb3JrdHJlZVBhdGhzIiwibG9nRXJyb3IiLCJnZXRMYXN0U2Vzc2lvbkxvZyIsImdldFNlc3Npb25JZEZyb21Mb2ciLCJpc0N1c3RvbVRpdGxlRW5hYmxlZCIsImlzTGl0ZUxvZyIsImxvYWRBbGxQcm9qZWN0c01lc3NhZ2VMb2dzIiwibG9hZEZ1bGxMb2ciLCJsb2FkU2FtZVJlcG9NZXNzYWdlTG9ncyIsInNlYXJjaFNlc3Npb25zQnlDdXN0b21UaXRsZSIsInZhbGlkYXRlVXVpZCIsIlJlc3VtZVJlc3VsdCIsInJlc3VsdFR5cGUiLCJhcmciLCJjb3VudCIsInJlc3VtZUhlbHBNZXNzYWdlIiwicmVzdWx0IiwiYm9sZCIsIlJlc3VtZUVycm9yIiwidDAiLCIkIiwiX2MiLCJtZXNzYWdlIiwiYXJncyIsIm9uRG9uZSIsInQxIiwidDIiLCJ0aW1lciIsInNldFRpbWVvdXQiLCJjbGVhclRpbWVvdXQiLCJ1c2VFZmZlY3QiLCJ0MyIsInBvaW50ZXIiLCJ0NCIsInQ1IiwiUmVzdW1lQ29tbWFuZCIsIm9uUmVzdW1lIiwib3B0aW9ucyIsImRpc3BsYXkiLCJzZXNzaW9uSWQiLCJsb2ciLCJlbnRyeXBvaW50IiwiUHJvbWlzZSIsIlJlYWN0Tm9kZSIsImxvZ3MiLCJzZXRMb2dzIiwidXNlU3RhdGUiLCJ3b3JrdHJlZVBhdGhzIiwic2V0V29ya3RyZWVQYXRocyIsImxvYWRpbmciLCJzZXRMb2FkaW5nIiwicmVzdW1pbmciLCJzZXRSZXN1bWluZyIsInNob3dBbGxQcm9qZWN0cyIsInNldFNob3dBbGxQcm9qZWN0cyIsInJvd3MiLCJpbnNpZGVNb2RhbCIsImxvYWRMb2dzIiwidXNlQ2FsbGJhY2siLCJhbGxQcm9qZWN0cyIsInBhdGhzIiwiYWxsTG9ncyIsInJlc3VtYWJsZSIsImZpbHRlclJlc3VtYWJsZVNlc3Npb25zIiwibGVuZ3RoIiwiX2VyciIsImluaXQiLCJoYW5kbGVUb2dnbGVBbGxQcm9qZWN0cyIsIm5ld1ZhbHVlIiwiaGFuZGxlU2VsZWN0IiwiZnVsbExvZyIsImNyb3NzUHJvamVjdENoZWNrIiwiaXNDcm9zc1Byb2plY3QiLCJpc1NhbWVSZXBvV29ya3RyZWUiLCJyYXciLCJjb21tYW5kIiwicHJvY2VzcyIsInN0ZG91dCIsIndyaXRlIiwiam9pbiIsImhhbmRsZUNhbmNlbCIsIk1hdGgiLCJmbG9vciIsImN1cnJlbnRTZXNzaW9uSWQiLCJmaWx0ZXIiLCJsIiwiaXNTaWRlY2hhaW4iLCJjYWxsIiwiY29udGV4dCIsInJlc3VtZSIsInVuZGVmaW5lZCIsImVycm9yIiwiRXJyb3IiLCJ0cmltIiwiRGF0ZSIsIm5vdyIsIm1heWJlU2Vzc2lvbklkIiwibWF0Y2hpbmdMb2dzIiwic29ydCIsImEiLCJiIiwibW9kaWZpZWQiLCJnZXRUaW1lIiwiZGlyZWN0TG9nIiwidGl0bGVNYXRjaGVzIiwiZXhhY3QiXSwic291cmNlcyI6WyJyZXN1bWUudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjaGFsayBmcm9tICdjaGFsaydcbmltcG9ydCB0eXBlIHsgVVVJRCB9IGZyb20gJ2NyeXB0bydcbmltcG9ydCBmaWd1cmVzIGZyb20gJ2ZpZ3VyZXMnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IGdldE9yaWdpbmFsQ3dkLCBnZXRTZXNzaW9uSWQgfSBmcm9tICcuLi8uLi9ib290c3RyYXAvc3RhdGUuanMnXG5pbXBvcnQgdHlwZSB7IENvbW1hbmRSZXN1bHREaXNwbGF5LCBSZXN1bWVFbnRyeXBvaW50IH0gZnJvbSAnLi4vLi4vY29tbWFuZHMuanMnXG5pbXBvcnQgeyBMb2dTZWxlY3RvciB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvTG9nU2VsZWN0b3IuanMnXG5pbXBvcnQgeyBNZXNzYWdlUmVzcG9uc2UgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL01lc3NhZ2VSZXNwb25zZS5qcydcbmltcG9ydCB7IFNwaW5uZXIgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL1NwaW5uZXIuanMnXG5pbXBvcnQgeyB1c2VJc0luc2lkZU1vZGFsIH0gZnJvbSAnLi4vLi4vY29udGV4dC9tb2RhbENvbnRleHQuanMnXG5pbXBvcnQgeyB1c2VUZXJtaW5hbFNpemUgfSBmcm9tICcuLi8uLi9ob29rcy91c2VUZXJtaW5hbFNpemUuanMnXG5pbXBvcnQgeyBzZXRDbGlwYm9hcmQgfSBmcm9tICcuLi8uLi9pbmsvdGVybWlvL29zYy5qcydcbmltcG9ydCB7IEJveCwgVGV4dCB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB0eXBlIHsgTG9jYWxKU1hDb21tYW5kQ2FsbCB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5pbXBvcnQgdHlwZSB7IExvZ09wdGlvbiB9IGZyb20gJy4uLy4uL3R5cGVzL2xvZ3MuanMnXG5pbXBvcnQgeyBhZ2VudGljU2Vzc2lvblNlYXJjaCB9IGZyb20gJy4uLy4uL3V0aWxzL2FnZW50aWNTZXNzaW9uU2VhcmNoLmpzJ1xuaW1wb3J0IHsgY2hlY2tDcm9zc1Byb2plY3RSZXN1bWUgfSBmcm9tICcuLi8uLi91dGlscy9jcm9zc1Byb2plY3RSZXN1bWUuanMnXG5pbXBvcnQgeyBnZXRXb3JrdHJlZVBhdGhzIH0gZnJvbSAnLi4vLi4vdXRpbHMvZ2V0V29ya3RyZWVQYXRocy5qcydcbmltcG9ydCB7IGxvZ0Vycm9yIH0gZnJvbSAnLi4vLi4vdXRpbHMvbG9nLmpzJ1xuaW1wb3J0IHtcbiAgZ2V0TGFzdFNlc3Npb25Mb2csXG4gIGdldFNlc3Npb25JZEZyb21Mb2csXG4gIGlzQ3VzdG9tVGl0bGVFbmFibGVkLFxuICBpc0xpdGVMb2csXG4gIGxvYWRBbGxQcm9qZWN0c01lc3NhZ2VMb2dzLFxuICBsb2FkRnVsbExvZyxcbiAgbG9hZFNhbWVSZXBvTWVzc2FnZUxvZ3MsXG4gIHNlYXJjaFNlc3Npb25zQnlDdXN0b21UaXRsZSxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvc2Vzc2lvblN0b3JhZ2UuanMnXG5pbXBvcnQgeyB2YWxpZGF0ZVV1aWQgfSBmcm9tICcuLi8uLi91dGlscy91dWlkLmpzJ1xuXG50eXBlIFJlc3VtZVJlc3VsdCA9XG4gIHwgeyByZXN1bHRUeXBlOiAnc2Vzc2lvbk5vdEZvdW5kJzsgYXJnOiBzdHJpbmcgfVxuICB8IHsgcmVzdWx0VHlwZTogJ211bHRpcGxlTWF0Y2hlcyc7IGFyZzogc3RyaW5nOyBjb3VudDogbnVtYmVyIH1cblxuZnVuY3Rpb24gcmVzdW1lSGVscE1lc3NhZ2UocmVzdWx0OiBSZXN1bWVSZXN1bHQpOiBzdHJpbmcge1xuICBzd2l0Y2ggKHJlc3VsdC5yZXN1bHRUeXBlKSB7XG4gICAgY2FzZSAnc2Vzc2lvbk5vdEZvdW5kJzpcbiAgICAgIHJldHVybiBgU2Vzc2lvbiAke2NoYWxrLmJvbGQocmVzdWx0LmFyZyl9IHdhcyBub3QgZm91bmQuYFxuICAgIGNhc2UgJ211bHRpcGxlTWF0Y2hlcyc6XG4gICAgICByZXR1cm4gYEZvdW5kICR7cmVzdWx0LmNvdW50fSBzZXNzaW9ucyBtYXRjaGluZyAke2NoYWxrLmJvbGQocmVzdWx0LmFyZyl9LiBQbGVhc2UgdXNlIC9yZXN1bWUgdG8gcGljayBhIHNwZWNpZmljIHNlc3Npb24uYFxuICB9XG59XG5cbmZ1bmN0aW9uIFJlc3VtZUVycm9yKHtcbiAgbWVzc2FnZSxcbiAgYXJncyxcbiAgb25Eb25lLFxufToge1xuICBtZXNzYWdlOiBzdHJpbmdcbiAgYXJnczogc3RyaW5nXG4gIG9uRG9uZTogKCkgPT4gdm9pZFxufSk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIFJlYWN0LnVzZUVmZmVjdCgoKSA9PiB7XG4gICAgY29uc3QgdGltZXIgPSBzZXRUaW1lb3V0KG9uRG9uZSwgMClcbiAgICByZXR1cm4gKCkgPT4gY2xlYXJUaW1lb3V0KHRpbWVyKVxuICB9LCBbb25Eb25lXSlcblxuICByZXR1cm4gKFxuICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgPFRleHQgZGltQ29sb3I+XG4gICAgICAgIHtmaWd1cmVzLnBvaW50ZXJ9IC9yZXN1bWUge2FyZ3N9XG4gICAgICA8L1RleHQ+XG4gICAgICA8TWVzc2FnZVJlc3BvbnNlPlxuICAgICAgICA8VGV4dD57bWVzc2FnZX08L1RleHQ+XG4gICAgICA8L01lc3NhZ2VSZXNwb25zZT5cbiAgICA8L0JveD5cbiAgKVxufVxuXG5mdW5jdGlvbiBSZXN1bWVDb21tYW5kKHtcbiAgb25Eb25lLFxuICBvblJlc3VtZSxcbn06IHtcbiAgb25Eb25lOiAoXG4gICAgcmVzdWx0Pzogc3RyaW5nLFxuICAgIG9wdGlvbnM/OiB7IGRpc3BsYXk/OiBDb21tYW5kUmVzdWx0RGlzcGxheSB9LFxuICApID0+IHZvaWRcbiAgb25SZXN1bWU6IChcbiAgICBzZXNzaW9uSWQ6IFVVSUQsXG4gICAgbG9nOiBMb2dPcHRpb24sXG4gICAgZW50cnlwb2ludDogUmVzdW1lRW50cnlwb2ludCxcbiAgKSA9PiBQcm9taXNlPHZvaWQ+XG59KTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgY29uc3QgW2xvZ3MsIHNldExvZ3NdID0gUmVhY3QudXNlU3RhdGU8TG9nT3B0aW9uW10+KFtdKVxuICBjb25zdCBbd29ya3RyZWVQYXRocywgc2V0V29ya3RyZWVQYXRoc10gPSBSZWFjdC51c2VTdGF0ZTxzdHJpbmdbXT4oW10pXG4gIGNvbnN0IFtsb2FkaW5nLCBzZXRMb2FkaW5nXSA9IFJlYWN0LnVzZVN0YXRlKHRydWUpXG4gIGNvbnN0IFtyZXN1bWluZywgc2V0UmVzdW1pbmddID0gUmVhY3QudXNlU3RhdGUoZmFsc2UpXG4gIGNvbnN0IFtzaG93QWxsUHJvamVjdHMsIHNldFNob3dBbGxQcm9qZWN0c10gPSBSZWFjdC51c2VTdGF0ZShmYWxzZSlcbiAgY29uc3QgeyByb3dzIH0gPSB1c2VUZXJtaW5hbFNpemUoKVxuICBjb25zdCBpbnNpZGVNb2RhbCA9IHVzZUlzSW5zaWRlTW9kYWwoKVxuXG4gIGNvbnN0IGxvYWRMb2dzID0gUmVhY3QudXNlQ2FsbGJhY2soXG4gICAgYXN5bmMgKGFsbFByb2plY3RzOiBib29sZWFuLCBwYXRoczogc3RyaW5nW10pID0+IHtcbiAgICAgIHNldExvYWRpbmcodHJ1ZSlcbiAgICAgIHRyeSB7XG4gICAgICAgIGNvbnN0IGFsbExvZ3MgPSBhbGxQcm9qZWN0c1xuICAgICAgICAgID8gYXdhaXQgbG9hZEFsbFByb2plY3RzTWVzc2FnZUxvZ3MoKVxuICAgICAgICAgIDogYXdhaXQgbG9hZFNhbWVSZXBvTWVzc2FnZUxvZ3MocGF0aHMpXG4gICAgICAgIGNvbnN0IHJlc3VtYWJsZSA9IGZpbHRlclJlc3VtYWJsZVNlc3Npb25zKGFsbExvZ3MsIGdldFNlc3Npb25JZCgpKVxuICAgICAgICBpZiAocmVzdW1hYmxlLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgIG9uRG9uZSgnTm8gY29udmVyc2F0aW9ucyBmb3VuZCB0byByZXN1bWUnKVxuICAgICAgICAgIHJldHVyblxuICAgICAgICB9XG4gICAgICAgIHNldExvZ3MocmVzdW1hYmxlKVxuICAgICAgfSBjYXRjaCAoX2Vycikge1xuICAgICAgICBvbkRvbmUoJ0ZhaWxlZCB0byBsb2FkIGNvbnZlcnNhdGlvbnMnKVxuICAgICAgfSBmaW5hbGx5IHtcbiAgICAgICAgc2V0TG9hZGluZyhmYWxzZSlcbiAgICAgIH1cbiAgICB9LFxuICAgIFtvbkRvbmVdLFxuICApXG5cbiAgUmVhY3QudXNlRWZmZWN0KCgpID0+IHtcbiAgICBhc3luYyBmdW5jdGlvbiBpbml0KCkge1xuICAgICAgY29uc3QgcGF0aHMgPSBhd2FpdCBnZXRXb3JrdHJlZVBhdGhzKGdldE9yaWdpbmFsQ3dkKCkpXG4gICAgICBzZXRXb3JrdHJlZVBhdGhzKHBhdGhzKVxuICAgICAgdm9pZCBsb2FkTG9ncyhmYWxzZSwgcGF0aHMpXG4gICAgfVxuICAgIHZvaWQgaW5pdCgpXG4gIH0sIFtsb2FkTG9nc10pXG5cbiAgY29uc3QgaGFuZGxlVG9nZ2xlQWxsUHJvamVjdHMgPSBSZWFjdC51c2VDYWxsYmFjaygoKSA9PiB7XG4gICAgY29uc3QgbmV3VmFsdWUgPSAhc2hvd0FsbFByb2plY3RzXG4gICAgc2V0U2hvd0FsbFByb2plY3RzKG5ld1ZhbHVlKVxuICAgIHZvaWQgbG9hZExvZ3MobmV3VmFsdWUsIHdvcmt0cmVlUGF0aHMpXG4gIH0sIFtzaG93QWxsUHJvamVjdHMsIGxvYWRMb2dzLCB3b3JrdHJlZVBhdGhzXSlcblxuICBhc3luYyBmdW5jdGlvbiBoYW5kbGVTZWxlY3QobG9nOiBMb2dPcHRpb24pIHtcbiAgICBjb25zdCBzZXNzaW9uSWQgPSB2YWxpZGF0ZVV1aWQoZ2V0U2Vzc2lvbklkRnJvbUxvZyhsb2cpKVxuICAgIGlmICghc2Vzc2lvbklkKSB7XG4gICAgICBvbkRvbmUoJ0ZhaWxlZCB0byByZXN1bWUgY29udmVyc2F0aW9uJylcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIC8vIExvYWQgZnVsbCBtZXNzYWdlcyBmb3IgbGl0ZSBsb2dzXG4gICAgY29uc3QgZnVsbExvZyA9IGlzTGl0ZUxvZyhsb2cpID8gYXdhaXQgbG9hZEZ1bGxMb2cobG9nKSA6IGxvZ1xuXG4gICAgLy8gQ2hlY2sgaWYgdGhpcyBjb252ZXJzYXRpb24gaXMgZnJvbSBhIGRpZmZlcmVudCBkaXJlY3RvcnlcbiAgICBjb25zdCBjcm9zc1Byb2plY3RDaGVjayA9IGNoZWNrQ3Jvc3NQcm9qZWN0UmVzdW1lKFxuICAgICAgZnVsbExvZyxcbiAgICAgIHNob3dBbGxQcm9qZWN0cyxcbiAgICAgIHdvcmt0cmVlUGF0aHMsXG4gICAgKVxuICAgIGlmIChjcm9zc1Byb2plY3RDaGVjay5pc0Nyb3NzUHJvamVjdCkge1xuICAgICAgaWYgKGNyb3NzUHJvamVjdENoZWNrLmlzU2FtZVJlcG9Xb3JrdHJlZSkge1xuICAgICAgICAvLyBTYW1lIHJlcG8gd29ya3RyZWUgLSBjYW4gcmVzdW1lIGRpcmVjdGx5XG4gICAgICAgIHNldFJlc3VtaW5nKHRydWUpXG4gICAgICAgIHZvaWQgb25SZXN1bWUoc2Vzc2lvbklkLCBmdWxsTG9nLCAnc2xhc2hfY29tbWFuZF9waWNrZXInKVxuICAgICAgICByZXR1cm5cbiAgICAgIH1cblxuICAgICAgLy8gRGlmZmVyZW50IHByb2plY3QgLSBzaG93IGNvbW1hbmQgaW5zdGVhZCBvZiByZXN1bWluZ1xuICAgICAgY29uc3QgcmF3ID0gYXdhaXQgc2V0Q2xpcGJvYXJkKGNyb3NzUHJvamVjdENoZWNrLmNvbW1hbmQpXG4gICAgICBpZiAocmF3KSBwcm9jZXNzLnN0ZG91dC53cml0ZShyYXcpXG5cbiAgICAgIC8vIEZvcm1hdCB0aGUgb3V0cHV0IG1lc3NhZ2VcbiAgICAgIGNvbnN0IG1lc3NhZ2UgPSBbXG4gICAgICAgICcnLFxuICAgICAgICAnVGhpcyBjb252ZXJzYXRpb24gaXMgZnJvbSBhIGRpZmZlcmVudCBkaXJlY3RvcnkuJyxcbiAgICAgICAgJycsXG4gICAgICAgICdUbyByZXN1bWUsIHJ1bjonLFxuICAgICAgICBgICAke2Nyb3NzUHJvamVjdENoZWNrLmNvbW1hbmR9YCxcbiAgICAgICAgJycsXG4gICAgICAgICcoQ29tbWFuZCBjb3BpZWQgdG8gY2xpcGJvYXJkKScsXG4gICAgICAgICcnLFxuICAgICAgXS5qb2luKCdcXG4nKVxuXG4gICAgICBvbkRvbmUobWVzc2FnZSwgeyBkaXNwbGF5OiAndXNlcicgfSlcbiAgICAgIHJldHVyblxuICAgIH1cblxuICAgIC8vIFNhbWUgZGlyZWN0b3J5IC0gcHJvY2VlZCB3aXRoIHJlc3VtZVxuICAgIHNldFJlc3VtaW5nKHRydWUpXG4gICAgdm9pZCBvblJlc3VtZShzZXNzaW9uSWQsIGZ1bGxMb2csICdzbGFzaF9jb21tYW5kX3BpY2tlcicpXG4gIH1cblxuICBmdW5jdGlvbiBoYW5kbGVDYW5jZWwoKSB7XG4gICAgb25Eb25lKCdSZXN1bWUgY2FuY2VsbGVkJywgeyBkaXNwbGF5OiAnc3lzdGVtJyB9KVxuICB9XG5cbiAgaWYgKGxvYWRpbmcpIHtcbiAgICByZXR1cm4gKFxuICAgICAgPEJveD5cbiAgICAgICAgPFNwaW5uZXIgLz5cbiAgICAgICAgPFRleHQ+IExvYWRpbmcgY29udmVyc2F0aW9uc+KApjwvVGV4dD5cbiAgICAgIDwvQm94PlxuICAgIClcbiAgfVxuXG4gIGlmIChyZXN1bWluZykge1xuICAgIHJldHVybiAoXG4gICAgICA8Qm94PlxuICAgICAgICA8U3Bpbm5lciAvPlxuICAgICAgICA8VGV4dD4gUmVzdW1pbmcgY29udmVyc2F0aW9u4oCmPC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgcmV0dXJuIChcbiAgICA8TG9nU2VsZWN0b3JcbiAgICAgIGxvZ3M9e2xvZ3N9XG4gICAgICBtYXhIZWlnaHQ9e2luc2lkZU1vZGFsID8gTWF0aC5mbG9vcihyb3dzIC8gMikgOiByb3dzIC0gMn1cbiAgICAgIG9uQ2FuY2VsPXtoYW5kbGVDYW5jZWx9XG4gICAgICBvblNlbGVjdD17aGFuZGxlU2VsZWN0fVxuICAgICAgb25Mb2dzQ2hhbmdlZD17KCkgPT4gbG9hZExvZ3Moc2hvd0FsbFByb2plY3RzLCB3b3JrdHJlZVBhdGhzKX1cbiAgICAgIHNob3dBbGxQcm9qZWN0cz17c2hvd0FsbFByb2plY3RzfVxuICAgICAgb25Ub2dnbGVBbGxQcm9qZWN0cz17aGFuZGxlVG9nZ2xlQWxsUHJvamVjdHN9XG4gICAgICBvbkFnZW50aWNTZWFyY2g9e2FnZW50aWNTZXNzaW9uU2VhcmNofVxuICAgIC8+XG4gIClcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGZpbHRlclJlc3VtYWJsZVNlc3Npb25zKFxuICBsb2dzOiBMb2dPcHRpb25bXSxcbiAgY3VycmVudFNlc3Npb25JZDogc3RyaW5nLFxuKTogTG9nT3B0aW9uW10ge1xuICByZXR1cm4gbG9ncy5maWx0ZXIoXG4gICAgbCA9PiAhbC5pc1NpZGVjaGFpbiAmJiBnZXRTZXNzaW9uSWRGcm9tTG9nKGwpICE9PSBjdXJyZW50U2Vzc2lvbklkLFxuICApXG59XG5cbmV4cG9ydCBjb25zdCBjYWxsOiBMb2NhbEpTWENvbW1hbmRDYWxsID0gYXN5bmMgKG9uRG9uZSwgY29udGV4dCwgYXJncykgPT4ge1xuICBjb25zdCBvblJlc3VtZSA9IGFzeW5jIChcbiAgICBzZXNzaW9uSWQ6IFVVSUQsXG4gICAgbG9nOiBMb2dPcHRpb24sXG4gICAgZW50cnlwb2ludDogUmVzdW1lRW50cnlwb2ludCxcbiAgKSA9PiB7XG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IGNvbnRleHQucmVzdW1lPy4oc2Vzc2lvbklkLCBsb2csIGVudHJ5cG9pbnQpXG4gICAgICBvbkRvbmUodW5kZWZpbmVkLCB7IGRpc3BsYXk6ICdza2lwJyB9KVxuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dFcnJvcihlcnJvciBhcyBFcnJvcilcbiAgICAgIG9uRG9uZShgRmFpbGVkIHRvIHJlc3VtZTogJHsoZXJyb3IgYXMgRXJyb3IpLm1lc3NhZ2V9YClcbiAgICB9XG4gIH1cblxuICBjb25zdCBhcmcgPSBhcmdzPy50cmltKClcblxuICAvLyBObyBhcmd1bWVudCBwcm92aWRlZCAtIHNob3cgcGlja2VyXG4gIGlmICghYXJnKSB7XG4gICAgcmV0dXJuIChcbiAgICAgIDxSZXN1bWVDb21tYW5kIGtleT17RGF0ZS5ub3coKX0gb25Eb25lPXtvbkRvbmV9IG9uUmVzdW1lPXtvblJlc3VtZX0gLz5cbiAgICApXG4gIH1cblxuICAvLyBMb2FkIGxvZ3MgdG8gc2VhcmNoIChpbmNsdWRlcyBzYW1lLXJlcG8gd29ya3RyZWVzKVxuICBjb25zdCB3b3JrdHJlZVBhdGhzID0gYXdhaXQgZ2V0V29ya3RyZWVQYXRocyhnZXRPcmlnaW5hbEN3ZCgpKVxuICBjb25zdCBsb2dzID0gYXdhaXQgbG9hZFNhbWVSZXBvTWVzc2FnZUxvZ3Mod29ya3RyZWVQYXRocylcbiAgaWYgKGxvZ3MubGVuZ3RoID09PSAwKSB7XG4gICAgY29uc3QgbWVzc2FnZSA9ICdObyBjb252ZXJzYXRpb25zIGZvdW5kIHRvIHJlc3VtZS4nXG4gICAgcmV0dXJuIChcbiAgICAgIDxSZXN1bWVFcnJvclxuICAgICAgICBtZXNzYWdlPXttZXNzYWdlfVxuICAgICAgICBhcmdzPXthcmd9XG4gICAgICAgIG9uRG9uZT17KCkgPT4gb25Eb25lKG1lc3NhZ2UpfVxuICAgICAgLz5cbiAgICApXG4gIH1cblxuICAvLyBGaXJzdCwgY2hlY2sgaWYgYXJnIGlzIGEgdmFsaWQgVVVJRFxuICBjb25zdCBtYXliZVNlc3Npb25JZCA9IHZhbGlkYXRlVXVpZChhcmcpXG4gIGlmIChtYXliZVNlc3Npb25JZCkge1xuICAgIGNvbnN0IG1hdGNoaW5nTG9ncyA9IGxvZ3NcbiAgICAgIC5maWx0ZXIobCA9PiBnZXRTZXNzaW9uSWRGcm9tTG9nKGwpID09PSBtYXliZVNlc3Npb25JZClcbiAgICAgIC5zb3J0KChhLCBiKSA9PiBiLm1vZGlmaWVkLmdldFRpbWUoKSAtIGEubW9kaWZpZWQuZ2V0VGltZSgpKVxuXG4gICAgaWYgKG1hdGNoaW5nTG9ncy5sZW5ndGggPiAwKSB7XG4gICAgICBjb25zdCBsb2cgPSBtYXRjaGluZ0xvZ3NbMF0hXG4gICAgICBjb25zdCBmdWxsTG9nID0gaXNMaXRlTG9nKGxvZykgPyBhd2FpdCBsb2FkRnVsbExvZyhsb2cpIDogbG9nXG4gICAgICB2b2lkIG9uUmVzdW1lKG1heWJlU2Vzc2lvbklkLCBmdWxsTG9nLCAnc2xhc2hfY29tbWFuZF9zZXNzaW9uX2lkJylcbiAgICAgIHJldHVybiBudWxsXG4gICAgfVxuXG4gICAgLy8gRW5yaWNoZWQgbG9ncyBkaWRuJ3QgZmluZCBpdCDigJQgdHJ5IGRpcmVjdCBmaWxlIGxvb2t1cC4gVGhpcyBoYW5kbGVzXG4gICAgLy8gc2Vzc2lvbnMgZmlsdGVyZWQgb3V0IGJ5IGVucmljaExvZ3MgKGUuZy4sIGZpcnN0IG1lc3NhZ2UgPjE2S0IgbWFrZXNcbiAgICAvLyBmaXJzdFByb21wdCBleHRyYWN0aW9uIGZhaWwsIGNhdXNpbmcgdGhlIHNlc3Npb24gdG8gYmUgZHJvcHBlZCkuXG4gICAgY29uc3QgZGlyZWN0TG9nID0gYXdhaXQgZ2V0TGFzdFNlc3Npb25Mb2cobWF5YmVTZXNzaW9uSWQpXG4gICAgaWYgKGRpcmVjdExvZykge1xuICAgICAgdm9pZCBvblJlc3VtZShtYXliZVNlc3Npb25JZCwgZGlyZWN0TG9nLCAnc2xhc2hfY29tbWFuZF9zZXNzaW9uX2lkJylcbiAgICAgIHJldHVybiBudWxsXG4gICAgfVxuICB9XG5cbiAgLy8gTmV4dCwgdHJ5IGV4YWN0IGN1c3RvbSB0aXRsZSBtYXRjaCAob25seSBpZiBmZWF0dXJlIGlzIGVuYWJsZWQpXG4gIGlmIChpc0N1c3RvbVRpdGxlRW5hYmxlZCgpKSB7XG4gICAgY29uc3QgdGl0bGVNYXRjaGVzID0gYXdhaXQgc2VhcmNoU2Vzc2lvbnNCeUN1c3RvbVRpdGxlKGFyZywge1xuICAgICAgZXhhY3Q6IHRydWUsXG4gICAgfSlcbiAgICBpZiAodGl0bGVNYXRjaGVzLmxlbmd0aCA9PT0gMSkge1xuICAgICAgY29uc3QgbG9nID0gdGl0bGVNYXRjaGVzWzBdIVxuICAgICAgY29uc3Qgc2Vzc2lvbklkID0gZ2V0U2Vzc2lvbklkRnJvbUxvZyhsb2cpXG4gICAgICBpZiAoc2Vzc2lvbklkKSB7XG4gICAgICAgIGNvbnN0IGZ1bGxMb2cgPSBpc0xpdGVMb2cobG9nKSA/IGF3YWl0IGxvYWRGdWxsTG9nKGxvZykgOiBsb2dcbiAgICAgICAgdm9pZCBvblJlc3VtZShzZXNzaW9uSWQsIGZ1bGxMb2csICdzbGFzaF9jb21tYW5kX3RpdGxlJylcbiAgICAgICAgcmV0dXJuIG51bGxcbiAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBNdWx0aXBsZSBtYXRjaGVzIC0gc2hvdyBlcnJvclxuICAgIGlmICh0aXRsZU1hdGNoZXMubGVuZ3RoID4gMSkge1xuICAgICAgY29uc3QgbWVzc2FnZSA9IHJlc3VtZUhlbHBNZXNzYWdlKHtcbiAgICAgICAgcmVzdWx0VHlwZTogJ211bHRpcGxlTWF0Y2hlcycsXG4gICAgICAgIGFyZyxcbiAgICAgICAgY291bnQ6IHRpdGxlTWF0Y2hlcy5sZW5ndGgsXG4gICAgICB9KVxuICAgICAgcmV0dXJuIChcbiAgICAgICAgPFJlc3VtZUVycm9yXG4gICAgICAgICAgbWVzc2FnZT17bWVzc2FnZX1cbiAgICAgICAgICBhcmdzPXthcmd9XG4gICAgICAgICAgb25Eb25lPXsoKSA9PiBvbkRvbmUobWVzc2FnZSl9XG4gICAgICAgIC8+XG4gICAgICApXG4gICAgfVxuICB9XG5cbiAgLy8gTm8gbWF0Y2ggZm91bmQgLSBzaG93IGVycm9yXG4gIGNvbnN0IG1lc3NhZ2UgPSByZXN1bWVIZWxwTWVzc2FnZSh7IHJlc3VsdFR5cGU6ICdzZXNzaW9uTm90Rm91bmQnLCBhcmcgfSlcbiAgcmV0dXJuIChcbiAgICA8UmVzdW1lRXJyb3IgbWVzc2FnZT17bWVzc2FnZX0gYXJncz17YXJnfSBvbkRvbmU9eygpID0+IG9uRG9uZShtZXNzYWdlKX0gLz5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBT0EsS0FBSyxNQUFNLE9BQU87QUFDekIsY0FBY0MsSUFBSSxRQUFRLFFBQVE7QUFDbEMsT0FBT0MsT0FBTyxNQUFNLFNBQVM7QUFDN0IsT0FBTyxLQUFLQyxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxjQUFjLEVBQUVDLFlBQVksUUFBUSwwQkFBMEI7QUFDdkUsY0FBY0Msb0JBQW9CLEVBQUVDLGdCQUFnQixRQUFRLG1CQUFtQjtBQUMvRSxTQUFTQyxXQUFXLFFBQVEsaUNBQWlDO0FBQzdELFNBQVNDLGVBQWUsUUFBUSxxQ0FBcUM7QUFDckUsU0FBU0MsT0FBTyxRQUFRLDZCQUE2QjtBQUNyRCxTQUFTQyxnQkFBZ0IsUUFBUSwrQkFBK0I7QUFDaEUsU0FBU0MsZUFBZSxR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 \ No newline at end of file diff --git a/src/commands/review/reviewRemote.ts b/src/commands/review/reviewRemote.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b80e33959d8a06075e59b451f72e4fdbffc1f98 --- /dev/null +++ b/src/commands/review/reviewRemote.ts @@ -0,0 +1,316 @@ +/** + * Teleported /ultrareview execution. Creates a CCR session with the current repo, + * sends the review prompt as the initial message, and registers a + * RemoteAgentTask so the polling loop pipes results back into the local + * session via task-notification. Mirrors the /ultraplan → CCR flow. + * + * TODO(#22051): pass useBundleMode once landed so local-only / uncommitted + * repo state is captured. The GitHub-clone path (current) only works for + * pushed branches on repos with the Claude GitHub app installed. + */ + +import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.js' +import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js' +import { + type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + logEvent, +} from '../../services/analytics/index.js' +import { fetchUltrareviewQuota } from '../../services/api/ultrareviewQuota.js' +import { fetchUtilization } from '../../services/api/usage.js' +import type { ToolUseContext } from '../../Tool.js' +import { + checkRemoteAgentEligibility, + formatPreconditionError, + getRemoteTaskSessionUrl, + registerRemoteAgentTask, +} from '../../tasks/RemoteAgentTask/RemoteAgentTask.js' +import { isEnterpriseSubscriber, isTeamSubscriber } from '../../utils/auth.js' +import { detectCurrentRepositoryWithHost } from '../../utils/detectRepository.js' +import { execFileNoThrow } from '../../utils/execFileNoThrow.js' +import { getDefaultBranch, gitExe } from '../../utils/git.js' +import { teleportToRemote } from '../../utils/teleport.js' + +// One-time session flag: once the user confirms overage billing via the +// dialog, all subsequent /ultrareview invocations in this session proceed +// without re-prompting. +let sessionOverageConfirmed = false + +export function confirmOverage(): void { + sessionOverageConfirmed = true +} + +export type OverageGate = + | { kind: 'proceed'; billingNote: string } + | { kind: 'not-enabled' } + | { kind: 'low-balance'; available: number } + | { kind: 'needs-confirm' } + +/** + * Determine whether the user can launch an ultrareview and under what + * billing terms. Fetches quota and utilization in parallel. + */ +export async function checkOverageGate(): Promise { + // Team and Enterprise plans include ultrareview — no free-review quota + // or Extra Usage dialog. The quota endpoint is scoped to consumer plans + // (pro/max); hitting it on team/ent would surface a confusing dialog. + if (isTeamSubscriber() || isEnterpriseSubscriber()) { + return { kind: 'proceed', billingNote: '' } + } + + const [quota, utilization] = await Promise.all([ + fetchUltrareviewQuota(), + fetchUtilization().catch(() => null), + ]) + + // No quota info (non-subscriber or endpoint down) — let it through, + // server-side billing will handle it. + if (!quota) { + return { kind: 'proceed', billingNote: '' } + } + + if (quota.reviews_remaining > 0) { + return { + kind: 'proceed', + billingNote: ` This is free ultrareview ${quota.reviews_used + 1} of ${quota.reviews_limit}.`, + } + } + + // Utilization fetch failed (transient network error, timeout, etc.) — + // let it through, same rationale as the quota fallback above. + if (!utilization) { + return { kind: 'proceed', billingNote: '' } + } + + // Free reviews exhausted — check Extra Usage setup. + const extraUsage = utilization.extra_usage + if (!extraUsage?.is_enabled) { + logEvent('tengu_review_overage_not_enabled', {}) + return { kind: 'not-enabled' } + } + + // Check available balance (null monthly_limit = unlimited). + const monthlyLimit = extraUsage.monthly_limit + const usedCredits = extraUsage.used_credits ?? 0 + const available = + monthlyLimit === null || monthlyLimit === undefined + ? Infinity + : monthlyLimit - usedCredits + + if (available < 10) { + logEvent('tengu_review_overage_low_balance', { available }) + return { kind: 'low-balance', available } + } + + if (!sessionOverageConfirmed) { + logEvent('tengu_review_overage_dialog_shown', {}) + return { kind: 'needs-confirm' } + } + + return { + kind: 'proceed', + billingNote: ' This review bills as Extra Usage.', + } +} + +/** + * Launch a teleported review session. Returns ContentBlockParam[] describing + * the launch outcome for injection into the local conversation (model is then + * queried with this content, so it can narrate the launch to the user). + * + * Returns ContentBlockParam[] with user-facing error messages on recoverable + * failures (missing merge-base, empty diff, bundle too large), or null on + * other failures so the caller falls through to the local-review prompt. + * Reason is captured in analytics. + * + * Caller must run checkOverageGate() BEFORE calling this function + * (ultrareviewCommand.tsx handles the dialog). + */ +export async function launchRemoteReview( + args: string, + context: ToolUseContext, + billingNote?: string, +): Promise { + const eligibility = await checkRemoteAgentEligibility() + // Synthetic DEFAULT_CODE_REVIEW_ENVIRONMENT_ID works without per-org CCR + // setup, so no_remote_environment isn't a blocker. Server-side quota + // consume at session creation routes billing: first N zero-rate, then + // anthropic:cccr org-service-key (overage-only). + if (!eligibility.eligible) { + const blockers = eligibility.errors.filter( + e => e.type !== 'no_remote_environment', + ) + if (blockers.length > 0) { + logEvent('tengu_review_remote_precondition_failed', { + precondition_errors: blockers + .map(e => e.type) + .join( + ',', + ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + }) + const reasons = blockers.map(formatPreconditionError).join('\n') + return [ + { + type: 'text', + text: `Ultrareview cannot launch:\n${reasons}`, + }, + ] + } + } + + const resolvedBillingNote = billingNote ?? '' + + const prNumber = args.trim() + const isPrNumber = /^\d+$/.test(prNumber) + // Synthetic code_review env. Go taggedid.FromUUID(TagEnvironment, + // UUID{...,0x02}) encodes with version prefix '01' — NOT Python's + // legacy tagged_id() format. Verified in prod. + const CODE_REVIEW_ENV_ID = 'env_011111111111111111111113' + // Lite-review bypasses bughunter.go entirely, so it doesn't see the + // webhook's bug_hunter_config (different GB project). These env vars are + // the only tuning surface — without them, run_hunt.sh's bash defaults + // apply (60min, 120s agent timeout), and 120s kills verifiers mid-run + // which causes infinite respawn. + // + // total_wallclock must stay below RemoteAgentTask's 30min poll timeout + // with headroom for finalization (~3min synthesis). Per-field guards + // match autoDream.ts — GB cache can return stale wrong-type values. + const raw = getFeatureValue_CACHED_MAY_BE_STALE | null>('tengu_review_bughunter_config', null) + const posInt = (v: unknown, fallback: number, max?: number): number => { + if (typeof v !== 'number' || !Number.isFinite(v)) return fallback + const n = Math.floor(v) + if (n <= 0) return fallback + return max !== undefined && n > max ? fallback : n + } + // Upper bounds: 27min on wallclock leaves ~3min for finalization under + // RemoteAgentTask's 30min poll timeout. If GB is set above that, the + // hang we're fixing comes back — fall to the safe default instead. + const commonEnvVars = { + BUGHUNTER_DRY_RUN: '1', + BUGHUNTER_FLEET_SIZE: String(posInt(raw?.fleet_size, 5, 20)), + BUGHUNTER_MAX_DURATION: String(posInt(raw?.max_duration_minutes, 10, 25)), + BUGHUNTER_AGENT_TIMEOUT: String( + posInt(raw?.agent_timeout_seconds, 600, 1800), + ), + BUGHUNTER_TOTAL_WALLCLOCK: String( + posInt(raw?.total_wallclock_minutes, 22, 27), + ), + ...(process.env.BUGHUNTER_DEV_BUNDLE_B64 && { + BUGHUNTER_DEV_BUNDLE_B64: process.env.BUGHUNTER_DEV_BUNDLE_B64, + }), + } + + let session + let command + let target + if (isPrNumber) { + // PR mode: refs/pull/N/head via github.com. Orchestrator --pr N. + const repo = await detectCurrentRepositoryWithHost() + if (!repo || repo.host !== 'github.com') { + logEvent('tengu_review_remote_precondition_failed', {}) + return null + } + session = await teleportToRemote({ + initialMessage: null, + description: `ultrareview: ${repo.owner}/${repo.name}#${prNumber}`, + signal: context.abortController.signal, + branchName: `refs/pull/${prNumber}/head`, + environmentId: CODE_REVIEW_ENV_ID, + environmentVariables: { + BUGHUNTER_PR_NUMBER: prNumber, + BUGHUNTER_REPOSITORY: `${repo.owner}/${repo.name}`, + ...commonEnvVars, + }, + }) + command = `/ultrareview ${prNumber}` + target = `${repo.owner}/${repo.name}#${prNumber}` + } else { + // Branch mode: bundle the working tree, orchestrator diffs against + // the fork point. No PR, no existing comments, no dedup. + const baseBranch = (await getDefaultBranch()) || 'main' + // Env-manager's `git remote remove origin` after bundle-clone + // deletes refs/remotes/origin/* — the base branch name won't resolve + // in the container. Pass the merge-base SHA instead: it's reachable + // from HEAD's history so `git diff ` works without a named ref. + const { stdout: mbOut, code: mbCode } = await execFileNoThrow( + gitExe(), + ['merge-base', baseBranch, 'HEAD'], + { preserveOutputOnError: false }, + ) + const mergeBaseSha = mbOut.trim() + if (mbCode !== 0 || !mergeBaseSha) { + logEvent('tengu_review_remote_precondition_failed', {}) + return [ + { + type: 'text', + text: `Could not find merge-base with ${baseBranch}. Make sure you're in a git repo with a ${baseBranch} branch.`, + }, + ] + } + + // Bail early on empty diffs instead of launching a container that + // will just echo "no changes". + const { stdout: diffStat, code: diffCode } = await execFileNoThrow( + gitExe(), + ['diff', '--shortstat', mergeBaseSha], + { preserveOutputOnError: false }, + ) + if (diffCode === 0 && !diffStat.trim()) { + logEvent('tengu_review_remote_precondition_failed', {}) + return [ + { + type: 'text', + text: `No changes against the ${baseBranch} fork point. Make some commits or stage files first.`, + }, + ] + } + + session = await teleportToRemote({ + initialMessage: null, + description: `ultrareview: ${baseBranch}`, + signal: context.abortController.signal, + useBundle: true, + environmentId: CODE_REVIEW_ENV_ID, + environmentVariables: { + BUGHUNTER_BASE_BRANCH: mergeBaseSha, + ...commonEnvVars, + }, + }) + if (!session) { + logEvent('tengu_review_remote_teleport_failed', {}) + return [ + { + type: 'text', + text: 'Repo is too large. Push a PR and use `/ultrareview ` instead.', + }, + ] + } + command = '/ultrareview' + target = baseBranch + } + + if (!session) { + logEvent('tengu_review_remote_teleport_failed', {}) + return null + } + registerRemoteAgentTask({ + remoteTaskType: 'ultrareview', + session, + command, + context, + isRemoteReview: true, + }) + logEvent('tengu_review_remote_launched', {}) + const sessionUrl = getRemoteTaskSessionUrl(session.id) + // Concise — the tool-output block is visible to the user, so the model + // shouldn't echo the same info. Just enough for Claude to acknowledge the + // launch without restating the target/URL (both already printed above). + return [ + { + type: 'text', + text: `Ultrareview launched for ${target} (~10–20 min, runs in the cloud). Track: ${sessionUrl}${resolvedBillingNote} Findings arrive via task-notification. Briefly acknowledge the launch to the user without repeating the target or URL — both are already visible in the tool output above.`, + }, + ] +} diff --git a/src/commands/sandbox-toggle/sandbox-toggle.tsx b/src/commands/sandbox-toggle/sandbox-toggle.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f56503c24a2a6ad8e4a7ebd267810360c2ee9568 --- /dev/null +++ b/src/commands/sandbox-toggle/sandbox-toggle.tsx @@ -0,0 +1,83 @@ +import { relative } from 'path'; +import React from 'react'; +import { getCwdState } from '../../bootstrap/state.js'; +import { SandboxSettings } from '../../components/sandbox/SandboxSettings.js'; +import { color } from '../../ink.js'; +import { getPlatform } from '../../utils/platform.js'; +import { addToExcludedCommands, SandboxManager } from '../../utils/sandbox/sandbox-adapter.js'; +import { getSettings_DEPRECATED, getSettingsFilePathForSource } from '../../utils/settings/settings.js'; +import type { ThemeName } from '../../utils/theme.js'; +export async function call(onDone: (result?: string) => void, _context: unknown, args?: string): Promise { + const settings = getSettings_DEPRECATED(); + const themeName: ThemeName = settings.theme as ThemeName || 'light'; + const platform = getPlatform(); + if (!SandboxManager.isSupportedPlatform()) { + // WSL1 users will see this since isSupportedPlatform returns false for WSL1 + const errorMessage = platform === 'wsl' ? 'Error: Sandboxing requires WSL2. WSL1 is not supported.' : 'Error: Sandboxing is currently only supported on macOS, Linux, and WSL2.'; + const message = color('error', themeName)(errorMessage); + onDone(message); + return null; + } + + // Check dependencies - get structured result with errors/warnings + const depCheck = SandboxManager.checkDependencies(); + + // Check if platform is in enabledPlatforms list (undocumented enterprise setting) + if (!SandboxManager.isPlatformInEnabledList()) { + const message = color('error', themeName)(`Error: Sandboxing is disabled for this platform (${platform}) via the enabledPlatforms setting.`); + onDone(message); + return null; + } + + // Check if sandbox settings are locked by higher-priority settings + if (SandboxManager.areSandboxSettingsLockedByPolicy()) { + const message = color('error', themeName)('Error: Sandbox settings are overridden by a higher-priority configuration and cannot be changed locally.'); + onDone(message); + return null; + } + + // Parse the arguments + const trimmedArgs = args?.trim() || ''; + + // If no args, show the interactive menu + if (!trimmedArgs) { + return ; + } + + // Handle subcommands + if (trimmedArgs) { + const parts = trimmedArgs.split(' '); + const subcommand = parts[0]; + if (subcommand === 'exclude') { + // Handle exclude subcommand + const commandPattern = trimmedArgs.slice('exclude '.length).trim(); + if (!commandPattern) { + const message = color('error', themeName)('Error: Please provide a command pattern to exclude (e.g., /sandbox exclude "npm run test:*")'); + onDone(message); + return null; + } + + // Remove quotes if present + const cleanPattern = commandPattern.replace(/^["']|["']$/g, ''); + + // Add to excludedCommands + addToExcludedCommands(cleanPattern); + + // Get the local settings path and make it relative to cwd + const localSettingsPath = getSettingsFilePathForSource('localSettings'); + const relativePath = localSettingsPath ? relative(getCwdState(), localSettingsPath) : '.claude/settings.local.json'; + const message = color('success', themeName)(`Added "${cleanPattern}" to excluded commands in ${relativePath}`); + onDone(message); + return null; + } else { + // Unknown subcommand + const message = color('error', themeName)(`Error: Unknown subcommand "${subcommand}". Available subcommand: exclude`); + onDone(message); + return null; + } + } + + // Should never reach here since we handle all cases above + return null; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJyZWxhdGl2ZSIsIlJlYWN0IiwiZ2V0Q3dkU3RhdGUiLCJTYW5kYm94U2V0dGluZ3MiLCJjb2xvciIsImdldFBsYXRmb3JtIiwiYWRkVG9FeGNsdWRlZENvbW1hbmRzIiwiU2FuZGJveE1hbmFnZXIiLCJnZXRTZXR0aW5nc19ERVBSRUNBVEVEIiwiZ2V0U2V0dGluZ3NGaWxlUGF0aEZvclNvdXJjZSIsIlRoZW1lTmFtZSIsImNhbGwiLCJvbkRvbmUiLCJyZXN1bHQiLCJfY29udGV4dCIsImFyZ3MiLCJQcm9taXNlIiwiUmVhY3ROb2RlIiwic2V0dGluZ3MiLCJ0aGVtZU5hbWUiLCJ0aGVtZSIsInBsYXRmb3JtIiwiaXNTdXBwb3J0ZWRQbGF0Zm9ybSIsImVycm9yTWVzc2FnZSIsIm1lc3NhZ2UiLCJkZXBDaGVjayIsImNoZWNrRGVwZW5kZW5jaWVzIiwiaXNQbGF0Zm9ybUluRW5hYmxlZExpc3QiLCJhcmVTYW5kYm94U2V0dGluZ3NMb2NrZWRCeVBvbGljeSIsInRyaW1tZWRBcmdzIiwidHJpbSIsInBhcnRzIiwic3BsaXQiLCJzdWJjb21tYW5kIiwiY29tbWFuZFBhdHRlcm4iLCJzbGljZSIsImxlbmd0aCIsImNsZWFuUGF0dGVybiIsInJlcGxhY2UiLCJsb2NhbFNldHRpbmdzUGF0aCIsInJlbGF0aXZlUGF0aCJdLCJzb3VyY2VzIjpbInNhbmRib3gtdG9nZ2xlLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyByZWxhdGl2ZSB9IGZyb20gJ3BhdGgnXG5pbXBvcnQgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBnZXRDd2RTdGF0ZSB9IGZyb20gJy4uLy4uL2Jvb3RzdHJhcC9zdGF0ZS5qcydcbmltcG9ydCB7IFNhbmRib3hTZXR0aW5ncyB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvc2FuZGJveC9TYW5kYm94U2V0dGluZ3MuanMnXG5pbXBvcnQgeyBjb2xvciB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB7IGdldFBsYXRmb3JtIH0gZnJvbSAnLi4vLi4vdXRpbHMvcGxhdGZvcm0uanMnXG5pbXBvcnQge1xuICBhZGRUb0V4Y2x1ZGVkQ29tbWFuZHMsXG4gIFNhbmRib3hNYW5hZ2VyLFxufSBmcm9tICcuLi8uLi91dGlscy9zYW5kYm94L3NhbmRib3gtYWRhcHRlci5qcydcbmltcG9ydCB7XG4gIGdldFNldHRpbmdzX0RFUFJFQ0FURUQsXG4gIGdldFNldHRpbmdzRmlsZVBhdGhGb3JTb3VyY2UsXG59IGZyb20gJy4uLy4uL3V0aWxzL3NldHRpbmdzL3NldHRpbmdzLmpzJ1xuaW1wb3J0IHR5cGUgeyBUaGVtZU5hbWUgfSBmcm9tICcuLi8uLi91dGlscy90aGVtZS5qcydcblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNhbGwoXG4gIG9uRG9uZTogKHJlc3VsdD86IHN0cmluZykgPT4gdm9pZCxcbiAgX2NvbnRleHQ6IHVua25vd24sXG4gIGFyZ3M/OiBzdHJpbmcsXG4pOiBQcm9taXNlPFJlYWN0LlJlYWN0Tm9kZSB8IG51bGw+IHtcbiAgY29uc3Qgc2V0dGluZ3MgPSBnZXRTZXR0aW5nc19ERVBSRUNBVEVEKClcbiAgY29uc3QgdGhlbWVOYW1lOiBUaGVtZU5hbWUgPSAoc2V0dGluZ3MudGhlbWUgYXMgVGhlbWVOYW1lKSB8fCAnbGlnaHQnXG5cbiAgY29uc3QgcGxhdGZvcm0gPSBnZXRQbGF0Zm9ybSgpXG5cbiAgaWYgKCFTYW5kYm94TWFuYWdlci5pc1N1cHBvcnRlZFBsYXRmb3JtKCkpIHtcbiAgICAvLyBXU0wxIHVzZXJzIHdpbGwgc2VlIHRoaXMgc2luY2UgaXNTdXBwb3J0ZWRQbGF0Zm9ybSByZXR1cm5zIGZhbHNlIGZvciBXU0wxXG4gICAgY29uc3QgZXJyb3JNZXNzYWdlID1cbiAgICAgIHBsYXRmb3JtID09PSAnd3NsJ1xuICAgICAgICA/ICdFcnJvcjogU2FuZGJveGluZyByZXF1aXJlcyBXU0wyLiBXU0wxIGlzIG5vdCBzdXBwb3J0ZWQuJ1xuICAgICAgICA6ICdFcnJvcjogU2FuZGJveGluZyBpcyBjdXJyZW50bHkgb25seSBzdXBwb3J0ZWQgb24gbWFjT1MsIExpbnV4LCBhbmQgV1NMMi4nXG4gICAgY29uc3QgbWVzc2FnZSA9IGNvbG9yKCdlcnJvcicsIHRoZW1lTmFtZSkoZXJyb3JNZXNzYWdlKVxuICAgIG9uRG9uZShtZXNzYWdlKVxuICAgIHJldHVybiBudWxsXG4gIH1cblxuICAvLyBDaGVjayBkZXBlbmRlbmNpZXMgLSBnZXQgc3RydWN0dXJlZCByZXN1bHQgd2l0aCBlcnJvcnMvd2FybmluZ3NcbiAgY29uc3QgZGVwQ2hlY2sgPSBTYW5kYm94TWFuYWdlci5jaGVja0RlcGVuZGVuY2llcygpXG5cbiAgLy8gQ2hlY2sgaWYgcGxhdGZvcm0gaXMgaW4gZW5hYmxlZFBsYXRmb3JtcyBsaXN0ICh1bmRvY3VtZW50ZWQgZW50ZXJwcmlzZSBzZXR0aW5nKVxuICBpZiAoIVNhbmRib3hNYW5hZ2VyLmlzUGxhdGZvcm1JbkVuYWJsZWRMaXN0KCkpIHtcbiAgICBjb25zdCBtZXNzYWdlID0gY29sb3IoXG4gICAgICAnZXJyb3InLFxuICAgICAgdGhlbWVOYW1lLFxuICAgICkoXG4gICAgICBgRXJyb3I6IFNhbmRib3hpbmcgaXMgZGlzYWJsZWQgZm9yIHRoaXMgcGxhdGZvcm0gKCR7cGxhdGZvcm19KSB2aWEgdGhlIGVuYWJsZWRQbGF0Zm9ybXMgc2V0dGluZy5gLFxuICAgIClcbiAgICBvbkRvbmUobWVzc2FnZSlcbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgLy8gQ2hlY2sgaWYgc2FuZGJveCBzZXR0aW5ncyBhcmUgbG9ja2VkIGJ5IGhpZ2hlci1wcmlvcml0eSBzZXR0aW5nc1xuICBpZiAoU2FuZGJveE1hbmFnZXIuYXJlU2FuZGJveFNldHRpbmdzTG9ja2VkQnlQb2xpY3koKSkge1xuICAgIGNvbnN0IG1lc3NhZ2UgPSBjb2xvcihcbiAgICAgICdlcnJvcicsXG4gICAgICB0aGVtZU5hbWUsXG4gICAgKShcbiAgICAgICdFcnJvcjogU2FuZGJveCBzZXR0aW5ncyBhcmUgb3ZlcnJpZGRlbiBieSBhIGhpZ2hlci1wcmlvcml0eSBjb25maWd1cmF0aW9uIGFuZCBjYW5ub3QgYmUgY2hhbmdlZCBsb2NhbGx5LicsXG4gICAgKVxuICAgIG9uRG9uZShtZXNzYWdlKVxuICAgIHJldHVybiBudWxsXG4gIH1cblxuICAvLyBQYXJzZSB0aGUgYXJndW1lbnRzXG4gIGNvbnN0IHRyaW1tZWRBcmdzID0gYXJncz8udHJpbSgpIHx8ICcnXG5cbiAgLy8gSWYgbm8gYXJncywgc2hvdyB0aGUgaW50ZXJhY3RpdmUgbWVudVxuICBpZiAoIXRyaW1tZWRBcmdzKSB7XG4gICAgcmV0dXJuIDxTYW5kYm94U2V0dGluZ3Mgb25Db21wbGV0ZT17b25Eb25lfSBkZXBDaGVjaz17ZGVwQ2hlY2t9IC8+XG4gIH1cblxuICAvLyBIYW5kbGUgc3ViY29tbWFuZHNcbiAgaWYgKHRyaW1tZWRBcmdzKSB7XG4gICAgY29uc3QgcGFydHMgPSB0cmltbWVkQXJncy5zcGxpdCgnICcpXG4gICAgY29uc3Qgc3ViY29tbWFuZCA9IHBhcnRzWzBdXG5cbiAgICBpZiAoc3ViY29tbWFuZCA9PT0gJ2V4Y2x1ZGUnKSB7XG4gICAgICAvLyBIYW5kbGUgZXhjbHVkZSBzdWJjb21tYW5kXG4gICAgICBjb25zdCBjb21tYW5kUGF0dGVybiA9IHRyaW1tZWRBcmdzLnNsaWNlKCdleGNsdWRlICcubGVuZ3RoKS50cmltKClcblxuICAgICAgaWYgKCFjb21tYW5kUGF0dGVybikge1xuICAgICAgICBjb25zdCBtZXNzYWdlID0gY29sb3IoXG4gICAgICAgICAgJ2Vycm9yJyxcbiAgICAgICAgICB0aGVtZU5hbWUsXG4gICAgICAgICkoXG4gICAgICAgICAgJ0Vycm9yOiBQbGVhc2UgcHJvdmlkZSBhIGNvbW1hbmQgcGF0dGVybiB0byBleGNsdWRlIChlLmcuLCAvc2FuZGJveCBleGNsdWRlIFwibnBtIHJ1biB0ZXN0OipcIiknLFxuICAgICAgICApXG4gICAgICAgIG9uRG9uZShtZXNzYWdlKVxuICAgICAgICByZXR1cm4gbnVsbFxuICAgICAgfVxuXG4gICAgICAvLyBSZW1vdmUgcXVvdGVzIGlmIHByZXNlbnRcbiAgICAgIGNvbnN0IGNsZWFuUGF0dGVybiA9IGNvbW1hbmRQYXR0ZXJuLnJlcGxhY2UoL15bXCInXXxbXCInXSQvZywgJycpXG5cbiAgICAgIC8vIEFkZCB0byBleGNsdWRlZENvbW1hbmRzXG4gICAgICBhZGRUb0V4Y2x1ZGVkQ29tbWFuZHMoY2xlYW5QYXR0ZXJuKVxuXG4gICAgICAvLyBHZXQgdGhlIGxvY2FsIHNldHRpbmdzIHBhdGggYW5kIG1ha2UgaXQgcmVsYXRpdmUgdG8gY3dkXG4gICAgICBjb25zdCBsb2NhbFNldHRpbmdzUGF0aCA9IGdldFNldHRpbmdzRmlsZVBhdGhGb3JTb3VyY2UoJ2xvY2FsU2V0dGluZ3MnKVxuICAgICAgY29uc3QgcmVsYXRpdmVQYXRoID0gbG9jYWxTZXR0aW5nc1BhdGhcbiAgICAgICAgPyByZWxhdGl2ZShnZXRDd2RTdGF0ZSgpLCBsb2NhbFNldHRpbmdzUGF0aClcbiAgICAgICAgOiAnLmNsYXVkZS9zZXR0aW5ncy5sb2NhbC5qc29uJ1xuXG4gICAgICBjb25zdCBtZXNzYWdlID0gY29sb3IoXG4gICAgICAgICdzdWNjZXNzJyxcbiAgICAgICAgdGhlbWVOYW1lLFxuICAgICAgKShgQWRkZWQgXCIke2NsZWFuUGF0dGVybn1cIiB0byBleGNsdWRlZCBjb21tYW5kcyBpbiAke3JlbGF0aXZlUGF0aH1gKVxuXG4gICAgICBvbkRvbmUobWVzc2FnZSlcbiAgICAgIHJldHVybiBudWxsXG4gICAgfSBlbHNlIHtcbiAgICAgIC8vIFVua25vd24gc3ViY29tbWFuZFxuICAgICAgY29uc3QgbWVzc2FnZSA9IGNvbG9yKFxuICAgICAgICAnZXJyb3InLFxuICAgICAgICB0aGVtZU5hbWUsXG4gICAgICApKFxuICAgICAgICBgRXJyb3I6IFVua25vd24gc3ViY29tbWFuZCBcIiR7c3ViY29tbWFuZH1cIi4gQXZhaWxhYmxlIHN1YmNvbW1hbmQ6IGV4Y2x1ZGVgLFxuICAgICAgKVxuICAgICAgb25Eb25lKG1lc3NhZ2UpXG4gICAgICByZXR1cm4gbnVsbFxuICAgIH1cbiAgfVxuXG4gIC8vIFNob3VsZCBuZXZlciByZWFjaCBoZXJlIHNpbmNlIHdlIGhhbmRsZSBhbGwgY2FzZXMgYWJvdmVcbiAgcmV0dXJuIG51bGxcbn1cbiJdLCJtYXBwaW5ncyI6IkFBQUEsU0FBU0EsUUFBUSxRQUFRLE1BQU07QUFDL0IsT0FBT0MsS0FBSyxNQUFNLE9BQU87QUFDekIsU0FBU0MsV0FBVyxRQUFRLDBCQUEwQjtBQUN0RCxTQUFTQyxlQUFlLFFBQVEsNkNBQTZDO0FBQzdFLFNBQVNDLEtBQUssUUFBUSxjQUFjO0FBQ3BDLFNBQVNDLFdBQVcsUUFBUSx5QkFBeUI7QUFDckQsU0FDRUMscUJBQXFCLEVBQ3JCQyxjQUFjLFFBQ1Qsd0NBQXdDO0FBQy9DLFNBQ0VDLHNCQUFzQixFQUN0QkMsNEJBQTRCLFFBQ3ZCLGtDQUFrQztBQUN6QyxjQUFjQyxTQUFTLFFBQVEsc0JBQXNCO0FBRXJELE9BQU8sZUFBZUMsSUFBSUEsQ0FDeEJDLE1BQU0sRUFBRSxDQUFDQyxNQUFlLENBQVIsRUFBRSxNQUFNLEVBQUUsR0FBRyxJQUFJLEVBQ2pDQyxRQUFRLEVBQUUsT0FBTyxFQUNqQkMsSUFBYSxDQUFSLEVBQUUsTUFBTSxDQUNkLEVBQUVDLE9BQU8sQ0FBQ2YsS0FBSyxDQUFDZ0IsU0FBUyxHQUFHLElBQUksQ0FBQyxDQUFDO0VBQ2pDLE1BQU1DLFFBQVEsR0FBR1Ysc0JBQXNCLENBQUMsQ0FBQztFQUN6QyxNQUFNVyxTQUFTLEVBQUVULFNBQVMsR0FBSVEsUUFBUSxDQUFDRSxLQUFLLElBQUlWLFNBQVMsSUFBSyxPQUFPO0VBRXJFLE1BQU1XLFFBQVEsR0FBR2hCLFdBQVcsQ0FBQyxDQUFDO0VBRTlCLElBQUksQ0FBQ0UsY0FBYyxDQUFDZSxtQkFBbUIsQ0FBQyxDQUFDLEVBQUU7SUFDekM7SUFDQSxNQUFNQyxZQUFZLEdBQ2hCRixRQUFRLEtBQUssS0FBSyxHQUNkLHlEQUF5RCxHQUN6RCwwRUFBMEU7SUFDaEYsTUFBTUcsT0FBTyxHQUFHcEIs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 \ No newline at end of file diff --git a/src/commands/session/index.ts b/src/commands/session/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c66187806427e72b2d7e3d63bba26bc8ecc340ec --- /dev/null +++ b/src/commands/session/index.ts @@ -0,0 +1,16 @@ +import { getIsRemoteMode } from '../../bootstrap/state.js' +import type { Command } from '../../commands.js' + +const session = { + type: 'local-jsx', + name: 'session', + aliases: ['remote'], + description: 'Show remote session URL and QR code', + isEnabled: () => getIsRemoteMode(), + get isHidden() { + return !getIsRemoteMode() + }, + load: () => import('./session.js'), +} satisfies Command + +export default session diff --git a/src/commands/skills/index.ts b/src/commands/skills/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..90e1d7f3ccbfea980f1af8d8a1a3869c1b5f3803 --- /dev/null +++ b/src/commands/skills/index.ts @@ -0,0 +1,10 @@ +import type { Command } from '../../commands.js' + +const skills = { + type: 'local-jsx', + name: 'skills', + description: 'List available skills', + load: () => import('./skills.js'), +} satisfies Command + +export default skills diff --git a/src/commands/skills/skills.tsx b/src/commands/skills/skills.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c9bf0e6cbae502bf43aebc4ef6c9c4b627a26126 --- /dev/null +++ b/src/commands/skills/skills.tsx @@ -0,0 +1,8 @@ +import * as React from 'react'; +import type { LocalJSXCommandContext } from '../../commands.js'; +import { SkillsMenu } from '../../components/skills/SkillsMenu.js'; +import type { LocalJSXCommandOnDone } from '../../types/command.js'; +export async function call(onDone: LocalJSXCommandOnDone, context: LocalJSXCommandContext): Promise { + return ; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIkxvY2FsSlNYQ29tbWFuZENvbnRleHQiLCJTa2lsbHNNZW51IiwiTG9jYWxKU1hDb21tYW5kT25Eb25lIiwiY2FsbCIsIm9uRG9uZSIsImNvbnRleHQiLCJQcm9taXNlIiwiUmVhY3ROb2RlIiwib3B0aW9ucyIsImNvbW1hbmRzIl0sInNvdXJjZXMiOlsic2tpbGxzLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB0eXBlIHsgTG9jYWxKU1hDb21tYW5kQ29udGV4dCB9IGZyb20gJy4uLy4uL2NvbW1hbmRzLmpzJ1xuaW1wb3J0IHsgU2tpbGxzTWVudSB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvc2tpbGxzL1NraWxsc01lbnUuanMnXG5pbXBvcnQgdHlwZSB7IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBjYWxsKFxuICBvbkRvbmU6IExvY2FsSlNYQ29tbWFuZE9uRG9uZSxcbiAgY29udGV4dDogTG9jYWxKU1hDb21tYW5kQ29udGV4dCxcbik6IFByb21pc2U8UmVhY3QuUmVhY3ROb2RlPiB7XG4gIHJldHVybiA8U2tpbGxzTWVudSBvbkV4aXQ9e29uRG9uZX0gY29tbWFuZHM9e2NvbnRleHQub3B0aW9ucy5jb21tYW5kc30gLz5cbn1cbiJdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixjQUFjQyxzQkFBc0IsUUFBUSxtQkFBbUI7QUFDL0QsU0FBU0MsVUFBVSxRQUFRLHVDQUF1QztBQUNsRSxjQUFjQyxxQkFBcUIsUUFBUSx3QkFBd0I7QUFFbkUsT0FBTyxlQUFlQyxJQUFJQSxDQUN4QkMsTUFBTSxFQUFFRixxQkFBcUIsRUFDN0JHLE9BQU8sRUFBRUwsc0JBQXNCLENBQ2hDLEVBQUVNLE9BQU8sQ0FBQ1AsS0FBSyxDQUFDUSxTQUFTLENBQUMsQ0FBQztFQUMxQixPQUFPLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxDQUFDSCxNQUFNLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQ0MsT0FBTyxDQUFDRyxPQUFPLENBQUNDLFFBQVEsQ0FBQyxHQUFHO0FBQzNFIiwiaWdub3JlTGlzdCI6W119 \ No newline at end of file diff --git a/src/commands/stickers/index.ts b/src/commands/stickers/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..ebca4530d310d91a8a3ee8dbfaa22800c931cd27 --- /dev/null +++ b/src/commands/stickers/index.ts @@ -0,0 +1,11 @@ +import type { Command } from '../../commands.js' + +const stickers = { + type: 'local', + name: 'stickers', + description: 'Order Claude Code stickers', + supportsNonInteractive: false, + load: () => import('./stickers.js'), +} satisfies Command + +export default stickers diff --git a/src/commands/stickers/stickers.ts b/src/commands/stickers/stickers.ts new file mode 100644 index 0000000000000000000000000000000000000000..ede51931ec6954866ca4e835ad42c0abc2543797 --- /dev/null +++ b/src/commands/stickers/stickers.ts @@ -0,0 +1,16 @@ +import type { LocalCommandResult } from '../../types/command.js' +import { openBrowser } from '../../utils/browser.js' + +export async function call(): Promise { + const url = 'https://www.stickermule.com/claudecode' + const success = await openBrowser(url) + + if (success) { + return { type: 'text', value: 'Opening sticker page in browser…' } + } else { + return { + type: 'text', + value: `Failed to open browser. Visit: ${url}`, + } + } +} diff --git a/src/commands/tag/index.ts b/src/commands/tag/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d0bd659b5208aad80296d4afb93b9aef57f8aa2 --- /dev/null +++ b/src/commands/tag/index.ts @@ -0,0 +1,12 @@ +import type { Command } from '../../commands.js' + +const tag = { + type: 'local-jsx', + name: 'tag', + description: 'Toggle a searchable tag on the current session', + isEnabled: () => process.env.USER_TYPE === 'ant', + argumentHint: '', + load: () => import('./tag.js'), +} satisfies Command + +export default tag diff --git a/src/commands/tag/tag.tsx b/src/commands/tag/tag.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3809a995153c34a7f4af9d67b18a82ff5fcd2bfb --- /dev/null +++ b/src/commands/tag/tag.tsx @@ -0,0 +1,215 @@ +import { c as _c } from "react/compiler-runtime"; +import chalk from 'chalk'; +import type { UUID } from 'crypto'; +import * as React from 'react'; +import { getSessionId } from '../../bootstrap/state.js'; +import type { CommandResultDisplay } from '../../commands.js'; +import { Select } from '../../components/CustomSelect/select.js'; +import { Dialog } from '../../components/design-system/Dialog.js'; +import { COMMON_HELP_ARGS, COMMON_INFO_ARGS } from '../../constants/xml.js'; +import { Box, Text } from '../../ink.js'; +import { logEvent } from '../../services/analytics/index.js'; +import type { LocalJSXCommandOnDone } from '../../types/command.js'; +import { recursivelySanitizeUnicode } from '../../utils/sanitization.js'; +import { getCurrentSessionTag, getTranscriptPath, saveTag } from '../../utils/sessionStorage.js'; +function ConfirmRemoveTag(t0) { + const $ = _c(11); + const { + tagName, + onConfirm, + onCancel + } = t0; + const t1 = `Current tag: #${tagName}`; + let t2; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t2 = This will remove the tag from the current session.; + $[0] = t2; + } else { + t2 = $[0]; + } + let t3; + if ($[1] !== onCancel || $[2] !== onConfirm) { + t3 = value => value === "yes" ? onConfirm() : onCancel(); + $[1] = onCancel; + $[2] = onConfirm; + $[3] = t3; + } else { + t3 = $[3]; + } + let t4; + if ($[4] === Symbol.for("react.memo_cache_sentinel")) { + t4 = [{ + label: "Yes, remove tag", + value: "yes" + }, { + label: "No, keep tag", + value: "no" + }]; + $[4] = t4; + } else { + t4 = $[4]; + } + let t5; + if ($[5] !== t3) { + t5 = {t2} { + bb61: switch (value) { + case "install": + { + handleInstall(); + break bb61; + } + case "tmux": + { + handleUseTmux(); + break bb61; + } + case "cancel": + { + onDone("cancelled"); + } + } + }} onCancel={() => onDone("cancelled")} />; + } + function renderInstalling() { + return Installing it2 using {packageManager}…This may take a moment.; + } + function renderInstallFailed() { + const options_0 = [{ + label: "Try again", + value: "retry", + description: "Retry the installation" + }]; + if (tmuxAvailable) { + options_0.push({ + label: "Use tmux instead", + value: "tmux", + description: "Falls back to tmux for teammate panes" + }); + } + options_0.push({ + label: "Cancel", + value: "cancel", + description: "Skip teammate spawning for now" + }); + return Installation failed{error && {error}}You can try installing manually:{" "}{packageManager === "uvx" ? "uv tool install it2" : packageManager === "pipx" ? "pipx install it2" : "pip install --user it2"} { + bb115: switch (value_1) { + case "retry": + { + setStep("verifying"); + verifyIt2Setup().then(result_1 => { + if (result_1.success) { + markIt2SetupComplete(); + setStep("success"); + setTimeout(onDone, 1500, "installed" as const); + } else { + setError(result_1.error || "Verification failed"); + setStep("failed"); + } + }); + break bb115; + } + case "tmux": + { + handleUseTmux(); + break bb115; + } + case "cancel": + { + onDone("cancelled"); + } + } + }} onCancel={() => onDone("cancelled")} />; + } + T1 = Pane; + t14 = "permission"; + T0 = Box; + t9 = "column"; + t10 = 1; + t11 = 1; + if ($[28] === Symbol.for("react.memo_cache_sentinel")) { + t12 = iTerm2 Split Pane Setup; + $[28] = t12; + } else { + t12 = $[28]; + } + t13 = renderContent(); + $[13] = error; + $[14] = handleInstall; + $[15] = handleUseTmux; + $[16] = onDone; + $[17] = packageManager; + $[18] = step; + $[19] = tmuxAvailable; + $[20] = T0; + $[21] = T1; + $[22] = t10; + $[23] = t11; + $[24] = t12; + $[25] = t13; + $[26] = t14; + $[27] = t9; + } else { + T0 = $[20]; + T1 = $[21]; + t10 = $[22]; + t11 = $[23]; + t12 = $[24]; + t13 = $[25]; + t14 = $[26]; + t9 = $[27]; + } + let t15; + if ($[29] !== exitState || $[30] !== step) { + t15 = step !== "installing" && step !== "verifying" && step !== "success" && {exitState.pending ? <>Press {exitState.keyName} again to exit : <>Esc to cancel}; + $[29] = exitState; + $[30] = step; + $[31] = t15; + } else { + t15 = $[31]; + } + let t16; + if ($[32] !== T0 || $[33] !== t10 || $[34] !== t11 || $[35] !== t12 || $[36] !== t13 || $[37] !== t15 || $[38] !== t9) { + t16 = {t12}{t13}{t15}; + $[32] = T0; + $[33] = t10; + $[34] = t11; + $[35] = t12; + $[36] = t13; + $[37] = t15; + $[38] = t9; + $[39] = t16; + } else { + t16 = $[39]; + } + let t17; + if ($[40] !== T1 || $[41] !== t14 || $[42] !== t16) { + t17 = {t16}; + $[40] = T1; + $[41] = t14; + $[42] = t16; + $[43] = t17; + } else { + t17 = $[43]; + } + return t17; +} +function _temp(line, i) { + return {line}; +} +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsInVzZUNhbGxiYWNrIiwidXNlRWZmZWN0IiwidXNlU3RhdGUiLCJPcHRpb25XaXRoRGVzY3JpcHRpb24iLCJTZWxlY3QiLCJQYW5lIiwiU3Bpbm5lciIsInVzZUV4aXRPbkN0cmxDRFdpdGhLZXliaW5kaW5ncyIsIkJveCIsIlRleHQiLCJ1c2VJbnB1dCIsInVzZUtleWJpbmRpbmciLCJkZXRlY3RQeXRob25QYWNrYWdlTWFuYWdlciIsImdldFB5dGhvbkFwaUluc3RydWN0aW9ucyIsImluc3RhbGxJdDIiLCJtYXJrSXQyU2V0dXBDb21wbGV0ZSIsIlB5dGhvblBhY2thZ2VNYW5hZ2VyIiwic2V0UHJlZmVyVG11eE92ZXJJdGVybTIiLCJ2ZXJpZnlJdDJTZXR1cCIsIlNldHVwU3RlcCIsIlByb3BzIiwib25Eb25lIiwicmVzdWx0IiwidG11eEF2YWlsYWJsZSIsIkl0MlNldHVwUHJvbXB0IiwidDAiLCIkIiwiX2MiLCJzdGVwIiwic2V0U3RlcCIsInBhY2thZ2VNYW5hZ2VyIiwic2V0UGFja2FnZU1hbmFnZXIiLCJlcnJvciIsInNldEVycm9yIiwiZXhpdFN0YXRlIiwidDEiLCJ0MiIsIlN5bWJvbCIsImZvciIsInRoZW4iLCJwbSIsInQzIiwiaGFuZGxlQ2FuY2VsIiwidDQiLCJ0NSIsImNvbnRleHQiLCJpc0FjdGl2ZSIsInQ2IiwiX2lucHV0Iiwia2V5IiwicmV0dXJuIiwic3VjY2VzcyIsInNldFRpbWVvdXQiLCJjb25zdCIsInQ3IiwiaGFuZGxlSW5zdGFsbCIsInJlc3VsdF8wIiwidDgiLCJoYW5kbGVVc2VUbXV4IiwiVDAiLCJUMSIsInQxMCIsInQxMSIsInQxMiIsInQxMyIsInQxNCIsInQ5IiwicmVuZGVyQ29udGVudCIsInJlbmRlckluaXRpYWxQcm9tcHQiLCJyZW5kZXJJbnN0YWxsaW5nIiwicmVuZGVySW5zdGFsbEZhaWxlZCIsInJlbmRlckFwaUluc3RydWN0aW9ucyIsInJlbmRlclZlcmlmeWluZyIsInJlbmRlclN1Y2Nlc3MiLCJyZW5kZXJGYWlsZWQiLCJvcHRpb25zIiwibGFiZWwiLCJ2YWx1ZSIsImRlc2NyaXB0aW9uIiwicHVzaCIsImJiNjEiLCJvcHRpb25zXzAiLCJ2YWx1ZV8wIiwiYmI4OSIsImluc3RydWN0aW9ucyIsIm1hcCIsIl90ZW1wIiwib3B0aW9uc18xIiwidmFsdWVfMSIsImJiMTE1IiwicmVzdWx0XzEiLCJ0MTUiLCJwZW5kaW5nIiwia2V5TmFtZSIsInQxNiIsInQxNyIsImxpbmUiLCJpIl0sInNvdXJjZXMiOlsiSXQyU2V0dXBQcm9tcHQudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBSZWFjdCwgeyB1c2VDYWxsYmFjaywgdXNlRWZmZWN0LCB1c2VTdGF0ZSB9IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHtcbiAgdHlwZSBPcHRpb25XaXRoRGVzY3JpcHRpb24sXG4gIFNlbGVjdCxcbn0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9DdXN0b21TZWxlY3QvaW5kZXguanMnXG5pbXBvcnQgeyBQYW5lIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL1BhbmUuanMnXG5pbXBvcnQgeyBTcGlubmVyIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9TcGlubmVyLmpzJ1xuaW1wb3J0IHsgdXNlRXhpdE9uQ3RybENEV2l0aEtleWJpbmRpbmdzIH0gZnJvbSAnLi4vLi4vaG9va3MvdXNlRXhpdE9uQ3RybENEV2l0aEtleWJpbmRpbmdzLmpzJ1xuLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIGN1c3RvbS1ydWxlcy9wcmVmZXItdXNlLWtleWJpbmRpbmdzIC0tIGVudGVyIHRvIHByb2NlZWQgdGhyb3VnaCBzZXR1cCBzdGVwc1xuaW1wb3J0IHsgQm94LCBUZXh0LCB1c2VJbnB1dCB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB7IHVzZUtleWJpbmRpbmcgfSBmcm9tICcuLi8uLi9rZXliaW5kaW5ncy91c2VLZXliaW5kaW5nLmpzJ1xuaW1wb3J0IHtcbiAgZGV0ZWN0UHl0aG9uUGFja2FnZU1hbmFnZXIsXG4gIGdldFB5dGhvbkFwaUluc3RydWN0aW9ucyxcbiAgaW5zdGFsbEl0MixcbiAgbWFya0l0MlNldHVwQ29tcGxldGUsXG4gIHR5cGUgUHl0aG9uUGFja2FnZU1hbmFnZXIsXG4gIHNldFByZWZlclRtdXhPdmVySXRlcm0yLFxuICB2ZXJpZnlJdDJTZXR1cCxcbn0gZnJvbSAnLi9iYWNrZW5kcy9pdDJTZXR1cC5qcydcblxudHlwZSBTZXR1cFN0ZXAgPVxuICB8ICdpbml0aWFsJ1xuICB8ICdpbnN0YWxsaW5nJ1xuICB8ICdpbnN0YWxsLWZhaWxlZCdcbiAgfCAndmVyaWZ5LWFwaSdcbiAgfCAnYXBpLWluc3RydWN0aW9ucydcbiAgfCAndmVyaWZ5aW5nJ1xuICB8ICdzdWNjZXNzJ1xuICB8ICdmYWlsZWQnXG5cbnR5cGUgUHJvcHMgPSB7XG4gIG9uRG9uZTogKHJlc3VsdDogJ2luc3RhbGxlZCcgfCAndXNlLXRtdXgnIHwgJ2NhbmNlbGxlZCcpID0+IHZvaWRcbiAgdG11eEF2YWlsYWJsZTogYm9vbGVhblxufVxuXG5leHBvcnQgZnVuY3Rpb24gSXQyU2V0dXBQcm9tcHQoe1xuICBvbkRvbmUsXG4gIHRtdXhBdmFpbGFibGUsXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IFtzdGVwLCBzZXRTdGVwXSA9IHVzZVN0YXRlPFNldHVwU3RlcD4oJ2luaXRpYWwnKVxuICBjb25zdCBbcGFja2FnZU1hbmFnZXIsIHNldFBhY2thZ2VNYW5hZ2VyXSA9XG4gICAgdXNlU3RhdGU8UHl0aG9uUGFja2FnZU1hbmFnZXIgfCBudWxsPihudWxsKVxuICBjb25zdCBbZXJyb3IsIHNldEVycm9yXSA9IHVzZVN0YXRlPHN0cmluZyB8IG51bGw+KG51bGwpXG4gIGNvbnN0IGV4aXRTdGF0ZSA9IHVzZUV4aXRPbkN0cmxDRFdpdGhLZXliaW5kaW5ncygpXG5cbiAgLy8gRGV0ZWN0IHBhY2thZ2UgbWFuYWdlciBvbiBtb3VudFxuICB1c2VFZmZlY3QoKCkgPT4ge1xuICAgIHZvaWQgZGV0ZWN0UHl0aG9uUGFja2FnZU1hbmFnZXIoKS50aGVuKHBtID0+IHtcbiAgICAgIHNldFBhY2thZ2VNYW5hZ2VyKHBtKVxuICAgIH0pXG4gIH0sIFtdKVxuXG4gIGNvbnN0IGhhbmRsZUNhbmNlbCA9IHVzZUNhbGxiYWNrKCgpID0+IHtcbiAgICBvbkRvbmUoJ2NhbmNlbGxlZCcpXG4gIH0sIFtvbkRvbmVdKVxuXG4gIHVzZUtleWJpbmRpbmcoJ2NvbmZpcm06bm8nLCBoYW5kbGVDYW5jZWwsIHtcbiAgICBjb250ZXh0OiAnQ29uZmlybWF0aW9uJyxcbiAgICBpc0FjdGl2ZTogc3RlcCAhPT0gJ2luc3RhbGxpbmcnICYmIHN0ZXAgIT09ICd2ZXJpZnlpbmcnLFxuICB9KVxuXG4gIC8vIEhhbmRsZSBrZXlib2FyZCBpbnB1dCBmb3IgdmVyaWZpY2F0aW9uIHN0ZXBcbiAgdXNlSW5wdXQoKF9pbnB1dCwga2V5KSA9PiB7XG4gICAgaWYgKHN0ZXAgPT09ICdhcGktaW5zdHJ1Y3Rpb25zJyAmJiBrZXkucmV0dXJuKSB7XG4gICAgICBzZXRTdGVwKCd2ZXJpZnlpbmcnKVxuICAgICAgdm9pZCB2ZXJpZnlJdDJTZXR1cCgpLnRoZW4ocmVzdWx0ID0+IHtcbiAgICAgICAgaWYgKHJlc3VsdC5zdWNjZXNzKSB7XG4gICAgICAgICAgbWFya0l0MlNldHVwQ29tcGxldGUoKVxuICAgICAgICAgIHNldFN0ZXAoJ3N1Y2Nlc3MnKVxuICAgICAgICAgIHNldFRpbWVvdXQob25Eb25lLCAxNTAwLCAnaW5zdGFsbGVkJyBhcyBjb25zdClcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBzZXRFcnJvcihyZXN1bHQuZXJyb3IgfHwgJ1ZlcmlmaWNhdGlvbiBmYWlsZWQnKVxuICAgICAgICAgIHNldFN0ZXAoJ2ZhaWxlZCcpXG4gICAgICAgIH1cbiAgICAgIH0pXG4gICAgfVxuICB9KVxuXG4gIC8vIEhhbmRsZSBpbnN0YWxsYXRpb25cbiAgYXN5bmMgZnVuY3Rpb24gaGFuZGxlSW5zdGFsbCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBpZiAoIXBhY2thZ2VNYW5hZ2VyKSB7XG4gICAgICBzZXRFcnJvcignTm8gUHl0aG9uIHBhY2thZ2UgbWFuYWdlciBmb3VuZCAodXZ4LCBwaXB4LCBvciBwaXApJylcbiAgICAgIHNldFN0ZXAoJ2ZhaWxlZCcpXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICBzZXRTdGVwKCdpbnN0YWxsaW5nJylcbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBpbnN0YWxsSXQyKHBhY2thZ2VNYW5hZ2VyKVxuXG4gICAgaWYgKHJlc3VsdC5zdWNjZXNzKSB7XG4gICAgICAvLyBTaG93IFB5dGhvbiBBUEkgaW5zdHJ1Y3Rpb25zXG4gICAgICBzZXRTdGVwKCdhcGktaW5zdHJ1Y3Rpb25zJylcbiAgICB9IGVsc2Uge1xuICAgICAgc2V0RXJyb3IocmVzdWx0LmVycm9yIHx8ICdJbnN0YWxsYXRpb24gZmFpbGVkJylcbiAgICAgIHNldFN0ZXAoJ2luc3RhbGwtZmFpbGVkJylcbiAgICB9XG4gIH1cblxuICAvLyBIYW5kbGUgdXNpbmcgdG11eCBpbnN0ZWFkXG4gIGZ1bmN0aW9uIGhhbmRsZVVzZVRtdXgoKTogdm9pZCB7XG4gICAgc2V0UHJlZmVyVG11eE92ZXJJdGVybTIodHJ1ZSlcbiAgICBvbkRvbmUoJ3VzZS10bXV4JylcbiAgfVxuXG4gIC8vIFJlbmRlciBiYXNlZCBvbiBjdXJyZW50IHN0ZXBcbiAgY29uc3QgcmVuZGVyQ29udGVudCA9ICgpOiBSZWFjdC5SZWFjdE5vZGUgPT4ge1xuICAgIHN3aXRjaCAoc3RlcCkge1xuICAgICAgY2FzZSAnaW5pdGlhbCc6XG4gICAgICAgIHJldHVybiByZW5kZXJJbml0aWFsUHJvbXB0KClcbiAgICAgIGNhc2UgJ2luc3RhbGxpbmcnOlxuICAgICAgICByZXR1cm4gcmVuZGVySW5zdGFsbGluZygpXG4gICAgICBjYXNlICdpbnN0YWxsLWZhaWxlZCc6XG4gICAgICAgIHJldHVybiByZW5kZXJJbnN0YWxsRmFpbGVkKClcbiAgICAgIGNhc2UgJ2FwaS1pbnN0cnVjdGlvbnMnOlxuICAgICAgICByZXR1cm4gcmVuZGVyQXBpSW5zdHJ1Y3Rpb25zKClcbiAgICAgIGNhc2UgJ3ZlcmlmeWluZyc6XG4gICAgICAgIHJldHVybiByZW5kZXJWZXJpZnlpbmcoKVxuICAgICAgY2FzZSAnc3VjY2Vzcyc6XG4gICAgICAgIHJldHVybiByZW5kZXJTdWNjZXNzKClcbiAgICAgIGNhc2UgJ2ZhaWxlZCc6XG4gICAgICAgIHJldHVybiByZW5kZXJGYWlsZWQoKVxuICAgICAgZGVmYXVsdDpcbiAgICAgICAgcmV0dXJuIG51bGxcbiAgICB9XG4gIH1cblxuICBmdW5jdGlvbiByZW5kZXJJbml0aWFsUHJvbXB0KCk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gICAgY29uc3Qgb3B0aW9uczogT3B0aW9uV2l0aERlc2NyaXB0aW9uPHN0cmluZz5bXSA9IFtcbiAgICAgIHtcbiAgICAgICAgbGFiZWw6ICdJbnN0YWxsIGl0MiBub3cnLFxuICAgICAgICB2YWx1ZTogJ2luc3RhbGwnLFxuICAgICAgICBkZXNjcmlwdGlvbjogcGFja2FnZU1hbmFnZXJcbiAgICAgICAgICA/IGBVc2VzICR7cGFja2FnZU1hbmFnZXJ9IHRvIGluc3RhbGwgdGhlIGl0MiBDTEkgdG9vbGBcbiAgICAgICAgICA6ICdSZXF1aXJlcyBQeXRob24gKHV2eCwgcGlweCwgb3IgcGlwKScsXG4gICAgICB9LFxuICAgIF1cblxuICAgIGlmICh0bXV4QXZhaWxhYmxlKSB7XG4gICAgICBvcHRpb25zLnB1c2goe1xuICAgICAgICBsYWJlbDogJ1VzZSB0bXV4IGluc3RlYWQnLFxuICAgICAgICB2YWx1ZTogJ3RtdXgnLFxuICAgICAgICBkZXNjcmlwdGlvbjogJ09wZW5zIHRlYW1tYXRlcyBpbiBhIHNlcGFyYXRlIHRtdXggc2Vzc2lvbicsXG4gICAgICB9KVxuICAgIH1cblxuICAgIG9wdGlvbnMucHVzaCh7XG4gICAgICBsYWJlbDogJ0NhbmNlbCcsXG4gICAgICB2YWx1ZTogJ2NhbmNlbCcsXG4gICAgICBkZXNjcmlwdGlvbjogJ1NraXAgdGVhbW1hdGUgc3Bhd25pbmcgZm9yIG5vdycsXG4gICAgfSlcblxuICAgIHJldHVybiAoXG4gICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIiBnYXA9ezF9PlxuICAgICAgICA8VGV4dD5cbiAgICAgICAgICBUbyB1c2UgbmF0aXZlIGlUZXJtMiBzcGxpdCBwYW5lcyBmb3IgdGVhbW1hdGVzLCB5b3UgbmVlZCB0aGV7JyAnfVxuICAgICAgICAgIDxUZXh0IGJvbGQ+aXQyPC9UZXh0PiBDTEkgdG9vbC5cbiAgICAgICAgPC9UZXh0PlxuICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICBUaGlzIGVuYWJsZXMgdGVhbW1hdGVzIHRvIGFwcGVhciBhcyBzcGxpdCBwYW5lcyB3aXRoaW4geW91ciBjdXJyZW50XG4gICAgICAgICAgd2luZG93LlxuICAgICAgICA8L1RleHQ+XG4gICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICA8U2VsZWN0XG4gICAgICAgICAgICBvcHRpb25zPXtvcHRpb25zfVxuICAgICAgICAgICAgb25DaGFuZ2U9e3ZhbHVlID0+IHtcbiAgICAgICAgICAgICAgc3dpdGNoICh2YWx1ZSkge1xuICAgICAgICAgICAgICAgIGNhc2UgJ2luc3RhbGwnOlxuICAgICAgICAgICAgICAgICAgdm9pZCBoYW5kbGVJbnN0YWxsKClcbiAgICAgICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgICAgICAgY2FzZSAndG11eCc6XG4gICAgICAgICAgICAgICAgICBoYW5kbGVVc2VUbXV4KClcbiAgICAgICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgICAgICAgY2FzZSAnY2FuY2VsJzpcbiAgICAgICAgICAgICAgICAgIG9uRG9uZSgnY2FuY2VsbGVkJylcbiAgICAgICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH19XG4gICAgICAgICAgICBvbkNhbmNlbD17KCkgPT4gb25Eb25lKCdjYW5jZWxsZWQnKX1cbiAgICAgICAgICAvPlxuICAgICAgICA8L0JveD5cbiAgICAgIDwvQm94PlxuICAgIClcbiAgfVxuXG4gIGZ1bmN0aW9uIHJlbmRlckluc3RhbGxpbmcoKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgICByZXR1cm4gKFxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCIgZ2FwPXsxfT5cbiAgICAgICAgPEJveD5cbiAgICAgICAgICA8U3Bpbm5lciAvPlxuICAgICAgICAgIDxUZXh0PiBJbnN0YWxsaW5nIGl0MiB1c2luZyB7cGFja2FnZU1hbmFnZXJ94oCmPC9UZXh0PlxuICAgICAgICA8L0JveD5cbiAgICAgICAgPFRleHQgZGltQ29sb3I+VGhpcyBtYXkgdGFrZSBhIG1vbWVudC48L1RleHQ+XG4gICAgICA8L0JveD5cbiAgICApXG4gIH1cblxuICBmdW5jdGlvbiByZW5kZXJJbnN0YWxsRmFpbGVkKCk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gICAgY29uc3Qgb3B0aW9uczogT3B0aW9uV2l0aERlc2NyaXB0aW9uPHN0cmluZz5bXSA9IFtcbiAgICAgIHtcbiAgICAgICAgbGFiZWw6ICdUcnkgYWdhaW4nLFxuICAgICAgICB2YWx1ZTogJ3JldHJ5JyxcbiAgICAgICAgZGVzY3JpcHRpb246ICdSZXRyeSB0aGUgaW5zdGFsbGF0aW9uJyxcbiAgICAgIH0sXG4gICAgXVxuXG4gICAgaWYgKHRtdXhBdmFpbGFibGUpIHtcbiAgICAgIG9wdGlvbnMucHVzaCh7XG4gICAgICAgIGxhYmVsOiAnVXNlIHRtdXggaW5zdGVhZCcsXG4gICAgICAgIHZhbHVlOiAndG11eCcsXG4gICAgICAgIGRlc2NyaXB0aW9uOiAnRmFsbHMgYmFjayB0byB0bXV4IGZvciB0ZWFtbWF0ZSBwYW5lcycsXG4gICAgICB9KVxuICAgIH1cblxuICAgIG9wdGlvbnMucHVzaCh7XG4gICAgICBsYWJlbDogJ0NhbmNlbCcsXG4gICAgICB2YWx1ZTogJ2NhbmNlbCcsXG4gICAgICBkZXNjcmlwdGlvbjogJ1NraXAgdGVhbW1hdGUgc3Bhd25pbmcgZm9yIG5vdycsXG4gICAgfSlcblxuICAgIHJldHVybiAoXG4gICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIiBnYXA9ezF9PlxuICAgICAgICA8VGV4dCBjb2xvcj1cImVycm9yXCI+SW5zdGFsbGF0aW9uIGZhaWxlZDwvVGV4dD5cbiAgICAgICAge2Vycm9yICYmIDxUZXh0IGRpbUNvbG9yPntlcnJvcn08L1RleHQ+fVxuICAgICAgICA8VGV4dCBkaW1Db2xvcj5cbiAgICAgICAgICBZb3UgY2FuIHRyeSBpbnN0YWxsaW5nIG1hbnVhbGx5OnsnICd9XG4gICAgICAgICAge3BhY2thZ2VNYW5hZ2VyID09PSAndXZ4J1xuICAgICAgICAgICAgPyAndXYgdG9vbCBpbnN0YWxsIGl0MidcbiAgICAgICAgICAgIDogcGFja2FnZU1hbmFnZXIgPT09ICdwaXB4J1xuICAgICAgICAgICAgICA/ICdwaXB4IGluc3RhbGwgaXQyJ1xuICAgICAgICAgICAgICA6ICdwaXAgaW5zdGFsbCAtLXVzZXIgaXQyJ31cbiAgICAgICAgPC9UZXh0PlxuICAgICAgICA8Qm94IG1hcmdpblRvcD17MX0+XG4gICAgICAgICAgPFNlbGVjdFxuICAgICAgICAgICAgb3B0aW9ucz17b3B0aW9uc31cbiAgICAgICAgICAgIG9uQ2hhbmdlPXt2YWx1ZSA9PiB7XG4gICAgICAgICAgICAgIHN3aXRjaCAodmFsdWUpIHtcbiAgICAgICAgICAgICAgICBjYXNlICdyZXRyeSc6XG4gICAgICAgICAgICAgICAgICB2b2lkIGhhbmRsZUluc3RhbGwoKVxuICAgICAgICAgICAgICAgICAgYnJlYWtcbiAgICAgICAgICAgICAgICBjYXNlICd0bXV4JzpcbiAgICAgICAgICAgICAgICAgIGhhbmRsZVVzZVRtdXgoKVxuICAgICAgICAgICAgICAgICAgYnJlYWtcbiAgICAgICAgICAgICAgICBjYXNlICdjYW5jZWwnOlxuICAgICAgICAgICAgICAgICAgb25Eb25lKCdjYW5jZWxsZWQnKVxuICAgICAgICAgICAgICAgICAgYnJlYWtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfX1cbiAgICAgICAgICAgIG9uQ2FuY2VsPXsoKSA9PiBvbkRvbmUoJ2NhbmNlbGxlZCcpfVxuICAgICAgICAgIC8+XG4gICAgICAgIDwvQm94PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgZnVuY3Rpb24gcmVuZGVyQXBpSW5zdHJ1Y3Rpb25zKCk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gICAgY29uc3QgaW5zdHJ1Y3Rpb25zID0gZ2V0UHl0aG9uQXBpSW5zdHJ1Y3Rpb25zKClcbiAgICByZXR1cm4gKFxuICAgICAgPEJveCBmbGV4RGlyZWN0aW9uPVwiY29sdW1uXCIgZ2FwPXsxfT5cbiAgICAgICAgPFRleHQgY29sb3I9XCJzdWNjZXNzXCI+4pyTIGl0MiBpbnN0YWxsZWQgc3VjY2Vzc2Z1bGx5PC9UZXh0PlxuICAgICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIiBtYXJnaW5Ub3A9ezF9PlxuICAgICAgICAgIHtpbnN0cnVjdGlvbnMubWFwKChsaW5lLCBpKSA9PiAoXG4gICAgICAgICAgICA8VGV4dCBrZXk9e2l9PntsaW5lfTwvVGV4dD5cbiAgICAgICAgICApKX1cbiAgICAgICAgPC9Cb3g+XG4gICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj5QcmVzcyBFbnRlciB3aGVuIHJlYWR5IHRvIHZlcmlmeeKApjwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICA8L0JveD5cbiAgICApXG4gIH1cblxuICBmdW5jdGlvbiByZW5kZXJWZXJpZnlpbmcoKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgICByZXR1cm4gKFxuICAgICAgPEJveD5cbiAgICAgICAgPFNwaW5uZXIgLz5cbiAgICAgICAgPFRleHQ+IFZlcmlmeWluZyBpdDIgY2FuIGNvbW11bmljYXRlIHdpdGggaVRlcm0y4oCmPC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgZnVuY3Rpb24gcmVuZGVyU3VjY2VzcygpOiBSZWFjdC5SZWFjdE5vZGUge1xuICAgIHJldHVybiAoXG4gICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIj5cbiAgICAgICAgPFRleHQgY29sb3I9XCJzdWNjZXNzXCI+4pyTIGlUZXJtMiBzcGxpdCBwYW5lIHN1cHBvcnQgaXMgcmVhZHk8L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPlRlYW1tYXRlcyB3aWxsIG5vdyBhcHBlYXIgYXMgc3BsaXQgcGFuZXMuPC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgZnVuY3Rpb24gcmVuZGVyRmFpbGVkKCk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gICAgY29uc3Qgb3B0aW9uczogT3B0aW9uV2l0aERlc2NyaXB0aW9uPHN0cmluZz5bXSA9IFtcbiAgICAgIHtcbiAgICAgICAgbGFiZWw6ICdUcnkgYWdhaW4nLFxuICAgICAgICB2YWx1ZTogJ3JldHJ5JyxcbiAgICAgICAgZGVzY3JpcHRpb246ICdWZXJpZnkgdGhlIGNvbm5lY3Rpb24gYWdhaW4nLFxuICAgICAgfSxcbiAgICBdXG5cbiAgICBpZiAodG11eEF2YWlsYWJsZSkge1xuICAgICAgb3B0aW9ucy5wdXNoKHtcbiAgICAgICAgbGFiZWw6ICdVc2UgdG11eCBpbnN0ZWFkJyxcbiAgICAgICAgdmFsdWU6ICd0bXV4JyxcbiAgICAgICAgZGVzY3JpcHRpb246ICdGYWxscyBiYWNrIHRvIHRtdXggZm9yIHRlYW1tYXRlIHBhbmVzJyxcbiAgICAgIH0pXG4gICAgfVxuXG4gICAgb3B0aW9ucy5wdXNoKHtcbiAgICAgIGxhYmVsOiAnQ2FuY2VsJyxcbiAgICAgIHZhbHVlOiAnY2FuY2VsJyxcbiAgICAgIGRlc2NyaXB0aW9uOiAnU2tpcCB0ZWFtbWF0ZSBzcGF3bmluZyBmb3Igbm93JyxcbiAgICB9KVxuXG4gICAgcmV0dXJuIChcbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiIGdhcD17MX0+XG4gICAgICAgIDxUZXh0IGNvbG9yPVwiZXJyb3JcIj5WZXJpZmljYXRpb24gZmFpbGVkPC9UZXh0PlxuICAgICAgICB7ZXJyb3IgJiYgPFRleHQgZGltQ29sb3I+e2Vycm9yfTwvVGV4dD59XG4gICAgICAgIDxUZXh0Pk1ha2Ugc3VyZTo8L1RleHQ+XG4gICAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiIHBhZGRpbmdMZWZ0PXsyfT5cbiAgICAgICAgICA8VGV4dD7CtyBQeXRob24gQVBJIGlzIGVuYWJsZWQgaW4gaVRlcm0yIHByZWZlcmVuY2VzPC9UZXh0PlxuICAgICAgICAgIDxUZXh0PsK3IFlvdSBtYXkgbmVlZCB0byByZXN0YXJ0IGlUZXJtMiBhZnRlciBlbmFibGluZzwvVGV4dD5cbiAgICAgICAgPC9Cb3g+XG4gICAgICAgIDxCb3ggbWFyZ2luVG9wPXsxfT5cbiAgICAgICAgICA8U2VsZWN0XG4gICAgICAgICAgICBvcHRpb25zPXtvcHRpb25zfVxuICAgICAgICAgICAgb25DaGFuZ2U9e3ZhbHVlID0+IHtcbiAgICAgICAgICAgICAgc3dpdGNoICh2YWx1ZSkge1xuICAgICAgICAgICAgICAgIGNhc2UgJ3JldHJ5JzpcbiAgICAgICAgICAgICAgICAgIHNldFN0ZXAoJ3ZlcmlmeWluZycpXG4gICAgICAgICAgICAgICAgICB2b2lkIHZlcmlmeUl0MlNldHVwKCkudGhlbihyZXN1bHQgPT4ge1xuICAgICAgICAgICAgICAgICAgICBpZiAocmVzdWx0LnN1Y2Nlc3MpIHtcbiAgICAgICAgICAgICAgICAgICAgICBtYXJrSXQyU2V0dXBDb21wbGV0ZSgpXG4gICAgICAgICAgICAgICAgICAgICAgc2V0U3RlcCgnc3VjY2VzcycpXG4gICAgICAgICAgICAgICAgICAgICAgc2V0VGltZW91dChvbkRvbmUsIDE1MDAsICdpbnN0YWxsZWQnIGFzIGNvbnN0KVxuICAgICAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICAgIHNldEVycm9yKHJlc3VsdC5lcnJvciB8fCAnVmVyaWZpY2F0aW9uIGZhaWxlZCcpXG4gICAgICAgICAgICAgICAgICAgICAgc2V0U3RlcCgnZmFpbGVkJylcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgfSlcbiAgICAgICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgICAgICAgY2FzZSAndG11eCc6XG4gICAgICAgICAgICAgICAgICBoYW5kbGVVc2VUbXV4KClcbiAgICAgICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgICAgICAgY2FzZSAnY2FuY2VsJzpcbiAgICAgICAgICAgICAgICAgIG9uRG9uZSgnY2FuY2VsbGVkJylcbiAgICAgICAgICAgICAgICAgIGJyZWFrXG4gICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH19XG4gICAgICAgICAgICBvbkNhbmNlbD17KCkgPT4gb25Eb25lKCdjYW5jZWxsZWQnKX1cbiAgICAgICAgICAvPlxuICAgICAgICA8L0JveD5cbiAgICAgIDwvQm94PlxuICAgIClcbiAgfVxuXG4gIHJldHVybiAoXG4gICAgPFBhbmUgY29sb3I9XCJwZXJtaXNzaW9uXCI+XG4gICAgICA8Qm94IGZsZXhEaXJlY3Rpb249XCJjb2x1bW5cIiBnYXA9ezF9IHBhZGRpbmdCb3R0b209ezF9PlxuICAgICAgICA8VGV4dCBib2xkIGNvbG9yPVwicGVybWlzc2lvblwiPlxuICAgICAgICAgIGlUZXJtMiBTcGxpdCBQYW5lIFNldHVwXG4gICAgICAgIDwvVGV4dD5cbiAgICAgICAge3JlbmRlckNvbnRlbnQoKX1cbiAgICAgICAge3N0ZXAgIT09ICdpbnN0YWxsaW5nJyAmJlxuICAgICAgICAgIHN0ZXAgIT09ICd2ZXJpZnlpbmcnICYmXG4gICAgICAgICAgc3RlcCAhPT0gJ3N1Y2Nlc3MnICYmIChcbiAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yIGl0YWxpYz5cbiAgICAgICAgICAgICAge2V4aXRTdGF0ZS5wZW5kaW5nID8gKFxuICAgICAgICAgICAgICAgIDw+UHJlc3Mge2V4aXRTdGF0ZS5rZXlOYW1lfSBhZ2FpbiB0byBleGl0PC8+XG4gICAgICAgICAgICAgICkgOiAoXG4gICAgICAgICAgICAgICAgPD5Fc2MgdG8gY2FuY2VsPC8+XG4gICAgICAgICAgICAgICl9XG4gICAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgKX1cbiAgICAgIDwvQm94PlxuICAgIDwvUGFuZT5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBT0EsS0FBSyxJQUFJQyxXQUFXLEVBQUVDLFNBQVMsRUFBRUMsUUFBUSxRQUFRLE9BQU87QUFDL0QsU0FDRSxLQUFLQyxxQkFBcUIsRUFDMUJDLE1BQU0sUUFDRCx3Q0FBd0M7QUFDL0MsU0FBU0MsSUFBSSxRQUFRLHdDQUF3QztBQUM3RCxTQUFTQyxPQUFPLFFBQVEsNkJBQTZCO0FBQ3JELFNBQVNDLDhCQUE4QixRQUFRLCtDQUErQztBQUM5RjtBQUNBLFNBQVNDLEdBQUcsRUFBRUMsSUFBSSxFQUFFQyxRQUFRLFFBQVEsY0FBYztBQUNsRCxTQUFTQyxhQUFhLFFBQVEsb0NBQW9DO0FBQ2xFLFNBQ0VDLDBCQUEwQixFQUMxQkMsd0JBQXdCLEVBQ3hCQyxVQUFVLEVBQ1ZDLG9CQUFvQixFQUNwQixLQUFLQyxvQkFBb0IsRUFDekJDLHVCQUF1QixFQUN2QkMsY0FBYyxRQUNULHdCQUF3QjtBQUUvQixLQUFLQyxTQUFTLEdBQ1YsU0FBUyxHQUNULFlBQVksR0FDWixnQkFBZ0IsR0FDaEIsWUFBWSxHQUNaLGtCQUFrQixHQUNsQixXQUFXLEdBQ1gsU0FBUyxHQUNULFFBQVE7QUFFWixLQUFLQyxLQUFLLEdBQUc7RUFDWEMsTUFBTSxFQUFFLENBQUNDLE1BQU0sRUFBRSxXQUFXLEdBQUcsVUFBVSxHQUFHLFdBQVcsRUFBRSxHQUFHLElBQUk7RUFDaEVDLGFBQWEsRUFBRSxPQUFPO0FBQ3hCLENBQUM7QUFFRCxPQUFPLFNBQUFDLGVBQUFDLEVBQUE7RUFBQSxNQUFBQyxDQUFBLEdBQUFDLEVBQUE7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 \ No newline at end of file diff --git a/src/utils/swarm/constants.ts b/src/utils/swarm/constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..8ebfb194b81d78d6fec32e8de76e5968c3b7177b --- /dev/null +++ b/src/utils/swarm/constants.ts @@ -0,0 +1,33 @@ +export const TEAM_LEAD_NAME = 'team-lead' +export const SWARM_SESSION_NAME = 'claude-swarm' +export const SWARM_VIEW_WINDOW_NAME = 'swarm-view' +export const TMUX_COMMAND = 'tmux' +export const HIDDEN_SESSION_NAME = 'claude-hidden' + +/** + * Gets the socket name for external swarm sessions (when user is not in tmux). + * Uses a separate socket to isolate swarm operations from user's tmux sessions. + * Includes PID to ensure multiple Claude instances don't conflict. + */ +export function getSwarmSocketName(): string { + return `claude-swarm-${process.pid}` +} + +/** + * Environment variable to override the command used to spawn teammate instances. + * If not set, defaults to process.execPath (the current Claude binary). + * This allows customization for different environments or testing. + */ +export const TEAMMATE_COMMAND_ENV_VAR = 'CLAUDE_CODE_TEAMMATE_COMMAND' + +/** + * Environment variable set on spawned teammates to indicate their assigned color. + * Used for colored output and pane identification. + */ +export const TEAMMATE_COLOR_ENV_VAR = 'CLAUDE_CODE_AGENT_COLOR' + +/** + * Environment variable set on spawned teammates to require plan mode before implementation. + * When set to 'true', teammates must enter plan mode and get approval before writing code. + */ +export const PLAN_MODE_REQUIRED_ENV_VAR = 'CLAUDE_CODE_PLAN_MODE_REQUIRED' diff --git a/src/utils/swarm/inProcessRunner.ts b/src/utils/swarm/inProcessRunner.ts new file mode 100644 index 0000000000000000000000000000000000000000..c85d959e1ad2ccc4ff3c8ba99cb5b725ba746755 --- /dev/null +++ b/src/utils/swarm/inProcessRunner.ts @@ -0,0 +1,1552 @@ +/** + * In-process teammate runner + * + * Wraps runAgent() for in-process teammates, providing: + * - AsyncLocalStorage-based context isolation via runWithTeammateContext() + * - Progress tracking and AppState updates + * - Idle notification to leader when complete + * - Plan mode approval flow support + * - Cleanup on completion or abort + */ + +import { feature } from 'bun:bundle' +import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/messages.mjs' +import { getSystemPrompt } from '../../constants/prompts.js' +import { TEAMMATE_MESSAGE_TAG } from '../../constants/xml.js' +import type { CanUseToolFn } from '../../hooks/useCanUseTool.js' +import { + processMailboxPermissionResponse, + registerPermissionCallback, + unregisterPermissionCallback, +} from '../../hooks/useSwarmPermissionPoller.js' +import { + type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + logEvent, +} from '../../services/analytics/index.js' +import { getAutoCompactThreshold } from '../../services/compact/autoCompact.js' +import { + buildPostCompactMessages, + compactConversation, + ERROR_MESSAGE_USER_ABORT, +} from '../../services/compact/compact.js' +import { resetMicrocompactState } from '../../services/compact/microCompact.js' +import type { AppState } from '../../state/AppState.js' +import type { Tool, ToolUseContext } from '../../Tool.js' +import { appendTeammateMessage } from '../../tasks/InProcessTeammateTask/InProcessTeammateTask.js' +import type { + InProcessTeammateTaskState, + TeammateIdentity, +} from '../../tasks/InProcessTeammateTask/types.js' +import { appendCappedMessage } from '../../tasks/InProcessTeammateTask/types.js' +import { + createActivityDescriptionResolver, + createProgressTracker, + getProgressUpdate, + updateProgressFromMessage, +} from '../../tasks/LocalAgentTask/LocalAgentTask.js' +import type { CustomAgentDefinition } from '../../tools/AgentTool/loadAgentsDir.js' +import { runAgent } from '../../tools/AgentTool/runAgent.js' +import { awaitClassifierAutoApproval } from '../../tools/BashTool/bashPermissions.js' +import { BASH_TOOL_NAME } from '../../tools/BashTool/toolName.js' +import { SEND_MESSAGE_TOOL_NAME } from '../../tools/SendMessageTool/constants.js' +import { TASK_CREATE_TOOL_NAME } from '../../tools/TaskCreateTool/constants.js' +import { TASK_GET_TOOL_NAME } from '../../tools/TaskGetTool/constants.js' +import { TASK_LIST_TOOL_NAME } from '../../tools/TaskListTool/constants.js' +import { TASK_UPDATE_TOOL_NAME } from '../../tools/TaskUpdateTool/constants.js' +import { TEAM_CREATE_TOOL_NAME } from '../../tools/TeamCreateTool/constants.js' +import { TEAM_DELETE_TOOL_NAME } from '../../tools/TeamDeleteTool/constants.js' +import type { Message } from '../../types/message.js' +import type { PermissionDecision } from '../../types/permissions.js' +import { + createAssistantAPIErrorMessage, + createUserMessage, +} from '../../utils/messages.js' +import { evictTaskOutput } from '../../utils/task/diskOutput.js' +import { evictTerminalTask } from '../../utils/task/framework.js' +import { tokenCountWithEstimation } from '../../utils/tokens.js' +import { createAbortController } from '../abortController.js' +import { type AgentContext, runWithAgentContext } from '../agentContext.js' +import { count } from '../array.js' +import { logForDebugging } from '../debug.js' +import { cloneFileStateCache } from '../fileStateCache.js' +import { + SUBAGENT_REJECT_MESSAGE, + SUBAGENT_REJECT_MESSAGE_WITH_REASON_PREFIX, +} from '../messages.js' +import type { ModelAlias } from '../model/aliases.js' +import { + applyPermissionUpdates, + persistPermissionUpdates, +} from '../permissions/PermissionUpdate.js' +import type { PermissionUpdate } from '../permissions/PermissionUpdateSchema.js' +import { hasPermissionsToUseTool } from '../permissions/permissions.js' +import { emitTaskTerminatedSdk } from '../sdkEventQueue.js' +import { sleep } from '../sleep.js' +import { jsonStringify } from '../slowOperations.js' +import { asSystemPrompt } from '../systemPromptType.js' +import { claimTask, listTasks, type Task, updateTask } from '../tasks.js' +import type { TeammateContext } from '../teammateContext.js' +import { runWithTeammateContext } from '../teammateContext.js' +import { + createIdleNotification, + getLastPeerDmSummary, + isPermissionResponse, + isShutdownRequest, + markMessageAsReadByIndex, + readMailbox, + writeToMailbox, +} from '../teammateMailbox.js' +import { unregisterAgent as unregisterPerfettoAgent } from '../telemetry/perfettoTracing.js' +import { createContentReplacementState } from '../toolResultStorage.js' +import { TEAM_LEAD_NAME } from './constants.js' +import { + getLeaderSetToolPermissionContext, + getLeaderToolUseConfirmQueue, +} from './leaderPermissionBridge.js' +import { + createPermissionRequest, + sendPermissionRequestViaMailbox, +} from './permissionSync.js' +import { TEAMMATE_SYSTEM_PROMPT_ADDENDUM } from './teammatePromptAddendum.js' + +type SetAppStateFn = (updater: (prev: AppState) => AppState) => void + +const PERMISSION_POLL_INTERVAL_MS = 500 + +/** + * Creates a canUseTool function for in-process teammates that properly resolves + * 'ask' permissions via the UI rather than treating them as denials. + * + * Always uses the leader's ToolUseConfirm dialog with a worker badge when + * the bridge is available, giving teammates the same tool-specific UI + * (BashPermissionRequest, FileEditToolDiff, etc.) as the leader's own tools. + * + * Falls back to the mailbox system when the bridge is unavailable: + * sends a permission request to the leader's inbox, waits for the response + * in the teammate's own mailbox. + */ +function createInProcessCanUseTool( + identity: TeammateIdentity, + abortController: AbortController, + onPermissionWaitMs?: (waitMs: number) => void, +): CanUseToolFn { + return async ( + tool, + input, + toolUseContext, + assistantMessage, + toolUseID, + forceDecision, + ) => { + const result = + forceDecision ?? + (await hasPermissionsToUseTool( + tool, + input, + toolUseContext, + assistantMessage, + toolUseID, + )) + + // Pass through allow/deny decisions directly + if (result.behavior !== 'ask') { + return result + } + + // For bash commands, try classifier auto-approval before showing leader dialog. + // Agents await the classifier result (rather than racing it against user + // interaction like the main agent). + if ( + feature('BASH_CLASSIFIER') && + tool.name === BASH_TOOL_NAME && + result.pendingClassifierCheck + ) { + const classifierDecision = await awaitClassifierAutoApproval( + result.pendingClassifierCheck, + abortController.signal, + toolUseContext.options.isNonInteractiveSession, + ) + if (classifierDecision) { + return { + behavior: 'allow', + updatedInput: input as Record, + decisionReason: classifierDecision, + } + } + } + + // Check if aborted before showing UI + if (abortController.signal.aborted) { + return { behavior: 'ask', message: SUBAGENT_REJECT_MESSAGE } + } + + const appState = toolUseContext.getAppState() + + const description = await (tool as Tool).description(input as never, { + isNonInteractiveSession: toolUseContext.options.isNonInteractiveSession, + toolPermissionContext: appState.toolPermissionContext, + tools: toolUseContext.options.tools, + }) + + if (abortController.signal.aborted) { + return { behavior: 'ask', message: SUBAGENT_REJECT_MESSAGE } + } + + const setToolUseConfirmQueue = getLeaderToolUseConfirmQueue() + + // Standard path: use ToolUseConfirm dialog with worker badge + if (setToolUseConfirmQueue) { + return new Promise(resolve => { + let decisionMade = false + const permissionStartMs = Date.now() + + // Report permission wait time to the caller so it can be + // subtracted from the displayed elapsed time. + const reportPermissionWait = () => { + onPermissionWaitMs?.(Date.now() - permissionStartMs) + } + + const onAbortListener = () => { + if (decisionMade) return + decisionMade = true + reportPermissionWait() + resolve({ behavior: 'ask', message: SUBAGENT_REJECT_MESSAGE }) + setToolUseConfirmQueue(queue => + queue.filter(item => item.toolUseID !== toolUseID), + ) + } + + abortController.signal.addEventListener('abort', onAbortListener, { + once: true, + }) + + setToolUseConfirmQueue(queue => [ + ...queue, + { + assistantMessage, + tool: tool as Tool, + description, + input, + toolUseContext, + toolUseID, + permissionResult: result, + permissionPromptStartTimeMs: permissionStartMs, + workerBadge: identity.color + ? { name: identity.agentName, color: identity.color } + : undefined, + onUserInteraction() { + // No-op for teammates (no classifier auto-approval) + }, + onAbort() { + if (decisionMade) return + decisionMade = true + abortController.signal.removeEventListener( + 'abort', + onAbortListener, + ) + reportPermissionWait() + resolve({ behavior: 'ask', message: SUBAGENT_REJECT_MESSAGE }) + }, + async onAllow( + updatedInput: Record, + permissionUpdates: PermissionUpdate[], + feedback?: string, + contentBlocks?: ContentBlockParam[], + ) { + if (decisionMade) return + decisionMade = true + abortController.signal.removeEventListener( + 'abort', + onAbortListener, + ) + reportPermissionWait() + persistPermissionUpdates(permissionUpdates) + // Write back permission updates to the leader's shared context + if (permissionUpdates.length > 0) { + const setToolPermissionContext = + getLeaderSetToolPermissionContext() + if (setToolPermissionContext) { + const currentAppState = toolUseContext.getAppState() + const updatedContext = applyPermissionUpdates( + currentAppState.toolPermissionContext, + permissionUpdates, + ) + // Preserve the leader's mode to prevent workers' + // transformed 'acceptEdits' context from leaking back + // to the coordinator + setToolPermissionContext(updatedContext, { + preserveMode: true, + }) + } + } + const trimmedFeedback = feedback?.trim() + resolve({ + behavior: 'allow', + updatedInput, + userModified: false, + acceptFeedback: trimmedFeedback || undefined, + ...(contentBlocks && + contentBlocks.length > 0 && { contentBlocks }), + }) + }, + onReject(feedback?: string, contentBlocks?: ContentBlockParam[]) { + if (decisionMade) return + decisionMade = true + abortController.signal.removeEventListener( + 'abort', + onAbortListener, + ) + reportPermissionWait() + const message = feedback + ? `${SUBAGENT_REJECT_MESSAGE_WITH_REASON_PREFIX}${feedback}` + : SUBAGENT_REJECT_MESSAGE + resolve({ behavior: 'ask', message, contentBlocks }) + }, + async recheckPermission() { + if (decisionMade) return + const freshResult = await hasPermissionsToUseTool( + tool, + input, + toolUseContext, + assistantMessage, + toolUseID, + ) + if (freshResult.behavior === 'allow') { + decisionMade = true + abortController.signal.removeEventListener( + 'abort', + onAbortListener, + ) + reportPermissionWait() + setToolUseConfirmQueue(queue => + queue.filter(item => item.toolUseID !== toolUseID), + ) + resolve({ + ...freshResult, + updatedInput: input, + userModified: false, + }) + } + }, + }, + ]) + }) + } + + // Fallback: use mailbox system when leader UI queue is unavailable + return new Promise(resolve => { + const request = createPermissionRequest({ + toolName: (tool as Tool).name, + toolUseId: toolUseID, + input, + description, + permissionSuggestions: result.suggestions, + workerId: identity.agentId, + workerName: identity.agentName, + workerColor: identity.color, + teamName: identity.teamName, + }) + + // Register callback to be invoked when the leader responds + registerPermissionCallback({ + requestId: request.id, + toolUseId: toolUseID, + onAllow( + updatedInput: Record | undefined, + permissionUpdates: PermissionUpdate[], + _feedback?: string, + contentBlocks?: ContentBlockParam[], + ) { + cleanup() + persistPermissionUpdates(permissionUpdates) + const finalInput = + updatedInput && Object.keys(updatedInput).length > 0 + ? updatedInput + : input + resolve({ + behavior: 'allow', + updatedInput: finalInput, + userModified: false, + ...(contentBlocks && contentBlocks.length > 0 && { contentBlocks }), + }) + }, + onReject(feedback?: string, contentBlocks?: ContentBlockParam[]) { + cleanup() + const message = feedback + ? `${SUBAGENT_REJECT_MESSAGE_WITH_REASON_PREFIX}${feedback}` + : SUBAGENT_REJECT_MESSAGE + resolve({ behavior: 'ask', message, contentBlocks }) + }, + }) + + // Send request to leader's mailbox + void sendPermissionRequestViaMailbox(request) + + // Poll teammate's mailbox for the response + const pollInterval = setInterval( + async (abortController, cleanup, resolve, identity, request) => { + if (abortController.signal.aborted) { + cleanup() + resolve({ behavior: 'ask', message: SUBAGENT_REJECT_MESSAGE }) + return + } + + const allMessages = await readMailbox( + identity.agentName, + identity.teamName, + ) + for (let i = 0; i < allMessages.length; i++) { + const msg = allMessages[i] + if (msg && !msg.read) { + const parsed = isPermissionResponse(msg.text) + if (parsed && parsed.request_id === request.id) { + await markMessageAsReadByIndex( + identity.agentName, + identity.teamName, + i, + ) + if (parsed.subtype === 'success') { + processMailboxPermissionResponse({ + requestId: parsed.request_id, + decision: 'approved', + updatedInput: parsed.response?.updated_input, + permissionUpdates: parsed.response?.permission_updates, + }) + } else { + processMailboxPermissionResponse({ + requestId: parsed.request_id, + decision: 'rejected', + feedback: parsed.error, + }) + } + return // Callback already resolves the promise + } + } + } + }, + PERMISSION_POLL_INTERVAL_MS, + abortController, + cleanup, + resolve, + identity, + request, + ) + + const onAbortListener = () => { + cleanup() + resolve({ behavior: 'ask', message: SUBAGENT_REJECT_MESSAGE }) + } + + abortController.signal.addEventListener('abort', onAbortListener, { + once: true, + }) + + function cleanup() { + clearInterval(pollInterval) + unregisterPermissionCallback(request.id) + abortController.signal.removeEventListener('abort', onAbortListener) + } + }) + } +} + +/** + * Formats a message as XML for injection into the conversation. + * This ensures the model sees messages in the same format as tmux teammates. + */ +function formatAsTeammateMessage( + from: string, + content: string, + color?: string, + summary?: string, +): string { + const colorAttr = color ? ` color="${color}"` : '' + const summaryAttr = summary ? ` summary="${summary}"` : '' + return `<${TEAMMATE_MESSAGE_TAG} teammate_id="${from}"${colorAttr}${summaryAttr}>\n${content}\n` +} + +/** + * Configuration for running an in-process teammate. + */ +export type InProcessRunnerConfig = { + /** Teammate identity for context */ + identity: TeammateIdentity + /** Task ID in AppState */ + taskId: string + /** Initial prompt for the teammate */ + prompt: string + /** Optional agent definition (for specialized agents) */ + agentDefinition?: CustomAgentDefinition + /** Teammate context for AsyncLocalStorage */ + teammateContext: TeammateContext + /** Parent's tool use context */ + toolUseContext: ToolUseContext + /** Abort controller linked to parent */ + abortController: AbortController + /** Optional model override for this teammate */ + model?: string + /** Optional system prompt override for this teammate */ + systemPrompt?: string + /** How to apply the system prompt: 'replace' or 'append' to default */ + systemPromptMode?: 'default' | 'replace' | 'append' + /** Tool permissions to auto-allow for this teammate */ + allowedTools?: string[] + /** Whether this teammate can show permission prompts for unlisted tools. + * When false (default), unlisted tools are auto-denied. */ + allowPermissionPrompts?: boolean + /** Short description of the task (used as summary for the initial prompt header) */ + description?: string + /** request_id of the API call that spawned this teammate, for lineage + * tracing on tengu_api_* events. */ + invokingRequestId?: string +} + +/** + * Result from running an in-process teammate. + */ +export type InProcessRunnerResult = { + /** Whether the run completed successfully */ + success: boolean + /** Error message if failed */ + error?: string + /** Messages produced by the agent */ + messages: Message[] +} + +/** + * Updates task state in AppState. + */ +function updateTaskState( + taskId: string, + updater: (task: InProcessTeammateTaskState) => InProcessTeammateTaskState, + setAppState: SetAppStateFn, +): void { + setAppState(prev => { + const task = prev.tasks[taskId] + if (!task || task.type !== 'in_process_teammate') { + return prev + } + const updated = updater(task) + if (updated === task) { + return prev + } + return { + ...prev, + tasks: { + ...prev.tasks, + [taskId]: updated, + }, + } + }) +} + +/** + * Sends a message to the leader's file-based mailbox. + * Uses the same mailbox system as tmux teammates for consistency. + */ +async function sendMessageToLeader( + from: string, + text: string, + color: string | undefined, + teamName: string, +): Promise { + await writeToMailbox( + TEAM_LEAD_NAME, + { + from, + text, + timestamp: new Date().toISOString(), + color, + }, + teamName, + ) +} + +/** + * Sends idle notification to the leader via file-based mailbox. + * Uses agentName (not agentId) for consistency with process-based teammates. + */ +async function sendIdleNotification( + agentName: string, + agentColor: string | undefined, + teamName: string, + options?: { + idleReason?: 'available' | 'interrupted' | 'failed' + summary?: string + completedTaskId?: string + completedStatus?: 'resolved' | 'blocked' | 'failed' + failureReason?: string + }, +): Promise { + const notification = createIdleNotification(agentName, options) + + await sendMessageToLeader( + agentName, + jsonStringify(notification), + agentColor, + teamName, + ) +} + +/** + * Find an available task from the team's task list. + * A task is available if it's pending, has no owner, and is not blocked. + */ +function findAvailableTask(tasks: Task[]): Task | undefined { + const unresolvedTaskIds = new Set( + tasks.filter(t => t.status !== 'completed').map(t => t.id), + ) + + return tasks.find(task => { + if (task.status !== 'pending') return false + if (task.owner) return false + return task.blockedBy.every(id => !unresolvedTaskIds.has(id)) + }) +} + +/** + * Format a task as a prompt for the teammate to work on. + */ +function formatTaskAsPrompt(task: Task): string { + let prompt = `Complete all open tasks. Start with task #${task.id}: \n\n ${task.subject}` + + if (task.description) { + prompt += `\n\n${task.description}` + } + + return prompt +} + +/** + * Try to claim an available task from the team's task list. + * Returns the formatted prompt if a task was claimed, or undefined if none available. + */ +async function tryClaimNextTask( + taskListId: string, + agentName: string, +): Promise { + try { + const tasks = await listTasks(taskListId) + const availableTask = findAvailableTask(tasks) + + if (!availableTask) { + return undefined + } + + const result = await claimTask(taskListId, availableTask.id, agentName) + + if (!result.success) { + logForDebugging( + `[inProcessRunner] Failed to claim task #${availableTask.id}: ${result.reason}`, + ) + return undefined + } + + // Also set status to in_progress so the UI reflects it immediately + await updateTask(taskListId, availableTask.id, { status: 'in_progress' }) + + logForDebugging( + `[inProcessRunner] Claimed task #${availableTask.id}: ${availableTask.subject}`, + ) + + return formatTaskAsPrompt(availableTask) + } catch (err) { + logForDebugging(`[inProcessRunner] Error checking task list: ${err}`) + return undefined + } +} + +/** + * Result of waiting for messages. + */ +type WaitResult = + | { + type: 'shutdown_request' + request: ReturnType + originalMessage: string + } + | { + type: 'new_message' + message: string + from: string + color?: string + summary?: string + } + | { + type: 'aborted' + } + +/** + * Waits for new prompts or shutdown request. + * Polls the teammate's mailbox every 500ms, checking for: + * - Shutdown request from leader (returned to caller for model decision) + * - New messages/prompts from leader + * - Abort signal + * + * This keeps the teammate alive in 'idle' state instead of terminating. + * Does NOT auto-approve shutdown - the model should make that decision. + */ +async function waitForNextPromptOrShutdown( + identity: TeammateIdentity, + abortController: AbortController, + taskId: string, + getAppState: () => AppState, + setAppState: SetAppStateFn, + taskListId: string, +): Promise { + const POLL_INTERVAL_MS = 500 + + logForDebugging( + `[inProcessRunner] ${identity.agentName} starting poll loop (abort=${abortController.signal.aborted})`, + ) + + let pollCount = 0 + while (!abortController.signal.aborted) { + // Check for in-memory pending messages on every iteration (from transcript viewing) + const appState = getAppState() + const task = appState.tasks[taskId] + if ( + task && + task.type === 'in_process_teammate' && + task.pendingUserMessages.length > 0 + ) { + const message = task.pendingUserMessages[0]! // Safe: checked length > 0 + // Pop the message from the queue + setAppState(prev => { + const prevTask = prev.tasks[taskId] + if (!prevTask || prevTask.type !== 'in_process_teammate') { + return prev + } + return { + ...prev, + tasks: { + ...prev.tasks, + [taskId]: { + ...prevTask, + pendingUserMessages: prevTask.pendingUserMessages.slice(1), + }, + }, + } + }) + logForDebugging( + `[inProcessRunner] ${identity.agentName} found pending user message (poll #${pollCount})`, + ) + return { + type: 'new_message', + message, + from: 'user', + } + } + + // Wait before next poll (skip on first iteration to check immediately) + if (pollCount > 0) { + await sleep(POLL_INTERVAL_MS) + } + pollCount++ + + // Check for abort + if (abortController.signal.aborted) { + logForDebugging( + `[inProcessRunner] ${identity.agentName} aborted while waiting (poll #${pollCount})`, + ) + return { type: 'aborted' } + } + + // Check for messages in mailbox + logForDebugging( + `[inProcessRunner] ${identity.agentName} poll #${pollCount}: checking mailbox`, + ) + try { + // Read all messages and scan unread for shutdown requests first. + // Shutdown requests are prioritized over regular messages to prevent + // starvation when peer-to-peer messages flood the queue. + const allMessages = await readMailbox( + identity.agentName, + identity.teamName, + ) + + // Scan all unread messages for shutdown requests (highest priority). + // readMailbox() already reads all messages from disk, so this scan + // adds only ~1-2ms of JSON parsing overhead. + let shutdownIndex = -1 + let shutdownParsed: ReturnType = null + for (let i = 0; i < allMessages.length; i++) { + const m = allMessages[i] + if (m && !m.read) { + const parsed = isShutdownRequest(m.text) + if (parsed) { + shutdownIndex = i + shutdownParsed = parsed + break + } + } + } + + if (shutdownIndex !== -1) { + const msg = allMessages[shutdownIndex]! + const skippedUnread = count( + allMessages.slice(0, shutdownIndex), + m => !m.read, + ) + logForDebugging( + `[inProcessRunner] ${identity.agentName} received shutdown request from ${shutdownParsed?.from} (prioritized over ${skippedUnread} unread messages)`, + ) + await markMessageAsReadByIndex( + identity.agentName, + identity.teamName, + shutdownIndex, + ) + return { + type: 'shutdown_request', + request: shutdownParsed, + originalMessage: msg.text, + } + } + + // No shutdown request found. Prioritize team-lead messages over peer + // messages — the leader represents user intent and coordination, so + // their messages should not be starved behind peer-to-peer chatter. + // Fall back to FIFO for peer messages. + let selectedIndex = -1 + + // Check for unread team-lead messages first + for (let i = 0; i < allMessages.length; i++) { + const m = allMessages[i] + if (m && !m.read && m.from === TEAM_LEAD_NAME) { + selectedIndex = i + break + } + } + + // Fall back to first unread message (any sender) + if (selectedIndex === -1) { + selectedIndex = allMessages.findIndex(m => !m.read) + } + + if (selectedIndex !== -1) { + const msg = allMessages[selectedIndex] + if (msg) { + logForDebugging( + `[inProcessRunner] ${identity.agentName} received new message from ${msg.from} (index ${selectedIndex})`, + ) + await markMessageAsReadByIndex( + identity.agentName, + identity.teamName, + selectedIndex, + ) + return { + type: 'new_message', + message: msg.text, + from: msg.from, + color: msg.color, + summary: msg.summary, + } + } + } + } catch (err) { + logForDebugging( + `[inProcessRunner] ${identity.agentName} poll error: ${err}`, + ) + // Continue polling even if one read fails + } + + // Check the team's task list for unclaimed tasks + const taskPrompt = await tryClaimNextTask(taskListId, identity.agentName) + if (taskPrompt) { + return { + type: 'new_message', + message: taskPrompt, + from: 'task-list', + } + } + } + + logForDebugging( + `[inProcessRunner] ${identity.agentName} exiting poll loop (abort=${abortController.signal.aborted}, polls=${pollCount})`, + ) + return { type: 'aborted' } +} + +/** + * Runs an in-process teammate with a continuous prompt loop. + * + * Executes runAgent() within the teammate's AsyncLocalStorage context, + * tracks progress, updates task state, sends idle notification on completion, + * then waits for new prompts or shutdown requests. + * + * Unlike background tasks, teammates stay alive and can receive multiple prompts. + * The loop only exits on abort or after shutdown is approved by the model. + * + * @param config - Runner configuration + * @returns Result with messages and success status + */ +export async function runInProcessTeammate( + config: InProcessRunnerConfig, +): Promise { + const { + identity, + taskId, + prompt, + description, + agentDefinition, + teammateContext, + toolUseContext, + abortController, + model, + systemPrompt, + systemPromptMode, + allowedTools, + allowPermissionPrompts, + invokingRequestId, + } = config + const { setAppState } = toolUseContext + + logForDebugging( + `[inProcessRunner] Starting agent loop for ${identity.agentId}`, + ) + + // Create AgentContext for analytics attribution + const agentContext: AgentContext = { + agentId: identity.agentId, + parentSessionId: identity.parentSessionId, + agentName: identity.agentName, + teamName: identity.teamName, + agentColor: identity.color, + planModeRequired: identity.planModeRequired, + isTeamLead: false, + agentType: 'teammate', + invokingRequestId, + invocationKind: 'spawn', + invocationEmitted: false, + } + + // Build system prompt based on systemPromptMode + let teammateSystemPrompt: string + if (systemPromptMode === 'replace' && systemPrompt) { + teammateSystemPrompt = systemPrompt + } else { + const fullSystemPromptParts = await getSystemPrompt( + toolUseContext.options.tools, + toolUseContext.options.mainLoopModel, + undefined, + toolUseContext.options.mcpClients, + ) + + const systemPromptParts = [ + ...fullSystemPromptParts, + TEAMMATE_SYSTEM_PROMPT_ADDENDUM, + ] + + // If custom agent definition provided, append its prompt + if (agentDefinition) { + const customPrompt = agentDefinition.getSystemPrompt() + if (customPrompt) { + systemPromptParts.push(`\n# Custom Agent Instructions\n${customPrompt}`) + } + + // Log agent memory loaded event for in-process teammates + if (agentDefinition.memory) { + logEvent('tengu_agent_memory_loaded', { + ...(process.env.USER_TYPE === 'ant' + ? { + agent_type: + agentDefinition.agentType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + } + : {}), + scope: + agentDefinition.memory as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + source: + 'in-process-teammate' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + }) + } + } + + // Append mode: add provided system prompt after default + if (systemPromptMode === 'append' && systemPrompt) { + systemPromptParts.push(systemPrompt) + } + + teammateSystemPrompt = systemPromptParts.join('\n') + } + + // Resolve agent definition - use full system prompt with teammate addendum + // IMPORTANT: Set permissionMode to 'default' so teammates always get full tool + // access regardless of the leader's permission mode. + const resolvedAgentDefinition: CustomAgentDefinition = { + agentType: identity.agentName, + whenToUse: `In-process teammate: ${identity.agentName}`, + getSystemPrompt: () => teammateSystemPrompt, + // Inject team-essential tools so teammates can always respond to + // shutdown requests, send messages, and coordinate via the task list, + // even with explicit tool lists + tools: agentDefinition?.tools + ? [ + ...new Set([ + ...agentDefinition.tools, + SEND_MESSAGE_TOOL_NAME, + TEAM_CREATE_TOOL_NAME, + TEAM_DELETE_TOOL_NAME, + TASK_CREATE_TOOL_NAME, + TASK_GET_TOOL_NAME, + TASK_LIST_TOOL_NAME, + TASK_UPDATE_TOOL_NAME, + ]), + ] + : ['*'], + source: 'projectSettings', + permissionMode: 'default', + // Propagate model from custom agent definition so getAgentModel() + // can use it as a fallback when no tool-level model is specified + ...(agentDefinition?.model ? { model: agentDefinition.model } : {}), + } + + // All messages across all prompts + const allMessages: Message[] = [] + // Wrap initial prompt with XML for proper styling in transcript view + const wrappedInitialPrompt = formatAsTeammateMessage( + 'team-lead', + prompt, + undefined, + description, + ) + let currentPrompt = wrappedInitialPrompt + let shouldExit = false + + // Try to claim an available task immediately so the UI can show activity + // from the very start. The idle loop handles claiming for subsequent tasks. + // Use parentSessionId as the task list ID since the leader creates tasks + // under its session ID, not the team name. + await tryClaimNextTask(identity.parentSessionId, identity.agentName) + + try { + // Add initial prompt to task.messages for display (wrapped with XML) + updateTaskState( + taskId, + task => ({ + ...task, + messages: appendCappedMessage( + task.messages, + createUserMessage({ content: wrappedInitialPrompt }), + ), + }), + setAppState, + ) + + // Per-teammate content replacement state. The while-loop below calls + // runAgent repeatedly over an accumulating `allMessages` buffer (which + // carries FULL original tool result content, not previews — query() yields + // originals, enforcement is non-mutating). Without persisting state across + // iterations, each call gets a fresh empty state from createSubagentContext + // and makes holistic replace-globally-largest decisions, diverging from + // earlier iterations' incremental frozen-first decisions → wire prefix + // differs → cache miss. Gated on parent to inherit feature-flag-off. + let teammateReplacementState = toolUseContext.contentReplacementState + ? createContentReplacementState() + : undefined + + // Main teammate loop - runs until abort or shutdown approved + while (!abortController.signal.aborted && !shouldExit) { + logForDebugging( + `[inProcessRunner] ${identity.agentId} processing prompt: ${currentPrompt.substring(0, 50)}...`, + ) + + // Create a per-turn abort controller for this iteration. + // This allows Escape to stop current work without killing the whole teammate. + // The lifecycle abortController still kills the whole teammate if needed. + const currentWorkAbortController = createAbortController() + + // Store the work controller in task state so UI can abort it + updateTaskState( + taskId, + task => ({ ...task, currentWorkAbortController }), + setAppState, + ) + + // Prepare prompt messages for this iteration + // For the first iteration, start fresh + // For subsequent iterations, pass accumulated messages as context + const userMessage = createUserMessage({ content: currentPrompt }) + const promptMessages: Message[] = [userMessage] + + // Check if compaction is needed before building context + let contextMessages = allMessages + const tokenCount = tokenCountWithEstimation(allMessages) + if ( + tokenCount > + getAutoCompactThreshold(toolUseContext.options.mainLoopModel) + ) { + logForDebugging( + `[inProcessRunner] ${identity.agentId} compacting history (${tokenCount} tokens)`, + ) + // Create an isolated copy of toolUseContext so that compaction + // does not clear the main session's readFileState cache or + // trigger the main session's UI callbacks. + const isolatedContext: ToolUseContext = { + ...toolUseContext, + readFileState: cloneFileStateCache(toolUseContext.readFileState), + onCompactProgress: undefined, + setStreamMode: undefined, + } + const compactedSummary = await compactConversation( + allMessages, + isolatedContext, + { + systemPrompt: asSystemPrompt([]), + userContext: {}, + systemContext: {}, + toolUseContext: isolatedContext, + forkContextMessages: [], + }, + true, // suppressFollowUpQuestions + undefined, // customInstructions + true, // isAutoCompact + ) + contextMessages = buildPostCompactMessages(compactedSummary) + // Reset microcompact state since full compact replaces all + // messages — old tool IDs are no longer relevant + resetMicrocompactState() + // Reset content replacement state — compact replaces all messages + // so old tool_use_ids are gone. Stale Map entries are harmless + // (UUID keys never match) but accumulate memory over long runs. + if (teammateReplacementState) { + teammateReplacementState = createContentReplacementState() + } + // Update allMessages in place with compacted version + allMessages.length = 0 + allMessages.push(...contextMessages) + + // Mirror compaction into task.messages — otherwise the AppState + // mirror grows unbounded (500 turns = 500+ messages, 10-50MB). + // Replace with the compacted messages, matching allMessages. + updateTaskState( + taskId, + task => ({ ...task, messages: [...contextMessages, userMessage] }), + setAppState, + ) + } + + // Pass previous messages as context to preserve conversation history + // allMessages accumulates all previous messages (user + assistant) from prior iterations + const forkContextMessages = + contextMessages.length > 0 ? [...contextMessages] : undefined + + // Add the user message to allMessages so it's included in future context + // This ensures the full conversation (user + assistant turns) is preserved + allMessages.push(userMessage) + + // Create fresh progress tracker for this prompt + const tracker = createProgressTracker() + const resolveActivity = createActivityDescriptionResolver( + toolUseContext.options.tools, + ) + const iterationMessages: Message[] = [] + + // Read current permission mode from task state (may have been cycled by leader via Shift+Tab) + const currentAppState = toolUseContext.getAppState() + const currentTask = currentAppState.tasks[taskId] + const currentPermissionMode = + currentTask && currentTask.type === 'in_process_teammate' + ? currentTask.permissionMode + : 'default' + const iterationAgentDefinition = { + ...resolvedAgentDefinition, + permissionMode: currentPermissionMode, + } + + // Track if this iteration was interrupted by work abort (not lifecycle abort) + let workWasAborted = false + + // Run agent within contexts + await runWithTeammateContext(teammateContext, async () => { + return runWithAgentContext(agentContext, async () => { + // Mark task as running (not idle) + updateTaskState( + taskId, + task => ({ ...task, status: 'running', isIdle: false }), + setAppState, + ) + + // Run the normal agent loop - same runAgent() used by AgentTool/subagents. + // This calls query() internally, so we share the core API infrastructure. + // Pass forkContextMessages to preserve conversation history across prompts. + // In-process teammates are async but run in the same process as the leader, + // so they CAN show permission prompts (unlike true background agents). + // Use currentWorkAbortController so Escape stops this turn only, not the teammate. + for await (const message of runAgent({ + agentDefinition: iterationAgentDefinition, + promptMessages, + toolUseContext, + canUseTool: createInProcessCanUseTool( + identity, + currentWorkAbortController, + (waitMs: number) => { + updateTaskState( + taskId, + task => ({ + ...task, + totalPausedMs: (task.totalPausedMs ?? 0) + waitMs, + }), + setAppState, + ) + }, + ), + isAsync: true, + canShowPermissionPrompts: allowPermissionPrompts ?? true, + forkContextMessages, + querySource: 'agent:custom', + override: { abortController: currentWorkAbortController }, + model: model as ModelAlias | undefined, + preserveToolUseResults: true, + availableTools: toolUseContext.options.tools, + allowedTools, + contentReplacementState: teammateReplacementState, + })) { + // Check lifecycle abort first (kills whole teammate) + if (abortController.signal.aborted) { + logForDebugging( + `[inProcessRunner] ${identity.agentId} lifecycle aborted`, + ) + break + } + + // Check work abort (stops current turn only) + if (currentWorkAbortController.signal.aborted) { + logForDebugging( + `[inProcessRunner] ${identity.agentId} current work aborted (Escape pressed)`, + ) + workWasAborted = true + break + } + + iterationMessages.push(message) + allMessages.push(message) + + updateProgressFromMessage( + tracker, + message, + resolveActivity, + toolUseContext.options.tools, + ) + const progress = getProgressUpdate(tracker) + + updateTaskState( + taskId, + task => { + // Track in-progress tool use IDs for animation in transcript view + let inProgressToolUseIDs = task.inProgressToolUseIDs + if (message.type === 'assistant') { + for (const block of message.message.content) { + if (block.type === 'tool_use') { + inProgressToolUseIDs = new Set([ + ...(inProgressToolUseIDs ?? []), + block.id, + ]) + } + } + } else if (message.type === 'user') { + const content = message.message.content + if (Array.isArray(content)) { + for (const block of content) { + if ( + typeof block === 'object' && + 'type' in block && + block.type === 'tool_result' + ) { + if (inProgressToolUseIDs) { + inProgressToolUseIDs = new Set(inProgressToolUseIDs) + inProgressToolUseIDs.delete(block.tool_use_id) + } + } + } + } + } + + return { + ...task, + progress, + messages: appendCappedMessage(task.messages, message), + inProgressToolUseIDs, + } + }, + setAppState, + ) + } + + return { success: true, messages: iterationMessages } + }) + }) + + // Clear the work controller from state (it's no longer valid) + updateTaskState( + taskId, + task => ({ ...task, currentWorkAbortController: undefined }), + setAppState, + ) + + // Check if lifecycle aborted during agent run (kills whole teammate) + if (abortController.signal.aborted) { + break + } + + // If work was aborted (Escape), log it and add interrupt message, then continue to idle state + if (workWasAborted) { + logForDebugging( + `[inProcessRunner] ${identity.agentId} work interrupted, returning to idle`, + ) + + // Add interrupt message to teammate's messages so it appears in their scrollback + const interruptMessage = createAssistantAPIErrorMessage({ + content: ERROR_MESSAGE_USER_ABORT, + }) + updateTaskState( + taskId, + task => ({ + ...task, + messages: appendCappedMessage(task.messages, interruptMessage), + }), + setAppState, + ) + } + + // Check if already idle before updating (to skip duplicate notification) + const prevAppState = toolUseContext.getAppState() + const prevTask = prevAppState.tasks[taskId] + const wasAlreadyIdle = + prevTask?.type === 'in_process_teammate' && prevTask.isIdle + + // Mark task as idle (NOT completed) and notify any waiters + updateTaskState( + taskId, + task => { + // Call any registered idle callbacks + task.onIdleCallbacks?.forEach(cb => cb()) + return { ...task, isIdle: true, onIdleCallbacks: [] } + }, + setAppState, + ) + + // Note: We do NOT automatically send the teammate's response to the leader. + // Teammates should use the Teammate tool to communicate with the leader. + // This matches process-based teammates where output is not visible to the leader. + + // Only send idle notification on transition to idle (not if already idle) + if (!wasAlreadyIdle) { + await sendIdleNotification( + identity.agentName, + identity.color, + identity.teamName, + { + idleReason: workWasAborted ? 'interrupted' : 'available', + summary: getLastPeerDmSummary(allMessages), + }, + ) + } else { + logForDebugging( + `[inProcessRunner] Skipping duplicate idle notification for ${identity.agentName}`, + ) + } + + logForDebugging( + `[inProcessRunner] ${identity.agentId} finished prompt, waiting for next`, + ) + + // Wait for next message or shutdown + const waitResult = await waitForNextPromptOrShutdown( + identity, + abortController, + taskId, + toolUseContext.getAppState, + setAppState, + identity.parentSessionId, + ) + + switch (waitResult.type) { + case 'shutdown_request': + // Pass shutdown request to model for decision + // Format as teammate-message for consistency with how tmux teammates receive it + // The model will use approveShutdown or rejectShutdown tool + logForDebugging( + `[inProcessRunner] ${identity.agentId} received shutdown request - passing to model`, + ) + currentPrompt = formatAsTeammateMessage( + waitResult.request?.from || 'team-lead', + waitResult.originalMessage, + ) + // Add shutdown request to task.messages for transcript display + appendTeammateMessage( + taskId, + createUserMessage({ content: currentPrompt }), + setAppState, + ) + break + + case 'new_message': + // New prompt from leader or teammate + logForDebugging( + `[inProcessRunner] ${identity.agentId} received new message from ${waitResult.from}`, + ) + // Messages from the user should be plain text (not wrapped in XML) + // Messages from other teammates get XML wrapper for identification + if (waitResult.from === 'user') { + currentPrompt = waitResult.message + } else { + currentPrompt = formatAsTeammateMessage( + waitResult.from, + waitResult.message, + waitResult.color, + waitResult.summary, + ) + // Add to task.messages for transcript display (only for non-user messages) + // Messages from 'user' come from pendingUserMessages which are already + // added by injectUserMessageToTeammate + appendTeammateMessage( + taskId, + createUserMessage({ content: currentPrompt }), + setAppState, + ) + } + break + + case 'aborted': + logForDebugging( + `[inProcessRunner] ${identity.agentId} aborted while waiting`, + ) + shouldExit = true + break + } + } + + // Mark as completed when exiting the loop + let alreadyTerminal = false + let toolUseId: string | undefined + updateTaskState( + taskId, + task => { + // killInProcessTeammate may have already set status:killed + + // notified:true + cleared fields. Don't overwrite (would flip + // killed → completed and double-emit the SDK bookend). + if (task.status !== 'running') { + alreadyTerminal = true + return task + } + toolUseId = task.toolUseId + task.onIdleCallbacks?.forEach(cb => cb()) + task.unregisterCleanup?.() + return { + ...task, + status: 'completed' as const, + notified: true, + endTime: Date.now(), + messages: task.messages?.length ? [task.messages.at(-1)!] : undefined, + pendingUserMessages: [], + inProgressToolUseIDs: undefined, + abortController: undefined, + unregisterCleanup: undefined, + currentWorkAbortController: undefined, + onIdleCallbacks: [], + } + }, + setAppState, + ) + void evictTaskOutput(taskId) + // Eagerly evict task from AppState since it's been consumed + evictTerminalTask(taskId, setAppState) + // notified:true pre-set → no XML notification → print.ts won't emit + // the SDK task_notification. Close the task_started bookend directly. + if (!alreadyTerminal) { + emitTaskTerminatedSdk(taskId, 'completed', { + toolUseId, + summary: identity.agentId, + }) + } + + unregisterPerfettoAgent(identity.agentId) + return { success: true, messages: allMessages } + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error' + + logForDebugging( + `[inProcessRunner] Agent ${identity.agentId} failed: ${errorMessage}`, + ) + + // Mark task as failed and notify any waiters + let alreadyTerminal = false + let toolUseId: string | undefined + updateTaskState( + taskId, + task => { + if (task.status !== 'running') { + alreadyTerminal = true + return task + } + toolUseId = task.toolUseId + task.onIdleCallbacks?.forEach(cb => cb()) + task.unregisterCleanup?.() + return { + ...task, + status: 'failed' as const, + notified: true, + error: errorMessage, + isIdle: true, + endTime: Date.now(), + onIdleCallbacks: [], + messages: task.messages?.length ? [task.messages.at(-1)!] : undefined, + pendingUserMessages: [], + inProgressToolUseIDs: undefined, + abortController: undefined, + unregisterCleanup: undefined, + currentWorkAbortController: undefined, + } + }, + setAppState, + ) + void evictTaskOutput(taskId) + // Eagerly evict task from AppState since it's been consumed + evictTerminalTask(taskId, setAppState) + // notified:true pre-set → no XML notification → close SDK bookend directly. + if (!alreadyTerminal) { + emitTaskTerminatedSdk(taskId, 'failed', { + toolUseId, + summary: identity.agentId, + }) + } + + // Send idle notification with failure via file-based mailbox + await sendIdleNotification( + identity.agentName, + identity.color, + identity.teamName, + { + idleReason: 'failed', + completedStatus: 'failed', + failureReason: errorMessage, + }, + ) + + unregisterPerfettoAgent(identity.agentId) + return { + success: false, + error: errorMessage, + messages: allMessages, + } + } +} + +/** + * Starts an in-process teammate in the background. + * + * This is the main entry point called after spawn. It starts the agent + * execution loop in a fire-and-forget manner. + * + * @param config - Runner configuration + */ +export function startInProcessTeammate(config: InProcessRunnerConfig): void { + // Extract agentId before the closure so the catch handler doesn't retain + // the full config object (including toolUseContext) while the promise is + // pending - which can be hours for a long-running teammate. + const agentId = config.identity.agentId + void runInProcessTeammate(config).catch(error => { + logForDebugging(`[inProcessRunner] Unhandled error in ${agentId}: ${error}`) + }) +} diff --git a/src/utils/swarm/leaderPermissionBridge.ts b/src/utils/swarm/leaderPermissionBridge.ts new file mode 100644 index 0000000000000000000000000000000000000000..4405b7314ace0e85e3b318d31b261a3a797f9371 --- /dev/null +++ b/src/utils/swarm/leaderPermissionBridge.ts @@ -0,0 +1,54 @@ +/** + * Leader Permission Bridge + * + * Module-level bridge that allows the REPL to register its setToolUseConfirmQueue + * and setToolPermissionContext functions for in-process teammates to use. + * + * When an in-process teammate requests permissions, it uses the standard + * ToolUseConfirm dialog rather than the worker permission badge. This bridge + * makes the REPL's queue setter and permission context setter accessible + * from non-React code in the in-process runner. + */ + +import type { ToolUseConfirm } from '../../components/permissions/PermissionRequest.js' +import type { ToolPermissionContext } from '../../Tool.js' + +export type SetToolUseConfirmQueueFn = ( + updater: (prev: ToolUseConfirm[]) => ToolUseConfirm[], +) => void + +export type SetToolPermissionContextFn = ( + context: ToolPermissionContext, + options?: { preserveMode?: boolean }, +) => void + +let registeredSetter: SetToolUseConfirmQueueFn | null = null +let registeredPermissionContextSetter: SetToolPermissionContextFn | null = null + +export function registerLeaderToolUseConfirmQueue( + setter: SetToolUseConfirmQueueFn, +): void { + registeredSetter = setter +} + +export function getLeaderToolUseConfirmQueue(): SetToolUseConfirmQueueFn | null { + return registeredSetter +} + +export function unregisterLeaderToolUseConfirmQueue(): void { + registeredSetter = null +} + +export function registerLeaderSetToolPermissionContext( + setter: SetToolPermissionContextFn, +): void { + registeredPermissionContextSetter = setter +} + +export function getLeaderSetToolPermissionContext(): SetToolPermissionContextFn | null { + return registeredPermissionContextSetter +} + +export function unregisterLeaderSetToolPermissionContext(): void { + registeredPermissionContextSetter = null +} diff --git a/src/utils/swarm/permissionSync.ts b/src/utils/swarm/permissionSync.ts new file mode 100644 index 0000000000000000000000000000000000000000..001e7a264c61794af4228dbe49b619fa78c315c9 --- /dev/null +++ b/src/utils/swarm/permissionSync.ts @@ -0,0 +1,928 @@ +/** + * Synchronized Permission Prompts for Agent Swarms + * + * This module provides infrastructure for coordinating permission prompts across + * multiple agents in a swarm. When a worker agent needs permission for a tool use, + * it can forward the request to the team leader, who can then approve or deny it. + * + * The system uses the teammate mailbox for message passing: + * - Workers send permission requests to the leader's mailbox + * - Leaders send permission responses to the worker's mailbox + * + * Flow: + * 1. Worker agent encounters a permission prompt + * 2. Worker sends a permission_request message to the leader's mailbox + * 3. Leader polls for mailbox messages and detects permission requests + * 4. User approves/denies via the leader's UI + * 5. Leader sends a permission_response message to the worker's mailbox + * 6. Worker polls mailbox for responses and continues execution + */ + +import { mkdir, readdir, readFile, unlink, writeFile } from 'fs/promises' +import { join } from 'path' +import { z } from 'zod/v4' +import { logForDebugging } from '../debug.js' +import { getErrnoCode } from '../errors.js' +import { lazySchema } from '../lazySchema.js' +import * as lockfile from '../lockfile.js' +import { logError } from '../log.js' +import type { PermissionUpdate } from '../permissions/PermissionUpdateSchema.js' +import { jsonParse, jsonStringify } from '../slowOperations.js' +import { + getAgentId, + getAgentName, + getTeammateColor, + getTeamName, +} from '../teammate.js' +import { + createPermissionRequestMessage, + createPermissionResponseMessage, + createSandboxPermissionRequestMessage, + createSandboxPermissionResponseMessage, + writeToMailbox, +} from '../teammateMailbox.js' +import { getTeamDir, readTeamFileAsync } from './teamHelpers.js' + +/** + * Full request schema for a permission request from a worker to the leader + */ +export const SwarmPermissionRequestSchema = lazySchema(() => + z.object({ + /** Unique identifier for this request */ + id: z.string(), + /** Worker's CLAUDE_CODE_AGENT_ID */ + workerId: z.string(), + /** Worker's CLAUDE_CODE_AGENT_NAME */ + workerName: z.string(), + /** Worker's CLAUDE_CODE_AGENT_COLOR */ + workerColor: z.string().optional(), + /** Team name for routing */ + teamName: z.string(), + /** Tool name requiring permission (e.g., "Bash", "Edit") */ + toolName: z.string(), + /** Original toolUseID from worker's context */ + toolUseId: z.string(), + /** Human-readable description of the tool use */ + description: z.string(), + /** Serialized tool input */ + input: z.record(z.string(), z.unknown()), + /** Suggested permission rules from the permission result */ + permissionSuggestions: z.array(z.unknown()), + /** Status of the request */ + status: z.enum(['pending', 'approved', 'rejected']), + /** Who resolved the request */ + resolvedBy: z.enum(['worker', 'leader']).optional(), + /** Timestamp when resolved */ + resolvedAt: z.number().optional(), + /** Rejection feedback message */ + feedback: z.string().optional(), + /** Modified input if changed by resolver */ + updatedInput: z.record(z.string(), z.unknown()).optional(), + /** "Always allow" rules applied during resolution */ + permissionUpdates: z.array(z.unknown()).optional(), + /** Timestamp when request was created */ + createdAt: z.number(), + }), +) + +export type SwarmPermissionRequest = z.infer< + ReturnType +> + +/** + * Resolution data returned when leader/worker resolves a request + */ +export type PermissionResolution = { + /** Decision: approved or rejected */ + decision: 'approved' | 'rejected' + /** Who resolved it */ + resolvedBy: 'worker' | 'leader' + /** Optional feedback message if rejected */ + feedback?: string + /** Optional updated input if the resolver modified it */ + updatedInput?: Record + /** Permission updates to apply (e.g., "always allow" rules) */ + permissionUpdates?: PermissionUpdate[] +} + +/** + * Get the base directory for a team's permission requests + * Path: ~/.claude/teams/{teamName}/permissions/ + */ +export function getPermissionDir(teamName: string): string { + return join(getTeamDir(teamName), 'permissions') +} + +/** + * Get the pending directory for a team + */ +function getPendingDir(teamName: string): string { + return join(getPermissionDir(teamName), 'pending') +} + +/** + * Get the resolved directory for a team + */ +function getResolvedDir(teamName: string): string { + return join(getPermissionDir(teamName), 'resolved') +} + +/** + * Ensure the permissions directory structure exists (async) + */ +async function ensurePermissionDirsAsync(teamName: string): Promise { + const permDir = getPermissionDir(teamName) + const pendingDir = getPendingDir(teamName) + const resolvedDir = getResolvedDir(teamName) + + for (const dir of [permDir, pendingDir, resolvedDir]) { + await mkdir(dir, { recursive: true }) + } +} + +/** + * Get the path to a pending request file + */ +function getPendingRequestPath(teamName: string, requestId: string): string { + return join(getPendingDir(teamName), `${requestId}.json`) +} + +/** + * Get the path to a resolved request file + */ +function getResolvedRequestPath(teamName: string, requestId: string): string { + return join(getResolvedDir(teamName), `${requestId}.json`) +} + +/** + * Generate a unique request ID + */ +export function generateRequestId(): string { + return `perm-${Date.now()}-${Math.random().toString(36).substring(2, 9)}` +} + +/** + * Create a new SwarmPermissionRequest object + */ +export function createPermissionRequest(params: { + toolName: string + toolUseId: string + input: Record + description: string + permissionSuggestions?: unknown[] + teamName?: string + workerId?: string + workerName?: string + workerColor?: string +}): SwarmPermissionRequest { + const teamName = params.teamName || getTeamName() + const workerId = params.workerId || getAgentId() + const workerName = params.workerName || getAgentName() + const workerColor = params.workerColor || getTeammateColor() + + if (!teamName) { + throw new Error('Team name is required for permission requests') + } + if (!workerId) { + throw new Error('Worker ID is required for permission requests') + } + if (!workerName) { + throw new Error('Worker name is required for permission requests') + } + + return { + id: generateRequestId(), + workerId, + workerName, + workerColor, + teamName, + toolName: params.toolName, + toolUseId: params.toolUseId, + description: params.description, + input: params.input, + permissionSuggestions: params.permissionSuggestions || [], + status: 'pending', + createdAt: Date.now(), + } +} + +/** + * Write a permission request to the pending directory with file locking + * Called by worker agents when they need permission approval from the leader + * + * @returns The written request + */ +export async function writePermissionRequest( + request: SwarmPermissionRequest, +): Promise { + await ensurePermissionDirsAsync(request.teamName) + + const pendingPath = getPendingRequestPath(request.teamName, request.id) + const lockDir = getPendingDir(request.teamName) + + // Create a directory-level lock file for atomic writes + const lockFilePath = join(lockDir, '.lock') + await writeFile(lockFilePath, '', 'utf-8') + + let release: (() => Promise) | undefined + try { + release = await lockfile.lock(lockFilePath) + + // Write the request file + await writeFile(pendingPath, jsonStringify(request, null, 2), 'utf-8') + + logForDebugging( + `[PermissionSync] Wrote pending request ${request.id} from ${request.workerName} for ${request.toolName}`, + ) + + return request + } catch (error) { + logForDebugging( + `[PermissionSync] Failed to write permission request: ${error}`, + ) + logError(error) + throw error + } finally { + if (release) { + await release() + } + } +} + +/** + * Read all pending permission requests for a team + * Called by the team leader to see what requests need attention + */ +export async function readPendingPermissions( + teamName?: string, +): Promise { + const team = teamName || getTeamName() + if (!team) { + logForDebugging('[PermissionSync] No team name available') + return [] + } + + const pendingDir = getPendingDir(team) + + let files: string[] + try { + files = await readdir(pendingDir) + } catch (e: unknown) { + const code = getErrnoCode(e) + if (code === 'ENOENT') { + return [] + } + logForDebugging(`[PermissionSync] Failed to read pending requests: ${e}`) + logError(e) + return [] + } + + const jsonFiles = files.filter(f => f.endsWith('.json') && f !== '.lock') + + const results = await Promise.all( + jsonFiles.map(async file => { + const filePath = join(pendingDir, file) + try { + const content = await readFile(filePath, 'utf-8') + const parsed = SwarmPermissionRequestSchema().safeParse( + jsonParse(content), + ) + if (parsed.success) { + return parsed.data + } + logForDebugging( + `[PermissionSync] Invalid request file ${file}: ${parsed.error.message}`, + ) + return null + } catch (err) { + logForDebugging( + `[PermissionSync] Failed to read request file ${file}: ${err}`, + ) + return null + } + }), + ) + + const requests = results.filter(r => r !== null) + + // Sort by creation time (oldest first) + requests.sort((a, b) => a.createdAt - b.createdAt) + + return requests +} + +/** + * Read a resolved permission request by ID + * Called by workers to check if their request has been resolved + * + * @returns The resolved request, or null if not yet resolved + */ +export async function readResolvedPermission( + requestId: string, + teamName?: string, +): Promise { + const team = teamName || getTeamName() + if (!team) { + return null + } + + const resolvedPath = getResolvedRequestPath(team, requestId) + + try { + const content = await readFile(resolvedPath, 'utf-8') + const parsed = SwarmPermissionRequestSchema().safeParse(jsonParse(content)) + if (parsed.success) { + return parsed.data + } + logForDebugging( + `[PermissionSync] Invalid resolved request ${requestId}: ${parsed.error.message}`, + ) + return null + } catch (e: unknown) { + const code = getErrnoCode(e) + if (code === 'ENOENT') { + return null + } + logForDebugging( + `[PermissionSync] Failed to read resolved request ${requestId}: ${e}`, + ) + logError(e) + return null + } +} + +/** + * Resolve a permission request + * Called by the team leader (or worker in self-resolution cases) + * + * Writes the resolution to resolved/, removes from pending/ + */ +export async function resolvePermission( + requestId: string, + resolution: PermissionResolution, + teamName?: string, +): Promise { + const team = teamName || getTeamName() + if (!team) { + logForDebugging('[PermissionSync] No team name available') + return false + } + + await ensurePermissionDirsAsync(team) + + const pendingPath = getPendingRequestPath(team, requestId) + const resolvedPath = getResolvedRequestPath(team, requestId) + const lockFilePath = join(getPendingDir(team), '.lock') + + await writeFile(lockFilePath, '', 'utf-8') + + let release: (() => Promise) | undefined + try { + release = await lockfile.lock(lockFilePath) + + // Read the pending request + let content: string + try { + content = await readFile(pendingPath, 'utf-8') + } catch (e: unknown) { + const code = getErrnoCode(e) + if (code === 'ENOENT') { + logForDebugging( + `[PermissionSync] Pending request not found: ${requestId}`, + ) + return false + } + throw e + } + + const parsed = SwarmPermissionRequestSchema().safeParse(jsonParse(content)) + if (!parsed.success) { + logForDebugging( + `[PermissionSync] Invalid pending request ${requestId}: ${parsed.error.message}`, + ) + return false + } + + const request = parsed.data + + // Update the request with resolution data + const resolvedRequest: SwarmPermissionRequest = { + ...request, + status: resolution.decision === 'approved' ? 'approved' : 'rejected', + resolvedBy: resolution.resolvedBy, + resolvedAt: Date.now(), + feedback: resolution.feedback, + updatedInput: resolution.updatedInput, + permissionUpdates: resolution.permissionUpdates, + } + + // Write to resolved directory + await writeFile( + resolvedPath, + jsonStringify(resolvedRequest, null, 2), + 'utf-8', + ) + + // Remove from pending directory + await unlink(pendingPath) + + logForDebugging( + `[PermissionSync] Resolved request ${requestId} with ${resolution.decision}`, + ) + + return true + } catch (error) { + logForDebugging(`[PermissionSync] Failed to resolve request: ${error}`) + logError(error) + return false + } finally { + if (release) { + await release() + } + } +} + +/** + * Clean up old resolved permission files + * Called periodically to prevent file accumulation + * + * @param teamName - Team name + * @param maxAgeMs - Maximum age in milliseconds (default: 1 hour) + */ +export async function cleanupOldResolutions( + teamName?: string, + maxAgeMs = 3600000, +): Promise { + const team = teamName || getTeamName() + if (!team) { + return 0 + } + + const resolvedDir = getResolvedDir(team) + + let files: string[] + try { + files = await readdir(resolvedDir) + } catch (e: unknown) { + const code = getErrnoCode(e) + if (code === 'ENOENT') { + return 0 + } + logForDebugging(`[PermissionSync] Failed to cleanup resolutions: ${e}`) + logError(e) + return 0 + } + + const now = Date.now() + const jsonFiles = files.filter(f => f.endsWith('.json')) + + const cleanupResults = await Promise.all( + jsonFiles.map(async file => { + const filePath = join(resolvedDir, file) + try { + const content = await readFile(filePath, 'utf-8') + const request = jsonParse(content) as SwarmPermissionRequest + + // Check if the resolution is old enough to clean up + // Use >= to handle edge case where maxAgeMs is 0 (clean up everything) + const resolvedAt = request.resolvedAt || request.createdAt + if (now - resolvedAt >= maxAgeMs) { + await unlink(filePath) + logForDebugging(`[PermissionSync] Cleaned up old resolution: ${file}`) + return 1 + } + return 0 + } catch { + // If we can't parse it, clean it up anyway + try { + await unlink(filePath) + return 1 + } catch { + // Ignore deletion errors + return 0 + } + } + }), + ) + + const cleanedCount = cleanupResults.reduce((sum, n) => sum + n, 0) + + if (cleanedCount > 0) { + logForDebugging( + `[PermissionSync] Cleaned up ${cleanedCount} old resolutions`, + ) + } + + return cleanedCount +} + +/** + * Legacy response type for worker polling + * Used for backward compatibility with worker integration code + */ +export type PermissionResponse = { + /** ID of the request this responds to */ + requestId: string + /** Decision: approved or denied */ + decision: 'approved' | 'denied' + /** Timestamp when response was created */ + timestamp: string + /** Optional feedback message if denied */ + feedback?: string + /** Optional updated input if the resolver modified it */ + updatedInput?: Record + /** Permission updates to apply (e.g., "always allow" rules) */ + permissionUpdates?: unknown[] +} + +/** + * Poll for a permission response (worker-side convenience function) + * Converts the resolved request into a simpler response format + * + * @returns The permission response, or null if not yet resolved + */ +export async function pollForResponse( + requestId: string, + _agentName?: string, + teamName?: string, +): Promise { + const resolved = await readResolvedPermission(requestId, teamName) + if (!resolved) { + return null + } + + return { + requestId: resolved.id, + decision: resolved.status === 'approved' ? 'approved' : 'denied', + timestamp: resolved.resolvedAt + ? new Date(resolved.resolvedAt).toISOString() + : new Date(resolved.createdAt).toISOString(), + feedback: resolved.feedback, + updatedInput: resolved.updatedInput, + permissionUpdates: resolved.permissionUpdates, + } +} + +/** + * Remove a worker's response after processing + * This is an alias for deleteResolvedPermission for backward compatibility + */ +export async function removeWorkerResponse( + requestId: string, + _agentName?: string, + teamName?: string, +): Promise { + await deleteResolvedPermission(requestId, teamName) +} + +/** + * Check if the current agent is a team leader + */ +export function isTeamLeader(teamName?: string): boolean { + const team = teamName || getTeamName() + if (!team) { + return false + } + + // Team leaders don't have an agent ID set, or their ID is 'team-lead' + const agentId = getAgentId() + + return !agentId || agentId === 'team-lead' +} + +/** + * Check if the current agent is a worker in a swarm + */ +export function isSwarmWorker(): boolean { + const teamName = getTeamName() + const agentId = getAgentId() + + return !!teamName && !!agentId && !isTeamLeader() +} + +/** + * Delete a resolved permission file + * Called after a worker has processed the resolution + */ +export async function deleteResolvedPermission( + requestId: string, + teamName?: string, +): Promise { + const team = teamName || getTeamName() + if (!team) { + return false + } + + const resolvedPath = getResolvedRequestPath(team, requestId) + + try { + await unlink(resolvedPath) + logForDebugging( + `[PermissionSync] Deleted resolved permission: ${requestId}`, + ) + return true + } catch (e: unknown) { + const code = getErrnoCode(e) + if (code === 'ENOENT') { + return false + } + logForDebugging( + `[PermissionSync] Failed to delete resolved permission: ${e}`, + ) + logError(e) + return false + } +} + +/** + * Submit a permission request (alias for writePermissionRequest) + * Provided for backward compatibility with worker integration code + */ +export const submitPermissionRequest = writePermissionRequest + +// ============================================================================ +// Mailbox-Based Permission System +// ============================================================================ + +/** + * Get the leader's name from the team file + * This is needed to send permission requests to the leader's mailbox + */ +export async function getLeaderName(teamName?: string): Promise { + const team = teamName || getTeamName() + if (!team) { + return null + } + + const teamFile = await readTeamFileAsync(team) + if (!teamFile) { + logForDebugging(`[PermissionSync] Team file not found for team: ${team}`) + return null + } + + const leadMember = teamFile.members.find( + m => m.agentId === teamFile.leadAgentId, + ) + return leadMember?.name || 'team-lead' +} + +/** + * Send a permission request to the leader via mailbox. + * This is the new mailbox-based approach that replaces the file-based pending directory. + * + * @param request - The permission request to send + * @returns true if the message was sent successfully + */ +export async function sendPermissionRequestViaMailbox( + request: SwarmPermissionRequest, +): Promise { + const leaderName = await getLeaderName(request.teamName) + if (!leaderName) { + logForDebugging( + `[PermissionSync] Cannot send permission request: leader name not found`, + ) + return false + } + + try { + // Create the permission request message + const message = createPermissionRequestMessage({ + request_id: request.id, + agent_id: request.workerName, + tool_name: request.toolName, + tool_use_id: request.toolUseId, + description: request.description, + input: request.input, + permission_suggestions: request.permissionSuggestions, + }) + + // Send to leader's mailbox (routes to in-process or file-based based on recipient) + await writeToMailbox( + leaderName, + { + from: request.workerName, + text: jsonStringify(message), + timestamp: new Date().toISOString(), + color: request.workerColor, + }, + request.teamName, + ) + + logForDebugging( + `[PermissionSync] Sent permission request ${request.id} to leader ${leaderName} via mailbox`, + ) + return true + } catch (error) { + logForDebugging( + `[PermissionSync] Failed to send permission request via mailbox: ${error}`, + ) + logError(error) + return false + } +} + +/** + * Send a permission response to a worker via mailbox. + * This is the new mailbox-based approach that replaces the file-based resolved directory. + * + * @param workerName - The worker's name to send the response to + * @param resolution - The permission resolution + * @param requestId - The original request ID + * @param teamName - The team name + * @returns true if the message was sent successfully + */ +export async function sendPermissionResponseViaMailbox( + workerName: string, + resolution: PermissionResolution, + requestId: string, + teamName?: string, +): Promise { + const team = teamName || getTeamName() + if (!team) { + logForDebugging( + `[PermissionSync] Cannot send permission response: team name not found`, + ) + return false + } + + try { + // Create the permission response message + const message = createPermissionResponseMessage({ + request_id: requestId, + subtype: resolution.decision === 'approved' ? 'success' : 'error', + error: resolution.feedback, + updated_input: resolution.updatedInput, + permission_updates: resolution.permissionUpdates, + }) + + // Get the sender name (leader's name) + const senderName = getAgentName() || 'team-lead' + + // Send to worker's mailbox (routes to in-process or file-based based on recipient) + await writeToMailbox( + workerName, + { + from: senderName, + text: jsonStringify(message), + timestamp: new Date().toISOString(), + }, + team, + ) + + logForDebugging( + `[PermissionSync] Sent permission response for ${requestId} to worker ${workerName} via mailbox`, + ) + return true + } catch (error) { + logForDebugging( + `[PermissionSync] Failed to send permission response via mailbox: ${error}`, + ) + logError(error) + return false + } +} + +// ============================================================================ +// Sandbox Permission Mailbox System +// ============================================================================ + +/** + * Generate a unique sandbox permission request ID + */ +export function generateSandboxRequestId(): string { + return `sandbox-${Date.now()}-${Math.random().toString(36).substring(2, 9)}` +} + +/** + * Send a sandbox permission request to the leader via mailbox. + * Called by workers when sandbox runtime needs network access approval. + * + * @param host - The host requesting network access + * @param requestId - Unique ID for this request + * @param teamName - Optional team name + * @returns true if the message was sent successfully + */ +export async function sendSandboxPermissionRequestViaMailbox( + host: string, + requestId: string, + teamName?: string, +): Promise { + const team = teamName || getTeamName() + if (!team) { + logForDebugging( + `[PermissionSync] Cannot send sandbox permission request: team name not found`, + ) + return false + } + + const leaderName = await getLeaderName(team) + if (!leaderName) { + logForDebugging( + `[PermissionSync] Cannot send sandbox permission request: leader name not found`, + ) + return false + } + + const workerId = getAgentId() + const workerName = getAgentName() + const workerColor = getTeammateColor() + + if (!workerId || !workerName) { + logForDebugging( + `[PermissionSync] Cannot send sandbox permission request: worker ID or name not found`, + ) + return false + } + + try { + const message = createSandboxPermissionRequestMessage({ + requestId, + workerId, + workerName, + workerColor, + host, + }) + + // Send to leader's mailbox (routes to in-process or file-based based on recipient) + await writeToMailbox( + leaderName, + { + from: workerName, + text: jsonStringify(message), + timestamp: new Date().toISOString(), + color: workerColor, + }, + team, + ) + + logForDebugging( + `[PermissionSync] Sent sandbox permission request ${requestId} for host ${host} to leader ${leaderName} via mailbox`, + ) + return true + } catch (error) { + logForDebugging( + `[PermissionSync] Failed to send sandbox permission request via mailbox: ${error}`, + ) + logError(error) + return false + } +} + +/** + * Send a sandbox permission response to a worker via mailbox. + * Called by the leader when approving/denying a sandbox network access request. + * + * @param workerName - The worker's name to send the response to + * @param requestId - The original request ID + * @param host - The host that was approved/denied + * @param allow - Whether the connection is allowed + * @param teamName - Optional team name + * @returns true if the message was sent successfully + */ +export async function sendSandboxPermissionResponseViaMailbox( + workerName: string, + requestId: string, + host: string, + allow: boolean, + teamName?: string, +): Promise { + const team = teamName || getTeamName() + if (!team) { + logForDebugging( + `[PermissionSync] Cannot send sandbox permission response: team name not found`, + ) + return false + } + + try { + const message = createSandboxPermissionResponseMessage({ + requestId, + host, + allow, + }) + + const senderName = getAgentName() || 'team-lead' + + // Send to worker's mailbox (routes to in-process or file-based based on recipient) + await writeToMailbox( + workerName, + { + from: senderName, + text: jsonStringify(message), + timestamp: new Date().toISOString(), + }, + team, + ) + + logForDebugging( + `[PermissionSync] Sent sandbox permission response for ${requestId} (host: ${host}, allow: ${allow}) to worker ${workerName} via mailbox`, + ) + return true + } catch (error) { + logForDebugging( + `[PermissionSync] Failed to send sandbox permission response via mailbox: ${error}`, + ) + logError(error) + return false + } +} diff --git a/src/utils/swarm/reconnection.ts b/src/utils/swarm/reconnection.ts new file mode 100644 index 0000000000000000000000000000000000000000..62a38cc931b47afb1f7255c298efa3092ffb61c9 --- /dev/null +++ b/src/utils/swarm/reconnection.ts @@ -0,0 +1,119 @@ +/** + * Swarm Reconnection Module + * + * Handles initialization of swarm context for teammates. + * - Fresh spawns: Initialize from CLI args (set in main.tsx via dynamicTeamContext) + * - Resumed sessions: Initialize from teamName/agentName stored in the transcript + */ + +import type { AppState } from '../../state/AppState.js' +import { logForDebugging } from '../debug.js' +import { logError } from '../log.js' +import { getDynamicTeamContext } from '../teammate.js' +import { getTeamFilePath, readTeamFile } from './teamHelpers.js' + +/** + * Computes the initial teamContext for AppState. + * + * This is called synchronously in main.tsx to compute the teamContext + * BEFORE the first render, eliminating the need for useEffect workarounds. + * + * @returns The teamContext object to include in initialState, or undefined if not a teammate + */ +export function computeInitialTeamContext(): + | AppState['teamContext'] + | undefined { + // dynamicTeamContext is set in main.tsx from CLI args + const context = getDynamicTeamContext() + + if (!context?.teamName || !context?.agentName) { + logForDebugging( + '[Reconnection] computeInitialTeamContext: No teammate context set (not a teammate)', + ) + return undefined + } + + const { teamName, agentId, agentName } = context + + // Read team file to get lead agent ID + const teamFile = readTeamFile(teamName) + if (!teamFile) { + logError( + new Error( + `[computeInitialTeamContext] Could not read team file for ${teamName}`, + ), + ) + return undefined + } + + const teamFilePath = getTeamFilePath(teamName) + + const isLeader = !agentId + + logForDebugging( + `[Reconnection] Computed initial team context for ${isLeader ? 'leader' : `teammate ${agentName}`} in team ${teamName}`, + ) + + return { + teamName, + teamFilePath, + leadAgentId: teamFile.leadAgentId, + selfAgentId: agentId, + selfAgentName: agentName, + isLeader, + teammates: {}, + } +} + +/** + * Initialize teammate context from a resumed session. + * + * This is called when resuming a session that has teamName/agentName stored + * in the transcript. It sets up teamContext in AppState so that heartbeat + * and other swarm features work correctly. + */ +export function initializeTeammateContextFromSession( + setAppState: (updater: (prev: AppState) => AppState) => void, + teamName: string, + agentName: string, +): void { + // Read team file to get lead agent ID + const teamFile = readTeamFile(teamName) + if (!teamFile) { + logError( + new Error( + `[initializeTeammateContextFromSession] Could not read team file for ${teamName} (agent: ${agentName})`, + ), + ) + return + } + + // Find the member in the team file to get their agentId + const member = teamFile.members.find(m => m.name === agentName) + if (!member) { + logForDebugging( + `[Reconnection] Member ${agentName} not found in team ${teamName} - may have been removed`, + ) + } + const agentId = member?.agentId + + const teamFilePath = getTeamFilePath(teamName) + + // Set teamContext in AppState + setAppState(prev => ({ + ...prev, + teamContext: { + teamName, + teamFilePath, + leadAgentId: teamFile.leadAgentId, + selfAgentId: agentId, + selfAgentName: agentName, + isLeader: false, + teammates: {}, + }, + })) + + logForDebugging( + `[Reconnection] Initialized agent context from session for ${agentName} in team ${teamName}`, + ) +} diff --git a/src/utils/swarm/spawnInProcess.ts b/src/utils/swarm/spawnInProcess.ts new file mode 100644 index 0000000000000000000000000000000000000000..7f548f28aa22b817ef5da72f7fc897b840688612 --- /dev/null +++ b/src/utils/swarm/spawnInProcess.ts @@ -0,0 +1,328 @@ +/** + * In-process teammate spawning + * + * Creates and registers an in-process teammate task. Unlike process-based + * teammates (tmux/iTerm2), in-process teammates run in the same Node.js + * process using AsyncLocalStorage for context isolation. + * + * The actual agent execution loop is handled by InProcessTeammateTask + * component (Task #14). This module handles: + * 1. Creating TeammateContext + * 2. Creating linked AbortController + * 3. Registering InProcessTeammateTaskState in AppState + * 4. Returning spawn result for backend + */ + +import sample from 'lodash-es/sample.js' +import { getSessionId } from '../../bootstrap/state.js' +import { getSpinnerVerbs } from '../../constants/spinnerVerbs.js' +import { TURN_COMPLETION_VERBS } from '../../constants/turnCompletionVerbs.js' +import type { AppState } from '../../state/AppState.js' +import { createTaskStateBase, generateTaskId } from '../../Task.js' +import type { + InProcessTeammateTaskState, + TeammateIdentity, +} from '../../tasks/InProcessTeammateTask/types.js' +import { createAbortController } from '../abortController.js' +import { formatAgentId } from '../agentId.js' +import { registerCleanup } from '../cleanupRegistry.js' +import { logForDebugging } from '../debug.js' +import { emitTaskTerminatedSdk } from '../sdkEventQueue.js' +import { evictTaskOutput } from '../task/diskOutput.js' +import { + evictTerminalTask, + registerTask, + STOPPED_DISPLAY_MS, +} from '../task/framework.js' +import { createTeammateContext } from '../teammateContext.js' +import { + isPerfettoTracingEnabled, + registerAgent as registerPerfettoAgent, + unregisterAgent as unregisterPerfettoAgent, +} from '../telemetry/perfettoTracing.js' +import { removeMemberByAgentId } from './teamHelpers.js' + +type SetAppStateFn = (updater: (prev: AppState) => AppState) => void + +/** + * Minimal context required for spawning an in-process teammate. + * This is a subset of ToolUseContext - only what spawnInProcessTeammate actually uses. + */ +export type SpawnContext = { + setAppState: SetAppStateFn + toolUseId?: string +} + +/** + * Configuration for spawning an in-process teammate. + */ +export type InProcessSpawnConfig = { + /** Display name for the teammate, e.g., "researcher" */ + name: string + /** Team this teammate belongs to */ + teamName: string + /** Initial prompt/task for the teammate */ + prompt: string + /** Optional UI color for the teammate */ + color?: string + /** Whether teammate must enter plan mode before implementing */ + planModeRequired: boolean + /** Optional model override for this teammate */ + model?: string +} + +/** + * Result from spawning an in-process teammate. + */ +export type InProcessSpawnOutput = { + /** Whether spawn was successful */ + success: boolean + /** Full agent ID (format: "name@team") */ + agentId: string + /** Task ID for tracking in AppState */ + taskId?: string + /** AbortController for this teammate (linked to parent) */ + abortController?: AbortController + /** Teammate context for AsyncLocalStorage */ + teammateContext?: ReturnType + /** Error message if spawn failed */ + error?: string +} + +/** + * Spawns an in-process teammate. + * + * Creates the teammate's context, registers the task in AppState, and returns + * the spawn result. The actual agent execution is driven by the + * InProcessTeammateTask component which uses runWithTeammateContext() to + * execute the agent loop with proper identity isolation. + * + * @param config - Spawn configuration + * @param context - Context with setAppState for registering task + * @returns Spawn result with teammate info + */ +export async function spawnInProcessTeammate( + config: InProcessSpawnConfig, + context: SpawnContext, +): Promise { + const { name, teamName, prompt, color, planModeRequired, model } = config + const { setAppState } = context + + // Generate deterministic agent ID + const agentId = formatAgentId(name, teamName) + const taskId = generateTaskId('in_process_teammate') + + logForDebugging( + `[spawnInProcessTeammate] Spawning ${agentId} (taskId: ${taskId})`, + ) + + try { + // Create independent AbortController for this teammate + // Teammates should not be aborted when the leader's query is interrupted + const abortController = createAbortController() + + // Get parent session ID for transcript correlation + const parentSessionId = getSessionId() + + // Create teammate identity (stored as plain data in AppState) + const identity: TeammateIdentity = { + agentId, + agentName: name, + teamName, + color, + planModeRequired, + parentSessionId, + } + + // Create teammate context for AsyncLocalStorage + // This will be used by runWithTeammateContext() during agent execution + const teammateContext = createTeammateContext({ + agentId, + agentName: name, + teamName, + color, + planModeRequired, + parentSessionId, + abortController, + }) + + // Register agent in Perfetto trace for hierarchy visualization + if (isPerfettoTracingEnabled()) { + registerPerfettoAgent(agentId, name, parentSessionId) + } + + // Create task state + const description = `${name}: ${prompt.substring(0, 50)}${prompt.length > 50 ? '...' : ''}` + + const taskState: InProcessTeammateTaskState = { + ...createTaskStateBase( + taskId, + 'in_process_teammate', + description, + context.toolUseId, + ), + type: 'in_process_teammate', + status: 'running', + identity, + prompt, + model, + abortController, + awaitingPlanApproval: false, + spinnerVerb: sample(getSpinnerVerbs()), + pastTenseVerb: sample(TURN_COMPLETION_VERBS), + permissionMode: planModeRequired ? 'plan' : 'default', + isIdle: false, + shutdownRequested: false, + lastReportedToolCount: 0, + lastReportedTokenCount: 0, + pendingUserMessages: [], + messages: [], // Initialize to empty array so getDisplayedMessages works immediately + } + + // Register cleanup handler for graceful shutdown + const unregisterCleanup = registerCleanup(async () => { + logForDebugging(`[spawnInProcessTeammate] Cleanup called for ${agentId}`) + abortController.abort() + // Task state will be updated by the execution loop when it detects abort + }) + taskState.unregisterCleanup = unregisterCleanup + + // Register task in AppState + registerTask(taskState, setAppState) + + logForDebugging( + `[spawnInProcessTeammate] Registered ${agentId} in AppState`, + ) + + return { + success: true, + agentId, + taskId, + abortController, + teammateContext, + } + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error during spawn' + logForDebugging( + `[spawnInProcessTeammate] Failed to spawn ${agentId}: ${errorMessage}`, + ) + return { + success: false, + agentId, + error: errorMessage, + } + } +} + +/** + * Kills an in-process teammate by aborting its controller. + * + * Note: This is the implementation called by InProcessBackend.kill(). + * + * @param taskId - Task ID of the teammate to kill + * @param setAppState - AppState setter + * @returns true if killed successfully + */ +export function killInProcessTeammate( + taskId: string, + setAppState: SetAppStateFn, +): boolean { + let killed = false + let teamName: string | null = null + let agentId: string | null = null + let toolUseId: string | undefined + let description: string | undefined + + setAppState((prev: AppState) => { + const task = prev.tasks[taskId] + if (!task || task.type !== 'in_process_teammate') { + return prev + } + + const teammateTask = task as InProcessTeammateTaskState + + if (teammateTask.status !== 'running') { + return prev + } + + // Capture identity for cleanup after state update + teamName = teammateTask.identity.teamName + agentId = teammateTask.identity.agentId + toolUseId = teammateTask.toolUseId + description = teammateTask.description + + // Abort the controller to stop execution + teammateTask.abortController?.abort() + + // Call cleanup handler + teammateTask.unregisterCleanup?.() + + // Update task state and remove from teamContext.teammates + killed = true + + // Call pending idle callbacks to unblock any waiters (e.g., engine.waitForIdle) + teammateTask.onIdleCallbacks?.forEach(cb => cb()) + + // Remove from teamContext.teammates using the agentId + let updatedTeamContext = prev.teamContext + if (prev.teamContext && prev.teamContext.teammates && agentId) { + const { [agentId]: _, ...remainingTeammates } = prev.teamContext.teammates + updatedTeamContext = { + ...prev.teamContext, + teammates: remainingTeammates, + } + } + + return { + ...prev, + teamContext: updatedTeamContext, + tasks: { + ...prev.tasks, + [taskId]: { + ...teammateTask, + status: 'killed' as const, + notified: true, + endTime: Date.now(), + onIdleCallbacks: [], // Clear callbacks to prevent stale references + messages: teammateTask.messages?.length + ? [teammateTask.messages[teammateTask.messages.length - 1]!] + : undefined, + pendingUserMessages: [], + inProgressToolUseIDs: undefined, + abortController: undefined, + unregisterCleanup: undefined, + currentWorkAbortController: undefined, + }, + }, + } + }) + + // Remove from team file (outside state updater to avoid file I/O in callback) + if (teamName && agentId) { + removeMemberByAgentId(teamName, agentId) + } + + if (killed) { + void evictTaskOutput(taskId) + // notified:true was pre-set so no XML notification fires; close the SDK + // task_started bookend directly. The in-process runner's own + // completion/failure emit guards on status==='running' so it won't + // double-emit after seeing status:killed. + emitTaskTerminatedSdk(taskId, 'stopped', { + toolUseId, + summary: description, + }) + setTimeout( + evictTerminalTask.bind(null, taskId, setAppState), + STOPPED_DISPLAY_MS, + ) + } + + // Release perfetto agent registry entry + if (agentId) { + unregisterPerfettoAgent(agentId) + } + + return killed +} diff --git a/src/utils/swarm/spawnUtils.ts b/src/utils/swarm/spawnUtils.ts new file mode 100644 index 0000000000000000000000000000000000000000..cfccdf5a22f520e8acb2c9411e809c84ab28ea2a --- /dev/null +++ b/src/utils/swarm/spawnUtils.ts @@ -0,0 +1,146 @@ +/** + * Shared utilities for spawning teammates across different backends. + */ + +import { + getChromeFlagOverride, + getFlagSettingsPath, + getInlinePlugins, + getMainLoopModelOverride, + getSessionBypassPermissionsMode, +} from '../../bootstrap/state.js' +import { quote } from '../bash/shellQuote.js' +import { isInBundledMode } from '../bundledMode.js' +import type { PermissionMode } from '../permissions/PermissionMode.js' +import { getTeammateModeFromSnapshot } from './backends/teammateModeSnapshot.js' +import { TEAMMATE_COMMAND_ENV_VAR } from './constants.js' + +/** + * Gets the command to use for spawning teammate processes. + * Uses TEAMMATE_COMMAND_ENV_VAR if set, otherwise falls back to the + * current process executable path. + */ +export function getTeammateCommand(): string { + if (process.env[TEAMMATE_COMMAND_ENV_VAR]) { + return process.env[TEAMMATE_COMMAND_ENV_VAR] + } + return isInBundledMode() ? process.execPath : process.argv[1]! +} + +/** + * Builds CLI flags to propagate from the current session to spawned teammates. + * This ensures teammates inherit important settings like permission mode, + * model selection, and plugin configuration from their parent. + * + * @param options.planModeRequired - If true, don't inherit bypass permissions (plan mode takes precedence) + * @param options.permissionMode - Permission mode to propagate + */ +export function buildInheritedCliFlags(options?: { + planModeRequired?: boolean + permissionMode?: PermissionMode +}): string { + const flags: string[] = [] + const { planModeRequired, permissionMode } = options || {} + + // Propagate permission mode to teammates, but NOT if plan mode is required + // Plan mode takes precedence over bypass permissions for safety + if (planModeRequired) { + // Don't inherit bypass permissions when plan mode is required + } else if ( + permissionMode === 'bypassPermissions' || + getSessionBypassPermissionsMode() + ) { + flags.push('--dangerously-skip-permissions') + } else if (permissionMode === 'acceptEdits') { + flags.push('--permission-mode acceptEdits') + } + + // Propagate --model if explicitly set via CLI + const modelOverride = getMainLoopModelOverride() + if (modelOverride) { + flags.push(`--model ${quote([modelOverride])}`) + } + + // Propagate --settings if set via CLI + const settingsPath = getFlagSettingsPath() + if (settingsPath) { + flags.push(`--settings ${quote([settingsPath])}`) + } + + // Propagate --plugin-dir for each inline plugin + const inlinePlugins = getInlinePlugins() + for (const pluginDir of inlinePlugins) { + flags.push(`--plugin-dir ${quote([pluginDir])}`) + } + + // Propagate --teammate-mode so tmux teammates use the same mode as leader + const sessionMode = getTeammateModeFromSnapshot() + flags.push(`--teammate-mode ${sessionMode}`) + + // Propagate --chrome / --no-chrome if explicitly set on the CLI + const chromeFlagOverride = getChromeFlagOverride() + if (chromeFlagOverride === true) { + flags.push('--chrome') + } else if (chromeFlagOverride === false) { + flags.push('--no-chrome') + } + + return flags.join(' ') +} + +/** + * Environment variables that must be explicitly forwarded to tmux-spawned + * teammates. Tmux may start a new login shell that doesn't inherit the + * parent's env, so we forward any that are set in the current process. + */ +const TEAMMATE_ENV_VARS = [ + // API provider selection — without these, teammates default to firstParty + // and send requests to the wrong endpoint (GitHub issue #23561) + 'CLAUDE_CODE_USE_BEDROCK', + 'CLAUDE_CODE_USE_VERTEX', + 'CLAUDE_CODE_USE_FOUNDRY', + // Custom API endpoint + 'ANTHROPIC_BASE_URL', + // Config directory override + 'CLAUDE_CONFIG_DIR', + // CCR marker — teammates need this for CCR-aware code paths. Auth finds + // its own way via /home/claude/.claude/remote/.oauth_token regardless; + // the FD env var wouldn't help (pipe FDs don't cross tmux). + 'CLAUDE_CODE_REMOTE', + // Auto-memory gate (memdir/paths.ts) checks REMOTE && !MEMORY_DIR to + // disable memory on ephemeral CCR filesystems. Forwarding REMOTE alone + // would flip teammates to memory-off when the parent has it on. + 'CLAUDE_CODE_REMOTE_MEMORY_DIR', + // Upstream proxy — the parent's MITM relay is reachable from teammates + // (same container network). Forward the proxy vars so teammates route + // customer-configured upstream traffic through the relay for credential + // injection. Without these, teammates bypass the proxy entirely. + 'HTTPS_PROXY', + 'https_proxy', + 'HTTP_PROXY', + 'http_proxy', + 'NO_PROXY', + 'no_proxy', + 'SSL_CERT_FILE', + 'NODE_EXTRA_CA_CERTS', + 'REQUESTS_CA_BUNDLE', + 'CURL_CA_BUNDLE', +] as const + +/** + * Builds the `env KEY=VALUE ...` string for teammate spawn commands. + * Always includes CLAUDECODE=1 and CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1, + * plus any provider/config env vars that are set in the current process. + */ +export function buildInheritedEnvVars(): string { + const envVars = ['CLAUDECODE=1', 'CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1'] + + for (const key of TEAMMATE_ENV_VARS) { + const value = process.env[key] + if (value !== undefined && value !== '') { + envVars.push(`${key}=${quote([value])}`) + } + } + + return envVars.join(' ') +} diff --git a/src/utils/swarm/teamHelpers.ts b/src/utils/swarm/teamHelpers.ts new file mode 100644 index 0000000000000000000000000000000000000000..66508fd7a66bab7515e99b364febaf60e8d73d9e --- /dev/null +++ b/src/utils/swarm/teamHelpers.ts @@ -0,0 +1,683 @@ +import { mkdirSync, readFileSync, writeFileSync } from 'fs' +import { mkdir, readFile, rm, writeFile } from 'fs/promises' +import { join } from 'path' +import { z } from 'zod/v4' +import { getSessionCreatedTeams } from '../../bootstrap/state.js' +import { logForDebugging } from '../debug.js' +import { getTeamsDir } from '../envUtils.js' +import { errorMessage, getErrnoCode } from '../errors.js' +import { execFileNoThrowWithCwd } from '../execFileNoThrow.js' +import { gitExe } from '../git.js' +import { lazySchema } from '../lazySchema.js' +import type { PermissionMode } from '../permissions/PermissionMode.js' +import { jsonParse, jsonStringify } from '../slowOperations.js' +import { getTasksDir, notifyTasksUpdated } from '../tasks.js' +import { getAgentName, getTeamName, isTeammate } from '../teammate.js' +import { type BackendType, isPaneBackend } from './backends/types.js' +import { TEAM_LEAD_NAME } from './constants.js' + +export const inputSchema = lazySchema(() => + z.strictObject({ + operation: z + .enum(['spawnTeam', 'cleanup']) + .describe( + 'Operation: spawnTeam to create a team, cleanup to remove team and task directories.', + ), + agent_type: z + .string() + .optional() + .describe( + 'Type/role of the team lead (e.g., "researcher", "test-runner"). ' + + 'Used for team file and inter-agent coordination.', + ), + team_name: z + .string() + .optional() + .describe('Name for the new team to create (required for spawnTeam).'), + description: z + .string() + .optional() + .describe('Team description/purpose (only used with spawnTeam).'), + }), +) + +// Output types for different operations +export type SpawnTeamOutput = { + team_name: string + team_file_path: string + lead_agent_id: string +} + +export type CleanupOutput = { + success: boolean + message: string + team_name?: string +} + +export type TeamAllowedPath = { + path: string // Directory path (absolute) + toolName: string // The tool this applies to (e.g., "Edit", "Write") + addedBy: string // Agent name who added this rule + addedAt: number // Timestamp when added +} + +export type TeamFile = { + name: string + description?: string + createdAt: number + leadAgentId: string + leadSessionId?: string // Actual session UUID of the leader (for discovery) + hiddenPaneIds?: string[] // Pane IDs that are currently hidden from the UI + teamAllowedPaths?: TeamAllowedPath[] // Paths all teammates can edit without asking + members: Array<{ + agentId: string + name: string + agentType?: string + model?: string + prompt?: string + color?: string + planModeRequired?: boolean + joinedAt: number + tmuxPaneId: string + cwd: string + worktreePath?: string + sessionId?: string + subscriptions: string[] + backendType?: BackendType + isActive?: boolean // false when idle, undefined/true when active + mode?: PermissionMode // Current permission mode for this teammate + }> +} + +export type Input = z.infer> +// Export SpawnTeamOutput as Output for backward compatibility +export type Output = SpawnTeamOutput + +/** + * Sanitizes a name for use in tmux window names, worktree paths, and file paths. + * Replaces all non-alphanumeric characters with hyphens and lowercases. + */ +export function sanitizeName(name: string): string { + return name.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase() +} + +/** + * Sanitizes an agent name for use in deterministic agent IDs. + * Replaces @ with - to prevent ambiguity in the agentName@teamName format. + */ +export function sanitizeAgentName(name: string): string { + return name.replace(/@/g, '-') +} + +/** + * Gets the path to a team's directory + */ +export function getTeamDir(teamName: string): string { + return join(getTeamsDir(), sanitizeName(teamName)) +} + +/** + * Gets the path to a team's config.json file + */ +export function getTeamFilePath(teamName: string): string { + return join(getTeamDir(teamName), 'config.json') +} + +/** + * Reads a team file by name (sync — for sync contexts like React render paths) + * @internal Exported for team discovery UI + */ +// sync IO: called from sync context +export function readTeamFile(teamName: string): TeamFile | null { + try { + const content = readFileSync(getTeamFilePath(teamName), 'utf-8') + return jsonParse(content) as TeamFile + } catch (e) { + if (getErrnoCode(e) === 'ENOENT') return null + logForDebugging( + `[TeammateTool] Failed to read team file for ${teamName}: ${errorMessage(e)}`, + ) + return null + } +} + +/** + * Reads a team file by name (async — for tool handlers and other async contexts) + */ +export async function readTeamFileAsync( + teamName: string, +): Promise { + try { + const content = await readFile(getTeamFilePath(teamName), 'utf-8') + return jsonParse(content) as TeamFile + } catch (e) { + if (getErrnoCode(e) === 'ENOENT') return null + logForDebugging( + `[TeammateTool] Failed to read team file for ${teamName}: ${errorMessage(e)}`, + ) + return null + } +} + +/** + * Writes a team file (sync — for sync contexts) + */ +// sync IO: called from sync context +function writeTeamFile(teamName: string, teamFile: TeamFile): void { + const teamDir = getTeamDir(teamName) + mkdirSync(teamDir, { recursive: true }) + writeFileSync(getTeamFilePath(teamName), jsonStringify(teamFile, null, 2)) +} + +/** + * Writes a team file (async — for tool handlers) + */ +export async function writeTeamFileAsync( + teamName: string, + teamFile: TeamFile, +): Promise { + const teamDir = getTeamDir(teamName) + await mkdir(teamDir, { recursive: true }) + await writeFile(getTeamFilePath(teamName), jsonStringify(teamFile, null, 2)) +} + +/** + * Removes a teammate from the team file by agent ID or name. + * Used by the leader when processing shutdown approvals. + */ +export function removeTeammateFromTeamFile( + teamName: string, + identifier: { agentId?: string; name?: string }, +): boolean { + const identifierStr = identifier.agentId || identifier.name + if (!identifierStr) { + logForDebugging( + '[TeammateTool] removeTeammateFromTeamFile called with no identifier', + ) + return false + } + + const teamFile = readTeamFile(teamName) + if (!teamFile) { + logForDebugging( + `[TeammateTool] Cannot remove teammate ${identifierStr}: failed to read team file for "${teamName}"`, + ) + return false + } + + const originalLength = teamFile.members.length + teamFile.members = teamFile.members.filter(m => { + if (identifier.agentId && m.agentId === identifier.agentId) return false + if (identifier.name && m.name === identifier.name) return false + return true + }) + + if (teamFile.members.length === originalLength) { + logForDebugging( + `[TeammateTool] Teammate ${identifierStr} not found in team file for "${teamName}"`, + ) + return false + } + + writeTeamFile(teamName, teamFile) + logForDebugging( + `[TeammateTool] Removed teammate from team file: ${identifierStr}`, + ) + return true +} + +/** + * Adds a pane ID to the hidden panes list in the team file. + * @param teamName - The name of the team + * @param paneId - The pane ID to hide + * @returns true if the pane was added to hidden list, false if team doesn't exist + */ +export function addHiddenPaneId(teamName: string, paneId: string): boolean { + const teamFile = readTeamFile(teamName) + if (!teamFile) { + return false + } + + const hiddenPaneIds = teamFile.hiddenPaneIds ?? [] + if (!hiddenPaneIds.includes(paneId)) { + hiddenPaneIds.push(paneId) + teamFile.hiddenPaneIds = hiddenPaneIds + writeTeamFile(teamName, teamFile) + logForDebugging( + `[TeammateTool] Added ${paneId} to hidden panes for team ${teamName}`, + ) + } + return true +} + +/** + * Removes a pane ID from the hidden panes list in the team file. + * @param teamName - The name of the team + * @param paneId - The pane ID to show (remove from hidden list) + * @returns true if the pane was removed from hidden list, false if team doesn't exist + */ +export function removeHiddenPaneId(teamName: string, paneId: string): boolean { + const teamFile = readTeamFile(teamName) + if (!teamFile) { + return false + } + + const hiddenPaneIds = teamFile.hiddenPaneIds ?? [] + const index = hiddenPaneIds.indexOf(paneId) + if (index !== -1) { + hiddenPaneIds.splice(index, 1) + teamFile.hiddenPaneIds = hiddenPaneIds + writeTeamFile(teamName, teamFile) + logForDebugging( + `[TeammateTool] Removed ${paneId} from hidden panes for team ${teamName}`, + ) + } + return true +} + +/** + * Removes a teammate from the team config file by pane ID. + * Also removes from hiddenPaneIds if present. + * @param teamName - The name of the team + * @param tmuxPaneId - The pane ID of the teammate to remove + * @returns true if the member was removed, false if team or member doesn't exist + */ +export function removeMemberFromTeam( + teamName: string, + tmuxPaneId: string, +): boolean { + const teamFile = readTeamFile(teamName) + if (!teamFile) { + return false + } + + const memberIndex = teamFile.members.findIndex( + m => m.tmuxPaneId === tmuxPaneId, + ) + if (memberIndex === -1) { + return false + } + + // Remove from members array + teamFile.members.splice(memberIndex, 1) + + // Also remove from hiddenPaneIds if present + if (teamFile.hiddenPaneIds) { + const hiddenIndex = teamFile.hiddenPaneIds.indexOf(tmuxPaneId) + if (hiddenIndex !== -1) { + teamFile.hiddenPaneIds.splice(hiddenIndex, 1) + } + } + + writeTeamFile(teamName, teamFile) + logForDebugging( + `[TeammateTool] Removed member with pane ${tmuxPaneId} from team ${teamName}`, + ) + return true +} + +/** + * Removes a teammate from a team's member list by agent ID. + * Use this for in-process teammates which all share the same tmuxPaneId. + * @param teamName - The name of the team + * @param agentId - The agent ID of the teammate to remove (e.g., "researcher@my-team") + * @returns true if the member was removed, false if team or member doesn't exist + */ +export function removeMemberByAgentId( + teamName: string, + agentId: string, +): boolean { + const teamFile = readTeamFile(teamName) + if (!teamFile) { + return false + } + + const memberIndex = teamFile.members.findIndex(m => m.agentId === agentId) + if (memberIndex === -1) { + return false + } + + // Remove from members array + teamFile.members.splice(memberIndex, 1) + + writeTeamFile(teamName, teamFile) + logForDebugging( + `[TeammateTool] Removed member ${agentId} from team ${teamName}`, + ) + return true +} + +/** + * Sets a team member's permission mode. + * Called when the team leader changes a teammate's mode via the TeamsDialog. + * @param teamName - The name of the team + * @param memberName - The name of the member to update + * @param mode - The new permission mode + */ +export function setMemberMode( + teamName: string, + memberName: string, + mode: PermissionMode, +): boolean { + const teamFile = readTeamFile(teamName) + if (!teamFile) { + return false + } + + const member = teamFile.members.find(m => m.name === memberName) + if (!member) { + logForDebugging( + `[TeammateTool] Cannot set member mode: member ${memberName} not found in team ${teamName}`, + ) + return false + } + + // Only write if the value is actually changing + if (member.mode === mode) { + return true + } + + // Create updated members array immutably + const updatedMembers = teamFile.members.map(m => + m.name === memberName ? { ...m, mode } : m, + ) + writeTeamFile(teamName, { ...teamFile, members: updatedMembers }) + logForDebugging( + `[TeammateTool] Set member ${memberName} in team ${teamName} to mode: ${mode}`, + ) + return true +} + +/** + * Sync the current teammate's mode to config.json so team lead sees it. + * No-op if not running as a teammate. + * @param mode - The permission mode to sync + * @param teamNameOverride - Optional team name override (uses env var if not provided) + */ +export function syncTeammateMode( + mode: PermissionMode, + teamNameOverride?: string, +): void { + if (!isTeammate()) return + const teamName = teamNameOverride ?? getTeamName() + const agentName = getAgentName() + if (teamName && agentName) { + setMemberMode(teamName, agentName, mode) + } +} + +/** + * Sets multiple team members' permission modes in a single atomic operation. + * Avoids race conditions when updating multiple teammates at once. + * @param teamName - The name of the team + * @param modeUpdates - Array of {memberName, mode} to update + */ +export function setMultipleMemberModes( + teamName: string, + modeUpdates: Array<{ memberName: string; mode: PermissionMode }>, +): boolean { + const teamFile = readTeamFile(teamName) + if (!teamFile) { + return false + } + + // Build a map of updates for efficient lookup + const updateMap = new Map(modeUpdates.map(u => [u.memberName, u.mode])) + + // Create updated members array immutably + let anyChanged = false + const updatedMembers = teamFile.members.map(member => { + const newMode = updateMap.get(member.name) + if (newMode !== undefined && member.mode !== newMode) { + anyChanged = true + return { ...member, mode: newMode } + } + return member + }) + + if (anyChanged) { + writeTeamFile(teamName, { ...teamFile, members: updatedMembers }) + logForDebugging( + `[TeammateTool] Set ${modeUpdates.length} member modes in team ${teamName}`, + ) + } + return true +} + +/** + * Sets a team member's active status. + * Called when a teammate becomes idle (isActive=false) or starts a new turn (isActive=true). + * @param teamName - The name of the team + * @param memberName - The name of the member to update + * @param isActive - Whether the member is active (true) or idle (false) + */ +export async function setMemberActive( + teamName: string, + memberName: string, + isActive: boolean, +): Promise { + const teamFile = await readTeamFileAsync(teamName) + if (!teamFile) { + logForDebugging( + `[TeammateTool] Cannot set member active: team ${teamName} not found`, + ) + return + } + + const member = teamFile.members.find(m => m.name === memberName) + if (!member) { + logForDebugging( + `[TeammateTool] Cannot set member active: member ${memberName} not found in team ${teamName}`, + ) + return + } + + // Only write if the value is actually changing + if (member.isActive === isActive) { + return + } + + member.isActive = isActive + await writeTeamFileAsync(teamName, teamFile) + logForDebugging( + `[TeammateTool] Set member ${memberName} in team ${teamName} to ${isActive ? 'active' : 'idle'}`, + ) +} + +/** + * Destroys a git worktree at the given path. + * First attempts to use `git worktree remove`, then falls back to rm -rf. + * Safe to call on non-existent paths. + */ +async function destroyWorktree(worktreePath: string): Promise { + // Read the .git file in the worktree to find the main repo + const gitFilePath = join(worktreePath, '.git') + let mainRepoPath: string | null = null + + try { + const gitFileContent = (await readFile(gitFilePath, 'utf-8')).trim() + // The .git file contains something like: gitdir: /path/to/repo/.git/worktrees/worktree-name + const match = gitFileContent.match(/^gitdir:\s*(.+)$/) + if (match && match[1]) { + // Extract the main repo .git directory (go up from .git/worktrees/name to .git) + const worktreeGitDir = match[1] + // Go up 2 levels from .git/worktrees/name to get to .git, then get parent for repo root + const mainGitDir = join(worktreeGitDir, '..', '..') + mainRepoPath = join(mainGitDir, '..') + } + } catch { + // Ignore errors reading .git file (path doesn't exist, not a file, etc.) + } + + // Try to remove using git worktree remove command + if (mainRepoPath) { + const result = await execFileNoThrowWithCwd( + gitExe(), + ['worktree', 'remove', '--force', worktreePath], + { cwd: mainRepoPath }, + ) + + if (result.code === 0) { + logForDebugging( + `[TeammateTool] Removed worktree via git: ${worktreePath}`, + ) + return + } + + // Check if the error is "not a working tree" (already removed) + if (result.stderr?.includes('not a working tree')) { + logForDebugging( + `[TeammateTool] Worktree already removed: ${worktreePath}`, + ) + return + } + + logForDebugging( + `[TeammateTool] git worktree remove failed, falling back to rm: ${result.stderr}`, + ) + } + + // Fallback: manually remove the directory + try { + await rm(worktreePath, { recursive: true, force: true }) + logForDebugging( + `[TeammateTool] Removed worktree directory manually: ${worktreePath}`, + ) + } catch (error) { + logForDebugging( + `[TeammateTool] Failed to remove worktree ${worktreePath}: ${errorMessage(error)}`, + ) + } +} + +/** + * Mark a team as created this session so it gets cleaned up on exit. + * Call this right after the initial writeTeamFile. TeamDelete should + * call unregisterTeamForSessionCleanup to prevent double-cleanup. + * Backing Set lives in bootstrap/state.ts so resetStateForTests() + * clears it between tests (avoids the PR #17615 cross-shard leak class). + */ +export function registerTeamForSessionCleanup(teamName: string): void { + getSessionCreatedTeams().add(teamName) +} + +/** + * Remove a team from session cleanup tracking (e.g., after explicit + * TeamDelete — already cleaned, don't try again on shutdown). + */ +export function unregisterTeamForSessionCleanup(teamName: string): void { + getSessionCreatedTeams().delete(teamName) +} + +/** + * Clean up all teams created this session that weren't explicitly deleted. + * Registered with gracefulShutdown from init.ts. + */ +export async function cleanupSessionTeams(): Promise { + const sessionCreatedTeams = getSessionCreatedTeams() + if (sessionCreatedTeams.size === 0) return + const teams = Array.from(sessionCreatedTeams) + logForDebugging( + `cleanupSessionTeams: removing ${teams.length} orphan team dir(s): ${teams.join(', ')}`, + ) + // Kill panes first — on SIGINT the teammate processes are still running; + // deleting directories alone would orphan them in open tmux/iTerm2 panes. + // (TeamDeleteTool's path doesn't need this — by then teammates have + // gracefully exited and useInboxPoller has already closed their panes.) + await Promise.allSettled(teams.map(name => killOrphanedTeammatePanes(name))) + await Promise.allSettled(teams.map(name => cleanupTeamDirectories(name))) + sessionCreatedTeams.clear() +} + +/** + * Best-effort kill of all pane-backed teammate panes for a team. + * Called from cleanupSessionTeams on ungraceful leader exit (SIGINT/SIGTERM). + * Dynamic imports avoid adding registry/detection to this module's static + * dep graph — this only runs at shutdown, so the import cost is irrelevant. + */ +async function killOrphanedTeammatePanes(teamName: string): Promise { + const teamFile = readTeamFile(teamName) + if (!teamFile) return + + const paneMembers = teamFile.members.filter( + m => + m.name !== TEAM_LEAD_NAME && + m.tmuxPaneId && + m.backendType && + isPaneBackend(m.backendType), + ) + if (paneMembers.length === 0) return + + const [{ ensureBackendsRegistered, getBackendByType }, { isInsideTmux }] = + await Promise.all([ + import('./backends/registry.js'), + import('./backends/detection.js'), + ]) + await ensureBackendsRegistered() + const useExternalSession = !(await isInsideTmux()) + + await Promise.allSettled( + paneMembers.map(async m => { + // filter above guarantees these; narrow for the type system + if (!m.tmuxPaneId || !m.backendType || !isPaneBackend(m.backendType)) { + return + } + const ok = await getBackendByType(m.backendType).killPane( + m.tmuxPaneId, + useExternalSession, + ) + logForDebugging( + `cleanupSessionTeams: killPane ${m.name} (${m.backendType} ${m.tmuxPaneId}) → ${ok}`, + ) + }), + ) +} + +/** + * Cleans up team and task directories for a given team name. + * Also cleans up git worktrees created for teammates. + * Called when a swarm session is terminated. + */ +export async function cleanupTeamDirectories(teamName: string): Promise { + const sanitizedName = sanitizeName(teamName) + + // Read team file to get worktree paths BEFORE deleting the team directory + const teamFile = readTeamFile(teamName) + const worktreePaths: string[] = [] + if (teamFile) { + for (const member of teamFile.members) { + if (member.worktreePath) { + worktreePaths.push(member.worktreePath) + } + } + } + + // Clean up worktrees first + for (const worktreePath of worktreePaths) { + await destroyWorktree(worktreePath) + } + + // Clean up team directory (~/.claude/teams/{team-name}/) + const teamDir = getTeamDir(teamName) + try { + await rm(teamDir, { recursive: true, force: true }) + logForDebugging(`[TeammateTool] Cleaned up team directory: ${teamDir}`) + } catch (error) { + logForDebugging( + `[TeammateTool] Failed to clean up team directory ${teamDir}: ${errorMessage(error)}`, + ) + } + + // Clean up tasks directory (~/.claude/tasks/{taskListId}/) + // The leader and teammates all store tasks under the sanitized team name. + const tasksDir = getTasksDir(sanitizedName) + try { + await rm(tasksDir, { recursive: true, force: true }) + logForDebugging(`[TeammateTool] Cleaned up tasks directory: ${tasksDir}`) + notifyTasksUpdated() + } catch (error) { + logForDebugging( + `[TeammateTool] Failed to clean up tasks directory ${tasksDir}: ${errorMessage(error)}`, + ) + } +} diff --git a/src/utils/swarm/teammateInit.ts b/src/utils/swarm/teammateInit.ts new file mode 100644 index 0000000000000000000000000000000000000000..db9cfb966905ced7ca9572664e85f49d439d8fdc --- /dev/null +++ b/src/utils/swarm/teammateInit.ts @@ -0,0 +1,129 @@ +/** + * Teammate Initialization Module + * + * Handles initialization for Claude Code instances running as teammates in a swarm. + * Registers a Stop hook to notify the team leader when the teammate becomes idle. + */ + +import type { AppState } from '../../state/AppState.js' +import { logForDebugging } from '../debug.js' +import { addFunctionHook } from '../hooks/sessionHooks.js' +import { applyPermissionUpdate } from '../permissions/PermissionUpdate.js' +import { jsonStringify } from '../slowOperations.js' +import { getTeammateColor } from '../teammate.js' +import { + createIdleNotification, + getLastPeerDmSummary, + writeToMailbox, +} from '../teammateMailbox.js' +import { readTeamFile, setMemberActive } from './teamHelpers.js' + +/** + * Initializes hooks for a teammate running in a swarm. + * Should be called early in session startup after AppState is available. + * + * Registers a Stop hook that sends an idle notification to the team leader + * when this teammate's session stops. + */ +export function initializeTeammateHooks( + setAppState: (updater: (prev: AppState) => AppState) => void, + sessionId: string, + teamInfo: { teamName: string; agentId: string; agentName: string }, +): void { + const { teamName, agentId, agentName } = teamInfo + + // Read team file to get leader ID + const teamFile = readTeamFile(teamName) + if (!teamFile) { + logForDebugging(`[TeammateInit] Team file not found for team: ${teamName}`) + return + } + + const leadAgentId = teamFile.leadAgentId + + // Apply team-wide allowed paths if any exist + if (teamFile.teamAllowedPaths && teamFile.teamAllowedPaths.length > 0) { + logForDebugging( + `[TeammateInit] Found ${teamFile.teamAllowedPaths.length} team-wide allowed path(s)`, + ) + + for (const allowedPath of teamFile.teamAllowedPaths) { + // For absolute paths (starting with /), prepend one / to create //path/** pattern + // For relative paths, just use path/** + const ruleContent = allowedPath.path.startsWith('/') + ? `/${allowedPath.path}/**` + : `${allowedPath.path}/**` + + logForDebugging( + `[TeammateInit] Applying team permission: ${allowedPath.toolName} allowed in ${allowedPath.path} (rule: ${ruleContent})`, + ) + + setAppState(prev => ({ + ...prev, + toolPermissionContext: applyPermissionUpdate( + prev.toolPermissionContext, + { + type: 'addRules', + rules: [ + { + toolName: allowedPath.toolName, + ruleContent, + }, + ], + behavior: 'allow', + destination: 'session', + }, + ), + })) + } + } + + // Find the leader's name from the members array + const leadMember = teamFile.members.find(m => m.agentId === leadAgentId) + const leadAgentName = leadMember?.name || 'team-lead' + + // Don't register hook if this agent is the leader + if (agentId === leadAgentId) { + logForDebugging( + '[TeammateInit] This agent is the team leader - skipping idle notification hook', + ) + return + } + + logForDebugging( + `[TeammateInit] Registering Stop hook for teammate ${agentName} to notify leader ${leadAgentName}`, + ) + + // Register Stop hook to notify leader when this teammate stops + addFunctionHook( + setAppState, + sessionId, + 'Stop', + '', // No matcher - applies to all Stop events + async (messages, _signal) => { + // Mark this teammate as idle in the team config (fire and forget) + void setMemberActive(teamName, agentName, false) + + // Send idle notification to the team leader using agent name (not UUID) + // Must await to ensure the write completes before process shutdown + const notification = createIdleNotification(agentName, { + idleReason: 'available', + summary: getLastPeerDmSummary(messages), + }) + await writeToMailbox(leadAgentName, { + from: agentName, + text: jsonStringify(notification), + timestamp: new Date().toISOString(), + color: getTeammateColor(), + }) + logForDebugging( + `[TeammateInit] Sent idle notification to leader ${leadAgentName}`, + ) + return true // Don't block the Stop + }, + 'Failed to send idle notification to team leader', + { + timeout: 10000, + }, + ) +} diff --git a/src/utils/swarm/teammateLayoutManager.ts b/src/utils/swarm/teammateLayoutManager.ts new file mode 100644 index 0000000000000000000000000000000000000000..2b26a3e17aecc2f7c6305173abab6533e30995fd --- /dev/null +++ b/src/utils/swarm/teammateLayoutManager.ts @@ -0,0 +1,107 @@ +import type { AgentColorName } from '../../tools/AgentTool/agentColorManager.js' +import { AGENT_COLORS } from '../../tools/AgentTool/agentColorManager.js' +import { detectAndGetBackend } from './backends/registry.js' +import type { PaneBackend } from './backends/types.js' + +// Track color assignments for teammates (persisted per session) +const teammateColorAssignments = new Map() +let colorIndex = 0 + +/** + * Gets the appropriate backend for the current environment. + * detectAndGetBackend() caches internally — no need for a second cache here. + */ +async function getBackend(): Promise { + return (await detectAndGetBackend()).backend +} + +/** + * Assigns a unique color to a teammate from the available palette. + * Colors are assigned in round-robin order. + */ +export function assignTeammateColor(teammateId: string): AgentColorName { + const existing = teammateColorAssignments.get(teammateId) + if (existing) { + return existing + } + + const color = AGENT_COLORS[colorIndex % AGENT_COLORS.length]! + teammateColorAssignments.set(teammateId, color) + colorIndex++ + + return color +} + +/** + * Gets the assigned color for a teammate, if any. + */ +export function getTeammateColor( + teammateId: string, +): AgentColorName | undefined { + return teammateColorAssignments.get(teammateId) +} + +/** + * Clears all teammate color assignments. + * Called during team cleanup to reset state for potential new teams. + */ +export function clearTeammateColors(): void { + teammateColorAssignments.clear() + colorIndex = 0 +} + +/** + * Checks if we're currently running inside a tmux session. + * Uses the detection module directly for this check. + */ +export async function isInsideTmux(): Promise { + const { isInsideTmux: checkTmux } = await import('./backends/detection.js') + return checkTmux() +} + +/** + * Creates a new teammate pane in the swarm view. + * Automatically selects the appropriate backend (tmux or iTerm2) based on environment. + * + * When running INSIDE tmux: + * - Uses TmuxBackend to split the current window + * - Leader stays on left (30%), teammates on right (70%) + * + * When running in iTerm2 (not in tmux) with it2 CLI: + * - Uses ITermBackend for native iTerm2 split panes + * + * When running OUTSIDE tmux/iTerm2: + * - Falls back to TmuxBackend with external claude-swarm session + */ +export async function createTeammatePaneInSwarmView( + teammateName: string, + teammateColor: AgentColorName, +): Promise<{ paneId: string; isFirstTeammate: boolean }> { + const backend = await getBackend() + return backend.createTeammatePaneInSwarmView(teammateName, teammateColor) +} + +/** + * Enables pane border status for a window (shows pane titles). + * Delegates to the detected backend. + */ +export async function enablePaneBorderStatus( + windowTarget?: string, + useSwarmSocket = false, +): Promise { + const backend = await getBackend() + return backend.enablePaneBorderStatus(windowTarget, useSwarmSocket) +} + +/** + * Sends a command to a specific pane. + * Delegates to the detected backend. + */ +export async function sendCommandToPane( + paneId: string, + command: string, + useSwarmSocket = false, +): Promise { + const backend = await getBackend() + return backend.sendCommandToPane(paneId, command, useSwarmSocket) +} diff --git a/src/utils/swarm/teammateModel.ts b/src/utils/swarm/teammateModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..0a49009b2c739956c292c5f7112eff6771acbb5f --- /dev/null +++ b/src/utils/swarm/teammateModel.ts @@ -0,0 +1,10 @@ +import { CLAUDE_OPUS_4_6_CONFIG } from '../model/configs.js' +import { getAPIProvider } from '../model/providers.js' + +// @[MODEL LAUNCH]: Update the fallback model below. +// When the user has never set teammateDefaultModel in /config, new teammates +// use Opus 4.6. Must be provider-aware so Bedrock/Vertex/Foundry customers get +// the correct model ID. +export function getHardcodedTeammateModelFallback(): string { + return CLAUDE_OPUS_4_6_CONFIG[getAPIProvider()] +} diff --git a/src/utils/swarm/teammatePromptAddendum.ts b/src/utils/swarm/teammatePromptAddendum.ts new file mode 100644 index 0000000000000000000000000000000000000000..1a5e227474763bfa0c86f22c69517cdf84a936d7 --- /dev/null +++ b/src/utils/swarm/teammatePromptAddendum.ts @@ -0,0 +1,18 @@ +/** + * Teammate-specific system prompt addendum. + * + * This is appended to the full main agent system prompt for teammates. + * It explains visibility constraints and communication requirements. + */ + +export const TEAMMATE_SYSTEM_PROMPT_ADDENDUM = ` +# Agent Teammate Communication + +IMPORTANT: You are running as an agent in a team. To communicate with anyone on your team: +- Use the SendMessage tool with \`to: ""\` to send messages to specific teammates +- Use the SendMessage tool with \`to: "*"\` sparingly for team-wide broadcasts + +Just writing a response in text is not visible to others on your team - you MUST use the SendMessage tool. + +The user interacts primarily with the team lead. Your work is coordinated through the task system and teammate messaging. +` diff --git a/src/utils/task/TaskOutput.ts b/src/utils/task/TaskOutput.ts new file mode 100644 index 0000000000000000000000000000000000000000..67cf1af8048c4263a1026532b81d579bc20613ee --- /dev/null +++ b/src/utils/task/TaskOutput.ts @@ -0,0 +1,390 @@ +import { unlink } from 'fs/promises' +import { CircularBuffer } from '../CircularBuffer.js' +import { logForDebugging } from '../debug.js' +import { readFileRange, tailFile } from '../fsOperations.js' +import { getMaxOutputLength } from '../shell/outputLimits.js' +import { safeJoinLines } from '../stringUtils.js' +import { DiskTaskOutput, getTaskOutputPath } from './diskOutput.js' + +const DEFAULT_MAX_MEMORY = 8 * 1024 * 1024 // 8MB +const POLL_INTERVAL_MS = 1000 +const PROGRESS_TAIL_BYTES = 4096 + +type ProgressCallback = ( + lastLines: string, + allLines: string, + totalLines: number, + totalBytes: number, + isIncomplete: boolean, +) => void + +/** + * Single source of truth for a shell command's output. + * + * For bash commands (file mode): both stdout and stderr go directly to + * a file via stdio fds — neither enters JS. Progress is extracted by + * polling the file tail. getStderr() returns '' since stderr is + * interleaved in the output file. + * + * For hooks (pipe mode): data flows through writeStdout()/writeStderr() + * and is buffered in memory, spilling to disk if it exceeds the limit. + */ +export class TaskOutput { + readonly taskId: string + readonly path: string + /** True when stdout goes to a file fd (bypassing JS). False for pipe mode (hooks). */ + readonly stdoutToFile: boolean + #stdoutBuffer = '' + #stderrBuffer = '' + #disk: DiskTaskOutput | null = null + #recentLines = new CircularBuffer(1000) + #totalLines = 0 + #totalBytes = 0 + #maxMemory: number + #onProgress: ProgressCallback | null + /** Set by getStdout() — true when the file was fully read (≤ maxOutputLength). */ + #outputFileRedundant = false + /** Set by getStdout() — total file size in bytes. */ + #outputFileSize = 0 + + // --- Shared poller state --- + + /** Registry of all file-mode TaskOutput instances with onProgress callbacks. */ + static #registry = new Map() + /** Subset of #registry currently being polled (visibility-driven by React). */ + static #activePolling = new Map() + static #pollInterval: ReturnType | null = null + + constructor( + taskId: string, + onProgress: ProgressCallback | null, + stdoutToFile = false, + maxMemory: number = DEFAULT_MAX_MEMORY, + ) { + this.taskId = taskId + this.path = getTaskOutputPath(taskId) + this.stdoutToFile = stdoutToFile + this.#maxMemory = maxMemory + this.#onProgress = onProgress + + // Register for polling when stdout goes to a file and progress is needed. + // Actual polling is started/stopped by React via startPolling/stopPolling. + if (stdoutToFile && onProgress) { + TaskOutput.#registry.set(taskId, this) + } + } + + /** + * Begin polling the output file for progress. Called from React + * useEffect when the progress component mounts. + */ + static startPolling(taskId: string): void { + const instance = TaskOutput.#registry.get(taskId) + if (!instance || !instance.#onProgress) { + return + } + TaskOutput.#activePolling.set(taskId, instance) + if (!TaskOutput.#pollInterval) { + TaskOutput.#pollInterval = setInterval(TaskOutput.#tick, POLL_INTERVAL_MS) + TaskOutput.#pollInterval.unref() + } + } + + /** + * Stop polling the output file. Called from React useEffect cleanup + * when the progress component unmounts. + */ + static stopPolling(taskId: string): void { + TaskOutput.#activePolling.delete(taskId) + if (TaskOutput.#activePolling.size === 0 && TaskOutput.#pollInterval) { + clearInterval(TaskOutput.#pollInterval) + TaskOutput.#pollInterval = null + } + } + + /** + * Shared tick: reads the file tail for every actively-polled task. + * Non-async body (.then) to avoid stacking if I/O is slow. + */ + static #tick(): void { + for (const [, entry] of TaskOutput.#activePolling) { + if (!entry.#onProgress) { + continue + } + void tailFile(entry.path, PROGRESS_TAIL_BYTES).then( + ({ content, bytesRead, bytesTotal }) => { + if (!entry.#onProgress) { + return + } + // Always call onProgress even when content is empty, so the + // progress loop wakes up and can check for backgrounding. + // Commands like `git log -S` produce no output for long periods. + if (!content) { + entry.#onProgress('', '', entry.#totalLines, bytesTotal, false) + return + } + // Count all newlines in the tail and capture slice points for the + // last 5 and last 100 lines. Uncapped so extrapolation stays accurate + // for dense output (short lines → >100 newlines in 4KB). + let pos = content.length + let n5 = 0 + let n100 = 0 + let lineCount = 0 + while (pos > 0) { + pos = content.lastIndexOf('\n', pos - 1) + lineCount++ + if (lineCount === 5) n5 = pos <= 0 ? 0 : pos + 1 + if (lineCount === 100) n100 = pos <= 0 ? 0 : pos + 1 + } + // lineCount is exact when the whole file fits in PROGRESS_TAIL_BYTES. + // Otherwise extrapolate from the tail sample; monotone max keeps the + // counter from going backwards when the tail has longer lines on one tick. + const totalLines = + bytesRead >= bytesTotal + ? lineCount + : Math.max( + entry.#totalLines, + Math.round((bytesTotal / bytesRead) * lineCount), + ) + entry.#totalLines = totalLines + entry.#totalBytes = bytesTotal + entry.#onProgress( + content.slice(n5), + content.slice(n100), + totalLines, + bytesTotal, + bytesRead < bytesTotal, + ) + }, + () => { + // File may not exist yet + }, + ) + } + } + + /** Write stdout data (pipe mode only — used by hooks). */ + writeStdout(data: string): void { + this.#writeBuffered(data, false) + } + + /** Write stderr data (always piped). */ + writeStderr(data: string): void { + this.#writeBuffered(data, true) + } + + #writeBuffered(data: string, isStderr: boolean): void { + this.#totalBytes += data.length + + this.#updateProgress(data) + + // Write to disk if already overflowed + if (this.#disk) { + this.#disk.append(isStderr ? `[stderr] ${data}` : data) + return + } + + // Check if this chunk would exceed the in-memory limit + const totalMem = + this.#stdoutBuffer.length + this.#stderrBuffer.length + data.length + if (totalMem > this.#maxMemory) { + this.#spillToDisk(isStderr ? data : null, isStderr ? null : data) + return + } + + if (isStderr) { + this.#stderrBuffer += data + } else { + this.#stdoutBuffer += data + } + } + + /** + * Single backward pass: count all newlines (for totalLines) and extract + * the last few lines as flat copies (for the CircularBuffer / progress). + * Only used in pipe mode (hooks). File mode uses the shared poller. + */ + #updateProgress(data: string): void { + const MAX_PROGRESS_BYTES = 4096 + const MAX_PROGRESS_LINES = 100 + + let lineCount = 0 + const lines: string[] = [] + let extractedBytes = 0 + let pos = data.length + + while (pos > 0) { + const prev = data.lastIndexOf('\n', pos - 1) + if (prev === -1) { + break + } + lineCount++ + if ( + lines.length < MAX_PROGRESS_LINES && + extractedBytes < MAX_PROGRESS_BYTES + ) { + const lineLen = pos - prev - 1 + if (lineLen > 0 && lineLen <= MAX_PROGRESS_BYTES - extractedBytes) { + const line = data.slice(prev + 1, pos) + if (line.trim()) { + lines.push(Buffer.from(line).toString()) + extractedBytes += lineLen + } + } + } + pos = prev + } + + this.#totalLines += lineCount + + for (let i = lines.length - 1; i >= 0; i--) { + this.#recentLines.add(lines[i]!) + } + + if (this.#onProgress && lines.length > 0) { + const recent = this.#recentLines.getRecent(5) + this.#onProgress( + safeJoinLines(recent, '\n'), + safeJoinLines(this.#recentLines.getRecent(100), '\n'), + this.#totalLines, + this.#totalBytes, + this.#disk !== null, + ) + } + } + + #spillToDisk(stderrChunk: string | null, stdoutChunk: string | null): void { + this.#disk = new DiskTaskOutput(this.taskId) + + // Flush existing buffers + if (this.#stdoutBuffer) { + this.#disk.append(this.#stdoutBuffer) + this.#stdoutBuffer = '' + } + if (this.#stderrBuffer) { + this.#disk.append(`[stderr] ${this.#stderrBuffer}`) + this.#stderrBuffer = '' + } + + // Write the chunk that triggered overflow + if (stdoutChunk) { + this.#disk.append(stdoutChunk) + } + if (stderrChunk) { + this.#disk.append(`[stderr] ${stderrChunk}`) + } + } + + /** + * Get stdout. In file mode, reads from the output file. + * In pipe mode, returns the in-memory buffer or tail from CircularBuffer. + */ + async getStdout(): Promise { + if (this.stdoutToFile) { + return this.#readStdoutFromFile() + } + // Pipe mode (hooks) — use in-memory data + if (this.#disk) { + const recent = this.#recentLines.getRecent(5) + const tail = safeJoinLines(recent, '\n') + const sizeKB = Math.round(this.#totalBytes / 1024) + const notice = `\nOutput truncated (${sizeKB}KB total). Full output saved to: ${this.path}` + return tail ? tail + notice : notice.trimStart() + } + return this.#stdoutBuffer + } + + async #readStdoutFromFile(): Promise { + const maxBytes = getMaxOutputLength() + try { + const result = await readFileRange(this.path, 0, maxBytes) + if (!result) { + this.#outputFileRedundant = true + return '' + } + const { content, bytesRead, bytesTotal } = result + // If the file fits, it's fully captured inline and can be deleted. + // If not, return what we read — processToolResultBlock handles + // the formatting and persistence downstream. + this.#outputFileSize = bytesTotal + this.#outputFileRedundant = bytesTotal <= bytesRead + return content + } catch (err) { + // Surface the error instead of silently returning empty. An ENOENT here + // means the output file was deleted while the command was running + // (historically: cross-session startup cleanup in the same project dir). + // Returning a diagnostic string keeps the tool_result non-empty, which + // avoids reminder-only-at-tail confusion downstream and tells the model + // (and us, via the transcript) what actually happened. + const code = + err instanceof Error && 'code' in err ? String(err.code) : 'unknown' + logForDebugging( + `TaskOutput.#readStdoutFromFile: failed to read ${this.path} (${code}): ${err}`, + ) + return `` + } + } + + /** Sync getter for ExecResult.stderr */ + getStderr(): string { + if (this.#disk) { + return '' + } + return this.#stderrBuffer + } + + get isOverflowed(): boolean { + return this.#disk !== null + } + + get totalLines(): number { + return this.#totalLines + } + + get totalBytes(): number { + return this.#totalBytes + } + + /** + * True after getStdout() when the output file was fully read. + * The file content is redundant (fully in ExecResult.stdout) and can be deleted. + */ + get outputFileRedundant(): boolean { + return this.#outputFileRedundant + } + + /** Total file size in bytes, set after getStdout() reads the file. */ + get outputFileSize(): number { + return this.#outputFileSize + } + + /** Force all buffered content to disk. Call when backgrounding. */ + spillToDisk(): void { + if (!this.#disk) { + this.#spillToDisk(null, null) + } + } + + async flush(): Promise { + await this.#disk?.flush() + } + + /** Delete the output file (fire-and-forget safe). */ + async deleteOutputFile(): Promise { + try { + await unlink(this.path) + } catch { + // File may already be deleted or not exist + } + } + + clear(): void { + this.#stdoutBuffer = '' + this.#stderrBuffer = '' + this.#recentLines.clear() + this.#onProgress = null + this.#disk?.cancel() + TaskOutput.stopPolling(this.taskId) + TaskOutput.#registry.delete(this.taskId) + } +} diff --git a/src/utils/task/diskOutput.ts b/src/utils/task/diskOutput.ts new file mode 100644 index 0000000000000000000000000000000000000000..b22018dbdc882934969666e6cfc3ecf9fa7512b9 --- /dev/null +++ b/src/utils/task/diskOutput.ts @@ -0,0 +1,451 @@ +import { constants as fsConstants } from 'fs' +import { + type FileHandle, + mkdir, + open, + stat, + symlink, + unlink, +} from 'fs/promises' +import { join } from 'path' +import { getSessionId } from '../../bootstrap/state.js' +import { getErrnoCode } from '../errors.js' +import { readFileRange, tailFile } from '../fsOperations.js' +import { logError } from '../log.js' +import { getProjectTempDir } from '../permissions/filesystem.js' + +// SECURITY: O_NOFOLLOW prevents following symlinks when opening task output files. +// Without this, an attacker in the sandbox could create symlinks in the tasks directory +// pointing to arbitrary files, causing Claude Code on the host to write to those files. +// O_NOFOLLOW is not available on Windows, but the sandbox attack vector is Unix-only. +const O_NOFOLLOW = fsConstants.O_NOFOLLOW ?? 0 + +const DEFAULT_MAX_READ_BYTES = 8 * 1024 * 1024 // 8MB + +/** + * Disk cap for task output files. In file mode (bash), a watchdog polls + * file size and kills the process. In pipe mode (hooks), DiskTaskOutput + * drops chunks past this limit. Shared so both caps stay in sync. + */ +export const MAX_TASK_OUTPUT_BYTES = 5 * 1024 * 1024 * 1024 +export const MAX_TASK_OUTPUT_BYTES_DISPLAY = '5GB' + +/** + * Get the task output directory for this session. + * Uses project temp directory so reads are auto-allowed by checkReadableInternalPath. + * + * The session ID is included so concurrent sessions in the same project don't + * clobber each other's output files. Startup cleanup in one session previously + * unlinked in-flight output files from other sessions — the writing process's fd + * keeps the inode alive but reads via path fail ENOENT, and getStdout() returned + * empty string (inc-4586 / boris-20260309-060423). + * + * The session ID is captured at FIRST CALL, not re-read on every invocation. + * /clear calls regenerateSessionId(), which would otherwise cause + * ensureOutputDir() to create a new-session path while existing TaskOutput + * instances still hold old-session paths — open() would ENOENT. Background + * bash tasks surviving /clear need their output files to stay reachable. + */ +let _taskOutputDir: string | undefined +export function getTaskOutputDir(): string { + if (_taskOutputDir === undefined) { + _taskOutputDir = join(getProjectTempDir(), getSessionId(), 'tasks') + } + return _taskOutputDir +} + +/** Test helper — clears the memoized dir. */ +export function _resetTaskOutputDirForTest(): void { + _taskOutputDir = undefined +} + +/** + * Ensure the task output directory exists + */ +async function ensureOutputDir(): Promise { + await mkdir(getTaskOutputDir(), { recursive: true }) +} + +/** + * Get the output file path for a task + */ +export function getTaskOutputPath(taskId: string): string { + return join(getTaskOutputDir(), `${taskId}.output`) +} + +// Tracks fire-and-forget promises (initTaskOutput, initTaskOutputAsSymlink, +// evictTaskOutput, #drain) so tests can drain before teardown. Prevents the +// async-ENOENT-after-teardown flake class (#24957, #25065): a voided async +// resumes after preload's afterEach nuked the temp dir → ENOENT → unhandled +// rejection → flaky test failure. allSettled so a rejection doesn't short- +// circuit the drain and leave other ops racing the rmSync. +const _pendingOps = new Set>() +function track(p: Promise): Promise { + _pendingOps.add(p) + void p.finally(() => _pendingOps.delete(p)).catch(() => {}) + return p +} + +/** + * Encapsulates async disk writes for a single task's output. + * + * Uses a flat array as a write queue processed by a single drain loop, + * so each chunk can be GC'd immediately after its write completes. + * This avoids the memory retention problem of chained .then() closures + * where every reaction captures its data until the whole chain resolves. + */ +export class DiskTaskOutput { + #path: string + #fileHandle: FileHandle | null = null + #queue: string[] = [] + #bytesWritten = 0 + #capped = false + #flushPromise: Promise | null = null + #flushResolve: (() => void) | null = null + + constructor(taskId: string) { + this.#path = getTaskOutputPath(taskId) + } + + append(content: string): void { + if (this.#capped) { + return + } + // content.length (UTF-16 code units) undercounts UTF-8 bytes by at most ~3×. + // Acceptable for a coarse disk-fill guard — avoids re-scanning every chunk. + this.#bytesWritten += content.length + if (this.#bytesWritten > MAX_TASK_OUTPUT_BYTES) { + this.#capped = true + this.#queue.push( + `\n[output truncated: exceeded ${MAX_TASK_OUTPUT_BYTES_DISPLAY} disk cap]\n`, + ) + } else { + this.#queue.push(content) + } + if (!this.#flushPromise) { + this.#flushPromise = new Promise(resolve => { + this.#flushResolve = resolve + }) + void track(this.#drain()) + } + } + + flush(): Promise { + return this.#flushPromise ?? Promise.resolve() + } + + cancel(): void { + this.#queue.length = 0 + } + + async #drainAllChunks(): Promise { + while (true) { + try { + if (!this.#fileHandle) { + await ensureOutputDir() + this.#fileHandle = await open( + this.#path, + process.platform === 'win32' + ? 'a' + : fsConstants.O_WRONLY | + fsConstants.O_APPEND | + fsConstants.O_CREAT | + O_NOFOLLOW, + ) + } + while (true) { + await this.#writeAllChunks() + if (this.#queue.length === 0) { + break + } + } + } finally { + if (this.#fileHandle) { + const fileHandle = this.#fileHandle + this.#fileHandle = null + await fileHandle.close() + } + } + // you could have another .append() while we're waiting for the file to close, so we check the queue again before fully exiting + if (this.#queue.length) { + continue + } + + break + } + } + + #writeAllChunks(): Promise { + // This code is extremely precise. + // You **must not** add an await here!! That will cause memory to balloon as the queue grows. + // It's okay to add an `await` to the caller of this method (e.g. #drainAllChunks) because that won't cause Buffer[] to be kept alive in memory. + return this.#fileHandle!.appendFile( + // This variable needs to get GC'd ASAP. + this.#queueToBuffers(), + ) + } + + /** Keep this in a separate method so that GC doesn't keep it alive for any longer than it should. */ + #queueToBuffers(): Buffer { + // Use .splice to in-place mutate the array, informing the GC it can free it. + const queue = this.#queue.splice(0, this.#queue.length) + + let totalLength = 0 + for (const str of queue) { + totalLength += Buffer.byteLength(str, 'utf8') + } + + const buffer = Buffer.allocUnsafe(totalLength) + let offset = 0 + for (const str of queue) { + offset += buffer.write(str, offset, 'utf8') + } + + return buffer + } + + async #drain(): Promise { + try { + await this.#drainAllChunks() + } catch (e) { + // Transient fs errors (EMFILE on busy CI, EPERM on Windows pending- + // delete) previously rode up through `void this.#drain()` as an + // unhandled rejection while the flush promise resolved anyway — callers + // saw an empty file with no error. Retry once for the transient case + // (queue is intact if open() failed), then log and give up. + logError(e) + if (this.#queue.length > 0) { + try { + await this.#drainAllChunks() + } catch (e2) { + logError(e2) + } + } + } finally { + const resolve = this.#flushResolve! + this.#flushPromise = null + this.#flushResolve = null + resolve() + } + } +} + +const outputs = new Map() + +/** + * Test helper — cancel pending writes, await in-flight ops, clear the map. + * backgroundShells.test.ts and other task tests spawn real shells that + * write through this module without afterEach cleanup; their entries + * leak into diskOutput.test.ts on the same shard. + * + * Awaits all tracked promises until the set stabilizes — a settling promise + * may spawn another (initTaskOutputAsSymlink's catch → initTaskOutput). + * Call this in afterEach BEFORE rmSync to avoid async-ENOENT-after-teardown. + */ +export async function _clearOutputsForTest(): Promise { + for (const output of outputs.values()) { + output.cancel() + } + while (_pendingOps.size > 0) { + await Promise.allSettled([..._pendingOps]) + } + outputs.clear() +} + +function getOrCreateOutput(taskId: string): DiskTaskOutput { + let output = outputs.get(taskId) + if (!output) { + output = new DiskTaskOutput(taskId) + outputs.set(taskId, output) + } + return output +} + +/** + * Append output to a task's disk file asynchronously. + * Creates the file if it doesn't exist. + */ +export function appendTaskOutput(taskId: string, content: string): void { + getOrCreateOutput(taskId).append(content) +} + +/** + * Wait for all pending writes for a task to complete. + * Useful before reading output to ensure all data is flushed. + */ +export async function flushTaskOutput(taskId: string): Promise { + const output = outputs.get(taskId) + if (output) { + await output.flush() + } +} + +/** + * Evict a task's DiskTaskOutput from the in-memory map after flushing. + * Unlike cleanupTaskOutput, this does not delete the output file on disk. + * Call this when a task completes and its output has been consumed. + */ +export function evictTaskOutput(taskId: string): Promise { + return track( + (async () => { + const output = outputs.get(taskId) + if (output) { + await output.flush() + outputs.delete(taskId) + } + })(), + ) +} + +/** + * Get delta (new content) since last read. + * Reads only from the byte offset, up to maxBytes — never loads the full file. + */ +export async function getTaskOutputDelta( + taskId: string, + fromOffset: number, + maxBytes: number = DEFAULT_MAX_READ_BYTES, +): Promise<{ content: string; newOffset: number }> { + try { + const result = await readFileRange( + getTaskOutputPath(taskId), + fromOffset, + maxBytes, + ) + if (!result) { + return { content: '', newOffset: fromOffset } + } + return { + content: result.content, + newOffset: fromOffset + result.bytesRead, + } + } catch (e) { + const code = getErrnoCode(e) + if (code === 'ENOENT') { + return { content: '', newOffset: fromOffset } + } + logError(e) + return { content: '', newOffset: fromOffset } + } +} + +/** + * Get output for a task, reading the tail of the file. + * Caps at maxBytes to avoid loading multi-GB files into memory. + */ +export async function getTaskOutput( + taskId: string, + maxBytes: number = DEFAULT_MAX_READ_BYTES, +): Promise { + try { + const { content, bytesTotal, bytesRead } = await tailFile( + getTaskOutputPath(taskId), + maxBytes, + ) + if (bytesTotal > bytesRead) { + return `[${Math.round((bytesTotal - bytesRead) / 1024)}KB of earlier output omitted]\n${content}` + } + return content + } catch (e) { + const code = getErrnoCode(e) + if (code === 'ENOENT') { + return '' + } + logError(e) + return '' + } +} + +/** + * Get the current size (offset) of a task's output file. + */ +export async function getTaskOutputSize(taskId: string): Promise { + try { + return (await stat(getTaskOutputPath(taskId))).size + } catch (e) { + const code = getErrnoCode(e) + if (code === 'ENOENT') { + return 0 + } + logError(e) + return 0 + } +} + +/** + * Clean up a task's output file and write queue. + */ +export async function cleanupTaskOutput(taskId: string): Promise { + const output = outputs.get(taskId) + if (output) { + output.cancel() + outputs.delete(taskId) + } + + try { + await unlink(getTaskOutputPath(taskId)) + } catch (e) { + const code = getErrnoCode(e) + if (code === 'ENOENT') { + return + } + logError(e) + } +} + +/** + * Initialize output file for a new task. + * Creates an empty file to ensure the path exists. + */ +export function initTaskOutput(taskId: string): Promise { + return track( + (async () => { + await ensureOutputDir() + const outputPath = getTaskOutputPath(taskId) + // SECURITY: O_NOFOLLOW prevents symlink-following attacks from the sandbox. + // O_EXCL ensures we create a new file and fail if something already exists at this path. + // On Windows, use string flags — numeric O_EXCL can produce EINVAL through libuv. + const fh = await open( + outputPath, + process.platform === 'win32' + ? 'wx' + : fsConstants.O_WRONLY | + fsConstants.O_CREAT | + fsConstants.O_EXCL | + O_NOFOLLOW, + ) + await fh.close() + return outputPath + })(), + ) +} + +/** + * Initialize output file as a symlink to another file (e.g., agent transcript). + * Tries to create the symlink first; if a file already exists, removes it and retries. + */ +export function initTaskOutputAsSymlink( + taskId: string, + targetPath: string, +): Promise { + return track( + (async () => { + try { + await ensureOutputDir() + const outputPath = getTaskOutputPath(taskId) + + try { + await symlink(targetPath, outputPath) + } catch { + await unlink(outputPath) + await symlink(targetPath, outputPath) + } + + return outputPath + } catch (error) { + logError(error) + return initTaskOutput(taskId) + } + })(), + ) +} diff --git a/src/utils/task/framework.ts b/src/utils/task/framework.ts new file mode 100644 index 0000000000000000000000000000000000000000..7bb5099683d1f85c6a72bf4af66152e9bf244a55 --- /dev/null +++ b/src/utils/task/framework.ts @@ -0,0 +1,308 @@ +import { + OUTPUT_FILE_TAG, + STATUS_TAG, + SUMMARY_TAG, + TASK_ID_TAG, + TASK_NOTIFICATION_TAG, + TASK_TYPE_TAG, + TOOL_USE_ID_TAG, +} from '../../constants/xml.js' +import type { AppState } from '../../state/AppState.js' +import { + isTerminalTaskStatus, + type TaskStatus, + type TaskType, +} from '../../Task.js' +import type { TaskState } from '../../tasks/types.js' +import { enqueuePendingNotification } from '../messageQueueManager.js' +import { enqueueSdkEvent } from '../sdkEventQueue.js' +import { getTaskOutputDelta, getTaskOutputPath } from './diskOutput.js' + +// Standard polling interval for all tasks +export const POLL_INTERVAL_MS = 1000 + +// Duration to display killed tasks before eviction +export const STOPPED_DISPLAY_MS = 3_000 + +// Grace period for terminal local_agent tasks in the coordinator panel +export const PANEL_GRACE_MS = 30_000 + +// Attachment type for task status updates +export type TaskAttachment = { + type: 'task_status' + taskId: string + toolUseId?: string + taskType: TaskType + status: TaskStatus + description: string + deltaSummary: string | null // New output since last attachment +} + +type SetAppState = (updater: (prev: AppState) => AppState) => void + +/** + * Update a task's state in AppState. + * Helper function for task implementations. + * Generic to allow type-safe updates for specific task types. + */ +export function updateTaskState( + taskId: string, + setAppState: SetAppState, + updater: (task: T) => T, +): void { + setAppState(prev => { + const task = prev.tasks?.[taskId] as T | undefined + if (!task) { + return prev + } + const updated = updater(task) + if (updated === task) { + // Updater returned the same reference (early-return no-op). Skip the + // spread so s.tasks subscribers don't re-render on unchanged state. + return prev + } + return { + ...prev, + tasks: { + ...prev.tasks, + [taskId]: updated, + }, + } + }) +} + +/** + * Register a new task in AppState. + */ +export function registerTask(task: TaskState, setAppState: SetAppState): void { + let isReplacement = false + setAppState(prev => { + const existing = prev.tasks[task.id] + isReplacement = existing !== undefined + // Carry forward UI-held state on re-register (resumeAgentBackground + // replaces the task; user's retain shouldn't reset). startTime keeps + // the panel sort stable; messages + diskLoaded preserve the viewed + // transcript across the replace (the user's just-appended prompt lives + // in messages and isn't on disk yet). + const merged = + existing && 'retain' in existing + ? { + ...task, + retain: existing.retain, + startTime: existing.startTime, + messages: existing.messages, + diskLoaded: existing.diskLoaded, + pendingMessages: existing.pendingMessages, + } + : task + return { ...prev, tasks: { ...prev.tasks, [task.id]: merged } } + }) + + // Replacement (resume) — not a new start. Skip to avoid double-emit. + if (isReplacement) return + + enqueueSdkEvent({ + type: 'system', + subtype: 'task_started', + task_id: task.id, + tool_use_id: task.toolUseId, + description: task.description, + task_type: task.type, + workflow_name: + 'workflowName' in task + ? (task.workflowName as string | undefined) + : undefined, + prompt: 'prompt' in task ? (task.prompt as string) : undefined, + }) +} + +/** + * Eagerly evict a terminal task from AppState. + * The task must be in a terminal state (completed/failed/killed) with notified=true. + * This allows memory to be freed without waiting for the next query loop iteration. + * The lazy GC in generateTaskAttachments() remains as a safety net. + */ +export function evictTerminalTask( + taskId: string, + setAppState: SetAppState, +): void { + setAppState(prev => { + const task = prev.tasks?.[taskId] + if (!task) return prev + if (!isTerminalTaskStatus(task.status)) return prev + if (!task.notified) return prev + // Panel grace period — blocks eviction until deadline passes. + // 'retain' in task narrows to LocalAgentTaskState (the only type with + // that field); evictAfter is optional so 'evictAfter' in task would + // miss tasks that haven't had it set yet. + if ('retain' in task && (task.evictAfter ?? Infinity) > Date.now()) { + return prev + } + const { [taskId]: _, ...remainingTasks } = prev.tasks + return { ...prev, tasks: remainingTasks } + }) +} + +/** + * Get all running tasks. + */ +export function getRunningTasks(state: AppState): TaskState[] { + const tasks = state.tasks ?? {} + return Object.values(tasks).filter(task => task.status === 'running') +} + +/** + * Generate attachments for tasks with new output or status changes. + * Called by the framework to create push notifications. + */ +export async function generateTaskAttachments(state: AppState): Promise<{ + attachments: TaskAttachment[] + // Only the offset patch — NOT the full task. The task may transition to + // completed during getTaskOutputDelta's async disk read, and spreading the + // full stale snapshot would clobber that transition (zombifying the task). + updatedTaskOffsets: Record + evictedTaskIds: string[] +}> { + const attachments: TaskAttachment[] = [] + const updatedTaskOffsets: Record = {} + const evictedTaskIds: string[] = [] + const tasks = state.tasks ?? {} + + for (const taskState of Object.values(tasks)) { + if (taskState.notified) { + switch (taskState.status) { + case 'completed': + case 'failed': + case 'killed': + // Evict terminal tasks — they've been consumed and can be GC'd + evictedTaskIds.push(taskState.id) + continue + case 'pending': + // Keep in map — hasn't run yet, but parent already knows about it + continue + case 'running': + // Fall through to running logic below + break + } + } + + if (taskState.status === 'running') { + const delta = await getTaskOutputDelta( + taskState.id, + taskState.outputOffset, + ) + if (delta.content) { + updatedTaskOffsets[taskState.id] = delta.newOffset + } + } + + // Completed tasks are NOT notified here — each task type handles its own + // completion notification via enqueuePendingNotification(). Generating + // attachments here would race with those per-type callbacks, causing + // dual delivery (one inline attachment + one separate API turn). + } + + return { attachments, updatedTaskOffsets, evictedTaskIds } +} + +/** + * Apply the outputOffset patches and evictions from generateTaskAttachments. + * Merges patches against FRESH prev.tasks (not the stale pre-await snapshot), + * so concurrent status transitions aren't clobbered. + */ +export function applyTaskOffsetsAndEvictions( + setAppState: SetAppState, + updatedTaskOffsets: Record, + evictedTaskIds: string[], +): void { + const offsetIds = Object.keys(updatedTaskOffsets) + if (offsetIds.length === 0 && evictedTaskIds.length === 0) { + return + } + setAppState(prev => { + let changed = false + const newTasks = { ...prev.tasks } + for (const id of offsetIds) { + const fresh = newTasks[id] + // Re-check status on fresh state — task may have completed during the + // await. If it's no longer running, the offset update is moot. + if (fresh?.status === 'running') { + newTasks[id] = { ...fresh, outputOffset: updatedTaskOffsets[id]! } + changed = true + } + } + for (const id of evictedTaskIds) { + const fresh = newTasks[id] + // Re-check terminal+notified on fresh state (TOCTOU: resume may have + // replaced the task during the generateTaskAttachments await) + if (!fresh || !isTerminalTaskStatus(fresh.status) || !fresh.notified) { + continue + } + if ('retain' in fresh && (fresh.evictAfter ?? Infinity) > Date.now()) { + continue + } + delete newTasks[id] + changed = true + } + return changed ? { ...prev, tasks: newTasks } : prev + }) +} + +/** + * Poll all running tasks and check for updates. + * This is the main polling loop called by the framework. + */ +export async function pollTasks( + getAppState: () => AppState, + setAppState: SetAppState, +): Promise { + const state = getAppState() + const { attachments, updatedTaskOffsets, evictedTaskIds } = + await generateTaskAttachments(state) + + applyTaskOffsetsAndEvictions(setAppState, updatedTaskOffsets, evictedTaskIds) + + // Send notifications for completed tasks + for (const attachment of attachments) { + enqueueTaskNotification(attachment) + } +} + +/** + * Enqueue a task notification to the message queue. + */ +function enqueueTaskNotification(attachment: TaskAttachment): void { + const statusText = getStatusText(attachment.status) + + const outputPath = getTaskOutputPath(attachment.taskId) + const toolUseIdLine = attachment.toolUseId + ? `\n<${TOOL_USE_ID_TAG}>${attachment.toolUseId}` + : '' + const message = `<${TASK_NOTIFICATION_TAG}> +<${TASK_ID_TAG}>${attachment.taskId}${toolUseIdLine} +<${TASK_TYPE_TAG}>${attachment.taskType} +<${OUTPUT_FILE_TAG}>${outputPath} +<${STATUS_TAG}>${attachment.status} +<${SUMMARY_TAG}>Task "${attachment.description}" ${statusText} +` + + enqueuePendingNotification({ value: message, mode: 'task-notification' }) +} + +/** + * Get human-readable status text. + */ +function getStatusText(status: TaskStatus): string { + switch (status) { + case 'completed': + return 'completed successfully' + case 'failed': + return 'failed' + case 'killed': + return 'was stopped' + case 'running': + return 'is running' + case 'pending': + return 'is pending' + } +} diff --git a/src/utils/task/outputFormatting.ts b/src/utils/task/outputFormatting.ts new file mode 100644 index 0000000000000000000000000000000000000000..8cc99c92a57dbc02e16a2c0b7428aa9e72116ab8 --- /dev/null +++ b/src/utils/task/outputFormatting.ts @@ -0,0 +1,38 @@ +import { validateBoundedIntEnvVar } from '../envValidation.js' +import { getTaskOutputPath } from './diskOutput.js' + +export const TASK_MAX_OUTPUT_UPPER_LIMIT = 160_000 +export const TASK_MAX_OUTPUT_DEFAULT = 32_000 + +export function getMaxTaskOutputLength(): number { + const result = validateBoundedIntEnvVar( + 'TASK_MAX_OUTPUT_LENGTH', + process.env.TASK_MAX_OUTPUT_LENGTH, + TASK_MAX_OUTPUT_DEFAULT, + TASK_MAX_OUTPUT_UPPER_LIMIT, + ) + return result.effective +} + +/** + * Format task output for API consumption, truncating if too large. + * When truncated, includes a header with the file path and returns + * the last N characters that fit within the limit. + */ +export function formatTaskOutput( + output: string, + taskId: string, +): { content: string; wasTruncated: boolean } { + const maxLen = getMaxTaskOutputLength() + + if (output.length <= maxLen) { + return { content: output, wasTruncated: false } + } + + const filePath = getTaskOutputPath(taskId) + const header = `[Truncated. Full output: ${filePath}]\n\n` + const availableSpace = maxLen - header.length + const truncated = output.slice(-availableSpace) + + return { content: header + truncated, wasTruncated: true } +} diff --git a/src/utils/task/sdkProgress.ts b/src/utils/task/sdkProgress.ts new file mode 100644 index 0000000000000000000000000000000000000000..1430df2955c4d2a5200479425fd0a10b8ffc482a --- /dev/null +++ b/src/utils/task/sdkProgress.ts @@ -0,0 +1,36 @@ +import type { SdkWorkflowProgress } from '../../types/tools.js' +import { enqueueSdkEvent } from '../sdkEventQueue.js' + +/** + * Emit a `task_progress` SDK event. Shared by background agents (per tool_use + * in runAsyncAgentLifecycle) and workflows (per flushProgress batch). Accepts + * already-computed primitives so callers can derive them from their own state + * shapes (ProgressTracker for agents, LocalWorkflowTaskState for workflows). + */ +export function emitTaskProgress(params: { + taskId: string + toolUseId: string | undefined + description: string + startTime: number + totalTokens: number + toolUses: number + lastToolName?: string + summary?: string + workflowProgress?: SdkWorkflowProgress[] +}): void { + enqueueSdkEvent({ + type: 'system', + subtype: 'task_progress', + task_id: params.taskId, + tool_use_id: params.toolUseId, + description: params.description, + usage: { + total_tokens: params.totalTokens, + tool_uses: params.toolUses, + duration_ms: Date.now() - params.startTime, + }, + last_tool_name: params.lastToolName, + summary: params.summary, + workflow_progress: params.workflowProgress, + }) +} diff --git a/src/utils/telemetry/betaSessionTracing.ts b/src/utils/telemetry/betaSessionTracing.ts new file mode 100644 index 0000000000000000000000000000000000000000..6699721e2e045805b46e3b27d352cd9f5f01e5f4 --- /dev/null +++ b/src/utils/telemetry/betaSessionTracing.ts @@ -0,0 +1,491 @@ +/** + * Beta Session Tracing for Claude Code + * + * This module contains beta tracing features enabled when + * ENABLE_BETA_TRACING_DETAILED=1 and BETA_TRACING_ENDPOINT are set. + * + * For external users, tracing is enabled in SDK/headless mode, or in + * interactive mode when the org is allowlisted via the + * tengu_trace_lantern GrowthBook gate. + * For ant users, tracing is enabled in all modes. + * + * Visibility Rules: + * | Content | External | Ant | + * |------------------|----------|------| + * | System prompts | ✅ | ✅ | + * | Model output | ✅ | ✅ | + * | Thinking output | ❌ | ✅ | + * | Tools | ✅ | ✅ | + * | new_context | ✅ | ✅ | + * + * Features: + * - Per-agent message tracking with hash-based deduplication + * - System prompt logging (once per unique hash) + * - Hook execution spans + * - Detailed new_context attributes for LLM requests + */ + +import type { Span } from '@opentelemetry/api' +import { createHash } from 'crypto' +import { getIsNonInteractiveSession } from '../../bootstrap/state.js' +import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js' +import { sanitizeToolNameForAnalytics } from '../../services/analytics/metadata.js' +import type { AssistantMessage, UserMessage } from '../../types/message.js' +import { isEnvTruthy } from '../envUtils.js' +import { jsonParse, jsonStringify } from '../slowOperations.js' +import { logOTelEvent } from './events.js' + +// Message type for API calls (UserMessage or AssistantMessage) +type APIMessage = UserMessage | AssistantMessage + +/** + * Track hashes we've already logged this session (system prompts, tools, etc). + * + * WHY: System prompts and tool schemas are large and rarely change within a session. + * Sending full content on every request would be wasteful. Instead, we hash and + * only log the full content once per unique hash. + */ +const seenHashes = new Set() + +/** + * Track the last reported message hash per querySource (agent) for incremental context. + * + * WHY: When debugging traces, we want to see what NEW information was added each turn, + * not the entire conversation history (which can be huge). By tracking the last message + * we reported per agent, we can compute and send only the delta (new messages since + * the last request). This is tracked per-agent (querySource) because different agents + * (main thread, subagents, warmup requests) have independent conversation contexts. + */ +const lastReportedMessageHash = new Map() + +/** + * Clear tracking state after compaction. + * Old hashes are irrelevant once messages have been replaced. + */ +export function clearBetaTracingState(): void { + seenHashes.clear() + lastReportedMessageHash.clear() +} + +const MAX_CONTENT_SIZE = 60 * 1024 // 60KB (Honeycomb limit is 64KB, staying safe) + +/** + * Check if beta detailed tracing is enabled. + * - Requires ENABLE_BETA_TRACING_DETAILED=1 and BETA_TRACING_ENDPOINT + * - For external users, enabled in SDK/headless mode OR when org is + * allowlisted via the tengu_trace_lantern GrowthBook gate + */ +export function isBetaTracingEnabled(): boolean { + const baseEnabled = + isEnvTruthy(process.env.ENABLE_BETA_TRACING_DETAILED) && + Boolean(process.env.BETA_TRACING_ENDPOINT) + + if (!baseEnabled) { + return false + } + + // For external users, enable in SDK/headless mode OR when org is allowlisted. + // Gate reads from disk cache, so first run after allowlisting returns false; + // works from second run onward (same behavior as enhanced_telemetry_beta). + if (process.env.USER_TYPE !== 'ant') { + return ( + getIsNonInteractiveSession() || + getFeatureValue_CACHED_MAY_BE_STALE('tengu_trace_lantern', false) + ) + } + + return true +} + +/** + * Truncate content to fit within Honeycomb limits. + */ +export function truncateContent( + content: string, + maxSize: number = MAX_CONTENT_SIZE, +): { content: string; truncated: boolean } { + if (content.length <= maxSize) { + return { content, truncated: false } + } + + return { + content: + content.slice(0, maxSize) + + '\n\n[TRUNCATED - Content exceeds 60KB limit]', + truncated: true, + } +} + +/** + * Generate a short hash (first 12 hex chars of SHA-256). + */ +function shortHash(content: string): string { + return createHash('sha256').update(content).digest('hex').slice(0, 12) +} + +/** + * Generate a hash for a system prompt. + */ +function hashSystemPrompt(systemPrompt: string): string { + return `sp_${shortHash(systemPrompt)}` +} + +/** + * Generate a hash for a message based on its content. + */ +function hashMessage(message: APIMessage): string { + const content = jsonStringify(message.message.content) + return `msg_${shortHash(content)}` +} + +// Regex to detect content wrapped in tags +const SYSTEM_REMINDER_REGEX = + /^\n?([\s\S]*?)\n?<\/system-reminder>$/ + +/** + * Check if text is entirely a system reminder (wrapped in tags). + * Returns the inner content if it is, null otherwise. + */ +function extractSystemReminderContent(text: string): string | null { + const match = text.trim().match(SYSTEM_REMINDER_REGEX) + return match && match[1] ? match[1].trim() : null +} + +/** + * Result of formatting messages - separates regular content from system reminders. + */ +interface FormattedMessages { + contextParts: string[] + systemReminders: string[] +} + +/** + * Format user messages for new_context display, separating system reminders. + * Only handles user messages (assistant messages are filtered out before this is called). + */ +function formatMessagesForContext(messages: UserMessage[]): FormattedMessages { + const contextParts: string[] = [] + const systemReminders: string[] = [] + + for (const message of messages) { + const content = message.message.content + if (typeof content === 'string') { + const reminderContent = extractSystemReminderContent(content) + if (reminderContent) { + systemReminders.push(reminderContent) + } else { + contextParts.push(`[USER]\n${content}`) + } + } else if (Array.isArray(content)) { + for (const block of content) { + if (block.type === 'text') { + const reminderContent = extractSystemReminderContent(block.text) + if (reminderContent) { + systemReminders.push(reminderContent) + } else { + contextParts.push(`[USER]\n${block.text}`) + } + } else if (block.type === 'tool_result') { + const resultContent = + typeof block.content === 'string' + ? block.content + : jsonStringify(block.content) + // Tool results can also contain system reminders (e.g., malware warning) + const reminderContent = extractSystemReminderContent(resultContent) + if (reminderContent) { + systemReminders.push(reminderContent) + } else { + contextParts.push( + `[TOOL RESULT: ${block.tool_use_id}]\n${resultContent}`, + ) + } + } + } + } + } + + return { contextParts, systemReminders } +} + +export interface LLMRequestNewContext { + /** System prompt (typically only on first request or if changed) */ + systemPrompt?: string + /** Query source identifying the agent/purpose (e.g., 'repl_main_thread', 'agent:builtin') */ + querySource?: string + /** Tool schemas sent with the request */ + tools?: string +} + +/** + * Add beta attributes to an interaction span. + * Adds new_context with the user prompt. + */ +export function addBetaInteractionAttributes( + span: Span, + userPrompt: string, +): void { + if (!isBetaTracingEnabled()) { + return + } + + const { content: truncatedPrompt, truncated } = truncateContent( + `[USER PROMPT]\n${userPrompt}`, + ) + span.setAttributes({ + new_context: truncatedPrompt, + ...(truncated && { + new_context_truncated: true, + new_context_original_length: userPrompt.length, + }), + }) +} + +/** + * Add beta attributes to an LLM request span. + * Handles system prompt logging and new_context computation. + */ +export function addBetaLLMRequestAttributes( + span: Span, + newContext?: LLMRequestNewContext, + messagesForAPI?: APIMessage[], +): void { + if (!isBetaTracingEnabled()) { + return + } + + // Add system prompt info to the span + if (newContext?.systemPrompt) { + const promptHash = hashSystemPrompt(newContext.systemPrompt) + const preview = newContext.systemPrompt.slice(0, 500) + + // Always add hash, preview, and length to the span + span.setAttribute('system_prompt_hash', promptHash) + span.setAttribute('system_prompt_preview', preview) + span.setAttribute('system_prompt_length', newContext.systemPrompt.length) + + // Log the full system prompt only once per unique hash this session + if (!seenHashes.has(promptHash)) { + seenHashes.add(promptHash) + + // Truncate for the log if needed + const { content: truncatedPrompt, truncated } = truncateContent( + newContext.systemPrompt, + ) + + void logOTelEvent('system_prompt', { + system_prompt_hash: promptHash, + system_prompt: truncatedPrompt, + system_prompt_length: String(newContext.systemPrompt.length), + ...(truncated && { system_prompt_truncated: 'true' }), + }) + } + } + + // Add tools info to the span + if (newContext?.tools) { + try { + const toolsArray = jsonParse(newContext.tools) as Record< + string, + unknown + >[] + + // Build array of {name, hash} for each tool + const toolsWithHashes = toolsArray.map(tool => { + const toolJson = jsonStringify(tool) + const toolHash = shortHash(toolJson) + return { + name: typeof tool.name === 'string' ? tool.name : 'unknown', + hash: toolHash, + json: toolJson, + } + }) + + // Set span attribute with array of name/hash pairs + span.setAttribute( + 'tools', + jsonStringify( + toolsWithHashes.map(({ name, hash }) => ({ name, hash })), + ), + ) + span.setAttribute('tools_count', toolsWithHashes.length) + + // Log each tool's full description once per unique hash + for (const { name, hash, json } of toolsWithHashes) { + if (!seenHashes.has(`tool_${hash}`)) { + seenHashes.add(`tool_${hash}`) + + const { content: truncatedTool, truncated } = truncateContent(json) + + void logOTelEvent('tool', { + tool_name: sanitizeToolNameForAnalytics(name), + tool_hash: hash, + tool: truncatedTool, + ...(truncated && { tool_truncated: 'true' }), + }) + } + } + } catch { + // If parsing fails, log the raw tools string + span.setAttribute('tools_parse_error', true) + } + } + + // Add new_context using hash-based tracking (visible to all users) + if (messagesForAPI && messagesForAPI.length > 0 && newContext?.querySource) { + const querySource = newContext.querySource + const lastHash = lastReportedMessageHash.get(querySource) + + // Find where the last reported message is in the array + let startIndex = 0 + if (lastHash) { + for (let i = 0; i < messagesForAPI.length; i++) { + const msg = messagesForAPI[i] + if (msg && hashMessage(msg) === lastHash) { + startIndex = i + 1 // Start after the last reported message + break + } + } + // If lastHash not found, startIndex stays 0 (send everything) + } + + // Get new messages (filter out assistant messages - we only want user input/tool results) + const newMessages = messagesForAPI + .slice(startIndex) + .filter((m): m is UserMessage => m.type === 'user') + + if (newMessages.length > 0) { + // Format new messages, separating system reminders from regular content + const { contextParts, systemReminders } = + formatMessagesForContext(newMessages) + + // Set new_context (regular user content and tool results) + if (contextParts.length > 0) { + const fullContext = contextParts.join('\n\n---\n\n') + const { content: truncatedContext, truncated } = + truncateContent(fullContext) + + span.setAttributes({ + new_context: truncatedContext, + new_context_message_count: newMessages.length, + ...(truncated && { + new_context_truncated: true, + new_context_original_length: fullContext.length, + }), + }) + } + + // Set system_reminders as a separate attribute + if (systemReminders.length > 0) { + const fullReminders = systemReminders.join('\n\n---\n\n') + const { content: truncatedReminders, truncated: remindersTruncated } = + truncateContent(fullReminders) + + span.setAttributes({ + system_reminders: truncatedReminders, + system_reminders_count: systemReminders.length, + ...(remindersTruncated && { + system_reminders_truncated: true, + system_reminders_original_length: fullReminders.length, + }), + }) + } + + // Update last reported hash to the last message in the array + const lastMessage = messagesForAPI[messagesForAPI.length - 1] + if (lastMessage) { + lastReportedMessageHash.set(querySource, hashMessage(lastMessage)) + } + } + } +} + +/** + * Add beta attributes to endLLMRequestSpan. + * Handles model_output and thinking_output truncation. + */ +export function addBetaLLMResponseAttributes( + endAttributes: Record, + metadata?: { + modelOutput?: string + thinkingOutput?: string + }, +): void { + if (!isBetaTracingEnabled() || !metadata) { + return + } + + // Add model_output (text content) - visible to all users + if (metadata.modelOutput !== undefined) { + const { content: modelOutput, truncated: outputTruncated } = + truncateContent(metadata.modelOutput) + endAttributes['response.model_output'] = modelOutput + if (outputTruncated) { + endAttributes['response.model_output_truncated'] = true + endAttributes['response.model_output_original_length'] = + metadata.modelOutput.length + } + } + + // Add thinking_output - ant-only + if ( + process.env.USER_TYPE === 'ant' && + metadata.thinkingOutput !== undefined + ) { + const { content: thinkingOutput, truncated: thinkingTruncated } = + truncateContent(metadata.thinkingOutput) + endAttributes['response.thinking_output'] = thinkingOutput + if (thinkingTruncated) { + endAttributes['response.thinking_output_truncated'] = true + endAttributes['response.thinking_output_original_length'] = + metadata.thinkingOutput.length + } + } +} + +/** + * Add beta attributes to startToolSpan. + * Adds tool_input with the serialized tool input. + */ +export function addBetaToolInputAttributes( + span: Span, + toolName: string, + toolInput: string, +): void { + if (!isBetaTracingEnabled()) { + return + } + + const { content: truncatedInput, truncated } = truncateContent( + `[TOOL INPUT: ${toolName}]\n${toolInput}`, + ) + span.setAttributes({ + tool_input: truncatedInput, + ...(truncated && { + tool_input_truncated: true, + tool_input_original_length: toolInput.length, + }), + }) +} + +/** + * Add beta attributes to endToolSpan. + * Adds new_context with the tool result. + */ +export function addBetaToolResultAttributes( + endAttributes: Record, + toolName: string | number | boolean, + toolResult: string, +): void { + if (!isBetaTracingEnabled()) { + return + } + + const { content: truncatedResult, truncated } = truncateContent( + `[TOOL RESULT: ${toolName}]\n${toolResult}`, + ) + endAttributes['new_context'] = truncatedResult + if (truncated) { + endAttributes['new_context_truncated'] = true + endAttributes['new_context_original_length'] = toolResult.length + } +} diff --git a/src/utils/telemetry/bigqueryExporter.ts b/src/utils/telemetry/bigqueryExporter.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f935c455a17efb74e854ad26c509ab525757416 --- /dev/null +++ b/src/utils/telemetry/bigqueryExporter.ts @@ -0,0 +1,252 @@ +import type { Attributes, HrTime } from '@opentelemetry/api' +import { type ExportResult, ExportResultCode } from '@opentelemetry/core' +import { + AggregationTemporality, + type MetricData, + type DataPoint as OTelDataPoint, + type PushMetricExporter, + type ResourceMetrics, +} from '@opentelemetry/sdk-metrics' +import axios from 'axios' +import { checkMetricsEnabled } from 'src/services/api/metricsOptOut.js' +import { getIsNonInteractiveSession } from '../../bootstrap/state.js' +import { getSubscriptionType, isClaudeAISubscriber } from '../auth.js' +import { checkHasTrustDialogAccepted } from '../config.js' +import { logForDebugging } from '../debug.js' +import { errorMessage, toError } from '../errors.js' +import { getAuthHeaders } from '../http.js' +import { logError } from '../log.js' +import { jsonStringify } from '../slowOperations.js' +import { getClaudeCodeUserAgent } from '../userAgent.js' + +type DataPoint = { + attributes: Record + value: number + timestamp: string +} + +type Metric = { + name: string + description?: string + unit?: string + data_points: DataPoint[] +} + +type InternalMetricsPayload = { + resource_attributes: Record + metrics: Metric[] +} + +export class BigQueryMetricsExporter implements PushMetricExporter { + private readonly endpoint: string + private readonly timeout: number + private pendingExports: Promise[] = [] + private isShutdown = false + + constructor(options: { timeout?: number } = {}) { + const defaultEndpoint = 'https://api.anthropic.com/api/claude_code/metrics' + + if ( + process.env.USER_TYPE === 'ant' && + process.env.ANT_CLAUDE_CODE_METRICS_ENDPOINT + ) { + this.endpoint = + process.env.ANT_CLAUDE_CODE_METRICS_ENDPOINT + + '/api/claude_code/metrics' + } else { + this.endpoint = defaultEndpoint + } + + this.timeout = options.timeout || 5000 + } + + async export( + metrics: ResourceMetrics, + resultCallback: (result: ExportResult) => void, + ): Promise { + if (this.isShutdown) { + resultCallback({ + code: ExportResultCode.FAILED, + error: new Error('Exporter has been shutdown'), + }) + return + } + + const exportPromise = this.doExport(metrics, resultCallback) + this.pendingExports.push(exportPromise) + + // Clean up completed exports + void exportPromise.finally(() => { + const index = this.pendingExports.indexOf(exportPromise) + if (index > -1) { + void this.pendingExports.splice(index, 1) + } + }) + } + + private async doExport( + metrics: ResourceMetrics, + resultCallback: (result: ExportResult) => void, + ): Promise { + try { + // Skip if trust not established in interactive mode + // This prevents triggering apiKeyHelper before trust dialog + const hasTrust = + checkHasTrustDialogAccepted() || getIsNonInteractiveSession() + if (!hasTrust) { + logForDebugging( + 'BigQuery metrics export: trust not established, skipping', + ) + resultCallback({ code: ExportResultCode.SUCCESS }) + return + } + + // Check organization-level metrics opt-out + const metricsStatus = await checkMetricsEnabled() + if (!metricsStatus.enabled) { + logForDebugging('Metrics export disabled by organization setting') + resultCallback({ code: ExportResultCode.SUCCESS }) + return + } + + const payload = this.transformMetricsForInternal(metrics) + + const authResult = getAuthHeaders() + if (authResult.error) { + logForDebugging(`Metrics export failed: ${authResult.error}`) + resultCallback({ + code: ExportResultCode.FAILED, + error: new Error(authResult.error), + }) + return + } + + const headers: Record = { + 'Content-Type': 'application/json', + 'User-Agent': getClaudeCodeUserAgent(), + ...authResult.headers, + } + + const response = await axios.post(this.endpoint, payload, { + timeout: this.timeout, + headers, + }) + + logForDebugging('BigQuery metrics exported successfully') + logForDebugging( + `BigQuery API Response: ${jsonStringify(response.data, null, 2)}`, + ) + resultCallback({ code: ExportResultCode.SUCCESS }) + } catch (error) { + logForDebugging(`BigQuery metrics export failed: ${errorMessage(error)}`) + logError(error) + resultCallback({ + code: ExportResultCode.FAILED, + error: toError(error), + }) + } + } + + private transformMetricsForInternal( + metrics: ResourceMetrics, + ): InternalMetricsPayload { + const attrs = metrics.resource.attributes + + const resourceAttributes: Record = { + 'service.name': (attrs['service.name'] as string) || 'claude-code', + 'service.version': (attrs['service.version'] as string) || 'unknown', + 'os.type': (attrs['os.type'] as string) || 'unknown', + 'os.version': (attrs['os.version'] as string) || 'unknown', + 'host.arch': (attrs['host.arch'] as string) || 'unknown', + 'aggregation.temporality': + this.selectAggregationTemporality() === AggregationTemporality.DELTA + ? 'delta' + : 'cumulative', + } + + // Only add wsl.version if it exists (omit instead of default) + if (attrs['wsl.version']) { + resourceAttributes['wsl.version'] = attrs['wsl.version'] as string + } + + // Add customer type and subscription type + if (isClaudeAISubscriber()) { + resourceAttributes['user.customer_type'] = 'claude_ai' + const subscriptionType = getSubscriptionType() + if (subscriptionType) { + resourceAttributes['user.subscription_type'] = subscriptionType + } + } else { + resourceAttributes['user.customer_type'] = 'api' + } + + const transformed = { + resource_attributes: resourceAttributes, + metrics: metrics.scopeMetrics.flatMap(scopeMetric => + scopeMetric.metrics.map(metric => ({ + name: metric.descriptor.name, + description: metric.descriptor.description, + unit: metric.descriptor.unit, + data_points: this.extractDataPoints(metric), + })), + ), + } + + return transformed + } + + private extractDataPoints(metric: MetricData): DataPoint[] { + const dataPoints = metric.dataPoints || [] + + return dataPoints + .filter( + (point): point is OTelDataPoint => + typeof point.value === 'number', + ) + .map(point => ({ + attributes: this.convertAttributes(point.attributes), + value: point.value, + timestamp: this.hrTimeToISOString( + point.endTime || point.startTime || [Date.now() / 1000, 0], + ), + })) + } + + async shutdown(): Promise { + this.isShutdown = true + await this.forceFlush() + logForDebugging('BigQuery metrics exporter shutdown complete') + } + + async forceFlush(): Promise { + await Promise.all(this.pendingExports) + logForDebugging('BigQuery metrics exporter flush complete') + } + + private convertAttributes( + attributes: Attributes | undefined, + ): Record { + const result: Record = {} + if (attributes) { + for (const [key, value] of Object.entries(attributes)) { + if (value !== undefined && value !== null) { + result[key] = String(value) + } + } + } + return result + } + + private hrTimeToISOString(hrTime: HrTime): string { + const [seconds, nanoseconds] = hrTime + const date = new Date(seconds * 1000 + nanoseconds / 1000000) + return date.toISOString() + } + + selectAggregationTemporality(): AggregationTemporality { + // DO NOT CHANGE THIS TO CUMULATIVE + // It would mess up the aggregation of metrics + // for CC Productivity metrics dashboard + return AggregationTemporality.DELTA + } +} diff --git a/src/utils/telemetry/events.ts b/src/utils/telemetry/events.ts new file mode 100644 index 0000000000000000000000000000000000000000..f93b8aaac0e57cb07a6142dfc3f66d2d5235c83a --- /dev/null +++ b/src/utils/telemetry/events.ts @@ -0,0 +1,75 @@ +import type { Attributes } from '@opentelemetry/api' +import { getEventLogger, getPromptId } from 'src/bootstrap/state.js' +import { logForDebugging } from '../debug.js' +import { isEnvTruthy } from '../envUtils.js' +import { getTelemetryAttributes } from '../telemetryAttributes.js' + +// Monotonically increasing counter for ordering events within a session +let eventSequence = 0 + +// Track whether we've already warned about a null event logger to avoid spamming +let hasWarnedNoEventLogger = false + +function isUserPromptLoggingEnabled() { + return isEnvTruthy(process.env.OTEL_LOG_USER_PROMPTS) +} + +export function redactIfDisabled(content: string): string { + return isUserPromptLoggingEnabled() ? content : '' +} + +export async function logOTelEvent( + eventName: string, + metadata: { [key: string]: string | undefined } = {}, +): Promise { + const eventLogger = getEventLogger() + if (!eventLogger) { + if (!hasWarnedNoEventLogger) { + hasWarnedNoEventLogger = true + logForDebugging( + `[3P telemetry] Event dropped (no event logger initialized): ${eventName}`, + { level: 'warn' }, + ) + } + return + } + + // Skip logging in test environment + if (process.env.NODE_ENV === 'test') { + return + } + + const attributes: Attributes = { + ...getTelemetryAttributes(), + 'event.name': eventName, + 'event.timestamp': new Date().toISOString(), + 'event.sequence': eventSequence++, + } + + // Add prompt ID to events (but not metrics, where it would cause unbounded cardinality) + const promptId = getPromptId() + if (promptId) { + attributes['prompt.id'] = promptId + } + + // Workspace directory from the desktop app (host path). Events only — + // filesystem paths are too high-cardinality for metric dimensions, and + // the BQ metrics pipeline must never see them. + const workspaceDir = process.env.CLAUDE_CODE_WORKSPACE_HOST_PATHS + if (workspaceDir) { + attributes['workspace.host_paths'] = workspaceDir.split('|') + } + + // Add metadata as attributes - all values are already strings + for (const [key, value] of Object.entries(metadata)) { + if (value !== undefined) { + attributes[key] = value + } + } + + // Emit log record as an event + eventLogger.emit({ + body: `claude_code.${eventName}`, + attributes, + }) +} diff --git a/src/utils/telemetry/instrumentation.ts b/src/utils/telemetry/instrumentation.ts new file mode 100644 index 0000000000000000000000000000000000000000..224f04815866aac847c5925673b72a7be245af9e --- /dev/null +++ b/src/utils/telemetry/instrumentation.ts @@ -0,0 +1,825 @@ +import { DiagLogLevel, diag, trace } from '@opentelemetry/api' +import { logs } from '@opentelemetry/api-logs' +// OTLP/Prometheus exporters are dynamically imported inside the protocol +// switch statements below. A process uses at most one protocol variant per +// signal, but static imports would load all 6 (~1.2MB) on every startup. +import { + envDetector, + hostDetector, + osDetector, + resourceFromAttributes, +} from '@opentelemetry/resources' +import { + BatchLogRecordProcessor, + ConsoleLogRecordExporter, + LoggerProvider, +} from '@opentelemetry/sdk-logs' +import { + ConsoleMetricExporter, + MeterProvider, + PeriodicExportingMetricReader, +} from '@opentelemetry/sdk-metrics' +import { + BasicTracerProvider, + BatchSpanProcessor, + ConsoleSpanExporter, +} from '@opentelemetry/sdk-trace-base' +import { + ATTR_SERVICE_NAME, + ATTR_SERVICE_VERSION, + SEMRESATTRS_HOST_ARCH, +} from '@opentelemetry/semantic-conventions' +import { HttpsProxyAgent } from 'https-proxy-agent' +import { + getLoggerProvider, + getMeterProvider, + getTracerProvider, + setEventLogger, + setLoggerProvider, + setMeterProvider, + setTracerProvider, +} from 'src/bootstrap/state.js' +import { + getOtelHeadersFromHelper, + getSubscriptionType, + is1PApiCustomer, + isClaudeAISubscriber, +} from 'src/utils/auth.js' +import { getPlatform, getWslVersion } from 'src/utils/platform.js' + +import { getCACertificates } from '../caCerts.js' +import { registerCleanup } from '../cleanupRegistry.js' +import { getHasFormattedOutput, logForDebugging } from '../debug.js' +import { isEnvTruthy } from '../envUtils.js' +import { errorMessage } from '../errors.js' +import { getMTLSConfig } from '../mtls.js' +import { getProxyUrl, shouldBypassProxy } from '../proxy.js' +import { getSettings_DEPRECATED } from '../settings/settings.js' +import { jsonStringify } from '../slowOperations.js' +import { profileCheckpoint } from '../startupProfiler.js' +import { isBetaTracingEnabled } from './betaSessionTracing.js' +import { BigQueryMetricsExporter } from './bigqueryExporter.js' +import { ClaudeCodeDiagLogger } from './logger.js' +import { initializePerfettoTracing } from './perfettoTracing.js' +import { + endInteractionSpan, + isEnhancedTelemetryEnabled, +} from './sessionTracing.js' + +const DEFAULT_METRICS_EXPORT_INTERVAL_MS = 60000 +const DEFAULT_LOGS_EXPORT_INTERVAL_MS = 5000 +const DEFAULT_TRACES_EXPORT_INTERVAL_MS = 5000 + +class TelemetryTimeoutError extends Error {} + +function telemetryTimeout(ms: number, message: string): Promise { + return new Promise((_, reject) => { + setTimeout( + (rej: (e: Error) => void, msg: string) => + rej(new TelemetryTimeoutError(msg)), + ms, + reject, + message, + ).unref() + }) +} + +export function bootstrapTelemetry() { + if (process.env.USER_TYPE === 'ant') { + // Read from ANT_ prefixed variables that are defined at build time + if (process.env.ANT_OTEL_METRICS_EXPORTER) { + process.env.OTEL_METRICS_EXPORTER = process.env.ANT_OTEL_METRICS_EXPORTER + } + if (process.env.ANT_OTEL_LOGS_EXPORTER) { + process.env.OTEL_LOGS_EXPORTER = process.env.ANT_OTEL_LOGS_EXPORTER + } + if (process.env.ANT_OTEL_TRACES_EXPORTER) { + process.env.OTEL_TRACES_EXPORTER = process.env.ANT_OTEL_TRACES_EXPORTER + } + if (process.env.ANT_OTEL_EXPORTER_OTLP_PROTOCOL) { + process.env.OTEL_EXPORTER_OTLP_PROTOCOL = + process.env.ANT_OTEL_EXPORTER_OTLP_PROTOCOL + } + if (process.env.ANT_OTEL_EXPORTER_OTLP_ENDPOINT) { + process.env.OTEL_EXPORTER_OTLP_ENDPOINT = + process.env.ANT_OTEL_EXPORTER_OTLP_ENDPOINT + } + if (process.env.ANT_OTEL_EXPORTER_OTLP_HEADERS) { + process.env.OTEL_EXPORTER_OTLP_HEADERS = + process.env.ANT_OTEL_EXPORTER_OTLP_HEADERS + } + } + + // Set default tempoality to 'delta' because it's the more sane default + if (!process.env.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE) { + process.env.OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE = 'delta' + } +} + +// Per OTEL spec, "none" means "no automatically configured exporter for this signal". +// https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#exporter-selection +export function parseExporterTypes(value: string | undefined): string[] { + return (value || '') + .trim() + .split(',') + .filter(Boolean) + .map(t => t.trim()) + .filter(t => t !== 'none') +} + +async function getOtlpReaders() { + const exporterTypes = parseExporterTypes(process.env.OTEL_METRICS_EXPORTER) + const exportInterval = parseInt( + process.env.OTEL_METRIC_EXPORT_INTERVAL || + DEFAULT_METRICS_EXPORT_INTERVAL_MS.toString(), + ) + + const exporters = [] + for (const exporterType of exporterTypes) { + if (exporterType === 'console') { + // Custom console exporter that shows resource attributes + const consoleExporter = new ConsoleMetricExporter() + const originalExport = consoleExporter.export.bind(consoleExporter) + + consoleExporter.export = (metrics, callback) => { + // Log resource attributes once at the start + if (metrics.resource && metrics.resource.attributes) { + // The console exporter is for debugging, so console output is intentional here + + logForDebugging('\n=== Resource Attributes ===') + logForDebugging(jsonStringify(metrics.resource.attributes)) + logForDebugging('===========================\n') + } + + return originalExport(metrics, callback) + } + + exporters.push(consoleExporter) + } else if (exporterType === 'otlp') { + const protocol = + process.env.OTEL_EXPORTER_OTLP_METRICS_PROTOCOL?.trim() || + process.env.OTEL_EXPORTER_OTLP_PROTOCOL?.trim() + + const httpConfig = getOTLPExporterConfig() + + switch (protocol) { + case 'grpc': { + // Lazy-import to keep @grpc/grpc-js (~700KB) out of the telemetry chunk + // when the protocol is http/protobuf (ant default) or http/json. + const { OTLPMetricExporter } = await import( + '@opentelemetry/exporter-metrics-otlp-grpc' + ) + exporters.push(new OTLPMetricExporter()) + break + } + case 'http/json': { + const { OTLPMetricExporter } = await import( + '@opentelemetry/exporter-metrics-otlp-http' + ) + exporters.push(new OTLPMetricExporter(httpConfig)) + break + } + case 'http/protobuf': { + const { OTLPMetricExporter } = await import( + '@opentelemetry/exporter-metrics-otlp-proto' + ) + exporters.push(new OTLPMetricExporter(httpConfig)) + break + } + default: + throw new Error( + `Unknown protocol set in OTEL_EXPORTER_OTLP_METRICS_PROTOCOL or OTEL_EXPORTER_OTLP_PROTOCOL env var: ${protocol}`, + ) + } + } else if (exporterType === 'prometheus') { + const { PrometheusExporter } = await import( + '@opentelemetry/exporter-prometheus' + ) + exporters.push(new PrometheusExporter()) + } else { + throw new Error( + `Unknown exporter type set in OTEL_EXPORTER_OTLP_METRICS_PROTOCOL or OTEL_EXPORTER_OTLP_PROTOCOL env var: ${exporterType}`, + ) + } + } + + return exporters.map(exporter => { + if ('export' in exporter) { + return new PeriodicExportingMetricReader({ + exporter, + exportIntervalMillis: exportInterval, + }) + } + return exporter + }) +} + +async function getOtlpLogExporters() { + const exporterTypes = parseExporterTypes(process.env.OTEL_LOGS_EXPORTER) + + const protocol = + process.env.OTEL_EXPORTER_OTLP_LOGS_PROTOCOL?.trim() || + process.env.OTEL_EXPORTER_OTLP_PROTOCOL?.trim() + const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT + + logForDebugging( + `[3P telemetry] getOtlpLogExporters: types=${jsonStringify(exporterTypes)}, protocol=${protocol}, endpoint=${endpoint}`, + ) + + const exporters = [] + for (const exporterType of exporterTypes) { + if (exporterType === 'console') { + exporters.push(new ConsoleLogRecordExporter()) + } else if (exporterType === 'otlp') { + const httpConfig = getOTLPExporterConfig() + + switch (protocol) { + case 'grpc': { + const { OTLPLogExporter } = await import( + '@opentelemetry/exporter-logs-otlp-grpc' + ) + exporters.push(new OTLPLogExporter()) + break + } + case 'http/json': { + const { OTLPLogExporter } = await import( + '@opentelemetry/exporter-logs-otlp-http' + ) + exporters.push(new OTLPLogExporter(httpConfig)) + break + } + case 'http/protobuf': { + const { OTLPLogExporter } = await import( + '@opentelemetry/exporter-logs-otlp-proto' + ) + exporters.push(new OTLPLogExporter(httpConfig)) + break + } + default: + throw new Error( + `Unknown protocol set in OTEL_EXPORTER_OTLP_LOGS_PROTOCOL or OTEL_EXPORTER_OTLP_PROTOCOL env var: ${protocol}`, + ) + } + } else { + throw new Error( + `Unknown exporter type set in OTEL_LOGS_EXPORTER env var: ${exporterType}`, + ) + } + } + + return exporters +} + +async function getOtlpTraceExporters() { + const exporterTypes = parseExporterTypes(process.env.OTEL_TRACES_EXPORTER) + + const exporters = [] + for (const exporterType of exporterTypes) { + if (exporterType === 'console') { + exporters.push(new ConsoleSpanExporter()) + } else if (exporterType === 'otlp') { + const protocol = + process.env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL?.trim() || + process.env.OTEL_EXPORTER_OTLP_PROTOCOL?.trim() + + const httpConfig = getOTLPExporterConfig() + + switch (protocol) { + case 'grpc': { + const { OTLPTraceExporter } = await import( + '@opentelemetry/exporter-trace-otlp-grpc' + ) + exporters.push(new OTLPTraceExporter()) + break + } + case 'http/json': { + const { OTLPTraceExporter } = await import( + '@opentelemetry/exporter-trace-otlp-http' + ) + exporters.push(new OTLPTraceExporter(httpConfig)) + break + } + case 'http/protobuf': { + const { OTLPTraceExporter } = await import( + '@opentelemetry/exporter-trace-otlp-proto' + ) + exporters.push(new OTLPTraceExporter(httpConfig)) + break + } + default: + throw new Error( + `Unknown protocol set in OTEL_EXPORTER_OTLP_TRACES_PROTOCOL or OTEL_EXPORTER_OTLP_PROTOCOL env var: ${protocol}`, + ) + } + } else { + throw new Error( + `Unknown exporter type set in OTEL_TRACES_EXPORTER env var: ${exporterType}`, + ) + } + } + + return exporters +} + +export function isTelemetryEnabled() { + return isEnvTruthy(process.env.CLAUDE_CODE_ENABLE_TELEMETRY) +} + +function getBigQueryExportingReader() { + const bigqueryExporter = new BigQueryMetricsExporter() + return new PeriodicExportingMetricReader({ + exporter: bigqueryExporter, + exportIntervalMillis: 5 * 60 * 1000, // 5mins for BigQuery metrics exporter to reduce load + }) +} + +function isBigQueryMetricsEnabled() { + // BigQuery metrics are enabled for: + // 1. API customers (excluding Claude.ai subscribers and Bedrock/Vertex) + // 2. Claude for Enterprise (C4E) users + // 3. Claude for Teams users + const subscriptionType = getSubscriptionType() + const isC4EOrTeamUser = + isClaudeAISubscriber() && + (subscriptionType === 'enterprise' || subscriptionType === 'team') + + return is1PApiCustomer() || isC4EOrTeamUser +} + +/** + * Initialize beta tracing - a separate code path for detailed debugging. + * Uses BETA_TRACING_ENDPOINT instead of OTEL_EXPORTER_OTLP_ENDPOINT. + */ +async function initializeBetaTracing( + resource: ReturnType, +): Promise { + const endpoint = process.env.BETA_TRACING_ENDPOINT + if (!endpoint) { + return + } + + const [{ OTLPTraceExporter }, { OTLPLogExporter }] = await Promise.all([ + import('@opentelemetry/exporter-trace-otlp-http'), + import('@opentelemetry/exporter-logs-otlp-http'), + ]) + + const httpConfig = { + url: `${endpoint}/v1/traces`, + } + + const logHttpConfig = { + url: `${endpoint}/v1/logs`, + } + + // Initialize trace exporter + const traceExporter = new OTLPTraceExporter(httpConfig) + const spanProcessor = new BatchSpanProcessor(traceExporter, { + scheduledDelayMillis: DEFAULT_TRACES_EXPORT_INTERVAL_MS, + }) + + const tracerProvider = new BasicTracerProvider({ + resource, + spanProcessors: [spanProcessor], + }) + + trace.setGlobalTracerProvider(tracerProvider) + setTracerProvider(tracerProvider) + + // Initialize log exporter + const logExporter = new OTLPLogExporter(logHttpConfig) + const loggerProvider = new LoggerProvider({ + resource, + processors: [ + new BatchLogRecordProcessor(logExporter, { + scheduledDelayMillis: DEFAULT_LOGS_EXPORT_INTERVAL_MS, + }), + ], + }) + + logs.setGlobalLoggerProvider(loggerProvider) + setLoggerProvider(loggerProvider) + + // Initialize event logger + const eventLogger = logs.getLogger( + 'com.anthropic.claude_code.events', + MACRO.VERSION, + ) + setEventLogger(eventLogger) + + // Setup flush handlers - flush both logs AND traces + process.on('beforeExit', async () => { + await loggerProvider?.forceFlush() + await tracerProvider?.forceFlush() + }) + + process.on('exit', () => { + void loggerProvider?.forceFlush() + void tracerProvider?.forceFlush() + }) +} + +export async function initializeTelemetry() { + profileCheckpoint('telemetry_init_start') + bootstrapTelemetry() + + // Console exporters call console.dir on a timer (5s logs/traces, 60s + // metrics), writing pretty-printed objects to stdout. In stream-json + // mode stdout is the SDK message channel; the first line (`{`) breaks + // the SDK's line reader. Stripped here (not main.tsx) because init.ts + // re-runs applyConfigEnvironmentVariables() inside initializeTelemetry- + // AfterTrust for remote-managed-settings users, and bootstrapTelemetry + // above copies ANT_OTEL_* for ant users — both would undo an earlier strip. + if (getHasFormattedOutput()) { + for (const key of [ + 'OTEL_METRICS_EXPORTER', + 'OTEL_LOGS_EXPORTER', + 'OTEL_TRACES_EXPORTER', + ] as const) { + const v = process.env[key] + if (v?.includes('console')) { + process.env[key] = v + .split(',') + .map(s => s.trim()) + .filter(s => s !== 'console') + .join(',') + } + } + } + + diag.setLogger(new ClaudeCodeDiagLogger(), DiagLogLevel.ERROR) + + // Initialize Perfetto tracing (independent of OTEL) + // Enable via CLAUDE_CODE_PERFETTO_TRACE=1 or CLAUDE_CODE_PERFETTO_TRACE= + initializePerfettoTracing() + + const readers = [] + + // Add customer exporters (if enabled) + const telemetryEnabled = isTelemetryEnabled() + logForDebugging( + `[3P telemetry] isTelemetryEnabled=${telemetryEnabled} (CLAUDE_CODE_ENABLE_TELEMETRY=${process.env.CLAUDE_CODE_ENABLE_TELEMETRY})`, + ) + if (telemetryEnabled) { + readers.push(...(await getOtlpReaders())) + } + + // Add BigQuery exporter (for API customers, C4E users, and internal users) + if (isBigQueryMetricsEnabled()) { + readers.push(getBigQueryExportingReader()) + } + + // Create base resource with service attributes + const platform = getPlatform() + const baseAttributes: Record = { + [ATTR_SERVICE_NAME]: 'claude-code', + [ATTR_SERVICE_VERSION]: MACRO.VERSION, + } + + // Add WSL-specific attributes if running on WSL + if (platform === 'wsl') { + const wslVersion = getWslVersion() + if (wslVersion) { + baseAttributes['wsl.version'] = wslVersion + } + } + + const baseResource = resourceFromAttributes(baseAttributes) + + // Use OpenTelemetry detectors + const osResource = resourceFromAttributes( + osDetector.detect().attributes || {}, + ) + + // Extract only host.arch from hostDetector + const hostDetected = hostDetector.detect() + const hostArchAttributes = hostDetected.attributes?.[SEMRESATTRS_HOST_ARCH] + ? { + [SEMRESATTRS_HOST_ARCH]: hostDetected.attributes[SEMRESATTRS_HOST_ARCH], + } + : {} + const hostArchResource = resourceFromAttributes(hostArchAttributes) + + const envResource = resourceFromAttributes( + envDetector.detect().attributes || {}, + ) + + // Merge resources - later resources take precedence + const resource = baseResource + .merge(osResource) + .merge(hostArchResource) + .merge(envResource) + + // Check if beta tracing is enabled - this is a separate code path + // Available to all users who set ENABLE_BETA_TRACING_DETAILED=1 and BETA_TRACING_ENDPOINT + if (isBetaTracingEnabled()) { + void initializeBetaTracing(resource).catch(e => + logForDebugging(`Beta tracing init failed: ${e}`, { level: 'error' }), + ) + // Still set up meter provider for metrics (but skip regular logs/traces setup) + const meterProvider = new MeterProvider({ + resource, + views: [], + readers, + }) + setMeterProvider(meterProvider) + + // Register shutdown for beta tracing + const shutdownTelemetry = async () => { + const timeoutMs = parseInt( + process.env.CLAUDE_CODE_OTEL_SHUTDOWN_TIMEOUT_MS || '2000', + ) + try { + endInteractionSpan() + + // Force flush + shutdown together inside the timeout. Previously forceFlush + // was awaited unbounded BEFORE the race, blocking exit on slow OTLP endpoints. + // Each provider's flush→shutdown is chained independently so a slow logger + // flush doesn't delay meterProvider/tracerProvider shutdown (no waterfall). + const loggerProvider = getLoggerProvider() + const tracerProvider = getTracerProvider() + + const chains: Promise[] = [meterProvider.shutdown()] + if (loggerProvider) { + chains.push( + loggerProvider.forceFlush().then(() => loggerProvider.shutdown()), + ) + } + if (tracerProvider) { + chains.push( + tracerProvider.forceFlush().then(() => tracerProvider.shutdown()), + ) + } + + await Promise.race([ + Promise.all(chains), + telemetryTimeout(timeoutMs, 'OpenTelemetry shutdown timeout'), + ]) + } catch { + // Ignore shutdown errors + } + } + registerCleanup(shutdownTelemetry) + + return meterProvider.getMeter('com.anthropic.claude_code', MACRO.VERSION) + } + + const meterProvider = new MeterProvider({ + resource, + views: [], + readers, + }) + + // Store reference in state for flushing + setMeterProvider(meterProvider) + + // Initialize logs if telemetry is enabled + if (telemetryEnabled) { + const logExporters = await getOtlpLogExporters() + logForDebugging( + `[3P telemetry] Created ${logExporters.length} log exporter(s)`, + ) + + if (logExporters.length > 0) { + const loggerProvider = new LoggerProvider({ + resource, + // Add batch processors for each exporter + processors: logExporters.map( + exporter => + new BatchLogRecordProcessor(exporter, { + scheduledDelayMillis: parseInt( + process.env.OTEL_LOGS_EXPORT_INTERVAL || + DEFAULT_LOGS_EXPORT_INTERVAL_MS.toString(), + ), + }), + ), + }) + + // Register the logger provider globally + logs.setGlobalLoggerProvider(loggerProvider) + setLoggerProvider(loggerProvider) + + // Initialize event logger + const eventLogger = logs.getLogger( + 'com.anthropic.claude_code.events', + MACRO.VERSION, + ) + setEventLogger(eventLogger) + logForDebugging('[3P telemetry] Event logger set successfully') + + // 'beforeExit' is emitted when Node.js empties its event loop and has no additional work to schedule. + // Unlike 'exit', it allows us to perform async operations, so it works well for letting + // network requests complete before the process exits naturally. + process.on('beforeExit', async () => { + await loggerProvider?.forceFlush() + // Also flush traces - they use BatchSpanProcessor which needs explicit flush + const tracerProvider = getTracerProvider() + await tracerProvider?.forceFlush() + }) + + process.on('exit', () => { + // Final attempt to flush logs and traces + void loggerProvider?.forceFlush() + void getTracerProvider()?.forceFlush() + }) + } + } + + // Initialize tracing if enhanced telemetry is enabled (BETA) + if (telemetryEnabled && isEnhancedTelemetryEnabled()) { + const traceExporters = await getOtlpTraceExporters() + if (traceExporters.length > 0) { + // Create span processors for each exporter + const spanProcessors = traceExporters.map( + exporter => + new BatchSpanProcessor(exporter, { + scheduledDelayMillis: parseInt( + process.env.OTEL_TRACES_EXPORT_INTERVAL || + DEFAULT_TRACES_EXPORT_INTERVAL_MS.toString(), + ), + }), + ) + + const tracerProvider = new BasicTracerProvider({ + resource, + spanProcessors, + }) + + // Register the tracer provider globally + trace.setGlobalTracerProvider(tracerProvider) + setTracerProvider(tracerProvider) + } + } + + // Shutdown metrics and logs on exit (flushes and closes exporters) + const shutdownTelemetry = async () => { + const timeoutMs = parseInt( + process.env.CLAUDE_CODE_OTEL_SHUTDOWN_TIMEOUT_MS || '2000', + ) + + try { + // End any active interaction span before shutdown + endInteractionSpan() + + const shutdownPromises = [meterProvider.shutdown()] + const loggerProvider = getLoggerProvider() + if (loggerProvider) { + shutdownPromises.push(loggerProvider.shutdown()) + } + const tracerProvider = getTracerProvider() + if (tracerProvider) { + shutdownPromises.push(tracerProvider.shutdown()) + } + + await Promise.race([ + Promise.all(shutdownPromises), + telemetryTimeout(timeoutMs, 'OpenTelemetry shutdown timeout'), + ]) + } catch (error) { + if (error instanceof Error && error.message.includes('timeout')) { + logForDebugging( + ` +OpenTelemetry telemetry flush timed out after ${timeoutMs}ms + +To resolve this issue, you can: +1. Increase the timeout by setting CLAUDE_CODE_OTEL_SHUTDOWN_TIMEOUT_MS env var (e.g., 5000 for 5 seconds) +2. Check if your OpenTelemetry backend is experiencing scalability issues +3. Disable OpenTelemetry by unsetting CLAUDE_CODE_ENABLE_TELEMETRY env var + +Current timeout: ${timeoutMs}ms +`, + { level: 'error' }, + ) + } + throw error + } + } + + // Always register shutdown (internal metrics are always enabled) + registerCleanup(shutdownTelemetry) + + return meterProvider.getMeter('com.anthropic.claude_code', MACRO.VERSION) +} + +/** + * Flush all pending telemetry data immediately. + * This should be called before logout or org switching to prevent data leakage. + */ +export async function flushTelemetry(): Promise { + const meterProvider = getMeterProvider() + if (!meterProvider) { + return + } + + const timeoutMs = parseInt( + process.env.CLAUDE_CODE_OTEL_FLUSH_TIMEOUT_MS || '5000', + ) + + try { + const flushPromises = [meterProvider.forceFlush()] + const loggerProvider = getLoggerProvider() + if (loggerProvider) { + flushPromises.push(loggerProvider.forceFlush()) + } + const tracerProvider = getTracerProvider() + if (tracerProvider) { + flushPromises.push(tracerProvider.forceFlush()) + } + + await Promise.race([ + Promise.all(flushPromises), + telemetryTimeout(timeoutMs, 'OpenTelemetry flush timeout'), + ]) + + logForDebugging('Telemetry flushed successfully') + } catch (error) { + if (error instanceof TelemetryTimeoutError) { + logForDebugging( + `Telemetry flush timed out after ${timeoutMs}ms. Some metrics may not be exported.`, + { level: 'warn' }, + ) + } else { + logForDebugging(`Telemetry flush failed: ${errorMessage(error)}`, { + level: 'error', + }) + } + // Don't throw - allow logout to continue even if flush fails + } +} + +function parseOtelHeadersEnvVar(): Record { + const headers: Record = {} + const envHeaders = process.env.OTEL_EXPORTER_OTLP_HEADERS + if (envHeaders) { + for (const pair of envHeaders.split(',')) { + const [key, ...valueParts] = pair.split('=') + if (key && valueParts.length > 0) { + headers[key.trim()] = valueParts.join('=').trim() + } + } + } + return headers +} + +/** + * Get configuration for OTLP exporters including: + * - HTTP agent options (proxy, mTLS) + * - Dynamic headers via otelHeadersHelper or static headers from env var + */ +function getOTLPExporterConfig() { + const proxyUrl = getProxyUrl() + const mtlsConfig = getMTLSConfig() + const settings = getSettings_DEPRECATED() + + // Build base config + const config: Record = {} + + // Parse static headers from env var once (doesn't change at runtime) + const staticHeaders = parseOtelHeadersEnvVar() + + // If otelHeadersHelper is configured, use async headers function for dynamic refresh + // Otherwise just return static headers if any exist + if (settings?.otelHeadersHelper) { + config.headers = async (): Promise> => { + const dynamicHeaders = getOtelHeadersFromHelper() + return { ...staticHeaders, ...dynamicHeaders } + } + } else if (Object.keys(staticHeaders).length > 0) { + config.headers = async (): Promise> => staticHeaders + } + + // Check if we should bypass proxy for OTEL endpoint + const otelEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT + if (!proxyUrl || (otelEndpoint && shouldBypassProxy(otelEndpoint))) { + // No proxy configured or OTEL endpoint should bypass proxy + const caCerts = getCACertificates() + if (mtlsConfig || caCerts) { + config.httpAgentOptions = { + ...mtlsConfig, + ...(caCerts && { ca: caCerts }), + } + } + return config + } + + // Return an HttpAgentFactory function that creates our proxy agent + const caCerts = getCACertificates() + const agentFactory = (_protocol: string) => { + // Create and return the proxy agent with mTLS and CA cert config + const proxyAgent = + mtlsConfig || caCerts + ? new HttpsProxyAgent(proxyUrl, { + ...(mtlsConfig && { + cert: mtlsConfig.cert, + key: mtlsConfig.key, + passphrase: mtlsConfig.passphrase, + }), + ...(caCerts && { ca: caCerts }), + }) + : new HttpsProxyAgent(proxyUrl) + + return proxyAgent + } + + config.httpAgentOptions = agentFactory + return config +} diff --git a/src/utils/telemetry/logger.ts b/src/utils/telemetry/logger.ts new file mode 100644 index 0000000000000000000000000000000000000000..0f080f5846fb9b6d2e038fee24a6ffcb43c4d389 --- /dev/null +++ b/src/utils/telemetry/logger.ts @@ -0,0 +1,26 @@ +import type { DiagLogger } from '@opentelemetry/api' +import { logForDebugging } from '../debug.js' +import { logError } from '../log.js' +export class ClaudeCodeDiagLogger implements DiagLogger { + error(message: string, ..._: unknown[]) { + logError(new Error(message)) + logForDebugging(`[3P telemetry] OTEL diag error: ${message}`, { + level: 'error', + }) + } + warn(message: string, ..._: unknown[]) { + logError(new Error(message)) + logForDebugging(`[3P telemetry] OTEL diag warn: ${message}`, { + level: 'warn', + }) + } + info(_message: string, ..._args: unknown[]) { + return + } + debug(_message: string, ..._args: unknown[]) { + return + } + verbose(_message: string, ..._args: unknown[]) { + return + } +} diff --git a/src/utils/telemetry/perfettoTracing.ts b/src/utils/telemetry/perfettoTracing.ts new file mode 100644 index 0000000000000000000000000000000000000000..ce3a87233595b642fb7d8a354363fea97bb393c8 --- /dev/null +++ b/src/utils/telemetry/perfettoTracing.ts @@ -0,0 +1,1120 @@ +/** + * Perfetto Tracing for Claude Code (Ant-only) + * + * This module generates traces in the Chrome Trace Event format that can be + * viewed in ui.perfetto.dev or Chrome's chrome://tracing. + * + * NOTE: This feature is ant-only and eliminated from external builds. + * + * The trace file includes: + * - Agent hierarchy (parent-child relationships in a swarm) + * - API requests with TTFT, TTLT, prompt length, cache stats, msg ID, speculative flag + * - Tool executions with name, duration, and token usage + * - User input waiting time + * + * Usage: + * 1. Enable via CLAUDE_CODE_PERFETTO_TRACE=1 or CLAUDE_CODE_PERFETTO_TRACE= + * 2. Optionally set CLAUDE_CODE_PERFETTO_WRITE_INTERVAL_S= to write the + * trace file periodically (default: write only on exit). + * 3. Run Claude Code normally + * 4. Trace file is written to ~/.claude/traces/trace-.json + * or to the specified path + * 5. Open in ui.perfetto.dev to visualize + */ + +import { feature } from 'bun:bundle' +import { mkdirSync, writeFileSync } from 'fs' +import { mkdir, writeFile } from 'fs/promises' +import { dirname, join } from 'path' +import { getSessionId } from '../../bootstrap/state.js' +import { registerCleanup } from '../cleanupRegistry.js' +import { logForDebugging } from '../debug.js' +import { + getClaudeConfigHomeDir, + isEnvDefinedFalsy, + isEnvTruthy, +} from '../envUtils.js' +import { errorMessage } from '../errors.js' +import { djb2Hash } from '../hash.js' +import { jsonStringify } from '../slowOperations.js' +import { getAgentId, getAgentName, getParentSessionId } from '../teammate.js' + +/** + * Chrome Trace Event format types + * See: https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU + */ + +export type TraceEventPhase = + | 'B' // Begin duration event + | 'E' // End duration event + | 'X' // Complete event (with duration) + | 'i' // Instant event + | 'C' // Counter event + | 'b' // Async begin + | 'n' // Async instant + | 'e' // Async end + | 'M' // Metadata event + +export type TraceEvent = { + name: string + cat: string + ph: TraceEventPhase + ts: number // Timestamp in microseconds + pid: number // Process ID (we use 1 for main, agent IDs for subagents) + tid: number // Thread ID (we use numeric hash of agent name or 1 for main) + dur?: number // Duration in microseconds (for 'X' events) + args?: Record + id?: string // For async events + scope?: string +} + +/** + * Agent info for tracking hierarchy + */ +type AgentInfo = { + agentId: string + agentName: string + parentAgentId?: string + processId: number + threadId: number +} + +/** + * Pending span for tracking begin/end pairs + */ +type PendingSpan = { + name: string + category: string + startTime: number + agentInfo: AgentInfo + args: Record +} + +// Global state for the Perfetto tracer +let isEnabled = false +let tracePath: string | null = null +// Metadata events (ph: 'M' — process/thread names, parent links) are kept +// separate so they survive eviction — Perfetto UI needs them to label +// tracks. Bounded by agent count (~3 events per agent). +const metadataEvents: TraceEvent[] = [] +const events: TraceEvent[] = [] +// events[] cap. Cron-driven sessions run for days; 22 push sites × many +// turns would otherwise grow unboundedly (periodicWrite flushes to disk but +// does not truncate — it writes the full snapshot). At ~300B/event this is +// ~30MB, enough trace history for any debugging session. Eviction drops the +// oldest half when hit, amortized O(1). +const MAX_EVENTS = 100_000 +const pendingSpans = new Map() +const agentRegistry = new Map() +let totalAgentCount = 0 +let startTimeMs = 0 +let spanIdCounter = 0 +let traceWritten = false // Flag to avoid double writes + +// Map agent IDs to numeric process IDs (Perfetto requires numeric IDs) +let processIdCounter = 1 +const agentIdToProcessId = new Map() + +// Periodic write interval handle +let writeIntervalId: ReturnType | null = null + +const STALE_SPAN_TTL_MS = 30 * 60 * 1000 // 30 minutes +const STALE_SPAN_CLEANUP_INTERVAL_MS = 60 * 1000 // 1 minute +let staleSpanCleanupId: ReturnType | null = null + +/** + * Convert a string to a numeric hash for use as thread ID + */ +function stringToNumericHash(str: string): number { + return Math.abs(djb2Hash(str)) || 1 // Ensure non-zero +} + +/** + * Get or create a numeric process ID for an agent + */ +function getProcessIdForAgent(agentId: string): number { + const existing = agentIdToProcessId.get(agentId) + if (existing !== undefined) return existing + + processIdCounter++ + agentIdToProcessId.set(agentId, processIdCounter) + return processIdCounter +} + +/** + * Get current agent info + */ +function getCurrentAgentInfo(): AgentInfo { + const agentId = getAgentId() ?? getSessionId() + const agentName = getAgentName() ?? 'main' + const parentSessionId = getParentSessionId() + + // Check if we've already registered this agent + const existing = agentRegistry.get(agentId) + if (existing) return existing + + const info: AgentInfo = { + agentId, + agentName, + parentAgentId: parentSessionId, + processId: agentId === getSessionId() ? 1 : getProcessIdForAgent(agentId), + threadId: stringToNumericHash(agentName), + } + + agentRegistry.set(agentId, info) + totalAgentCount++ + return info +} + +/** + * Get timestamp in microseconds relative to trace start + */ +function getTimestamp(): number { + return (Date.now() - startTimeMs) * 1000 +} + +/** + * Generate a unique span ID + */ +function generateSpanId(): string { + return `span_${++spanIdCounter}` +} + +/** + * Evict pending spans older than STALE_SPAN_TTL_MS. + * Mirrors the TTL cleanup pattern in sessionTracing.ts. + */ +function evictStaleSpans(): void { + const now = getTimestamp() + const ttlUs = STALE_SPAN_TTL_MS * 1000 // Convert ms to microseconds + for (const [spanId, span] of pendingSpans) { + if (now - span.startTime > ttlUs) { + // Emit an end event so the span shows up in the trace as incomplete + events.push({ + name: span.name, + cat: span.category, + ph: 'E', + ts: now, + pid: span.agentInfo.processId, + tid: span.agentInfo.threadId, + args: { + ...span.args, + evicted: true, + duration_ms: (now - span.startTime) / 1000, + }, + }) + pendingSpans.delete(spanId) + } + } +} + +/** + * Build the full trace document (Chrome Trace JSON format). + */ +function buildTraceDocument(): string { + return jsonStringify({ + traceEvents: [...metadataEvents, ...events], + metadata: { + session_id: getSessionId(), + trace_start_time: new Date(startTimeMs).toISOString(), + agent_count: totalAgentCount, + total_event_count: metadataEvents.length + events.length, + }, + }) +} + +/** + * Drop the oldest half of events[] when over MAX_EVENTS. Called from the + * stale-span cleanup interval (60s). The half-batch splice keeps this + * amortized O(1) — we don't pay splice cost per-push. A synthetic marker + * is inserted so the gap is visible in ui.perfetto.dev. + */ +function evictOldestEvents(): void { + if (events.length < MAX_EVENTS) return + const dropped = events.splice(0, MAX_EVENTS / 2) + events.unshift({ + name: 'trace_truncated', + cat: '__metadata', + ph: 'i', + ts: dropped[dropped.length - 1]?.ts ?? 0, + pid: 1, + tid: 0, + args: { dropped_events: dropped.length }, + }) + logForDebugging( + `[Perfetto] Evicted ${dropped.length} oldest events (cap ${MAX_EVENTS})`, + ) +} + +/** + * Initialize Perfetto tracing + * Call this early in the application lifecycle + */ +export function initializePerfettoTracing(): void { + const envValue = process.env.CLAUDE_CODE_PERFETTO_TRACE + logForDebugging( + `[Perfetto] initializePerfettoTracing called, env value: ${envValue}`, + ) + + // Wrap in feature() for dead code elimination - entire block removed from external builds + if (feature('PERFETTO_TRACING')) { + if (!envValue || isEnvDefinedFalsy(envValue)) { + logForDebugging( + '[Perfetto] Tracing disabled (env var not set or disabled)', + ) + return + } + + isEnabled = true + startTimeMs = Date.now() + + // Determine trace file path + if (isEnvTruthy(envValue)) { + const tracesDir = join(getClaudeConfigHomeDir(), 'traces') + tracePath = join(tracesDir, `trace-${getSessionId()}.json`) + } else { + // Use the provided path + tracePath = envValue + } + + logForDebugging( + `[Perfetto] Tracing enabled, will write to: ${tracePath}, isEnabled=${isEnabled}`, + ) + + // Start periodic full-trace write if CLAUDE_CODE_PERFETTO_WRITE_INTERVAL_S is a positive integer + const intervalSec = parseInt( + process.env.CLAUDE_CODE_PERFETTO_WRITE_INTERVAL_S ?? '', + 10, + ) + if (intervalSec > 0) { + writeIntervalId = setInterval(() => { + void periodicWrite() + }, intervalSec * 1000) + // Don't let the interval keep the process alive on its own + if (writeIntervalId.unref) writeIntervalId.unref() + logForDebugging( + `[Perfetto] Periodic write enabled, interval: ${intervalSec}s`, + ) + } + + // Start stale span cleanup interval + staleSpanCleanupId = setInterval(() => { + evictStaleSpans() + evictOldestEvents() + }, STALE_SPAN_CLEANUP_INTERVAL_MS) + if (staleSpanCleanupId.unref) staleSpanCleanupId.unref() + + // Register cleanup to write final trace on exit + registerCleanup(async () => { + logForDebugging('[Perfetto] Cleanup callback invoked') + await writePerfettoTrace() + }) + + // Also register a beforeExit handler as a fallback + // This ensures the trace is written even if cleanup registry is not called + process.on('beforeExit', () => { + logForDebugging('[Perfetto] beforeExit handler invoked') + void writePerfettoTrace() + }) + + // Register a synchronous exit handler as a last resort + // This is the final fallback to ensure trace is written before process exits + process.on('exit', () => { + if (!traceWritten) { + logForDebugging( + '[Perfetto] exit handler invoked, writing trace synchronously', + ) + writePerfettoTraceSync() + } + }) + + // Emit process metadata events for main process + const mainAgent = getCurrentAgentInfo() + emitProcessMetadata(mainAgent) + } +} + +/** + * Emit metadata events for a process/agent + */ +function emitProcessMetadata(agentInfo: AgentInfo): void { + if (!isEnabled) return + + // Process name + metadataEvents.push({ + name: 'process_name', + cat: '__metadata', + ph: 'M', + ts: 0, + pid: agentInfo.processId, + tid: 0, + args: { name: agentInfo.agentName }, + }) + + // Thread name (same as process for now) + metadataEvents.push({ + name: 'thread_name', + cat: '__metadata', + ph: 'M', + ts: 0, + pid: agentInfo.processId, + tid: agentInfo.threadId, + args: { name: agentInfo.agentName }, + }) + + // Add parent info if available + if (agentInfo.parentAgentId) { + metadataEvents.push({ + name: 'parent_agent', + cat: '__metadata', + ph: 'M', + ts: 0, + pid: agentInfo.processId, + tid: 0, + args: { + parent_agent_id: agentInfo.parentAgentId, + }, + }) + } +} + +/** + * Check if Perfetto tracing is enabled + */ +export function isPerfettoTracingEnabled(): boolean { + return isEnabled +} + +/** + * Register a new agent in the trace + * Call this when a subagent/teammate is spawned + */ +export function registerAgent( + agentId: string, + agentName: string, + parentAgentId?: string, +): void { + if (!isEnabled) return + + const info: AgentInfo = { + agentId, + agentName, + parentAgentId, + processId: getProcessIdForAgent(agentId), + threadId: stringToNumericHash(agentName), + } + + agentRegistry.set(agentId, info) + totalAgentCount++ + emitProcessMetadata(info) +} + +/** + * Unregister an agent from the trace. + * Call this when an agent completes, fails, or is aborted to free memory. + */ +export function unregisterAgent(agentId: string): void { + if (!isEnabled) return + agentRegistry.delete(agentId) + agentIdToProcessId.delete(agentId) +} + +/** + * Start an API call span + */ +export function startLLMRequestPerfettoSpan(args: { + model: string + promptTokens?: number + messageId?: string + isSpeculative?: boolean + querySource?: string +}): string { + if (!isEnabled) return '' + + const spanId = generateSpanId() + const agentInfo = getCurrentAgentInfo() + + pendingSpans.set(spanId, { + name: 'API Call', + category: 'api', + startTime: getTimestamp(), + agentInfo, + args: { + model: args.model, + prompt_tokens: args.promptTokens, + message_id: args.messageId, + is_speculative: args.isSpeculative ?? false, + query_source: args.querySource, + }, + }) + + // Emit begin event + events.push({ + name: 'API Call', + cat: 'api', + ph: 'B', + ts: pendingSpans.get(spanId)!.startTime, + pid: agentInfo.processId, + tid: agentInfo.threadId, + args: pendingSpans.get(spanId)!.args, + }) + + return spanId +} + +/** + * End an API call span with response metadata + */ +export function endLLMRequestPerfettoSpan( + spanId: string, + metadata: { + ttftMs?: number + ttltMs?: number + promptTokens?: number + outputTokens?: number + cacheReadTokens?: number + cacheCreationTokens?: number + messageId?: string + success?: boolean + error?: string + /** Time spent in pre-request setup (client creation, retries) before the successful attempt */ + requestSetupMs?: number + /** Timestamps (Date.now()) of each attempt start — used to emit retry sub-spans */ + attemptStartTimes?: number[] + }, +): void { + if (!isEnabled || !spanId) return + + const pending = pendingSpans.get(spanId) + if (!pending) return + + const endTime = getTimestamp() + const duration = endTime - pending.startTime + + const promptTokens = + metadata.promptTokens ?? (pending.args.prompt_tokens as number | undefined) + const ttftMs = metadata.ttftMs + const ttltMs = metadata.ttltMs + const outputTokens = metadata.outputTokens + const cacheReadTokens = metadata.cacheReadTokens + + // Compute derived metrics + // ITPS: input tokens per second (prompt processing speed) + const itps = + ttftMs !== undefined && promptTokens !== undefined && ttftMs > 0 + ? Math.round((promptTokens / (ttftMs / 1000)) * 100) / 100 + : undefined + + // OTPS: output tokens per second (sampling speed) + const samplingMs = + ttltMs !== undefined && ttftMs !== undefined ? ttltMs - ttftMs : undefined + const otps = + samplingMs !== undefined && outputTokens !== undefined && samplingMs > 0 + ? Math.round((outputTokens / (samplingMs / 1000)) * 100) / 100 + : undefined + + // Cache hit rate: percentage of prompt tokens from cache + const cacheHitRate = + cacheReadTokens !== undefined && + promptTokens !== undefined && + promptTokens > 0 + ? Math.round((cacheReadTokens / promptTokens) * 10000) / 100 + : undefined + + const requestSetupMs = metadata.requestSetupMs + const attemptStartTimes = metadata.attemptStartTimes + + // Merge metadata with original args + const args = { + ...pending.args, + ttft_ms: ttftMs, + ttlt_ms: ttltMs, + prompt_tokens: promptTokens, + output_tokens: outputTokens, + cache_read_tokens: cacheReadTokens, + cache_creation_tokens: metadata.cacheCreationTokens, + message_id: metadata.messageId ?? pending.args.message_id, + success: metadata.success ?? true, + error: metadata.error, + duration_ms: duration / 1000, + request_setup_ms: requestSetupMs, + // Derived metrics + itps, + otps, + cache_hit_rate_pct: cacheHitRate, + } + + // Emit Request Setup sub-span when there was measurable setup time + // (client creation, param building, retries before the successful attempt) + const setupUs = + requestSetupMs !== undefined && requestSetupMs > 0 + ? requestSetupMs * 1000 + : 0 + if (setupUs > 0) { + const setupEndTs = pending.startTime + setupUs + + events.push({ + name: 'Request Setup', + cat: 'api,setup', + ph: 'B', + ts: pending.startTime, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + args: { + request_setup_ms: requestSetupMs, + attempt_count: attemptStartTimes?.length ?? 1, + }, + }) + + // Emit retry attempt sub-spans within Request Setup. + // Each failed attempt runs from its start to the next attempt's start. + if (attemptStartTimes && attemptStartTimes.length > 1) { + // attemptStartTimes[0] is the reference point (first attempt). + // Convert wall-clock deltas into Perfetto-relative microseconds. + const baseWallMs = attemptStartTimes[0]! + for (let i = 0; i < attemptStartTimes.length - 1; i++) { + const attemptStartUs = + pending.startTime + (attemptStartTimes[i]! - baseWallMs) * 1000 + const attemptEndUs = + pending.startTime + (attemptStartTimes[i + 1]! - baseWallMs) * 1000 + + events.push({ + name: `Attempt ${i + 1} (retry)`, + cat: 'api,retry', + ph: 'B', + ts: attemptStartUs, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + args: { attempt: i + 1 }, + }) + events.push({ + name: `Attempt ${i + 1} (retry)`, + cat: 'api,retry', + ph: 'E', + ts: attemptEndUs, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + }) + } + } + + events.push({ + name: 'Request Setup', + cat: 'api,setup', + ph: 'E', + ts: setupEndTs, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + }) + } + + // Emit sub-spans for First Token and Sampling phases (before API Call end) + // Using B/E pairs in proper nesting order for correct Perfetto visualization + if (ttftMs !== undefined) { + // First Token starts after request setup (if any) + const firstTokenStartTs = pending.startTime + setupUs + const firstTokenEndTs = firstTokenStartTs + ttftMs * 1000 + + // First Token phase: from successful attempt start to first token + events.push({ + name: 'First Token', + cat: 'api,ttft', + ph: 'B', + ts: firstTokenStartTs, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + args: { + ttft_ms: ttftMs, + prompt_tokens: promptTokens, + itps, + cache_hit_rate_pct: cacheHitRate, + }, + }) + events.push({ + name: 'First Token', + cat: 'api,ttft', + ph: 'E', + ts: firstTokenEndTs, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + }) + + // Sampling phase: from first token to last token + // Note: samplingMs = ttltMs - ttftMs still includes setup time in ttltMs, + // so we compute the actual sampling duration for the span as the time from + // first token to API call end (endTime), not samplingMs directly. + const actualSamplingMs = + ttltMs !== undefined ? ttltMs - ttftMs - setupUs / 1000 : undefined + if (actualSamplingMs !== undefined && actualSamplingMs > 0) { + events.push({ + name: 'Sampling', + cat: 'api,sampling', + ph: 'B', + ts: firstTokenEndTs, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + args: { + sampling_ms: actualSamplingMs, + output_tokens: outputTokens, + otps, + }, + }) + events.push({ + name: 'Sampling', + cat: 'api,sampling', + ph: 'E', + ts: firstTokenEndTs + actualSamplingMs * 1000, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + }) + } + } + + // Emit API Call end event (after sub-spans) + events.push({ + name: pending.name, + cat: pending.category, + ph: 'E', + ts: endTime, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + args, + }) + + pendingSpans.delete(spanId) +} + +/** + * Start a tool execution span + */ +export function startToolPerfettoSpan( + toolName: string, + args?: Record, +): string { + if (!isEnabled) return '' + + const spanId = generateSpanId() + const agentInfo = getCurrentAgentInfo() + + pendingSpans.set(spanId, { + name: `Tool: ${toolName}`, + category: 'tool', + startTime: getTimestamp(), + agentInfo, + args: { + tool_name: toolName, + ...args, + }, + }) + + // Emit begin event + events.push({ + name: `Tool: ${toolName}`, + cat: 'tool', + ph: 'B', + ts: pendingSpans.get(spanId)!.startTime, + pid: agentInfo.processId, + tid: agentInfo.threadId, + args: pendingSpans.get(spanId)!.args, + }) + + return spanId +} + +/** + * End a tool execution span + */ +export function endToolPerfettoSpan( + spanId: string, + metadata?: { + success?: boolean + error?: string + resultTokens?: number + }, +): void { + if (!isEnabled || !spanId) return + + const pending = pendingSpans.get(spanId) + if (!pending) return + + const endTime = getTimestamp() + const duration = endTime - pending.startTime + + const args = { + ...pending.args, + success: metadata?.success ?? true, + error: metadata?.error, + result_tokens: metadata?.resultTokens, + duration_ms: duration / 1000, + } + + // Emit end event + events.push({ + name: pending.name, + cat: pending.category, + ph: 'E', + ts: endTime, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + args, + }) + + pendingSpans.delete(spanId) +} + +/** + * Start a user input waiting span + */ +export function startUserInputPerfettoSpan(context?: string): string { + if (!isEnabled) return '' + + const spanId = generateSpanId() + const agentInfo = getCurrentAgentInfo() + + pendingSpans.set(spanId, { + name: 'Waiting for User Input', + category: 'user_input', + startTime: getTimestamp(), + agentInfo, + args: { + context, + }, + }) + + // Emit begin event + events.push({ + name: 'Waiting for User Input', + cat: 'user_input', + ph: 'B', + ts: pendingSpans.get(spanId)!.startTime, + pid: agentInfo.processId, + tid: agentInfo.threadId, + args: pendingSpans.get(spanId)!.args, + }) + + return spanId +} + +/** + * End a user input waiting span + */ +export function endUserInputPerfettoSpan( + spanId: string, + metadata?: { + decision?: string + source?: string + }, +): void { + if (!isEnabled || !spanId) return + + const pending = pendingSpans.get(spanId) + if (!pending) return + + const endTime = getTimestamp() + const duration = endTime - pending.startTime + + const args = { + ...pending.args, + decision: metadata?.decision, + source: metadata?.source, + duration_ms: duration / 1000, + } + + // Emit end event + events.push({ + name: pending.name, + cat: pending.category, + ph: 'E', + ts: endTime, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + args, + }) + + pendingSpans.delete(spanId) +} + +/** + * Emit an instant event (marker) + */ +export function emitPerfettoInstant( + name: string, + category: string, + args?: Record, +): void { + if (!isEnabled) return + + const agentInfo = getCurrentAgentInfo() + + events.push({ + name, + cat: category, + ph: 'i', + ts: getTimestamp(), + pid: agentInfo.processId, + tid: agentInfo.threadId, + args, + }) +} + +/** + * Emit a counter event for tracking metrics over time + */ +export function emitPerfettoCounter( + name: string, + values: Record, +): void { + if (!isEnabled) return + + const agentInfo = getCurrentAgentInfo() + + events.push({ + name, + cat: 'counter', + ph: 'C', + ts: getTimestamp(), + pid: agentInfo.processId, + tid: agentInfo.threadId, + args: values, + }) +} + +/** + * Start an interaction span (wraps a full user request cycle) + */ +export function startInteractionPerfettoSpan(userPrompt?: string): string { + if (!isEnabled) return '' + + const spanId = generateSpanId() + const agentInfo = getCurrentAgentInfo() + + pendingSpans.set(spanId, { + name: 'Interaction', + category: 'interaction', + startTime: getTimestamp(), + agentInfo, + args: { + user_prompt_length: userPrompt?.length, + }, + }) + + // Emit begin event + events.push({ + name: 'Interaction', + cat: 'interaction', + ph: 'B', + ts: pendingSpans.get(spanId)!.startTime, + pid: agentInfo.processId, + tid: agentInfo.threadId, + args: pendingSpans.get(spanId)!.args, + }) + + return spanId +} + +/** + * End an interaction span + */ +export function endInteractionPerfettoSpan(spanId: string): void { + if (!isEnabled || !spanId) return + + const pending = pendingSpans.get(spanId) + if (!pending) return + + const endTime = getTimestamp() + const duration = endTime - pending.startTime + + // Emit end event + events.push({ + name: pending.name, + cat: pending.category, + ph: 'E', + ts: endTime, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + args: { + ...pending.args, + duration_ms: duration / 1000, + }, + }) + + pendingSpans.delete(spanId) +} + +// --------------------------------------------------------------------------- +// Periodic write helpers +// --------------------------------------------------------------------------- + +/** + * Stop the periodic write timer. + */ +function stopWriteInterval(): void { + if (staleSpanCleanupId) { + clearInterval(staleSpanCleanupId) + staleSpanCleanupId = null + } + if (writeIntervalId) { + clearInterval(writeIntervalId) + writeIntervalId = null + } +} + +/** + * Force-close any remaining open spans at session end. + */ +function closeOpenSpans(): void { + for (const [spanId, pending] of pendingSpans) { + const endTime = getTimestamp() + events.push({ + name: pending.name, + cat: pending.category, + ph: 'E', + ts: endTime, + pid: pending.agentInfo.processId, + tid: pending.agentInfo.threadId, + args: { + ...pending.args, + incomplete: true, + duration_ms: (endTime - pending.startTime) / 1000, + }, + }) + pendingSpans.delete(spanId) + } +} + +/** + * Write the full trace to disk. Errors are logged but swallowed so that a + * transient I/O problem does not crash the session — the next periodic tick + * (or the final exit write) will retry with a complete snapshot. + */ +async function periodicWrite(): Promise { + if (!isEnabled || !tracePath || traceWritten) return + + try { + await mkdir(dirname(tracePath), { recursive: true }) + await writeFile(tracePath, buildTraceDocument()) + logForDebugging( + `[Perfetto] Periodic write: ${events.length} events to ${tracePath}`, + ) + } catch (error) { + logForDebugging( + `[Perfetto] Periodic write failed: ${errorMessage(error)}`, + { level: 'error' }, + ) + } +} + +/** + * Final async write: close open spans and write the complete trace. + * Idempotent — sets `traceWritten` on success so subsequent calls are no-ops. + */ +async function writePerfettoTrace(): Promise { + if (!isEnabled || !tracePath || traceWritten) { + logForDebugging( + `[Perfetto] Skipping final write: isEnabled=${isEnabled}, tracePath=${tracePath}, traceWritten=${traceWritten}`, + ) + return + } + + stopWriteInterval() + closeOpenSpans() + + logForDebugging( + `[Perfetto] writePerfettoTrace called: events=${events.length}`, + ) + + try { + await mkdir(dirname(tracePath), { recursive: true }) + await writeFile(tracePath, buildTraceDocument()) + traceWritten = true + logForDebugging(`[Perfetto] Trace finalized at: ${tracePath}`) + } catch (error) { + logForDebugging( + `[Perfetto] Failed to write final trace: ${errorMessage(error)}`, + { level: 'error' }, + ) + } +} + +/** + * Final synchronous write (fallback for process 'exit' handler where async is forbidden). + */ +function writePerfettoTraceSync(): void { + if (!isEnabled || !tracePath || traceWritten) { + logForDebugging( + `[Perfetto] Skipping final sync write: isEnabled=${isEnabled}, tracePath=${tracePath}, traceWritten=${traceWritten}`, + ) + return + } + + stopWriteInterval() + closeOpenSpans() + + logForDebugging( + `[Perfetto] writePerfettoTraceSync called: events=${events.length}`, + ) + + try { + const dir = dirname(tracePath) + // eslint-disable-next-line custom-rules/no-sync-fs -- Only called from process.on('exit') handler + mkdirSync(dir, { recursive: true }) + // eslint-disable-next-line custom-rules/no-sync-fs, eslint-plugin-n/no-sync -- Required for process 'exit' handler which doesn't support async + writeFileSync(tracePath, buildTraceDocument()) + traceWritten = true + logForDebugging(`[Perfetto] Trace finalized synchronously at: ${tracePath}`) + } catch (error) { + logForDebugging( + `[Perfetto] Failed to write final trace synchronously: ${errorMessage(error)}`, + { level: 'error' }, + ) + } +} + +/** + * Get all recorded events (for testing) + */ +export function getPerfettoEvents(): TraceEvent[] { + return [...metadataEvents, ...events] +} + +/** + * Reset the tracer state (for testing) + */ +export function resetPerfettoTracer(): void { + if (staleSpanCleanupId) { + clearInterval(staleSpanCleanupId) + staleSpanCleanupId = null + } + stopWriteInterval() + metadataEvents.length = 0 + events.length = 0 + pendingSpans.clear() + agentRegistry.clear() + agentIdToProcessId.clear() + totalAgentCount = 0 + processIdCounter = 1 + spanIdCounter = 0 + isEnabled = false + tracePath = null + startTimeMs = 0 + traceWritten = false +} + +/** + * Trigger a periodic write immediately (for testing) + */ +export async function triggerPeriodicWriteForTesting(): Promise { + await periodicWrite() +} + +/** + * Evict stale spans immediately (for testing) + */ +export function evictStaleSpansForTesting(): void { + evictStaleSpans() +} + +export const MAX_EVENTS_FOR_TESTING = MAX_EVENTS +export function evictOldestEventsForTesting(): void { + evictOldestEvents() +} diff --git a/src/utils/telemetry/pluginTelemetry.ts b/src/utils/telemetry/pluginTelemetry.ts new file mode 100644 index 0000000000000000000000000000000000000000..30600e74efc3dc099752de12636fad6568f30e10 --- /dev/null +++ b/src/utils/telemetry/pluginTelemetry.ts @@ -0,0 +1,289 @@ +/** + * Plugin telemetry helpers — shared field builders for plugin lifecycle events. + * + * Implements the twin-column privacy pattern: every user-defined-name field + * emits both a raw value (routed to PII-tagged _PROTO_* BQ columns) and a + * redacted twin (real name iff marketplace ∈ allowlist, else 'third-party'). + * + * plugin_id_hash provides an opaque per-plugin aggregation key with no privacy + * dependency — sha256(name@marketplace + FIXED_SALT) truncated to 16 chars. + * This answers distinct-count and per-plugin-trend questions that the + * redacted column can't, without exposing user-defined names. + */ + +import { createHash } from 'crypto' +import { sep } from 'path' +import { + type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + type AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED, + logEvent, +} from '../../services/analytics/index.js' +import type { + LoadedPlugin, + PluginError, + PluginManifest, +} from '../../types/plugin.js' +import { + isOfficialMarketplaceName, + parsePluginIdentifier, +} from '../plugins/pluginIdentifier.js' + +// builtinPlugins.ts:BUILTIN_MARKETPLACE_NAME — inlined to avoid the cycle +// through commands.js. Marketplace schemas.ts enforces 'builtin' is reserved. +const BUILTIN_MARKETPLACE_NAME = 'builtin' + +// Fixed salt for plugin_id_hash. Same constant across all repos and emission +// sites. Not per-org, not rotated — per-org salt would defeat cross-org +// distinct-count, rotation would break trend lines. Customers can compute the +// same hash on their known plugin names to reverse-match their own telemetry. +const PLUGIN_ID_HASH_SALT = 'claude-plugin-telemetry-v1' + +/** + * Opaque per-plugin aggregation key. Input is the name@marketplace string as + * it appears in enabledPlugins keys, lowercased on the marketplace suffix for + * reproducibility. 16-char truncation keeps BQ GROUP BY cardinality manageable + * while making collisions negligible at projected 10k-plugin scale. Name case + * is preserved in both branches (enabledPlugins keys are case-sensitive). + */ +export function hashPluginId(name: string, marketplace?: string): string { + const key = marketplace ? `${name}@${marketplace.toLowerCase()}` : name + return createHash('sha256') + .update(key + PLUGIN_ID_HASH_SALT) + .digest('hex') + .slice(0, 16) +} + +/** + * 4-value scope enum for plugin origin. Distinct from PluginScope + * (managed/user/project/local) which is installation-target — this is + * marketplace-origin. + * + * - official: from an allowlisted Anthropic marketplace + * - default-bundle: ships with product (@builtin), auto-enabled + * - org: enterprise admin-pushed via managed settings (policySettings) + * - user-local: user added marketplace or local plugin + */ +export type TelemetryPluginScope = + | 'official' + | 'org' + | 'user-local' + | 'default-bundle' + +export function getTelemetryPluginScope( + name: string, + marketplace: string | undefined, + managedNames: Set | null, +): TelemetryPluginScope { + if (marketplace === BUILTIN_MARKETPLACE_NAME) return 'default-bundle' + if (isOfficialMarketplaceName(marketplace)) return 'official' + if (managedNames?.has(name)) return 'org' + return 'user-local' +} + +/** + * How a plugin arrived in the session. Splits self-selected from org-pushed + * — plugin_scope alone doesn't (an official plugin can be user-installed OR + * org-pushed; both are scope='official'). + */ +export type EnabledVia = + | 'user-install' + | 'org-policy' + | 'default-enable' + | 'seed-mount' + +/** How a skill/command invocation was triggered. */ +export type InvocationTrigger = + | 'user-slash' + | 'claude-proactive' + | 'nested-skill' + +/** Where a skill invocation executes. */ +export type SkillExecutionContext = 'fork' | 'inline' | 'remote' + +/** How a plugin install was initiated. */ +export type InstallSource = + | 'cli-explicit' + | 'ui-discover' + | 'ui-suggestion' + | 'deep-link' + +export function getEnabledVia( + plugin: LoadedPlugin, + managedNames: Set | null, + seedDirs: string[], +): EnabledVia { + if (plugin.isBuiltin) return 'default-enable' + if (managedNames?.has(plugin.name)) return 'org-policy' + // Trailing sep: /opt/plugins must not match /opt/plugins-extra + if ( + seedDirs.some(dir => + plugin.path.startsWith(dir.endsWith(sep) ? dir : dir + sep), + ) + ) { + return 'seed-mount' + } + return 'user-install' +} + +/** + * Common plugin telemetry fields keyed off name@marketplace. Returns the + * hash, scope enum, and the redacted-twin columns. Callers add the raw + * _PROTO_* fields separately (those require the PII-tagged marker type). + */ +export function buildPluginTelemetryFields( + name: string, + marketplace: string | undefined, + managedNames: Set | null = null, +): { + plugin_id_hash: AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS + plugin_scope: AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS + plugin_name_redacted: AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS + marketplace_name_redacted: AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS + is_official_plugin: boolean +} { + const scope = getTelemetryPluginScope(name, marketplace, managedNames) + // Both official marketplaces and builtin plugins are Anthropic-controlled + // — safe to expose real names in the redacted columns. + const isAnthropicControlled = + scope === 'official' || scope === 'default-bundle' + return { + plugin_id_hash: hashPluginId( + name, + marketplace, + ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + plugin_scope: + scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + plugin_name_redacted: (isAnthropicControlled + ? name + : 'third-party') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + marketplace_name_redacted: (isAnthropicControlled && marketplace + ? marketplace + : 'third-party') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + is_official_plugin: isAnthropicControlled, + } +} + +/** + * Per-invocation callers (SkillTool, processSlashCommand) pass + * managedNames=null — the session-level tengu_plugin_enabled_for_session + * event carries the authoritative plugin_scope, and per-invocation rows can + * join on plugin_id_hash to recover it. This keeps hot-path call sites free + * of the extra settings read. + */ +export function buildPluginCommandTelemetryFields( + pluginInfo: { pluginManifest: PluginManifest; repository: string }, + managedNames: Set | null = null, +): ReturnType { + const { marketplace } = parsePluginIdentifier(pluginInfo.repository) + return buildPluginTelemetryFields( + pluginInfo.pluginManifest.name, + marketplace, + managedNames, + ) +} + +/** + * Emit tengu_plugin_enabled_for_session once per enabled plugin at session + * start. Supplements tengu_skill_loaded (which still fires per-skill) — use + * this for plugin-level aggregates instead of DISTINCT-on-prefix hacks. + * A plugin with 5 skills emits 5 skill_loaded rows but 1 of these. + */ +export function logPluginsEnabledForSession( + plugins: LoadedPlugin[], + managedNames: Set | null, + seedDirs: string[], +): void { + for (const plugin of plugins) { + const { marketplace } = parsePluginIdentifier(plugin.repository) + + logEvent('tengu_plugin_enabled_for_session', { + _PROTO_plugin_name: + plugin.name as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED, + ...(marketplace && { + _PROTO_marketplace_name: + marketplace as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED, + }), + ...buildPluginTelemetryFields(plugin.name, marketplace, managedNames), + enabled_via: getEnabledVia( + plugin, + managedNames, + seedDirs, + ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + skill_path_count: + (plugin.skillsPath ? 1 : 0) + (plugin.skillsPaths?.length ?? 0), + command_path_count: + (plugin.commandsPath ? 1 : 0) + (plugin.commandsPaths?.length ?? 0), + has_mcp: plugin.manifest.mcpServers !== undefined, + has_hooks: plugin.hooksConfig !== undefined, + ...(plugin.manifest.version && { + version: plugin.manifest + .version as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + }), + }) + } +} + +/** + * Bounded-cardinality error bucket for CLI plugin operation failures. + * Maps free-form error messages to 5 stable categories so dashboard + * GROUP BY stays tractable. + */ +export type PluginCommandErrorCategory = + | 'network' + | 'not-found' + | 'permission' + | 'validation' + | 'unknown' + +export function classifyPluginCommandError( + error: unknown, +): PluginCommandErrorCategory { + const msg = String((error as { message?: unknown })?.message ?? error) + if ( + /ENOTFOUND|ECONNREFUSED|EAI_AGAIN|ETIMEDOUT|ECONNRESET|network|Could not resolve|Connection refused|timed out/i.test( + msg, + ) + ) { + return 'network' + } + if (/\b404\b|not found|does not exist|no such plugin/i.test(msg)) { + return 'not-found' + } + if (/\b40[13]\b|EACCES|EPERM|permission denied|unauthorized/i.test(msg)) { + return 'permission' + } + if (/invalid|malformed|schema|validation|parse error/i.test(msg)) { + return 'validation' + } + return 'unknown' +} + +/** + * Emit tengu_plugin_load_failed once per error surfaced by session-start + * plugin loading. Pairs with tengu_plugin_enabled_for_session so dashboards + * can compute a load-success rate. PluginError.type is already a bounded + * enum — use it directly as error_category. + */ +export function logPluginLoadErrors( + errors: PluginError[], + managedNames: Set | null, +): void { + for (const err of errors) { + const { name, marketplace } = parsePluginIdentifier(err.source) + // Not all PluginError variants carry a plugin name (some have pluginId, + // some are marketplace-level). Use the 'plugin' property if present, + // fall back to the name parsed from err.source. + const pluginName = 'plugin' in err && err.plugin ? err.plugin : name + logEvent('tengu_plugin_load_failed', { + error_category: + err.type as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + _PROTO_plugin_name: + pluginName as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED, + ...(marketplace && { + _PROTO_marketplace_name: + marketplace as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED, + }), + ...buildPluginTelemetryFields(pluginName, marketplace, managedNames), + }) + } +} diff --git a/src/utils/telemetry/sessionTracing.ts b/src/utils/telemetry/sessionTracing.ts new file mode 100644 index 0000000000000000000000000000000000000000..0ee4f38117cbc3ae4ed5206be07dfc08cd256a4e --- /dev/null +++ b/src/utils/telemetry/sessionTracing.ts @@ -0,0 +1,927 @@ +/** + * Session Tracing for Claude Code using OpenTelemetry (BETA) + * + * This module provides a high-level API for creating and managing spans + * to trace Claude Code workflows. Each user interaction creates a root + * interaction span, which contains operation spans (LLM requests, tool calls, etc.). + * + * Requirements: + * - Enhanced telemetry is enabled via feature('ENHANCED_TELEMETRY_BETA') + * - Configure OTEL_TRACES_EXPORTER (console, otlp, etc.) + */ + +import { feature } from 'bun:bundle' +import { context as otelContext, type Span, trace } from '@opentelemetry/api' +import { AsyncLocalStorage } from 'async_hooks' +import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js' +import type { AssistantMessage, UserMessage } from '../../types/message.js' +import { isEnvDefinedFalsy, isEnvTruthy } from '../envUtils.js' +import { getTelemetryAttributes } from '../telemetryAttributes.js' +import { + addBetaInteractionAttributes, + addBetaLLMRequestAttributes, + addBetaLLMResponseAttributes, + addBetaToolInputAttributes, + addBetaToolResultAttributes, + isBetaTracingEnabled, + type LLMRequestNewContext, + truncateContent, +} from './betaSessionTracing.js' +import { + endInteractionPerfettoSpan, + endLLMRequestPerfettoSpan, + endToolPerfettoSpan, + endUserInputPerfettoSpan, + isPerfettoTracingEnabled, + startInteractionPerfettoSpan, + startLLMRequestPerfettoSpan, + startToolPerfettoSpan, + startUserInputPerfettoSpan, +} from './perfettoTracing.js' + +// Re-export for callers +export type { Span } +export { isBetaTracingEnabled, type LLMRequestNewContext } + +// Message type for API calls (UserMessage or AssistantMessage) +type APIMessage = UserMessage | AssistantMessage + +type SpanType = + | 'interaction' + | 'llm_request' + | 'tool' + | 'tool.blocked_on_user' + | 'tool.execution' + | 'hook' + +interface SpanContext { + span: Span + startTime: number + attributes: Record + ended?: boolean + perfettoSpanId?: string +} + +// ALS stores SpanContext directly so it holds a strong reference while a span +// is active. With that, activeSpans can use WeakRef — when ALS is cleared +// (enterWith(undefined)) and no other code holds the SpanContext, GC can collect +// it and the WeakRef goes stale. +const interactionContext = new AsyncLocalStorage() +const toolContext = new AsyncLocalStorage() +const activeSpans = new Map>() +// Spans not stored in ALS (LLM request, blocked-on-user, tool execution, hook) +// need a strong reference to prevent GC from collecting the SpanContext before +// the corresponding end* function retrieves it. +const strongSpans = new Map() +let interactionSequence = 0 +let _cleanupIntervalStarted = false + +const SPAN_TTL_MS = 30 * 60 * 1000 // 30 minutes + +function getSpanId(span: Span): string { + return span.spanContext().spanId || '' +} + +/** + * Lazily start a background interval that evicts orphaned spans from activeSpans. + * + * Normal teardown calls endInteractionSpan / endToolSpan, which delete spans + * immediately. This interval is a safety net for spans that were never ended + * (e.g. aborted streams, uncaught exceptions mid-query) — without it they + * accumulate in activeSpans indefinitely, holding references to Span objects + * and the OpenTelemetry context chain. + * + * Initialized on the first startInteractionSpan call (not at module load) to + * avoid triggering the no-top-level-side-effects lint rule and to keep the + * interval from running in processes that never start a span. + * unref() prevents the timer from keeping the process alive after all other + * work is done. + */ +function ensureCleanupInterval(): void { + if (_cleanupIntervalStarted) return + _cleanupIntervalStarted = true + const interval = setInterval(() => { + const cutoff = Date.now() - SPAN_TTL_MS + for (const [spanId, weakRef] of activeSpans) { + const ctx = weakRef.deref() + if (ctx === undefined) { + activeSpans.delete(spanId) + strongSpans.delete(spanId) + } else if (ctx.startTime < cutoff) { + if (!ctx.ended) ctx.span.end() // flush any recorded attributes to the exporter + activeSpans.delete(spanId) + strongSpans.delete(spanId) + } + } + }, 60_000) + if (typeof interval.unref === 'function') { + interval.unref() // Node.js / Bun: don't block process exit + } +} + +/** + * Check if enhanced telemetry is enabled. + * Priority: env var override > ant build > GrowthBook gate + */ +export function isEnhancedTelemetryEnabled(): boolean { + if (feature('ENHANCED_TELEMETRY_BETA')) { + const env = + process.env.CLAUDE_CODE_ENHANCED_TELEMETRY_BETA ?? + process.env.ENABLE_ENHANCED_TELEMETRY_BETA + if (isEnvTruthy(env)) { + return true + } + if (isEnvDefinedFalsy(env)) { + return false + } + return ( + process.env.USER_TYPE === 'ant' || + getFeatureValue_CACHED_MAY_BE_STALE('enhanced_telemetry_beta', false) + ) + } + return false +} + +/** + * Check if any tracing is enabled (either standard enhanced telemetry OR beta tracing) + */ +function isAnyTracingEnabled(): boolean { + return isEnhancedTelemetryEnabled() || isBetaTracingEnabled() +} + +function getTracer() { + return trace.getTracer('com.anthropic.claude_code.tracing', '1.0.0') +} + +function createSpanAttributes( + spanType: SpanType, + customAttributes: Record = {}, +): Record { + const baseAttributes = getTelemetryAttributes() + + const attributes: Record = { + ...baseAttributes, + 'span.type': spanType, + ...customAttributes, + } + + return attributes +} + +/** + * Start an interaction span. This wraps a user request -> Claude response cycle. + * This is now a root span that includes all session-level attributes. + * Sets the interaction context for all subsequent operations. + */ +export function startInteractionSpan(userPrompt: string): Span { + ensureCleanupInterval() + + // Start Perfetto span regardless of OTel tracing state + const perfettoSpanId = isPerfettoTracingEnabled() + ? startInteractionPerfettoSpan(userPrompt) + : undefined + + if (!isAnyTracingEnabled()) { + // Still track Perfetto span even if OTel is disabled + if (perfettoSpanId) { + const dummySpan = trace.getActiveSpan() || getTracer().startSpan('dummy') + const spanId = getSpanId(dummySpan) + const spanContextObj: SpanContext = { + span: dummySpan, + startTime: Date.now(), + attributes: {}, + perfettoSpanId, + } + activeSpans.set(spanId, new WeakRef(spanContextObj)) + interactionContext.enterWith(spanContextObj) + return dummySpan + } + return trace.getActiveSpan() || getTracer().startSpan('dummy') + } + + const tracer = getTracer() + const isUserPromptLoggingEnabled = isEnvTruthy( + process.env.OTEL_LOG_USER_PROMPTS, + ) + const promptToLog = isUserPromptLoggingEnabled ? userPrompt : '' + + interactionSequence++ + + const attributes = createSpanAttributes('interaction', { + user_prompt: promptToLog, + user_prompt_length: userPrompt.length, + 'interaction.sequence': interactionSequence, + }) + + const span = tracer.startSpan('claude_code.interaction', { + attributes, + }) + + // Add experimental attributes (new_context) + addBetaInteractionAttributes(span, userPrompt) + + const spanId = getSpanId(span) + const spanContextObj: SpanContext = { + span, + startTime: Date.now(), + attributes, + perfettoSpanId, + } + activeSpans.set(spanId, new WeakRef(spanContextObj)) + + interactionContext.enterWith(spanContextObj) + + return span +} + +export function endInteractionSpan(): void { + const spanContext = interactionContext.getStore() + if (!spanContext) { + return + } + + if (spanContext.ended) { + return + } + + // End Perfetto span + if (spanContext.perfettoSpanId) { + endInteractionPerfettoSpan(spanContext.perfettoSpanId) + } + + if (!isAnyTracingEnabled()) { + spanContext.ended = true + activeSpans.delete(getSpanId(spanContext.span)) + // Clear the store so async continuations created after this point (timers, + // promise callbacks, I/O) do not inherit a reference to the ended span. + // enterWith(undefined) is intentional: exit(() => {}) is a no-op because it + // only suppresses the store inside the callback and returns immediately. + interactionContext.enterWith(undefined) + return + } + + const duration = Date.now() - spanContext.startTime + spanContext.span.setAttributes({ + 'interaction.duration_ms': duration, + }) + + spanContext.span.end() + spanContext.ended = true + activeSpans.delete(getSpanId(spanContext.span)) + interactionContext.enterWith(undefined) +} + +export function startLLMRequestSpan( + model: string, + newContext?: LLMRequestNewContext, + messagesForAPI?: APIMessage[], + fastMode?: boolean, +): Span { + // Start Perfetto span regardless of OTel tracing state + const perfettoSpanId = isPerfettoTracingEnabled() + ? startLLMRequestPerfettoSpan({ + model, + querySource: newContext?.querySource, + messageId: undefined, // Will be set in endLLMRequestSpan + }) + : undefined + + if (!isAnyTracingEnabled()) { + // Still track Perfetto span even if OTel is disabled + if (perfettoSpanId) { + const dummySpan = trace.getActiveSpan() || getTracer().startSpan('dummy') + const spanId = getSpanId(dummySpan) + const spanContextObj: SpanContext = { + span: dummySpan, + startTime: Date.now(), + attributes: { model }, + perfettoSpanId, + } + activeSpans.set(spanId, new WeakRef(spanContextObj)) + strongSpans.set(spanId, spanContextObj) + return dummySpan + } + return trace.getActiveSpan() || getTracer().startSpan('dummy') + } + + const tracer = getTracer() + const parentSpanCtx = interactionContext.getStore() + + const attributes = createSpanAttributes('llm_request', { + model: model, + 'llm_request.context': parentSpanCtx ? 'interaction' : 'standalone', + speed: fastMode ? 'fast' : 'normal', + }) + + const ctx = parentSpanCtx + ? trace.setSpan(otelContext.active(), parentSpanCtx.span) + : otelContext.active() + const span = tracer.startSpan('claude_code.llm_request', { attributes }, ctx) + + // Add query_source (agent name) if provided + if (newContext?.querySource) { + span.setAttribute('query_source', newContext.querySource) + } + + // Add experimental attributes (system prompt, new_context) + addBetaLLMRequestAttributes(span, newContext, messagesForAPI) + + const spanId = getSpanId(span) + const spanContextObj: SpanContext = { + span, + startTime: Date.now(), + attributes, + perfettoSpanId, + } + activeSpans.set(spanId, new WeakRef(spanContextObj)) + strongSpans.set(spanId, spanContextObj) + + return span +} + +/** + * End an LLM request span and attach response metadata. + * + * @param span - Optional. The exact span returned by startLLMRequestSpan(). + * IMPORTANT: When multiple LLM requests run in parallel (e.g., warmup requests, + * topic classifier, file path extractor, main thread), you MUST pass the specific span + * to ensure responses are attached to the correct request. Without it, responses may be + * incorrectly attached to whichever span happens to be "last" in the activeSpans map. + * + * If not provided, falls back to finding the most recent llm_request span (legacy behavior). + */ +export function endLLMRequestSpan( + span?: Span, + metadata?: { + inputTokens?: number + outputTokens?: number + cacheReadTokens?: number + cacheCreationTokens?: number + success?: boolean + statusCode?: number + error?: string + attempt?: number + modelResponse?: string + /** Text output from the model (non-thinking content) */ + modelOutput?: string + /** Thinking/reasoning output from the model */ + thinkingOutput?: string + /** Whether the output included tool calls (look at tool spans for details) */ + hasToolCall?: boolean + /** Time to first token in milliseconds */ + ttftMs?: number + /** Time spent in pre-request setup before the successful attempt */ + requestSetupMs?: number + /** Timestamps (Date.now()) of each attempt start — used to emit retry sub-spans */ + attemptStartTimes?: number[] + }, +): void { + let llmSpanContext: SpanContext | undefined + + if (span) { + // Use the provided span directly - this is the correct approach for parallel requests + const spanId = getSpanId(span) + llmSpanContext = activeSpans.get(spanId)?.deref() + } else { + // Legacy fallback: find the most recent llm_request span + // WARNING: This can cause mismatched responses when multiple requests are in flight + llmSpanContext = Array.from(activeSpans.values()) + .findLast(r => { + const ctx = r.deref() + return ( + ctx?.attributes['span.type'] === 'llm_request' || + ctx?.attributes['model'] + ) + }) + ?.deref() + } + + if (!llmSpanContext) { + // Span was already ended or never tracked + return + } + + const duration = Date.now() - llmSpanContext.startTime + + // End Perfetto span with full metadata + if (llmSpanContext.perfettoSpanId) { + endLLMRequestPerfettoSpan(llmSpanContext.perfettoSpanId, { + ttftMs: metadata?.ttftMs, + ttltMs: duration, // Time to last token is the total duration + promptTokens: metadata?.inputTokens, + outputTokens: metadata?.outputTokens, + cacheReadTokens: metadata?.cacheReadTokens, + cacheCreationTokens: metadata?.cacheCreationTokens, + success: metadata?.success, + error: metadata?.error, + requestSetupMs: metadata?.requestSetupMs, + attemptStartTimes: metadata?.attemptStartTimes, + }) + } + + if (!isAnyTracingEnabled()) { + const spanId = getSpanId(llmSpanContext.span) + activeSpans.delete(spanId) + strongSpans.delete(spanId) + return + } + + const endAttributes: Record = { + duration_ms: duration, + } + + if (metadata) { + if (metadata.inputTokens !== undefined) + endAttributes['input_tokens'] = metadata.inputTokens + if (metadata.outputTokens !== undefined) + endAttributes['output_tokens'] = metadata.outputTokens + if (metadata.cacheReadTokens !== undefined) + endAttributes['cache_read_tokens'] = metadata.cacheReadTokens + if (metadata.cacheCreationTokens !== undefined) + endAttributes['cache_creation_tokens'] = metadata.cacheCreationTokens + if (metadata.success !== undefined) + endAttributes['success'] = metadata.success + if (metadata.statusCode !== undefined) + endAttributes['status_code'] = metadata.statusCode + if (metadata.error !== undefined) endAttributes['error'] = metadata.error + if (metadata.attempt !== undefined) + endAttributes['attempt'] = metadata.attempt + if (metadata.hasToolCall !== undefined) + endAttributes['response.has_tool_call'] = metadata.hasToolCall + if (metadata.ttftMs !== undefined) + endAttributes['ttft_ms'] = metadata.ttftMs + + // Add experimental response attributes (model_output, thinking_output) + addBetaLLMResponseAttributes(endAttributes, metadata) + } + + llmSpanContext.span.setAttributes(endAttributes) + llmSpanContext.span.end() + + const spanId = getSpanId(llmSpanContext.span) + activeSpans.delete(spanId) + strongSpans.delete(spanId) +} + +export function startToolSpan( + toolName: string, + toolAttributes?: Record, + toolInput?: string, +): Span { + // Start Perfetto span regardless of OTel tracing state + const perfettoSpanId = isPerfettoTracingEnabled() + ? startToolPerfettoSpan(toolName, toolAttributes) + : undefined + + if (!isAnyTracingEnabled()) { + // Still track Perfetto span even if OTel is disabled + if (perfettoSpanId) { + const dummySpan = trace.getActiveSpan() || getTracer().startSpan('dummy') + const spanId = getSpanId(dummySpan) + const spanContextObj: SpanContext = { + span: dummySpan, + startTime: Date.now(), + attributes: { 'span.type': 'tool', tool_name: toolName }, + perfettoSpanId, + } + activeSpans.set(spanId, new WeakRef(spanContextObj)) + toolContext.enterWith(spanContextObj) + return dummySpan + } + return trace.getActiveSpan() || getTracer().startSpan('dummy') + } + + const tracer = getTracer() + const parentSpanCtx = interactionContext.getStore() + + const attributes = createSpanAttributes('tool', { + tool_name: toolName, + ...toolAttributes, + }) + + const ctx = parentSpanCtx + ? trace.setSpan(otelContext.active(), parentSpanCtx.span) + : otelContext.active() + const span = tracer.startSpan('claude_code.tool', { attributes }, ctx) + + // Add experimental tool input attributes + if (toolInput) { + addBetaToolInputAttributes(span, toolName, toolInput) + } + + const spanId = getSpanId(span) + const spanContextObj: SpanContext = { + span, + startTime: Date.now(), + attributes, + perfettoSpanId, + } + activeSpans.set(spanId, new WeakRef(spanContextObj)) + + toolContext.enterWith(spanContextObj) + + return span +} + +export function startToolBlockedOnUserSpan(): Span { + // Start Perfetto span regardless of OTel tracing state + const perfettoSpanId = isPerfettoTracingEnabled() + ? startUserInputPerfettoSpan('tool_permission') + : undefined + + if (!isAnyTracingEnabled()) { + // Still track Perfetto span even if OTel is disabled + if (perfettoSpanId) { + const dummySpan = trace.getActiveSpan() || getTracer().startSpan('dummy') + const spanId = getSpanId(dummySpan) + const spanContextObj: SpanContext = { + span: dummySpan, + startTime: Date.now(), + attributes: { 'span.type': 'tool.blocked_on_user' }, + perfettoSpanId, + } + activeSpans.set(spanId, new WeakRef(spanContextObj)) + strongSpans.set(spanId, spanContextObj) + return dummySpan + } + return trace.getActiveSpan() || getTracer().startSpan('dummy') + } + + const tracer = getTracer() + const parentSpanCtx = toolContext.getStore() + + const attributes = createSpanAttributes('tool.blocked_on_user') + + const ctx = parentSpanCtx + ? trace.setSpan(otelContext.active(), parentSpanCtx.span) + : otelContext.active() + const span = tracer.startSpan( + 'claude_code.tool.blocked_on_user', + { attributes }, + ctx, + ) + + const spanId = getSpanId(span) + const spanContextObj: SpanContext = { + span, + startTime: Date.now(), + attributes, + perfettoSpanId, + } + activeSpans.set(spanId, new WeakRef(spanContextObj)) + strongSpans.set(spanId, spanContextObj) + + return span +} + +export function endToolBlockedOnUserSpan( + decision?: string, + source?: string, +): void { + const blockedSpanContext = Array.from(activeSpans.values()) + .findLast( + r => r.deref()?.attributes['span.type'] === 'tool.blocked_on_user', + ) + ?.deref() + + if (!blockedSpanContext) { + return + } + + // End Perfetto span + if (blockedSpanContext.perfettoSpanId) { + endUserInputPerfettoSpan(blockedSpanContext.perfettoSpanId, { + decision, + source, + }) + } + + if (!isAnyTracingEnabled()) { + const spanId = getSpanId(blockedSpanContext.span) + activeSpans.delete(spanId) + strongSpans.delete(spanId) + return + } + + const duration = Date.now() - blockedSpanContext.startTime + const attributes: Record = { + duration_ms: duration, + } + + if (decision) { + attributes['decision'] = decision + } + if (source) { + attributes['source'] = source + } + + blockedSpanContext.span.setAttributes(attributes) + blockedSpanContext.span.end() + + const spanId = getSpanId(blockedSpanContext.span) + activeSpans.delete(spanId) + strongSpans.delete(spanId) +} + +export function startToolExecutionSpan(): Span { + if (!isAnyTracingEnabled()) { + return trace.getActiveSpan() || getTracer().startSpan('dummy') + } + + const tracer = getTracer() + const parentSpanCtx = toolContext.getStore() + + const attributes = createSpanAttributes('tool.execution') + + const ctx = parentSpanCtx + ? trace.setSpan(otelContext.active(), parentSpanCtx.span) + : otelContext.active() + const span = tracer.startSpan( + 'claude_code.tool.execution', + { attributes }, + ctx, + ) + + const spanId = getSpanId(span) + const spanContextObj: SpanContext = { + span, + startTime: Date.now(), + attributes, + } + activeSpans.set(spanId, new WeakRef(spanContextObj)) + strongSpans.set(spanId, spanContextObj) + + return span +} + +export function endToolExecutionSpan(metadata?: { + success?: boolean + error?: string +}): void { + if (!isAnyTracingEnabled()) { + return + } + + const executionSpanContext = Array.from(activeSpans.values()) + .findLast(r => r.deref()?.attributes['span.type'] === 'tool.execution') + ?.deref() + + if (!executionSpanContext) { + return + } + + const duration = Date.now() - executionSpanContext.startTime + const attributes: Record = { + duration_ms: duration, + } + + if (metadata) { + if (metadata.success !== undefined) attributes['success'] = metadata.success + if (metadata.error !== undefined) attributes['error'] = metadata.error + } + + executionSpanContext.span.setAttributes(attributes) + executionSpanContext.span.end() + + const spanId = getSpanId(executionSpanContext.span) + activeSpans.delete(spanId) + strongSpans.delete(spanId) +} + +export function endToolSpan(toolResult?: string, resultTokens?: number): void { + const toolSpanContext = toolContext.getStore() + + if (!toolSpanContext) { + return + } + + // End Perfetto span + if (toolSpanContext.perfettoSpanId) { + endToolPerfettoSpan(toolSpanContext.perfettoSpanId, { + success: true, + resultTokens, + }) + } + + if (!isAnyTracingEnabled()) { + const spanId = getSpanId(toolSpanContext.span) + activeSpans.delete(spanId) + // Same reasoning as interactionContext above: clear so subsequent async + // work doesn't hold a stale reference to the ended tool span. + toolContext.enterWith(undefined) + return + } + + const duration = Date.now() - toolSpanContext.startTime + const endAttributes: Record = { + duration_ms: duration, + } + + // Add experimental tool result attributes (new_context) + if (toolResult) { + const toolName = toolSpanContext.attributes['tool_name'] || 'unknown' + addBetaToolResultAttributes(endAttributes, toolName, toolResult) + } + + if (resultTokens !== undefined) { + endAttributes['result_tokens'] = resultTokens + } + + toolSpanContext.span.setAttributes(endAttributes) + toolSpanContext.span.end() + + const spanId = getSpanId(toolSpanContext.span) + activeSpans.delete(spanId) + toolContext.enterWith(undefined) +} + +function isToolContentLoggingEnabled(): boolean { + return isEnvTruthy(process.env.OTEL_LOG_TOOL_CONTENT) +} + +/** + * Add a span event with tool content/output data. + * Only logs if OTEL_LOG_TOOL_CONTENT=1 is set. + * Truncates content if it exceeds MAX_CONTENT_SIZE. + */ +export function addToolContentEvent( + eventName: string, + attributes: Record, +): void { + if (!isAnyTracingEnabled() || !isToolContentLoggingEnabled()) { + return + } + + const currentSpanCtx = toolContext.getStore() + if (!currentSpanCtx) { + return + } + + // Truncate string attributes that might be large + const processedAttributes: Record = {} + for (const [key, value] of Object.entries(attributes)) { + if (typeof value === 'string') { + const { content, truncated } = truncateContent(value) + processedAttributes[key] = content + if (truncated) { + processedAttributes[`${key}_truncated`] = true + processedAttributes[`${key}_original_length`] = value.length + } + } else { + processedAttributes[key] = value + } + } + + currentSpanCtx.span.addEvent(eventName, processedAttributes) +} + +export function getCurrentSpan(): Span | null { + if (!isAnyTracingEnabled()) { + return null + } + + return ( + toolContext.getStore()?.span ?? interactionContext.getStore()?.span ?? null + ) +} + +export async function executeInSpan( + spanName: string, + fn: (span: Span) => Promise, + attributes?: Record, +): Promise { + if (!isAnyTracingEnabled()) { + return fn(trace.getActiveSpan() || getTracer().startSpan('dummy')) + } + + const tracer = getTracer() + const parentSpanCtx = toolContext.getStore() ?? interactionContext.getStore() + + const finalAttributes = createSpanAttributes('tool', { + ...attributes, + }) + + const ctx = parentSpanCtx + ? trace.setSpan(otelContext.active(), parentSpanCtx.span) + : otelContext.active() + const span = tracer.startSpan(spanName, { attributes: finalAttributes }, ctx) + + const spanId = getSpanId(span) + const spanContextObj: SpanContext = { + span, + startTime: Date.now(), + attributes: finalAttributes, + } + activeSpans.set(spanId, new WeakRef(spanContextObj)) + strongSpans.set(spanId, spanContextObj) + + try { + const result = await fn(span) + span.end() + activeSpans.delete(spanId) + strongSpans.delete(spanId) + return result + } catch (error) { + if (error instanceof Error) { + span.recordException(error) + } + span.end() + activeSpans.delete(spanId) + strongSpans.delete(spanId) + throw error + } +} + +/** + * Start a hook execution span. + * Only creates a span when beta tracing is enabled. + * @param hookEvent The hook event type (e.g., 'PreToolUse', 'PostToolUse') + * @param hookName The full hook name (e.g., 'PreToolUse:Write') + * @param numHooks The number of hooks being executed + * @param hookDefinitions JSON string of hook definitions for tracing + * @returns The span (or a dummy span if tracing is disabled) + */ +export function startHookSpan( + hookEvent: string, + hookName: string, + numHooks: number, + hookDefinitions: string, +): Span { + if (!isBetaTracingEnabled()) { + return trace.getActiveSpan() || getTracer().startSpan('dummy') + } + + const tracer = getTracer() + const parentSpanCtx = toolContext.getStore() ?? interactionContext.getStore() + + const attributes = createSpanAttributes('hook', { + hook_event: hookEvent, + hook_name: hookName, + num_hooks: numHooks, + hook_definitions: hookDefinitions, + }) + + const ctx = parentSpanCtx + ? trace.setSpan(otelContext.active(), parentSpanCtx.span) + : otelContext.active() + const span = tracer.startSpan('claude_code.hook', { attributes }, ctx) + + const spanId = getSpanId(span) + const spanContextObj: SpanContext = { + span, + startTime: Date.now(), + attributes, + } + activeSpans.set(spanId, new WeakRef(spanContextObj)) + strongSpans.set(spanId, spanContextObj) + + return span +} + +/** + * End a hook execution span with outcome metadata. + * Only does work when beta tracing is enabled. + * @param span The span to end (returned from startHookSpan) + * @param metadata The outcome metadata for the hook execution + */ +export function endHookSpan( + span: Span, + metadata?: { + numSuccess?: number + numBlocking?: number + numNonBlockingError?: number + numCancelled?: number + }, +): void { + if (!isBetaTracingEnabled()) { + return + } + + const spanId = getSpanId(span) + const spanContext = activeSpans.get(spanId)?.deref() + + if (!spanContext) { + return + } + + const duration = Date.now() - spanContext.startTime + const endAttributes: Record = { + duration_ms: duration, + } + + if (metadata) { + if (metadata.numSuccess !== undefined) + endAttributes['num_success'] = metadata.numSuccess + if (metadata.numBlocking !== undefined) + endAttributes['num_blocking'] = metadata.numBlocking + if (metadata.numNonBlockingError !== undefined) + endAttributes['num_non_blocking_error'] = metadata.numNonBlockingError + if (metadata.numCancelled !== undefined) + endAttributes['num_cancelled'] = metadata.numCancelled + } + + spanContext.span.setAttributes(endAttributes) + spanContext.span.end() + activeSpans.delete(spanId) + strongSpans.delete(spanId) +} diff --git a/src/utils/telemetry/skillLoadedEvent.ts b/src/utils/telemetry/skillLoadedEvent.ts new file mode 100644 index 0000000000000000000000000000000000000000..a84a58bb6a5ad9dd5756998f1c5a9b40da9b8812 --- /dev/null +++ b/src/utils/telemetry/skillLoadedEvent.ts @@ -0,0 +1,39 @@ +import { getSkillToolCommands } from '../../commands.js' +import { + type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + type AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED, + logEvent, +} from '../../services/analytics/index.js' +import { getCharBudget } from '../../tools/SkillTool/prompt.js' + +/** + * Logs a tengu_skill_loaded event for each skill available at session startup. + * This enables analytics on which skills are available across sessions. + */ +export async function logSkillsLoaded( + cwd: string, + contextWindowTokens: number, +): Promise { + const skills = await getSkillToolCommands(cwd) + const skillBudget = getCharBudget(contextWindowTokens) + + for (const skill of skills) { + if (skill.type !== 'prompt') continue + + logEvent('tengu_skill_loaded', { + // _PROTO_skill_name routes to the privileged skill_name BQ column. + // Unredacted names don't go in additional_metadata. + _PROTO_skill_name: + skill.name as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED, + skill_source: + skill.source as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + skill_loaded_from: + skill.loadedFrom as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + skill_budget: skillBudget, + ...(skill.kind && { + skill_kind: + skill.kind as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + }), + }) + } +} diff --git a/src/utils/teleport/api.ts b/src/utils/teleport/api.ts new file mode 100644 index 0000000000000000000000000000000000000000..c3a666e218de1aac876ba5441047eacfc72c0b71 --- /dev/null +++ b/src/utils/teleport/api.ts @@ -0,0 +1,466 @@ +import axios, { type AxiosRequestConfig, type AxiosResponse } from 'axios' +import { randomUUID } from 'crypto' +import { getOauthConfig } from 'src/constants/oauth.js' +import { getOrganizationUUID } from 'src/services/oauth/client.js' +import z from 'zod/v4' +import { getClaudeAIOAuthTokens } from '../auth.js' +import { logForDebugging } from '../debug.js' +import { parseGitHubRepository } from '../detectRepository.js' +import { errorMessage, toError } from '../errors.js' +import { lazySchema } from '../lazySchema.js' +import { logError } from '../log.js' +import { sleep } from '../sleep.js' +import { jsonStringify } from '../slowOperations.js' + +// Retry configuration for teleport API requests +const TELEPORT_RETRY_DELAYS = [2000, 4000, 8000, 16000] // 4 retries with exponential backoff +const MAX_TELEPORT_RETRIES = TELEPORT_RETRY_DELAYS.length + +export const CCR_BYOC_BETA = 'ccr-byoc-2025-07-29' + +/** + * Checks if an axios error is a transient network error that should be retried + */ +export function isTransientNetworkError(error: unknown): boolean { + if (!axios.isAxiosError(error)) { + return false + } + + // Retry on network errors (no response received) + if (!error.response) { + return true + } + + // Retry on server errors (5xx) + if (error.response.status >= 500) { + return true + } + + // Don't retry on client errors (4xx) - they're not transient + return false +} + +/** + * Makes an axios GET request with automatic retry for transient network errors + * Uses exponential backoff: 2s, 4s, 8s, 16s (4 retries = 5 total attempts) + */ +export async function axiosGetWithRetry( + url: string, + config?: AxiosRequestConfig, +): Promise> { + let lastError: unknown + + for (let attempt = 0; attempt <= MAX_TELEPORT_RETRIES; attempt++) { + try { + return await axios.get(url, config) + } catch (error) { + lastError = error + + // Don't retry if this isn't a transient error + if (!isTransientNetworkError(error)) { + throw error + } + + // Don't retry if we've exhausted all retries + if (attempt >= MAX_TELEPORT_RETRIES) { + logForDebugging( + `Teleport request failed after ${attempt + 1} attempts: ${errorMessage(error)}`, + ) + throw error + } + + const delay = TELEPORT_RETRY_DELAYS[attempt] ?? 2000 + logForDebugging( + `Teleport request failed (attempt ${attempt + 1}/${MAX_TELEPORT_RETRIES + 1}), retrying in ${delay}ms: ${errorMessage(error)}`, + ) + await sleep(delay) + } + } + + throw lastError +} + +// Types matching the actual Sessions API response from api/schemas/sessions/sessions.py +export type SessionStatus = 'requires_action' | 'running' | 'idle' | 'archived' + +export type GitSource = { + type: 'git_repository' + url: string + revision?: string | null + allow_unrestricted_git_push?: boolean +} + +export type KnowledgeBaseSource = { + type: 'knowledge_base' + knowledge_base_id: string +} + +export type SessionContextSource = GitSource | KnowledgeBaseSource + +// Outcome types from api/schemas/sandbox.py +export type OutcomeGitInfo = { + type: 'github' + repo: string + branches: string[] +} + +export type GitRepositoryOutcome = { + type: 'git_repository' + git_info: OutcomeGitInfo +} + +export type Outcome = GitRepositoryOutcome + +export type SessionContext = { + sources: SessionContextSource[] + cwd: string + outcomes: Outcome[] | null + custom_system_prompt: string | null + append_system_prompt: string | null + model: string | null + // Seed filesystem with a git bundle on Files API + seed_bundle_file_id?: string + github_pr?: { owner: string; repo: string; number: number } + reuse_outcome_branches?: boolean +} + +export type SessionResource = { + type: 'session' + id: string + title: string | null + session_status: SessionStatus + environment_id: string + created_at: string + updated_at: string + session_context: SessionContext +} + +export type ListSessionsResponse = { + data: SessionResource[] + has_more: boolean + first_id: string | null + last_id: string | null +} + +export const CodeSessionSchema = lazySchema(() => + z.object({ + id: z.string(), + title: z.string(), + description: z.string(), + status: z.enum([ + 'idle', + 'working', + 'waiting', + 'completed', + 'archived', + 'cancelled', + 'rejected', + ]), + repo: z + .object({ + name: z.string(), + owner: z.object({ + login: z.string(), + }), + default_branch: z.string().optional(), + }) + .nullable(), + turns: z.array(z.string()), + created_at: z.string(), + updated_at: z.string(), + }), +) + +// Export the inferred type from the Zod schema +export type CodeSession = z.infer> + +/** + * Validates and prepares for API requests + * @returns Object containing access token and organization UUID + */ +export async function prepareApiRequest(): Promise<{ + accessToken: string + orgUUID: string +}> { + const accessToken = getClaudeAIOAuthTokens()?.accessToken + if (accessToken === undefined) { + throw new Error( + 'Claude Code web sessions require authentication with a Claude.ai account. API key authentication is not sufficient. Please run /login to authenticate, or check your authentication status with /status.', + ) + } + + const orgUUID = await getOrganizationUUID() + if (!orgUUID) { + throw new Error('Unable to get organization UUID') + } + + return { accessToken, orgUUID } +} + +/** + * Fetches code sessions from the new Sessions API (/v1/sessions) + * @returns Array of code sessions + */ +export async function fetchCodeSessionsFromSessionsAPI(): Promise< + CodeSession[] +> { + const { accessToken, orgUUID } = await prepareApiRequest() + + const url = `${getOauthConfig().BASE_API_URL}/v1/sessions` + + try { + const headers = { + ...getOAuthHeaders(accessToken), + 'anthropic-beta': 'ccr-byoc-2025-07-29', + 'x-organization-uuid': orgUUID, + } + + const response = await axiosGetWithRetry(url, { + headers, + }) + + if (response.status !== 200) { + throw new Error(`Failed to fetch code sessions: ${response.statusText}`) + } + + // Transform SessionResource[] to CodeSession[] format + const sessions: CodeSession[] = response.data.data.map(session => { + // Extract repository info from git sources + const gitSource = session.session_context.sources.find( + (source): source is GitSource => source.type === 'git_repository', + ) + + let repo: CodeSession['repo'] = null + if (gitSource?.url) { + // Parse GitHub URL using the existing utility function + const repoPath = parseGitHubRepository(gitSource.url) + if (repoPath) { + const [owner, name] = repoPath.split('/') + if (owner && name) { + repo = { + name, + owner: { + login: owner, + }, + default_branch: gitSource.revision || undefined, + } + } + } + } + + return { + id: session.id, + title: session.title || 'Untitled', + description: '', // SessionResource doesn't have description field + status: session.session_status as CodeSession['status'], // Map session_status to status + repo, + turns: [], // SessionResource doesn't have turns field + created_at: session.created_at, + updated_at: session.updated_at, + } + }) + + return sessions + } catch (error) { + const err = toError(error) + logError(err) + throw error + } +} + +/** + * Creates OAuth headers for API requests + * @param accessToken The OAuth access token + * @returns Headers object with Authorization, Content-Type, and anthropic-version + */ +export function getOAuthHeaders(accessToken: string): Record { + return { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + 'anthropic-version': '2023-06-01', + } +} + +/** + * Fetches a single session by ID from the Sessions API + * @param sessionId The session ID to fetch + * @returns The session resource + */ +export async function fetchSession( + sessionId: string, +): Promise { + const { accessToken, orgUUID } = await prepareApiRequest() + + const url = `${getOauthConfig().BASE_API_URL}/v1/sessions/${sessionId}` + const headers = { + ...getOAuthHeaders(accessToken), + 'anthropic-beta': 'ccr-byoc-2025-07-29', + 'x-organization-uuid': orgUUID, + } + + const response = await axios.get(url, { + headers, + timeout: 15000, + validateStatus: status => status < 500, + }) + + if (response.status !== 200) { + // Extract error message from response if available + const errorData = response.data as { error?: { message?: string } } + const apiMessage = errorData?.error?.message + + if (response.status === 404) { + throw new Error(`Session not found: ${sessionId}`) + } + + if (response.status === 401) { + throw new Error('Session expired. Please run /login to sign in again.') + } + + throw new Error( + apiMessage || + `Failed to fetch session: ${response.status} ${response.statusText}`, + ) + } + + return response.data +} + +/** + * Extracts the first branch name from a session's git repository outcomes + * @param session The session resource to extract from + * @returns The first branch name, or undefined if none found + */ +export function getBranchFromSession( + session: SessionResource, +): string | undefined { + const gitOutcome = session.session_context.outcomes?.find( + (outcome): outcome is GitRepositoryOutcome => + outcome.type === 'git_repository', + ) + return gitOutcome?.git_info?.branches[0] +} + +/** + * Content for a remote session message. + * Accepts a plain string or an array of content blocks (text, image, etc.) + * following the Anthropic API messages spec. + */ +export type RemoteMessageContent = + | string + | Array<{ type: string; [key: string]: unknown }> + +/** + * Sends a user message event to an existing remote session via the Sessions API + * @param sessionId The session ID to send the event to + * @param messageContent The user message content (string or content blocks) + * @param opts.uuid Optional UUID for the event — callers that added a local + * UserMessage first should pass its UUID so echo filtering can dedup + * @returns Promise True if successful, false otherwise + */ +export async function sendEventToRemoteSession( + sessionId: string, + messageContent: RemoteMessageContent, + opts?: { uuid?: string }, +): Promise { + try { + const { accessToken, orgUUID } = await prepareApiRequest() + + const url = `${getOauthConfig().BASE_API_URL}/v1/sessions/${sessionId}/events` + const headers = { + ...getOAuthHeaders(accessToken), + 'anthropic-beta': 'ccr-byoc-2025-07-29', + 'x-organization-uuid': orgUUID, + } + + const userEvent = { + uuid: opts?.uuid ?? randomUUID(), + session_id: sessionId, + type: 'user', + parent_tool_use_id: null, + message: { + role: 'user', + content: messageContent, + }, + } + + const requestBody = { + events: [userEvent], + } + + logForDebugging( + `[sendEventToRemoteSession] Sending event to session ${sessionId}`, + ) + // The endpoint may block until the CCR worker is ready. Observed ~2.6s + // in normal cases; allow a generous margin for cold-start containers. + const response = await axios.post(url, requestBody, { + headers, + validateStatus: status => status < 500, + timeout: 30000, + }) + + if (response.status === 200 || response.status === 201) { + logForDebugging( + `[sendEventToRemoteSession] Successfully sent event to session ${sessionId}`, + ) + return true + } + + logForDebugging( + `[sendEventToRemoteSession] Failed with status ${response.status}: ${jsonStringify(response.data)}`, + ) + return false + } catch (error) { + logForDebugging(`[sendEventToRemoteSession] Error: ${errorMessage(error)}`) + return false + } +} + +/** + * Updates the title of an existing remote session via the Sessions API + * @param sessionId The session ID to update + * @param title The new title for the session + * @returns Promise True if successful, false otherwise + */ +export async function updateSessionTitle( + sessionId: string, + title: string, +): Promise { + try { + const { accessToken, orgUUID } = await prepareApiRequest() + + const url = `${getOauthConfig().BASE_API_URL}/v1/sessions/${sessionId}` + const headers = { + ...getOAuthHeaders(accessToken), + 'anthropic-beta': 'ccr-byoc-2025-07-29', + 'x-organization-uuid': orgUUID, + } + + logForDebugging( + `[updateSessionTitle] Updating title for session ${sessionId}: "${title}"`, + ) + const response = await axios.patch( + url, + { title }, + { + headers, + validateStatus: status => status < 500, + }, + ) + + if (response.status === 200) { + logForDebugging( + `[updateSessionTitle] Successfully updated title for session ${sessionId}`, + ) + return true + } + + logForDebugging( + `[updateSessionTitle] Failed with status ${response.status}: ${jsonStringify(response.data)}`, + ) + return false + } catch (error) { + logForDebugging(`[updateSessionTitle] Error: ${errorMessage(error)}`) + return false + } +} diff --git a/src/utils/teleport/environmentSelection.ts b/src/utils/teleport/environmentSelection.ts new file mode 100644 index 0000000000000000000000000000000000000000..8b1f0dff49fae297ff886dee498a1d484666628f --- /dev/null +++ b/src/utils/teleport/environmentSelection.ts @@ -0,0 +1,77 @@ +import { SETTING_SOURCES, type SettingSource } from '../settings/constants.js' +import { + getSettings_DEPRECATED, + getSettingsForSource, +} from '../settings/settings.js' +import { type EnvironmentResource, fetchEnvironments } from './environments.js' + +export type EnvironmentSelectionInfo = { + availableEnvironments: EnvironmentResource[] + selectedEnvironment: EnvironmentResource | null + selectedEnvironmentSource: SettingSource | null +} + +/** + * Gets information about available environments and the currently selected one. + * + * @returns Promise containing: + * - availableEnvironments: all environments from the API + * - selectedEnvironment: the environment that would be used (based on settings or first available), + * or null if no environments are available + * - selectedEnvironmentSource: the SettingSource where defaultEnvironmentId is configured, + * or null if using the default (first environment) + */ +export async function getEnvironmentSelectionInfo(): Promise { + // Fetch available environments + const environments = await fetchEnvironments() + + if (environments.length === 0) { + return { + availableEnvironments: [], + selectedEnvironment: null, + selectedEnvironmentSource: null, + } + } + + // Get the merged settings to see what would actually be used + const mergedSettings = getSettings_DEPRECATED() + const defaultEnvironmentId = mergedSettings?.remote?.defaultEnvironmentId + + // Find which environment would be selected + let selectedEnvironment: EnvironmentResource = + environments.find(env => env.kind !== 'bridge') ?? environments[0]! + let selectedEnvironmentSource: SettingSource | null = null + + if (defaultEnvironmentId) { + const matchingEnvironment = environments.find( + env => env.environment_id === defaultEnvironmentId, + ) + + if (matchingEnvironment) { + selectedEnvironment = matchingEnvironment + + // Find which source has this setting + // Iterate from lowest to highest priority, so the last match wins (highest priority) + for (let i = SETTING_SOURCES.length - 1; i >= 0; i--) { + const source = SETTING_SOURCES[i] + if (!source || source === 'flagSettings') { + // Skip flagSettings as it's not a normal source we check + continue + } + const sourceSettings = getSettingsForSource(source) + if ( + sourceSettings?.remote?.defaultEnvironmentId === defaultEnvironmentId + ) { + selectedEnvironmentSource = source + break + } + } + } + } + + return { + availableEnvironments: environments, + selectedEnvironment, + selectedEnvironmentSource, + } +} diff --git a/src/utils/teleport/environments.ts b/src/utils/teleport/environments.ts new file mode 100644 index 0000000000000000000000000000000000000000..19242406fb59cd1e2c9975b512a499410dd7bcae --- /dev/null +++ b/src/utils/teleport/environments.ts @@ -0,0 +1,120 @@ +import axios from 'axios' +import { getOauthConfig } from 'src/constants/oauth.js' +import { getOrganizationUUID } from 'src/services/oauth/client.js' +import { getClaudeAIOAuthTokens } from '../auth.js' +import { toError } from '../errors.js' +import { logError } from '../log.js' +import { getOAuthHeaders } from './api.js' + +export type EnvironmentKind = 'anthropic_cloud' | 'byoc' | 'bridge' +export type EnvironmentState = 'active' + +export type EnvironmentResource = { + kind: EnvironmentKind + environment_id: string + name: string + created_at: string + state: EnvironmentState +} + +export type EnvironmentListResponse = { + environments: EnvironmentResource[] + has_more: boolean + first_id: string | null + last_id: string | null +} + +/** + * Fetches the list of available environments from the Environment API + * @returns Promise Array of available environments + * @throws Error if the API request fails or no access token is available + */ +export async function fetchEnvironments(): Promise { + const accessToken = getClaudeAIOAuthTokens()?.accessToken + if (!accessToken) { + throw new Error( + 'Claude Code web sessions require authentication with a Claude.ai account. API key authentication is not sufficient. Please run /login to authenticate, or check your authentication status with /status.', + ) + } + + const orgUUID = await getOrganizationUUID() + if (!orgUUID) { + throw new Error('Unable to get organization UUID') + } + + const url = `${getOauthConfig().BASE_API_URL}/v1/environment_providers` + + try { + const headers = { + ...getOAuthHeaders(accessToken), + 'x-organization-uuid': orgUUID, + } + + const response = await axios.get(url, { + headers, + timeout: 15000, + }) + + if (response.status !== 200) { + throw new Error( + `Failed to fetch environments: ${response.status} ${response.statusText}`, + ) + } + + return response.data.environments + } catch (error) { + const err = toError(error) + logError(err) + throw new Error(`Failed to fetch environments: ${err.message}`) + } +} + +/** + * Creates a default anthropic_cloud environment for users who have none. + * Uses the public environment_providers route (same auth as fetchEnvironments). + */ +export async function createDefaultCloudEnvironment( + name: string, +): Promise { + const accessToken = getClaudeAIOAuthTokens()?.accessToken + if (!accessToken) { + throw new Error('No access token available') + } + const orgUUID = await getOrganizationUUID() + if (!orgUUID) { + throw new Error('Unable to get organization UUID') + } + + const url = `${getOauthConfig().BASE_API_URL}/v1/environment_providers/cloud/create` + const response = await axios.post( + url, + { + name, + kind: 'anthropic_cloud', + description: '', + config: { + environment_type: 'anthropic', + cwd: '/home/user', + init_script: null, + environment: {}, + languages: [ + { name: 'python', version: '3.11' }, + { name: 'node', version: '20' }, + ], + network_config: { + allowed_hosts: [], + allow_default_hosts: true, + }, + }, + }, + { + headers: { + ...getOAuthHeaders(accessToken), + 'anthropic-beta': 'ccr-byoc-2025-07-29', + 'x-organization-uuid': orgUUID, + }, + timeout: 15000, + }, + ) + return response.data +} diff --git a/src/utils/teleport/gitBundle.ts b/src/utils/teleport/gitBundle.ts new file mode 100644 index 0000000000000000000000000000000000000000..0e53c5dc4cdf4ddd1d10e54ca6b18d1e8baa264c --- /dev/null +++ b/src/utils/teleport/gitBundle.ts @@ -0,0 +1,292 @@ +/** + * Git bundle creation + upload for CCR seed-bundle seeding. + * + * Flow: + * 1. git stash create → update-ref refs/seed/stash (makes it reachable) + * 2. git bundle create --all (packs refs/seed/stash + its objects) + * 3. Upload to /v1/files + * 4. Cleanup refs/seed/stash (don't pollute user's repo) + * 5. Caller sets seed_bundle_file_id on SessionContext + */ + +import { stat, unlink } from 'fs/promises' +import { + type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + logEvent, +} from 'src/services/analytics/index.js' +import { getFeatureValue_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js' +import { type FilesApiConfig, uploadFile } from '../../services/api/filesApi.js' +import { getCwd } from '../cwd.js' +import { logForDebugging } from '../debug.js' +import { execFileNoThrowWithCwd } from '../execFileNoThrow.js' +import { findGitRoot, gitExe } from '../git.js' +import { generateTempFilePath } from '../tempfile.js' + +// Tunable via tengu_ccr_bundle_max_bytes. +const DEFAULT_BUNDLE_MAX_BYTES = 100 * 1024 * 1024 + +type BundleScope = 'all' | 'head' | 'squashed' + +export type BundleUploadResult = + | { + success: true + fileId: string + bundleSizeBytes: number + scope: BundleScope + hasWip: boolean + } + | { success: false; error: string; failReason?: BundleFailReason } + +type BundleFailReason = 'git_error' | 'too_large' | 'empty_repo' + +type BundleCreateResult = + | { ok: true; size: number; scope: BundleScope } + | { ok: false; error: string; failReason: BundleFailReason } + +// Bundle --all → HEAD → squashed-root. HEAD drops side branches/tags but +// keeps full current-branch history. Squashed-root is a single parentless +// commit of HEAD's tree (or the stash tree if WIP exists) — no history, +// just the snapshot. Receiver needs refs/seed/root handling for that tier. +async function _bundleWithFallback( + gitRoot: string, + bundlePath: string, + maxBytes: number, + hasStash: boolean, + signal: AbortSignal | undefined, +): Promise { + // --all picks up refs/seed/stash; HEAD needs it explicit. + const extra = hasStash ? ['refs/seed/stash'] : [] + const mkBundle = (base: string) => + execFileNoThrowWithCwd( + gitExe(), + ['bundle', 'create', bundlePath, base, ...extra], + { cwd: gitRoot, abortSignal: signal }, + ) + + const allResult = await mkBundle('--all') + if (allResult.code !== 0) { + return { + ok: false, + error: `git bundle create --all failed (${allResult.code}): ${allResult.stderr.slice(0, 200)}`, + failReason: 'git_error', + } + } + + const { size: allSize } = await stat(bundlePath) + if (allSize <= maxBytes) { + return { ok: true, size: allSize, scope: 'all' } + } + + // bundle create overwrites in place. + logForDebugging( + `[gitBundle] --all bundle is ${(allSize / 1024 / 1024).toFixed(1)}MB (> ${(maxBytes / 1024 / 1024).toFixed(0)}MB), retrying HEAD-only`, + ) + const headResult = await mkBundle('HEAD') + if (headResult.code !== 0) { + return { + ok: false, + error: `git bundle create HEAD failed (${headResult.code}): ${headResult.stderr.slice(0, 200)}`, + failReason: 'git_error', + } + } + + const { size: headSize } = await stat(bundlePath) + if (headSize <= maxBytes) { + return { ok: true, size: headSize, scope: 'head' } + } + + // Last resort: squash to a single parentless commit. Uses the stash tree + // when WIP exists (bakes uncommitted changes in — can't bundle the stash + // ref separately since its parents would drag history back). + logForDebugging( + `[gitBundle] HEAD bundle is ${(headSize / 1024 / 1024).toFixed(1)}MB, retrying squashed-root`, + ) + const treeRef = hasStash ? 'refs/seed/stash^{tree}' : 'HEAD^{tree}' + const commitTree = await execFileNoThrowWithCwd( + gitExe(), + ['commit-tree', treeRef, '-m', 'seed'], + { cwd: gitRoot, abortSignal: signal }, + ) + if (commitTree.code !== 0) { + return { + ok: false, + error: `git commit-tree failed (${commitTree.code}): ${commitTree.stderr.slice(0, 200)}`, + failReason: 'git_error', + } + } + const squashedSha = commitTree.stdout.trim() + await execFileNoThrowWithCwd( + gitExe(), + ['update-ref', 'refs/seed/root', squashedSha], + { cwd: gitRoot }, + ) + const squashResult = await execFileNoThrowWithCwd( + gitExe(), + ['bundle', 'create', bundlePath, 'refs/seed/root'], + { cwd: gitRoot, abortSignal: signal }, + ) + if (squashResult.code !== 0) { + return { + ok: false, + error: `git bundle create refs/seed/root failed (${squashResult.code}): ${squashResult.stderr.slice(0, 200)}`, + failReason: 'git_error', + } + } + const { size: squashSize } = await stat(bundlePath) + if (squashSize <= maxBytes) { + return { ok: true, size: squashSize, scope: 'squashed' } + } + + return { + ok: false, + error: + 'Repo is too large to bundle. Please setup GitHub on https://claude.ai/code', + failReason: 'too_large', + } +} + +// Bundle the repo and upload to Files API; return file_id for +// seed_bundle_file_id. --all → HEAD → squashed-root fallback chain. +// Tracked WIP via stash create → refs/seed/stash (or baked into the +// squashed tree); untracked not captured. +export async function createAndUploadGitBundle( + config: FilesApiConfig, + opts?: { cwd?: string; signal?: AbortSignal }, +): Promise { + const workdir = opts?.cwd ?? getCwd() + const gitRoot = findGitRoot(workdir) + if (!gitRoot) { + return { success: false, error: 'Not in a git repository' } + } + + // Sweep stale refs from a crashed prior run before --all bundles them. + // Runs before the empty-repo check so it's never skipped by an early return. + for (const ref of ['refs/seed/stash', 'refs/seed/root']) { + await execFileNoThrowWithCwd(gitExe(), ['update-ref', '-d', ref], { + cwd: gitRoot, + }) + } + + // `git bundle create` refuses to create an empty bundle (exit 128), and + // `stash create` fails with "You do not have the initial commit yet". + // Check for any refs (not just HEAD) so orphan branches with commits + // elsewhere still bundle — `--all` packs those refs regardless of HEAD. + const refCheck = await execFileNoThrowWithCwd( + gitExe(), + ['for-each-ref', '--count=1', 'refs/'], + { cwd: gitRoot }, + ) + if (refCheck.code === 0 && refCheck.stdout.trim() === '') { + logEvent('tengu_ccr_bundle_upload', { + outcome: + 'empty_repo' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + }) + return { + success: false, + error: 'Repository has no commits yet', + failReason: 'empty_repo', + } + } + + // stash create writes a dangling commit — doesn't touch refs/stash or + // the working tree. Untracked files intentionally excluded. + const stashResult = await execFileNoThrowWithCwd( + gitExe(), + ['stash', 'create'], + { cwd: gitRoot, abortSignal: opts?.signal }, + ) + // exit 0 + empty stdout = nothing to stash. Nonzero is rare; non-fatal. + const wipStashSha = stashResult.code === 0 ? stashResult.stdout.trim() : '' + const hasWip = wipStashSha !== '' + if (stashResult.code !== 0) { + logForDebugging( + `[gitBundle] git stash create failed (${stashResult.code}), proceeding without WIP: ${stashResult.stderr.slice(0, 200)}`, + ) + } else if (hasWip) { + logForDebugging(`[gitBundle] Captured WIP as stash ${wipStashSha}`) + // env-runner reads the SHA via bundle list-heads refs/seed/stash. + await execFileNoThrowWithCwd( + gitExe(), + ['update-ref', 'refs/seed/stash', wipStashSha], + { cwd: gitRoot }, + ) + } + + const bundlePath = generateTempFilePath('ccr-seed', '.bundle') + + // git leaves a partial file on nonzero exit (e.g. empty-repo 128). + try { + const maxBytes = + getFeatureValue_CACHED_MAY_BE_STALE( + 'tengu_ccr_bundle_max_bytes', + null, + ) ?? DEFAULT_BUNDLE_MAX_BYTES + + const bundle = await _bundleWithFallback( + gitRoot, + bundlePath, + maxBytes, + hasWip, + opts?.signal, + ) + + if (!bundle.ok) { + logForDebugging(`[gitBundle] ${bundle.error}`) + logEvent('tengu_ccr_bundle_upload', { + outcome: + bundle.failReason as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + max_bytes: maxBytes, + }) + return { + success: false, + error: bundle.error, + failReason: bundle.failReason, + } + } + + // Fixed relativePath so CCR can locate it. + const upload = await uploadFile(bundlePath, '_source_seed.bundle', config, { + signal: opts?.signal, + }) + + if (!upload.success) { + logEvent('tengu_ccr_bundle_upload', { + outcome: + 'failed' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + }) + return { success: false, error: upload.error } + } + + logForDebugging( + `[gitBundle] Uploaded ${upload.size} bytes as file_id ${upload.fileId}`, + ) + logEvent('tengu_ccr_bundle_upload', { + outcome: + 'success' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + size_bytes: upload.size, + scope: + bundle.scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, + has_wip: hasWip, + }) + return { + success: true, + fileId: upload.fileId, + bundleSizeBytes: upload.size, + scope: bundle.scope, + hasWip, + } + } finally { + try { + await unlink(bundlePath) + } catch { + logForDebugging(`[gitBundle] Could not delete ${bundlePath} (non-fatal)`) + } + // Always delete — also sweeps a stale ref from a crashed prior run. + // update-ref -d on a missing ref exits 0. + for (const ref of ['refs/seed/stash', 'refs/seed/root']) { + await execFileNoThrowWithCwd(gitExe(), ['update-ref', '-d', ref], { + cwd: gitRoot, + }) + } + } +} diff --git a/src/utils/tmuxSocket.ts b/src/utils/tmuxSocket.ts new file mode 100644 index 0000000000000000000000000000000000000000..510720a005c1f76bdceae3d4802895f9c363bcc9 --- /dev/null +++ b/src/utils/tmuxSocket.ts @@ -0,0 +1,427 @@ +/** + * TMUX SOCKET ISOLATION + * ===================== + * This module manages an isolated tmux socket for Claude's operations. + * + * WHY THIS EXISTS: + * Without isolation, Claude could accidentally affect the user's tmux sessions. + * For example, running `tmux kill-session` via the Bash tool would kill the + * user's current session if they started Claude from within tmux. + * + * HOW IT WORKS: + * 1. Claude creates its own tmux socket: `claude-` (e.g., `claude-12345`) + * 2. ALL Tmux tool commands use this socket via the `-L` flag + * 3. ALL Bash tool commands inherit TMUX env var pointing to this socket + * (set in Shell.ts via getClaudeTmuxEnv()) + * + * This means ANY tmux command run through Claude - whether via the Tmux tool + * directly or via Bash - will operate on Claude's isolated socket, NOT the + * user's tmux session. + * + * IMPORTANT: The user's original TMUX env var is NOT used. After socket + * initialization, getClaudeTmuxEnv() returns a value that overrides the + * user's TMUX in all child processes spawned by Shell.ts. + */ + +import { posix } from 'path' +import { registerCleanup } from './cleanupRegistry.js' +import { logForDebugging } from './debug.js' +import { toError } from './errors.js' +import { execFileNoThrow } from './execFileNoThrow.js' +import { logError } from './log.js' +import { getPlatform } from './platform.js' + +// Constants for tmux socket management +const TMUX_COMMAND = 'tmux' +const CLAUDE_SOCKET_PREFIX = 'claude' + +/** + * Executes a tmux command, routing through WSL on Windows. + * On Windows, tmux only exists inside WSL — WSL interop lets the tmux session + * launch .exe files as native Win32 processes while stdin/stdout flow through + * the WSL pty. + */ +async function execTmux( + args: string[], + opts?: { useCwd?: boolean }, +): Promise<{ stdout: string; stderr: string; code: number }> { + if (getPlatform() === 'windows') { + // -e execs tmux directly without the login shell. Without it, wsl hands the + // command line to bash which eats `#` as a comment: `display-message -p + // #{socket_path},#{pid}` below becomes `display-message -p ` → exit 1 → + // we silently fall back to the guessed path and never learn the real + // server PID. Same root cause as TungstenTool/utils.ts:execTmuxCommand. + const result = await execFileNoThrow('wsl', ['-e', TMUX_COMMAND, ...args], { + env: { ...process.env, WSL_UTF8: '1' }, + ...opts, + }) + return { + stdout: result.stdout || '', + stderr: result.stderr || '', + code: result.code || 0, + } + } + const result = await execFileNoThrow(TMUX_COMMAND, args, opts) + return { + stdout: result.stdout || '', + stderr: result.stderr || '', + code: result.code || 0, + } +} + +// Socket state - initialized lazily when Tmux tool is first used or a tmux command is run +let socketName: string | null = null +let socketPath: string | null = null +let serverPid: number | null = null +let isInitializing = false +let initPromise: Promise | null = null + +// tmux availability - checked once upfront +let tmuxAvailabilityChecked = false +let tmuxAvailable = false + +// Track whether the Tmux tool has been used at least once +// Used to defer socket initialization until actually needed +let tmuxToolUsed = false + +/** + * Gets the socket name for Claude's isolated tmux session. + * Format: claude- + */ +export function getClaudeSocketName(): string { + if (!socketName) { + socketName = `${CLAUDE_SOCKET_PREFIX}-${process.pid}` + } + return socketName +} + +/** + * Gets the socket path if the socket has been initialized. + * Returns null if not yet initialized. + */ +export function getClaudeSocketPath(): string | null { + return socketPath +} + +/** + * Sets socket info after initialization. + * Called after the tmux session is created. + */ +export function setClaudeSocketInfo(path: string, pid: number): void { + socketPath = path + serverPid = pid +} + +/** + * Returns whether the socket has been initialized. + */ +export function isSocketInitialized(): boolean { + return socketPath !== null && serverPid !== null +} + +/** + * Gets the TMUX environment variable value for Claude's isolated socket. + * + * CRITICAL: This value is used by Shell.ts to override the TMUX env var + * in ALL child processes. This ensures that any `tmux` command run via + * the Bash tool will operate on Claude's socket, NOT the user's session. + * + * Format: "socket_path,server_pid,pane_index" (matches tmux's TMUX env var) + * Example: "/tmp/tmux-501/claude-12345,54321,0" + * + * Returns null if socket is not yet initialized. + * When null, Shell.ts does not override TMUX, preserving user's environment. + */ +export function getClaudeTmuxEnv(): string | null { + if (!socketPath || serverPid === null) { + return null + } + return `${socketPath},${serverPid},0` +} + +/** + * Checks if tmux is available on this system. + * This is checked once and cached for the lifetime of the process. + * + * When tmux is not available: + * - TungstenTool (Tmux) will not work + * - TeammateTool will not work (it uses tmux for pane management) + * - Bash commands will run without tmux isolation + */ +export async function checkTmuxAvailable(): Promise { + if (!tmuxAvailabilityChecked) { + const result = + getPlatform() === 'windows' + ? await execFileNoThrow('wsl', ['-e', TMUX_COMMAND, '-V'], { + env: { ...process.env, WSL_UTF8: '1' }, + useCwd: false, + }) + : await execFileNoThrow('which', [TMUX_COMMAND], { + useCwd: false, + }) + tmuxAvailable = result.code === 0 + if (!tmuxAvailable) { + logForDebugging( + `[Socket] tmux is not installed. The Tmux tool and Teammate tool will not be available.`, + ) + } + tmuxAvailabilityChecked = true + } + return tmuxAvailable +} + +/** + * Returns the cached tmux availability status. + * Returns false if availability hasn't been checked yet. + * Use checkTmuxAvailable() to perform the check. + */ +export function isTmuxAvailable(): boolean { + return tmuxAvailabilityChecked && tmuxAvailable +} + +/** + * Marks that the Tmux tool has been used at least once. + * Called by TungstenTool before initialization. + * After this is called, Shell.ts will initialize the socket for subsequent Bash commands. + */ +export function markTmuxToolUsed(): void { + tmuxToolUsed = true +} + +/** + * Returns whether the Tmux tool has been used at least once. + * Used by Shell.ts to decide whether to initialize the socket. + */ +export function hasTmuxToolBeenUsed(): boolean { + return tmuxToolUsed +} + +/** + * Ensures the socket is initialized with a tmux session. + * Called by Shell.ts when the Tmux tool has been used or the command includes "tmux". + * Safe to call multiple times; will only initialize once. + * + * If tmux is not installed, this function returns gracefully without + * initializing the socket. getClaudeTmuxEnv() will return null, and + * Bash commands will run without tmux isolation. + */ +export async function ensureSocketInitialized(): Promise { + // Already initialized + if (isSocketInitialized()) { + return + } + + // Check if tmux is available before trying to use it + const available = await checkTmuxAvailable() + if (!available) { + return + } + + // Another call is already initializing - wait for it but don't propagate errors + // The original caller handles the error and sets up graceful degradation + if (isInitializing && initPromise) { + try { + await initPromise + } catch { + // Ignore - the original caller logs the error + } + return + } + + isInitializing = true + initPromise = doInitialize() + + try { + await initPromise + } catch (error) { + // Log error but don't throw - graceful degradation + const err = toError(error) + logError(err) + logForDebugging( + `[Socket] Failed to initialize tmux socket: ${err.message}. Tmux isolation will be disabled.`, + ) + } finally { + isInitializing = false + } +} + +/** + * Kills the tmux server for Claude's isolated socket. + * Called during graceful shutdown to clean up resources. + */ +async function killTmuxServer(): Promise { + const socket = getClaudeSocketName() + logForDebugging(`[Socket] Killing tmux server for socket: ${socket}`) + + const result = await execTmux(['-L', socket, 'kill-server']) + + if (result.code === 0) { + logForDebugging(`[Socket] Successfully killed tmux server`) + } else { + // Server may already be dead, which is fine + logForDebugging( + `[Socket] Failed to kill tmux server (exit ${result.code}): ${result.stderr}`, + ) + } +} + +async function doInitialize(): Promise { + const socket = getClaudeSocketName() + + // Create a new session with our custom socket + // Pass CLAUDE_CODE_SKIP_PROMPT_HISTORY via -e so it's set in the initial shell environment + // + // On Windows, the tmux server inherits WSL_INTEROP from the short-lived + // wsl.exe that spawns it; once `new-session -d` detaches and wsl.exe exits, + // that socket stops servicing requests. Any cli.exe launched inside the pane + // then hits `UtilAcceptVsock: accept4 failed 110` (ETIMEDOUT). Observed on + // 2026-03-25: server PID 386 (started alongside /init at WSL boot) inherited + // /run/WSL/383_interop — init's own socket, which listens but doesn't handle + // interop. /run/WSL/1_interop is a stable symlink WSL maintains to the real + // handler; pin the server to it so interop survives the spawning wsl.exe. + const result = await execTmux([ + '-L', + socket, + 'new-session', + '-d', + '-s', + 'base', + '-e', + 'CLAUDE_CODE_SKIP_PROMPT_HISTORY=true', + ...(getPlatform() === 'windows' + ? ['-e', 'WSL_INTEROP=/run/WSL/1_interop'] + : []), + ]) + + if (result.code !== 0) { + // Session might already exist from a previous run with same PID (unlikely but possible) + // Check if the session exists + const checkResult = await execTmux([ + '-L', + socket, + 'has-session', + '-t', + 'base', + ]) + if (checkResult.code !== 0) { + throw new Error( + `Failed to create tmux session on socket ${socket}: ${result.stderr}`, + ) + } + } + + // Register cleanup to kill the tmux server on exit + registerCleanup(killTmuxServer) + + // Set CLAUDE_CODE_SKIP_PROMPT_HISTORY in the tmux GLOBAL environment (-g). + // Without -g this would only apply to the 'base' session, and new sessions + // created by TungstenTool (e.g. 'test', 'verify') would not inherit it. + // Any Claude Code instance spawned on this socket will inherit this env var, + // preventing test/verification sessions from polluting the user's real + // command history and --resume session list. + await execTmux([ + '-L', + socket, + 'set-environment', + '-g', + 'CLAUDE_CODE_SKIP_PROMPT_HISTORY', + 'true', + ]) + + // Same WSL_INTEROP pin as the new-session -e above, but in the GLOBAL env + // so sessions created by TungstenTool inherit it too. The -e on new-session + // only covers the base session's initial shell; a later `new-session -s cc` + // inherits the SERVER's env, which still holds the stale socket from the + // wsl.exe that spawned it. + if (getPlatform() === 'windows') { + await execTmux([ + '-L', + socket, + 'set-environment', + '-g', + 'WSL_INTEROP', + '/run/WSL/1_interop', + ]) + } + + // Get the socket path and server PID + const infoResult = await execTmux([ + '-L', + socket, + 'display-message', + '-p', + '#{socket_path},#{pid}', + ]) + + if (infoResult.code === 0) { + const [path, pidStr] = infoResult.stdout.trim().split(',') + if (path && pidStr) { + const pid = parseInt(pidStr, 10) + if (!isNaN(pid)) { + setClaudeSocketInfo(path, pid) + return + } + } + // Parsing failed - log and fall through to fallback + logForDebugging( + `[Socket] Failed to parse socket info from tmux output: "${infoResult.stdout.trim()}". Using fallback path.`, + ) + } else { + // Command failed - log and fall through to fallback + logForDebugging( + `[Socket] Failed to get socket info via display-message (exit ${infoResult.code}): ${infoResult.stderr}. Using fallback path.`, + ) + } + + // Fallback: construct the socket path from standard tmux location + // tmux sockets are typically at $TMPDIR/tmux-/ (or /tmp/tmux-/ if TMPDIR is not set) + // On Windows this path is inside WSL, so always use POSIX separators. + // process.getuid() is undefined on Windows; WSL default user is root (uid 0) in CI. + const uid = process.getuid?.() ?? 0 + const baseTmpDir = process.env.TMPDIR || '/tmp' + const fallbackPath = posix.join(baseTmpDir, `tmux-${uid}`, socket) + + // Get server PID separately + const pidResult = await execTmux([ + '-L', + socket, + 'display-message', + '-p', + '#{pid}', + ]) + + if (pidResult.code === 0) { + const pid = parseInt(pidResult.stdout.trim(), 10) + if (!isNaN(pid)) { + logForDebugging( + `[Socket] Using fallback socket path: ${fallbackPath} (server PID: ${pid})`, + ) + setClaudeSocketInfo(fallbackPath, pid) + return + } + // PID parsing failed + logForDebugging( + `[Socket] Failed to parse server PID from tmux output: "${pidResult.stdout.trim()}"`, + ) + } else { + logForDebugging( + `[Socket] Failed to get server PID (exit ${pidResult.code}): ${pidResult.stderr}`, + ) + } + + throw new Error( + `Failed to get socket info for ${socket}: primary="${infoResult.stderr}", fallback="${pidResult.stderr}"`, + ) +} + +// For testing purposes +export function resetSocketState(): void { + socketName = null + socketPath = null + serverPid = null + isInitializing = false + initPromise = null + tmuxAvailabilityChecked = false + tmuxAvailable = false + tmuxToolUsed = false +} diff --git a/src/utils/todo/types.ts b/src/utils/todo/types.ts new file mode 100644 index 0000000000000000000000000000000000000000..639435456a5707699f3f8057723e319ea32830fa --- /dev/null +++ b/src/utils/todo/types.ts @@ -0,0 +1,18 @@ +import { z } from 'zod/v4' +import { lazySchema } from '../lazySchema.js' + +const TodoStatusSchema = lazySchema(() => + z.enum(['pending', 'in_progress', 'completed']), +) + +export const TodoItemSchema = lazySchema(() => + z.object({ + content: z.string().min(1, 'Content cannot be empty'), + status: TodoStatusSchema(), + activeForm: z.string().min(1, 'Active form cannot be empty'), + }), +) +export type TodoItem = z.infer> + +export const TodoListSchema = lazySchema(() => z.array(TodoItemSchema())) +export type TodoList = z.infer> diff --git a/src/utils/tokenBudget.ts b/src/utils/tokenBudget.ts new file mode 100644 index 0000000000000000000000000000000000000000..058c5351aae287a84865332fc4dbe6ca1f9827f9 --- /dev/null +++ b/src/utils/tokenBudget.ts @@ -0,0 +1,73 @@ +// Shorthand (+500k) anchored to start/end to avoid false positives in natural language. +// Verbose (use/spend 2M tokens) matches anywhere. +const SHORTHAND_START_RE = /^\s*\+(\d+(?:\.\d+)?)\s*(k|m|b)\b/i +// Lookbehind (?<=\s) is avoided — it defeats YARR JIT in JSC, and the +// interpreter scans O(n) even with the $ anchor. Capture the whitespace +// instead; callers offset match.index by 1 where position matters. +const SHORTHAND_END_RE = /\s\+(\d+(?:\.\d+)?)\s*(k|m|b)\s*[.!?]?\s*$/i +const VERBOSE_RE = /\b(?:use|spend)\s+(\d+(?:\.\d+)?)\s*(k|m|b)\s*tokens?\b/i +const VERBOSE_RE_G = new RegExp(VERBOSE_RE.source, 'gi') + +const MULTIPLIERS: Record = { + k: 1_000, + m: 1_000_000, + b: 1_000_000_000, +} + +function parseBudgetMatch(value: string, suffix: string): number { + return parseFloat(value) * MULTIPLIERS[suffix.toLowerCase()]! +} + +export function parseTokenBudget(text: string): number | null { + const startMatch = text.match(SHORTHAND_START_RE) + if (startMatch) return parseBudgetMatch(startMatch[1]!, startMatch[2]!) + const endMatch = text.match(SHORTHAND_END_RE) + if (endMatch) return parseBudgetMatch(endMatch[1]!, endMatch[2]!) + const verboseMatch = text.match(VERBOSE_RE) + if (verboseMatch) return parseBudgetMatch(verboseMatch[1]!, verboseMatch[2]!) + return null +} + +export function findTokenBudgetPositions( + text: string, +): Array<{ start: number; end: number }> { + const positions: Array<{ start: number; end: number }> = [] + const startMatch = text.match(SHORTHAND_START_RE) + if (startMatch) { + const offset = + startMatch.index! + + startMatch[0].length - + startMatch[0].trimStart().length + positions.push({ + start: offset, + end: startMatch.index! + startMatch[0].length, + }) + } + const endMatch = text.match(SHORTHAND_END_RE) + if (endMatch) { + // Avoid double-counting when input is just "+500k" + const endStart = endMatch.index! + 1 // +1: regex includes leading \s + const alreadyCovered = positions.some( + p => endStart >= p.start && endStart < p.end, + ) + if (!alreadyCovered) { + positions.push({ + start: endStart, + end: endMatch.index! + endMatch[0].length, + }) + } + } + for (const match of text.matchAll(VERBOSE_RE_G)) { + positions.push({ start: match.index, end: match.index + match[0].length }) + } + return positions +} + +export function getBudgetContinuationMessage( + pct: number, + turnTokens: number, + budget: number, +): string { + const fmt = (n: number): string => new Intl.NumberFormat('en-US').format(n) + return `Stopped at ${pct}% of token target (${fmt(turnTokens)} / ${fmt(budget)}). Keep working \u2014 do not summarize.` +} diff --git a/src/utils/tokens.ts b/src/utils/tokens.ts new file mode 100644 index 0000000000000000000000000000000000000000..c56da552ee0802590f93891249cbe445a5ab8326 --- /dev/null +++ b/src/utils/tokens.ts @@ -0,0 +1,261 @@ +import type { BetaUsage as Usage } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs' +import { roughTokenCountEstimationForMessages } from '../services/tokenEstimation.js' +import type { AssistantMessage, Message } from '../types/message.js' +import { SYNTHETIC_MESSAGES, SYNTHETIC_MODEL } from './messages.js' +import { jsonStringify } from './slowOperations.js' + +export function getTokenUsage(message: Message): Usage | undefined { + if ( + message?.type === 'assistant' && + 'usage' in message.message && + !( + message.message.content[0]?.type === 'text' && + SYNTHETIC_MESSAGES.has(message.message.content[0].text) + ) && + message.message.model !== SYNTHETIC_MODEL + ) { + return message.message.usage + } + return undefined +} + +/** + * Get the API response id for an assistant message with real (non-synthetic) usage. + * Used to identify split assistant records that came from the same API response — + * when parallel tool calls are streamed, each content block becomes a separate + * AssistantMessage record, but they all share the same message.id. + */ +function getAssistantMessageId(message: Message): string | undefined { + if ( + message?.type === 'assistant' && + 'id' in message.message && + message.message.model !== SYNTHETIC_MODEL + ) { + return message.message.id + } + return undefined +} + +/** + * Calculate total context window tokens from an API response's usage data. + * Includes input_tokens + cache tokens + output_tokens. + * + * This represents the full context size at the time of that API call. + * Use tokenCountWithEstimation() when you need context size from messages. + */ +export function getTokenCountFromUsage(usage: Usage): number { + return ( + usage.input_tokens + + (usage.cache_creation_input_tokens ?? 0) + + (usage.cache_read_input_tokens ?? 0) + + usage.output_tokens + ) +} + +export function tokenCountFromLastAPIResponse(messages: Message[]): number { + let i = messages.length - 1 + while (i >= 0) { + const message = messages[i] + const usage = message ? getTokenUsage(message) : undefined + if (usage) { + return getTokenCountFromUsage(usage) + } + i-- + } + return 0 +} + +/** + * Final context window size from the last API response's usage.iterations[-1]. + * Used for task_budget.remaining computation across compaction boundaries — + * the server's budget countdown is context-based, so remaining decrements by + * the pre-compact final window, not billing spend. See monorepo + * api/api/sampling/prompt/renderer.py:292 for the server-side computation. + * + * Falls back to top-level input_tokens + output_tokens when iterations is + * absent (no server-side tool loops, so top-level usage IS the final window). + * Both paths exclude cache tokens to match #304930's formula. + */ +export function finalContextTokensFromLastResponse( + messages: Message[], +): number { + let i = messages.length - 1 + while (i >= 0) { + const message = messages[i] + const usage = message ? getTokenUsage(message) : undefined + if (usage) { + // Stainless types don't include iterations yet — cast like advisor.ts:43 + const iterations = ( + usage as { + iterations?: Array<{ + input_tokens: number + output_tokens: number + }> | null + } + ).iterations + if (iterations && iterations.length > 0) { + const last = iterations.at(-1)! + return last.input_tokens + last.output_tokens + } + // No iterations → no server tool loop → top-level usage IS the final + // window. Match the iterations path's formula (input + output, no cache) + // rather than getTokenCountFromUsage — #304930 defines final window as + // non-cache input + output. Whether the server's budget countdown + // (renderer.py:292 calculate_context_tokens) counts cache the same way + // is an open question; aligning with the iterations path keeps the two + // branches consistent until that's resolved. + return usage.input_tokens + usage.output_tokens + } + i-- + } + return 0 +} + +/** + * Get only the output_tokens from the last API response. + * This excludes input context (system prompt, tools, prior messages). + * + * WARNING: Do NOT use this for threshold comparisons (autocompact, session memory). + * Use tokenCountWithEstimation() instead, which measures full context size. + * This function is only useful for measuring how many tokens Claude generated + * in a single response, not how full the context window is. + */ +export function messageTokenCountFromLastAPIResponse( + messages: Message[], +): number { + let i = messages.length - 1 + while (i >= 0) { + const message = messages[i] + const usage = message ? getTokenUsage(message) : undefined + if (usage) { + return usage.output_tokens + } + i-- + } + return 0 +} + +export function getCurrentUsage(messages: Message[]): { + input_tokens: number + output_tokens: number + cache_creation_input_tokens: number + cache_read_input_tokens: number +} | null { + for (let i = messages.length - 1; i >= 0; i--) { + const message = messages[i] + const usage = message ? getTokenUsage(message) : undefined + if (usage) { + return { + input_tokens: usage.input_tokens, + output_tokens: usage.output_tokens, + cache_creation_input_tokens: usage.cache_creation_input_tokens ?? 0, + cache_read_input_tokens: usage.cache_read_input_tokens ?? 0, + } + } + } + return null +} + +export function doesMostRecentAssistantMessageExceed200k( + messages: Message[], +): boolean { + const THRESHOLD = 200_000 + + const lastAsst = messages.findLast(m => m.type === 'assistant') + if (!lastAsst) return false + const usage = getTokenUsage(lastAsst) + return usage ? getTokenCountFromUsage(usage) > THRESHOLD : false +} + +/** + * Calculate the character content length of an assistant message. + * Used for spinner token estimation (characters / 4 ≈ tokens). + * This is used when subagent streaming events are filtered out and we + * need to count content from completed messages instead. + * + * Counts the same content that handleMessageFromStream would count via deltas: + * - text (text_delta) + * - thinking (thinking_delta) + * - redacted_thinking data + * - tool_use input (input_json_delta) + * Note: signature_delta is excluded from streaming counts (not model output). + */ +export function getAssistantMessageContentLength( + message: AssistantMessage, +): number { + let contentLength = 0 + for (const block of message.message.content) { + if (block.type === 'text') { + contentLength += block.text.length + } else if (block.type === 'thinking') { + contentLength += block.thinking.length + } else if (block.type === 'redacted_thinking') { + contentLength += block.data.length + } else if (block.type === 'tool_use') { + contentLength += jsonStringify(block.input).length + } + } + return contentLength +} + +/** + * Get the current context window size in tokens. + * + * This is the CANONICAL function for measuring context size when checking + * thresholds (autocompact, session memory init, etc.). Uses the last API + * response's token count (input + output + cache) plus estimates for any + * messages added since. + * + * Always use this instead of: + * - Cumulative token counting (which double-counts as context grows) + * - messageTokenCountFromLastAPIResponse (which only counts output_tokens) + * - tokenCountFromLastAPIResponse (which doesn't estimate new messages) + * + * Implementation note on parallel tool calls: when the model makes multiple + * tool calls in one response, the streaming code emits a SEPARATE assistant + * record per content block (all sharing the same message.id and usage), and + * the query loop interleaves each tool_result immediately after its tool_use. + * So the messages array looks like: + * [..., assistant(id=A), user(result), assistant(id=A), user(result), ...] + * If we stop at the LAST assistant record, we only estimate the one tool_result + * after it and miss all the earlier interleaved tool_results — which will ALL + * be in the next API request. To avoid undercounting, after finding a usage- + * bearing record we walk back to the FIRST sibling with the same message.id + * so every interleaved tool_result is included in the rough estimate. + */ +export function tokenCountWithEstimation(messages: readonly Message[]): number { + let i = messages.length - 1 + while (i >= 0) { + const message = messages[i] + const usage = message ? getTokenUsage(message) : undefined + if (message && usage) { + // Walk back past any earlier sibling records split from the same API + // response (same message.id) so interleaved tool_results between them + // are included in the estimation slice. + const responseId = getAssistantMessageId(message) + if (responseId) { + let j = i - 1 + while (j >= 0) { + const prior = messages[j] + const priorId = prior ? getAssistantMessageId(prior) : undefined + if (priorId === responseId) { + // Earlier split of the same API response — anchor here instead. + i = j + } else if (priorId !== undefined) { + // Hit a different API response — stop walking. + break + } + // priorId === undefined: a user/tool_result/attachment message, + // possibly interleaved between splits — keep walking. + j-- + } + } + return ( + getTokenCountFromUsage(usage) + + roughTokenCountEstimationForMessages(messages.slice(i + 1)) + ) + } + i-- + } + return roughTokenCountEstimationForMessages(messages) +} diff --git a/src/utils/toolPool.ts b/src/utils/toolPool.ts new file mode 100644 index 0000000000000000000000000000000000000000..b756d91537402f8a4a23e9fefb75a328a878bd26 --- /dev/null +++ b/src/utils/toolPool.ts @@ -0,0 +1,79 @@ +import { feature } from 'bun:bundle' +import partition from 'lodash-es/partition.js' +import uniqBy from 'lodash-es/uniqBy.js' +import { COORDINATOR_MODE_ALLOWED_TOOLS } from '../constants/tools.js' +import { isMcpTool } from '../services/mcp/utils.js' +import type { Tool, ToolPermissionContext, Tools } from '../Tool.js' + +// MCP tool name suffixes for PR activity subscription. These are lightweight +// orchestration actions the coordinator calls directly rather than delegating +// to workers. Matched by suffix since the MCP server name prefix may vary. +const PR_ACTIVITY_TOOL_SUFFIXES = [ + 'subscribe_pr_activity', + 'unsubscribe_pr_activity', +] + +export function isPrActivitySubscriptionTool(name: string): boolean { + return PR_ACTIVITY_TOOL_SUFFIXES.some(suffix => name.endsWith(suffix)) +} + +// Dead code elimination: conditional imports for feature-gated modules +/* eslint-disable @typescript-eslint/no-require-imports */ +const coordinatorModeModule = feature('COORDINATOR_MODE') + ? (require('../coordinator/coordinatorMode.js') as typeof import('../coordinator/coordinatorMode.js')) + : null +/* eslint-enable @typescript-eslint/no-require-imports */ + +/** + * Filters a tool array to the set allowed in coordinator mode. + * Shared between the REPL path (mergeAndFilterTools) and the headless + * path (main.tsx) so both stay in sync. + * + * PR activity subscription tools are always allowed since subscription + * management is orchestration. + */ +export function applyCoordinatorToolFilter(tools: Tools): Tools { + return tools.filter( + t => + COORDINATOR_MODE_ALLOWED_TOOLS.has(t.name) || + isPrActivitySubscriptionTool(t.name), + ) +} + +/** + * Pure function that merges tool pools and applies coordinator mode filtering. + * + * Lives in a React-free file so print.ts can import it without pulling + * react/ink into the SDK module graph. The useMergedTools hook delegates + * to this function inside useMemo. + * + * @param initialTools - Extra tools to include (built-in + startup MCP from props). + * @param assembled - Tools from assembleToolPool (built-in + MCP, deduped). + * @param mode - The permission context mode. + * @returns Merged, deduplicated, and coordinator-filtered tool array. + */ +export function mergeAndFilterTools( + initialTools: Tools, + assembled: Tools, + mode: ToolPermissionContext['mode'], +): Tools { + // Merge initialTools on top - they take precedence in deduplication. + // initialTools may include built-in tools (from getTools() in REPL.tsx) which + // overlap with assembled tools. uniqBy handles this deduplication. + // Partition-sort for prompt-cache stability (same as assembleToolPool): + // built-ins must stay a contiguous prefix for the server's cache policy. + const [mcp, builtIn] = partition( + uniqBy([...initialTools, ...assembled], 'name'), + isMcpTool, + ) + const byName = (a: Tool, b: Tool) => a.name.localeCompare(b.name) + const tools = [...builtIn.sort(byName), ...mcp.sort(byName)] + + if (feature('COORDINATOR_MODE') && coordinatorModeModule) { + if (coordinatorModeModule.isCoordinatorMode()) { + return applyCoordinatorToolFilter(tools) + } + } + + return tools +} diff --git a/src/utils/toolResultStorage.ts b/src/utils/toolResultStorage.ts new file mode 100644 index 0000000000000000000000000000000000000000..f4dfef32663554772a53557930b5651c3ef0c976 --- /dev/null +++ b/src/utils/toolResultStorage.ts @@ -0,0 +1,1040 @@ +/** + * Utility for persisting large tool results to disk instead of truncating them. + */ + +import type { ToolResultBlockParam } from '@anthropic-ai/sdk/resources/index.mjs' +import { mkdir, writeFile } from 'fs/promises' +import { join } from 'path' +import { getOriginalCwd, getSessionId } from '../bootstrap/state.js' +import { + BYTES_PER_TOKEN, + DEFAULT_MAX_RESULT_SIZE_CHARS, + MAX_TOOL_RESULT_BYTES, + MAX_TOOL_RESULTS_PER_MESSAGE_CHARS, +} from '../constants/toolLimits.js' +import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js' +import { logEvent } from '../services/analytics/index.js' +import { sanitizeToolNameForAnalytics } from '../services/analytics/metadata.js' +import type { Message } from '../types/message.js' +import { logForDebugging } from './debug.js' +import { getErrnoCode, toError } from './errors.js' +import { formatFileSize } from './format.js' +import { logError } from './log.js' +import { getProjectDir } from './sessionStorage.js' +import { jsonStringify } from './slowOperations.js' + +// Subdirectory name for tool results within a session +export const TOOL_RESULTS_SUBDIR = 'tool-results' + +// XML tag used to wrap persisted output messages +export const PERSISTED_OUTPUT_TAG = '' +export const PERSISTED_OUTPUT_CLOSING_TAG = '' + +// Message used when tool result content was cleared without persisting to file +export const TOOL_RESULT_CLEARED_MESSAGE = '[Old tool result content cleared]' + +/** + * GrowthBook override map: tool name -> persistence threshold (chars). + * When a tool name is present in this map, that value is used directly as the + * effective threshold, bypassing the Math.min() clamp against the 50k default. + * Tools absent from the map use the hardcoded fallback. + * Flag default is {} (no overrides == behavior unchanged). + */ +const PERSIST_THRESHOLD_OVERRIDE_FLAG = 'tengu_satin_quoll' + +/** + * Resolve the effective persistence threshold for a tool. + * GrowthBook override wins when present; otherwise falls back to the declared + * per-tool cap clamped by the global default. + * + * Defensive: GrowthBook's cache returns `cached !== undefined ? cached : default`, + * so a flag served as `null` leaks through. We guard with optional chaining and a + * typeof check so any non-object flag value (null, string, number) falls through + * to the hardcoded default instead of throwing on index or returning 0. + */ +export function getPersistenceThreshold( + toolName: string, + declaredMaxResultSizeChars: number, +): number { + // Infinity = hard opt-out. Read self-bounds via maxTokens; persisting its + // output to a file the model reads back with Read is circular. Checked + // before the GB override so tengu_satin_quoll can't force it back on. + if (!Number.isFinite(declaredMaxResultSizeChars)) { + return declaredMaxResultSizeChars + } + const overrides = getFeatureValue_CACHED_MAY_BE_STALE | null>(PERSIST_THRESHOLD_OVERRIDE_FLAG, {}) + const override = overrides?.[toolName] + if ( + typeof override === 'number' && + Number.isFinite(override) && + override > 0 + ) { + return override + } + return Math.min(declaredMaxResultSizeChars, DEFAULT_MAX_RESULT_SIZE_CHARS) +} + +// Result of persisting a tool result to disk +export type PersistedToolResult = { + filepath: string + originalSize: number + isJson: boolean + preview: string + hasMore: boolean +} + +// Error result when persistence fails +export type PersistToolResultError = { + error: string +} + +/** + * Get the session directory (projectDir/sessionId) + */ +function getSessionDir(): string { + return join(getProjectDir(getOriginalCwd()), getSessionId()) +} + +/** + * Get the tool results directory for this session (projectDir/sessionId/tool-results) + */ +export function getToolResultsDir(): string { + return join(getSessionDir(), TOOL_RESULTS_SUBDIR) +} + +// Preview size in bytes for the reference message +export const PREVIEW_SIZE_BYTES = 2000 + +/** + * Get the filepath where a tool result would be persisted. + */ +export function getToolResultPath(id: string, isJson: boolean): string { + const ext = isJson ? 'json' : 'txt' + return join(getToolResultsDir(), `${id}.${ext}`) +} + +/** + * Ensure the session-specific tool results directory exists + */ +export async function ensureToolResultsDir(): Promise { + try { + await mkdir(getToolResultsDir(), { recursive: true }) + } catch { + // Directory may already exist + } +} + +/** + * Persist a tool result to disk and return information about the persisted file + * + * @param content - The tool result content to persist (string or array of content blocks) + * @param toolUseId - The ID of the tool use that produced the result + * @returns Information about the persisted file including filepath and preview + */ +export async function persistToolResult( + content: NonNullable, + toolUseId: string, +): Promise { + const isJson = Array.isArray(content) + + // Check for non-text content - we can only persist text blocks + if (isJson) { + const hasNonTextContent = content.some(block => block.type !== 'text') + if (hasNonTextContent) { + return { + error: 'Cannot persist tool results containing non-text content', + } + } + } + + await ensureToolResultsDir() + const filepath = getToolResultPath(toolUseId, isJson) + const contentStr = isJson ? jsonStringify(content, null, 2) : content + + // tool_use_id is unique per invocation and content is deterministic for a + // given id, so skip if the file already exists. This prevents re-writing + // the same content on every API turn when microcompact replays the + // original messages. Use 'wx' instead of a stat-then-write race. + try { + await writeFile(filepath, contentStr, { encoding: 'utf-8', flag: 'wx' }) + logForDebugging( + `Persisted tool result to ${filepath} (${formatFileSize(contentStr.length)})`, + ) + } catch (error) { + if (getErrnoCode(error) !== 'EEXIST') { + logError(toError(error)) + return { error: getFileSystemErrorMessage(toError(error)) } + } + // EEXIST: already persisted on a prior turn, fall through to preview + } + + // Generate a preview + const { preview, hasMore } = generatePreview(contentStr, PREVIEW_SIZE_BYTES) + + return { + filepath, + originalSize: contentStr.length, + isJson, + preview, + hasMore, + } +} + +/** + * Build a message for large tool results with preview + */ +export function buildLargeToolResultMessage( + result: PersistedToolResult, +): string { + let message = `${PERSISTED_OUTPUT_TAG}\n` + message += `Output too large (${formatFileSize(result.originalSize)}). Full output saved to: ${result.filepath}\n\n` + message += `Preview (first ${formatFileSize(PREVIEW_SIZE_BYTES)}):\n` + message += result.preview + message += result.hasMore ? '\n...\n' : '\n' + message += PERSISTED_OUTPUT_CLOSING_TAG + return message +} + +/** + * Process a tool result for inclusion in a message. + * Maps the result to the API format and persists large results to disk. + */ +export async function processToolResultBlock( + tool: { + name: string + maxResultSizeChars: number + mapToolResultToToolResultBlockParam: ( + result: T, + toolUseID: string, + ) => ToolResultBlockParam + }, + toolUseResult: T, + toolUseID: string, +): Promise { + const toolResultBlock = tool.mapToolResultToToolResultBlockParam( + toolUseResult, + toolUseID, + ) + return maybePersistLargeToolResult( + toolResultBlock, + tool.name, + getPersistenceThreshold(tool.name, tool.maxResultSizeChars), + ) +} + +/** + * Process a pre-mapped tool result block. Applies persistence for large results + * without re-calling mapToolResultToToolResultBlockParam. + */ +export async function processPreMappedToolResultBlock( + toolResultBlock: ToolResultBlockParam, + toolName: string, + maxResultSizeChars: number, +): Promise { + return maybePersistLargeToolResult( + toolResultBlock, + toolName, + getPersistenceThreshold(toolName, maxResultSizeChars), + ) +} + +/** + * True when a tool_result's content is empty or effectively empty. Covers: + * undefined/null/'', whitespace-only strings, empty arrays, and arrays whose + * only blocks are text blocks with empty/whitespace text. Non-text blocks + * (images, tool_reference) are treated as non-empty. + */ +export function isToolResultContentEmpty( + content: ToolResultBlockParam['content'], +): boolean { + if (!content) return true + if (typeof content === 'string') return content.trim() === '' + if (!Array.isArray(content)) return false + if (content.length === 0) return true + return content.every( + block => + typeof block === 'object' && + 'type' in block && + block.type === 'text' && + 'text' in block && + (typeof block.text !== 'string' || block.text.trim() === ''), + ) +} + +/** + * Handle large tool results by persisting to disk instead of truncating. + * Returns the original block if no persistence needed, or a modified block + * with the content replaced by a reference to the persisted file. + */ +async function maybePersistLargeToolResult( + toolResultBlock: ToolResultBlockParam, + toolName: string, + persistenceThreshold?: number, +): Promise { + // Check size first before doing any async work - most tool results are small + const content = toolResultBlock.content + + // inc-4586: Empty tool_result content at the prompt tail causes some models + // (notably capybara) to emit the \n\nHuman: stop sequence and end their turn + // with zero output. The server renderer inserts no \n\nAssistant: marker after + // tool results, so a bare \n\n pattern-matches to a turn + // boundary. Several tools can legitimately produce empty output (silent-success + // shell commands, MCP servers returning content:[], REPL statements, etc.). + // Inject a short marker so the model always has something to react to. + if (isToolResultContentEmpty(content)) { + logEvent('tengu_tool_empty_result', { + toolName: sanitizeToolNameForAnalytics(toolName), + }) + return { + ...toolResultBlock, + content: `(${toolName} completed with no output)`, + } + } + // Narrow after the emptiness guard — content is non-nullish past this point. + if (!content) { + return toolResultBlock + } + + // Skip persistence for image content blocks - they need to be sent as-is to Claude + if (hasImageBlock(content)) { + return toolResultBlock + } + + const size = contentSize(content) + + // Use tool-specific threshold if provided, otherwise fall back to global limit + const threshold = persistenceThreshold ?? MAX_TOOL_RESULT_BYTES + if (size <= threshold) { + return toolResultBlock + } + + // Persist the entire content as a unit + const result = await persistToolResult(content, toolResultBlock.tool_use_id) + if (isPersistError(result)) { + // If persistence failed, return the original block unchanged + return toolResultBlock + } + + const message = buildLargeToolResultMessage(result) + + // Log analytics + logEvent('tengu_tool_result_persisted', { + toolName: sanitizeToolNameForAnalytics(toolName), + originalSizeBytes: result.originalSize, + persistedSizeBytes: message.length, + estimatedOriginalTokens: Math.ceil(result.originalSize / BYTES_PER_TOKEN), + estimatedPersistedTokens: Math.ceil(message.length / BYTES_PER_TOKEN), + thresholdUsed: threshold, + }) + + return { ...toolResultBlock, content: message } +} + +/** + * Generate a preview of content, truncating at a newline boundary when possible. + */ +export function generatePreview( + content: string, + maxBytes: number, +): { preview: string; hasMore: boolean } { + if (content.length <= maxBytes) { + return { preview: content, hasMore: false } + } + + // Find the last newline within the limit to avoid cutting mid-line + const truncated = content.slice(0, maxBytes) + const lastNewline = truncated.lastIndexOf('\n') + + // If we found a newline reasonably close to the limit, use it + // Otherwise fall back to the exact limit + const cutPoint = lastNewline > maxBytes * 0.5 ? lastNewline : maxBytes + + return { preview: content.slice(0, cutPoint), hasMore: true } +} + +/** + * Type guard to check if persist result is an error + */ +export function isPersistError( + result: PersistedToolResult | PersistToolResultError, +): result is PersistToolResultError { + return 'error' in result +} + +// --- Message-level aggregate tool result budget --- +// +// Tracks replacement state across turns so enforceToolResultBudget makes the +// same choices every time (preserves prompt cache prefix). + +/** + * Per-conversation-thread state for the aggregate tool result budget. + * State must be stable to preserve prompt cache: + * - seenIds: results that have passed through the budget check (replaced + * or not). Once seen, a result's fate is frozen for the conversation. + * - replacements: subset of seenIds that were persisted to disk and + * replaced with previews, mapped to the exact preview string shown to + * the model. Re-application is a Map lookup — no file I/O, guaranteed + * byte-identical, cannot fail. + * + * Lifecycle: one instance per conversation thread, carried on ToolUseContext. + * Main thread: REPL provisions once, never resets — stale entries after + * /clear, rewind, resume, or compact are never looked up (tool_use_ids are + * UUIDs) so they're harmless. Subagents: createSubagentContext clones the + * parent's state by default (cache-sharing forks like agentSummary need + * identical decisions), or resumeAgentBackground threads one reconstructed + * from sidechain records. + */ +export type ContentReplacementState = { + seenIds: Set + replacements: Map +} + +export function createContentReplacementState(): ContentReplacementState { + return { seenIds: new Set(), replacements: new Map() } +} + +/** + * Clone replacement state for a cache-sharing fork (e.g. agentSummary). + * The fork needs state identical to the source at fork time so + * enforceToolResultBudget makes the same choices → same wire prefix → + * prompt cache hit. Mutating the clone does not affect the source. + */ +export function cloneContentReplacementState( + source: ContentReplacementState, +): ContentReplacementState { + return { + seenIds: new Set(source.seenIds), + replacements: new Map(source.replacements), + } +} + +/** + * Resolve the per-message aggregate budget limit. GrowthBook override + * (tengu_hawthorn_window) wins when present and a finite positive number; + * otherwise falls back to the hardcoded constant. Defensive typeof/finite + * check: GrowthBook's cache returns `cached !== undefined ? cached : default`, + * so a flag served as null/string/NaN leaks through. + */ +export function getPerMessageBudgetLimit(): number { + const override = getFeatureValue_CACHED_MAY_BE_STALE( + 'tengu_hawthorn_window', + null, + ) + if ( + typeof override === 'number' && + Number.isFinite(override) && + override > 0 + ) { + return override + } + return MAX_TOOL_RESULTS_PER_MESSAGE_CHARS +} + +/** + * Provision replacement state for a new conversation thread. + * + * Encapsulates the feature-flag gate + reconstruct-vs-fresh choice: + * - Flag off → undefined (query.ts skips enforcement entirely) + * - No initialMessages (cold start) → fresh + * - initialMessages present → reconstruct (freeze all candidate IDs so the + * budget never replaces content the model already saw unreplaced). Empty + * or absent records freeze everything; non-empty records additionally + * populate the replacements Map for byte-identical re-apply. + */ +export function provisionContentReplacementState( + initialMessages?: Message[], + initialContentReplacements?: ContentReplacementRecord[], +): ContentReplacementState | undefined { + const enabled = getFeatureValue_CACHED_MAY_BE_STALE( + 'tengu_hawthorn_steeple', + false, + ) + if (!enabled) return undefined + if (initialMessages) { + return reconstructContentReplacementState( + initialMessages, + initialContentReplacements ?? [], + ) + } + return createContentReplacementState() +} + +/** + * Serializable record of one content-replacement decision. Written to the + * transcript as a ContentReplacementEntry so decisions survive resume. + * Discriminated by `kind` so future replacement mechanisms (user text, + * offloaded images) can share the same transcript entry type. + * + * `replacement` is the exact string the model saw — stored rather than + * derived on resume so code changes to the preview template, size formatting, + * or path layout can't silently break prompt cache. + */ +export type ContentReplacementRecord = { + kind: 'tool-result' + toolUseId: string + replacement: string +} + +export type ToolResultReplacementRecord = Extract< + ContentReplacementRecord, + { kind: 'tool-result' } +> + +type ToolResultCandidate = { + toolUseId: string + content: NonNullable + size: number +} + +type CandidatePartition = { + mustReapply: Array + frozen: ToolResultCandidate[] + fresh: ToolResultCandidate[] +} + +function isContentAlreadyCompacted( + content: ToolResultBlockParam['content'], +): boolean { + // All budget-produced content starts with the tag (buildLargeToolResultMessage). + // `.startsWith()` avoids false-positives when the tag appears anywhere else + // in the content (e.g., reading this source file). + return typeof content === 'string' && content.startsWith(PERSISTED_OUTPUT_TAG) +} + +function hasImageBlock( + content: NonNullable, +): boolean { + return ( + Array.isArray(content) && + content.some( + b => typeof b === 'object' && 'type' in b && b.type === 'image', + ) + ) +} + +function contentSize( + content: NonNullable, +): number { + if (typeof content === 'string') return content.length + // Sum text-block lengths directly. Slightly under-counts vs serialized + // (no JSON framing), but the budget is a rough token heuristic anyway. + // Avoids allocating a content-sized string every enforcement pass. + return content.reduce( + (sum, b) => sum + (b.type === 'text' ? b.text.length : 0), + 0, + ) +} + +/** + * Walk messages and build tool_use_id → tool_name from assistant tool_use + * blocks. tool_use always precedes its tool_result (model calls, then result + * arrives), so by the time budget enforcement sees a result, its name is known. + */ +function buildToolNameMap(messages: Message[]): Map { + const map = new Map() + for (const message of messages) { + if (message.type !== 'assistant') continue + const content = message.message.content + if (!Array.isArray(content)) continue + for (const block of content) { + if (block.type === 'tool_use') { + map.set(block.id, block.name) + } + } + } + return map +} + +/** + * Extract candidate tool_result blocks from a single user message: blocks + * that are non-empty, non-image, and not already compacted by tag (i.e. by + * the per-tool limit, or an earlier iteration of this same query call). + * Returns [] for messages with no eligible blocks. + */ +function collectCandidatesFromMessage(message: Message): ToolResultCandidate[] { + if (message.type !== 'user' || !Array.isArray(message.message.content)) { + return [] + } + return message.message.content.flatMap(block => { + if (block.type !== 'tool_result' || !block.content) return [] + if (isContentAlreadyCompacted(block.content)) return [] + if (hasImageBlock(block.content)) return [] + return [ + { + toolUseId: block.tool_use_id, + content: block.content, + size: contentSize(block.content), + }, + ] + }) +} + +/** + * Extract candidate tool_result blocks grouped by API-level user message. + * + * normalizeMessagesForAPI merges consecutive user messages into one + * (Bedrock compat; 1P does the same server-side), so parallel tool + * results that arrive as N separate user messages in our state become + * ONE user message on the wire. The budget must group the same way or + * it would see N under-budget messages instead of one over-budget + * message and fail to enforce exactly when it matters most. + * + * A "group" is a maximal run of user messages NOT separated by an + * assistant message. Only assistant messages create wire-level + * boundaries — normalizeMessagesForAPI filters out progress entirely + * and merges attachment / system(local_command) INTO adjacent user + * blocks, so those types do NOT break groups here either. + * + * This matters for abort-during-parallel-tools paths: agent_progress + * messages (non-ephemeral, persisted in REPL state) can interleave + * between fresh tool_result messages. If we flushed on progress, those + * tool_results would split into under-budget groups, slip through + * unreplaced, get frozen, then be merged by normalizeMessagesForAPI + * into one over-budget wire message — defeating the feature. + * + * Only groups with at least one eligible candidate are returned. + */ +function collectCandidatesByMessage( + messages: Message[], +): ToolResultCandidate[][] { + const groups: ToolResultCandidate[][] = [] + let current: ToolResultCandidate[] = [] + + const flush = () => { + if (current.length > 0) groups.push(current) + current = [] + } + + // Track all assistant message.ids seen so far — same-ID fragments are + // merged by normalizeMessagesForAPI (messages.ts ~2126 walks back PAST + // different-ID assistants via `continue`), so any re-appearance of a + // previously-seen ID must NOT create a group boundary. Two scenarios: + // • Consecutive: streamingToolExecution yields one AssistantMessage per + // content_block_stop (same id); a fast tool drains between blocks; + // abort/hook-stop leaves [asst(X), user(trA), asst(X), user(trB)]. + // • Interleaved: coordinator/teammate streams mix different responses + // so [asst(X), user(trA), asst(Y), user(trB), asst(X), user(trC)]. + // In both, normalizeMessagesForAPI merges the X fragments into one wire + // assistant, and their following tool_results merge into one wire user + // message — so the budget must see them as one group too. + const seenAsstIds = new Set() + for (const message of messages) { + if (message.type === 'user') { + current.push(...collectCandidatesFromMessage(message)) + } else if (message.type === 'assistant') { + if (!seenAsstIds.has(message.message.id)) { + flush() + seenAsstIds.add(message.message.id) + } + } + // progress / attachment / system are filtered or merged by + // normalizeMessagesForAPI — they don't create wire boundaries. + } + flush() + + return groups +} + +/** + * Partition candidates by their prior decision state: + * - mustReapply: previously replaced → re-apply the cached replacement for + * prefix stability + * - frozen: previously seen and left unreplaced → off-limits (replacing + * now would change a prefix that was already cached) + * - fresh: never seen → eligible for new replacement decisions + */ +function partitionByPriorDecision( + candidates: ToolResultCandidate[], + state: ContentReplacementState, +): CandidatePartition { + return candidates.reduce( + (acc, c) => { + const replacement = state.replacements.get(c.toolUseId) + if (replacement !== undefined) { + acc.mustReapply.push({ ...c, replacement }) + } else if (state.seenIds.has(c.toolUseId)) { + acc.frozen.push(c) + } else { + acc.fresh.push(c) + } + return acc + }, + { mustReapply: [], frozen: [], fresh: [] }, + ) +} + +/** + * Pick the largest fresh results to replace until the model-visible total + * (frozen + remaining fresh) is at or under budget, or fresh is exhausted. + * If frozen results alone exceed budget we accept the overage — microcompact + * will eventually clear them. + */ +function selectFreshToReplace( + fresh: ToolResultCandidate[], + frozenSize: number, + limit: number, +): ToolResultCandidate[] { + const sorted = [...fresh].sort((a, b) => b.size - a.size) + const selected: ToolResultCandidate[] = [] + let remaining = frozenSize + fresh.reduce((sum, c) => sum + c.size, 0) + for (const c of sorted) { + if (remaining <= limit) break + selected.push(c) + // We don't know the replacement size until after persist, but previews + // are ~2K and results hitting this path are much larger, so subtracting + // the full size is a close approximation for selection purposes. + remaining -= c.size + } + return selected +} + +/** + * Return a new Message[] where each tool_result block whose id appears in + * replacementMap has its content replaced. Messages and blocks with no + * replacements are passed through by reference. + */ +function replaceToolResultContents( + messages: Message[], + replacementMap: Map, +): Message[] { + return messages.map(message => { + if (message.type !== 'user' || !Array.isArray(message.message.content)) { + return message + } + const content = message.message.content + const needsReplace = content.some( + b => b.type === 'tool_result' && replacementMap.has(b.tool_use_id), + ) + if (!needsReplace) return message + return { + ...message, + message: { + ...message.message, + content: content.map(block => { + if (block.type !== 'tool_result') return block + const replacement = replacementMap.get(block.tool_use_id) + return replacement === undefined + ? block + : { ...block, content: replacement } + }), + }, + } + }) +} + +async function buildReplacement( + candidate: ToolResultCandidate, +): Promise<{ content: string; originalSize: number } | null> { + const result = await persistToolResult(candidate.content, candidate.toolUseId) + if (isPersistError(result)) return null + return { + content: buildLargeToolResultMessage(result), + originalSize: result.originalSize, + } +} + +/** + * Enforce the per-message budget on aggregate tool result size. + * + * For each user message whose tool_result blocks together exceed the + * per-message limit (see getPerMessageBudgetLimit), the largest FRESH + * (never-before-seen) results in THAT message are persisted to disk and + * replaced with previews. + * Messages are evaluated independently — a 150K result in one message and + * a 150K result in another are both under budget and untouched. + * + * State is tracked by tool_use_id in `state`. Once a result is seen its + * fate is frozen: previously-replaced results get the same replacement + * re-applied every turn from the cached preview string (zero I/O, + * byte-identical), and previously-unreplaced results are never replaced + * later (would break prompt cache). + * + * Each turn adds at most one new user message with tool_result blocks, + * so the per-message loop typically does the budget check at most once; + * all prior messages just re-apply cached replacements. + * + * @param state — MUTATED: seenIds and replacements are updated in place + * to record choices made this call. The caller holds a stable reference + * across turns; returning a new object would require error-prone ref + * updates after every query. + * + * Returns `{ messages, newlyReplaced }`: + * - messages: same array instance when no replacement is needed + * - newlyReplaced: replacements made THIS call (not re-applies). + * Caller persists these to the transcript for resume reconstruction. + */ +export async function enforceToolResultBudget( + messages: Message[], + state: ContentReplacementState, + skipToolNames: ReadonlySet = new Set(), +): Promise<{ + messages: Message[] + newlyReplaced: ToolResultReplacementRecord[] +}> { + const candidatesByMessage = collectCandidatesByMessage(messages) + const nameByToolUseId = + skipToolNames.size > 0 ? buildToolNameMap(messages) : undefined + const shouldSkip = (id: string): boolean => + nameByToolUseId !== undefined && + skipToolNames.has(nameByToolUseId.get(id) ?? '') + // Resolve once per call. A mid-session flag change only affects FRESH + // messages (prior decisions are frozen via seenIds/replacements), so + // prompt cache for already-seen content is preserved regardless. + const limit = getPerMessageBudgetLimit() + + // Walk each API-level message group independently. For previously-processed messages + // (all IDs in seenIds) this just re-applies cached replacements. For the + // single new message this turn added, it runs the budget check. + const replacementMap = new Map() + const toPersist: ToolResultCandidate[] = [] + let reappliedCount = 0 + let messagesOverBudget = 0 + + for (const candidates of candidatesByMessage) { + const { mustReapply, frozen, fresh } = partitionByPriorDecision( + candidates, + state, + ) + + // Re-apply: pure Map lookups. No file I/O, byte-identical, cannot fail. + mustReapply.forEach(c => replacementMap.set(c.toolUseId, c.replacement)) + reappliedCount += mustReapply.length + + // Fresh means this is a new message. Check its per-message budget. + // (A previously-processed message has fresh.length === 0 because all + // its IDs were added to seenIds when first seen.) + if (fresh.length === 0) { + // mustReapply/frozen are already in seenIds from their first pass — + // re-adding is a no-op but keeps the invariant explicit. + candidates.forEach(c => state.seenIds.add(c.toolUseId)) + continue + } + + // Tools with maxResultSizeChars: Infinity (Read) — never persist. + // Mark as seen (frozen) so the decision sticks across turns. They don't + // count toward freshSize; if that lets the group slip under budget and + // the wire message is still large, that's the contract — Read's own + // maxTokens is the bound, not this wrapper. + const skipped = fresh.filter(c => shouldSkip(c.toolUseId)) + skipped.forEach(c => state.seenIds.add(c.toolUseId)) + const eligible = fresh.filter(c => !shouldSkip(c.toolUseId)) + + const frozenSize = frozen.reduce((sum, c) => sum + c.size, 0) + const freshSize = eligible.reduce((sum, c) => sum + c.size, 0) + + const selected = + frozenSize + freshSize > limit + ? selectFreshToReplace(eligible, frozenSize, limit) + : [] + + // Mark non-persisting candidates as seen NOW (synchronously). IDs + // selected for persist are marked seen AFTER the await, alongside + // replacements.set — keeps the pair atomic under observation so no + // concurrent reader (once subagents share state) ever sees X∈seenIds + // but X∉replacements, which would misclassify X as frozen and send + // full content while the main thread sends the preview → cache miss. + const selectedIds = new Set(selected.map(c => c.toolUseId)) + candidates + .filter(c => !selectedIds.has(c.toolUseId)) + .forEach(c => state.seenIds.add(c.toolUseId)) + + if (selected.length === 0) continue + messagesOverBudget++ + toPersist.push(...selected) + } + + if (replacementMap.size === 0 && toPersist.length === 0) { + return { messages, newlyReplaced: [] } + } + + // Fresh: concurrent persist for all selected candidates across all + // messages. In practice toPersist comes from a single message per turn. + const freshReplacements = await Promise.all( + toPersist.map(async c => [c, await buildReplacement(c)] as const), + ) + const newlyReplaced: ToolResultReplacementRecord[] = [] + let replacedSize = 0 + for (const [candidate, replacement] of freshReplacements) { + // Mark seen HERE, post-await, atomically with replacements.set for + // success cases. For persist failures (replacement === null) the ID + // is seen-but-unreplaced — the original content was sent to the + // model, so treating it as frozen going forward is correct. + state.seenIds.add(candidate.toolUseId) + if (replacement === null) continue + replacedSize += candidate.size + replacementMap.set(candidate.toolUseId, replacement.content) + state.replacements.set(candidate.toolUseId, replacement.content) + newlyReplaced.push({ + kind: 'tool-result', + toolUseId: candidate.toolUseId, + replacement: replacement.content, + }) + logEvent('tengu_tool_result_persisted_message_budget', { + originalSizeBytes: replacement.originalSize, + persistedSizeBytes: replacement.content.length, + estimatedOriginalTokens: Math.ceil( + replacement.originalSize / BYTES_PER_TOKEN, + ), + estimatedPersistedTokens: Math.ceil( + replacement.content.length / BYTES_PER_TOKEN, + ), + }) + } + + if (replacementMap.size === 0) { + return { messages, newlyReplaced: [] } + } + + if (newlyReplaced.length > 0) { + logForDebugging( + `Per-message budget: persisted ${newlyReplaced.length} tool results ` + + `across ${messagesOverBudget} over-budget message(s), ` + + `shed ~${formatFileSize(replacedSize)}, ${reappliedCount} re-applied`, + ) + logEvent('tengu_message_level_tool_result_budget_enforced', { + resultsPersisted: newlyReplaced.length, + messagesOverBudget, + replacedSizeBytes: replacedSize, + reapplied: reappliedCount, + }) + } + + return { + messages: replaceToolResultContents(messages, replacementMap), + newlyReplaced, + } +} + +/** + * Query-loop integration point for the aggregate budget. + * + * Gates on `state` (undefined means feature disabled → no-op return), + * applies enforcement, and fires an optional transcript-write callback + * for new replacements. The caller (query.ts) owns the persistence gate + * — it passes a callback only for querySources that read records back on + * resume (repl_main_thread*, agent:*); ephemeral runForkedAgent callers + * (agentSummary, sessionMemory, /btw, compact) pass undefined. + * + * @returns messages with replacements applied, or the input array unchanged + * when the feature is off or no replacement occurred. + */ +export async function applyToolResultBudget( + messages: Message[], + state: ContentReplacementState | undefined, + writeToTranscript?: (records: ToolResultReplacementRecord[]) => void, + skipToolNames?: ReadonlySet, +): Promise { + if (!state) return messages + const result = await enforceToolResultBudget(messages, state, skipToolNames) + if (result.newlyReplaced.length > 0) { + writeToTranscript?.(result.newlyReplaced) + } + return result.messages +} + +/** + * Reconstruct replacement state from content-replacement records loaded from + * the transcript. Used on resume so the budget makes the same choices it + * made in the original session (prompt cache stability). + * + * Accepts the full ContentReplacementRecord[] from LogOption (may include + * future non-tool-result kinds); only tool-result records are applied here. + * + * - replacements: populated directly from the stored replacement strings. + * Records for IDs not in messages (e.g. after compact) are skipped — + * they're inert anyway. + * - seenIds: every candidate tool_use_id in the loaded messages. A result + * being in the transcript means it was sent to the model, so it was seen. + * This freezes unreplaced results against future replacement. + * - inheritedReplacements: gap-fill for fork-subagent resume. A fork's + * original run applies parent-inherited replacements via mustReapply + * (never persisted — not newlyReplaced). On resume the sidechain has + * the original content but no record, so records alone would classify + * it as frozen. The parent's live state still has the mapping; copy + * it for IDs in messages that records don't cover. No-op for non-fork + * resumes (parent IDs aren't in the subagent's messages). + */ +export function reconstructContentReplacementState( + messages: Message[], + records: ContentReplacementRecord[], + inheritedReplacements?: ReadonlyMap, +): ContentReplacementState { + const state = createContentReplacementState() + const candidateIds = new Set( + collectCandidatesByMessage(messages) + .flat() + .map(c => c.toolUseId), + ) + + for (const id of candidateIds) { + state.seenIds.add(id) + } + for (const r of records) { + if (r.kind === 'tool-result' && candidateIds.has(r.toolUseId)) { + state.replacements.set(r.toolUseId, r.replacement) + } + } + if (inheritedReplacements) { + for (const [id, replacement] of inheritedReplacements) { + if (candidateIds.has(id) && !state.replacements.has(id)) { + state.replacements.set(id, replacement) + } + } + } + return state +} + +/** + * AgentTool-resume variant: encapsulates the feature-flag gate + parent + * gap-fill so both AgentTool.call and resumeAgentBackground share one + * implementation. Returns undefined when parentState is undefined (feature + * off); otherwise reconstructs from sidechain records with parent's live + * replacements filling gaps for fork-inherited mustReapply entries. + * + * Kept out of AgentTool.tsx — that file is at the feature() DCE complexity + * cliff and cannot tolerate even +1 net source line without silently + * breaking feature('TRANSCRIPT_CLASSIFIER') eval in tests. + */ +export function reconstructForSubagentResume( + parentState: ContentReplacementState | undefined, + resumedMessages: Message[], + sidechainRecords: ContentReplacementRecord[], +): ContentReplacementState | undefined { + if (!parentState) return undefined + return reconstructContentReplacementState( + resumedMessages, + sidechainRecords, + parentState.replacements, + ) +} + +/** + * Get a human-readable error message from a filesystem error + */ +function getFileSystemErrorMessage(error: Error): string { + // Node.js filesystem errors have a 'code' property + // eslint-disable-next-line no-restricted-syntax -- uses .path, not just .code + const nodeError = error as NodeJS.ErrnoException + if (nodeError.code) { + switch (nodeError.code) { + case 'ENOENT': + return `Directory not found: ${nodeError.path ?? 'unknown path'}` + case 'EACCES': + return `Permission denied: ${nodeError.path ?? 'unknown path'}` + case 'ENOSPC': + return 'No space left on device' + case 'EROFS': + return 'Read-only file system' + case 'EMFILE': + return 'Too many open files' + case 'EEXIST': + return `File already exists: ${nodeError.path ?? 'unknown path'}` + default: + return `${nodeError.code}: ${nodeError.message}` + } + } + return error.message +} diff --git a/src/utils/ultraplan/ccrSession.ts b/src/utils/ultraplan/ccrSession.ts new file mode 100644 index 0000000000000000000000000000000000000000..a72b594e645f57d1fe88494349d4c9d95e468a7c --- /dev/null +++ b/src/utils/ultraplan/ccrSession.ts @@ -0,0 +1,349 @@ +// CCR session polling for /ultraplan. Waits for an approved ExitPlanMode +// tool_result, then extracts the plan text. Uses pollRemoteSessionEvents +// (shared with RemoteAgentTask) for pagination + typed SDKMessage[]. +// Plan mode is set via set_permission_mode control_request in +// teleportToRemote's CreateSession events array. + +import type { + ToolResultBlockParam, + ToolUseBlock, +} from '@anthropic-ai/sdk/resources' +import type { SDKMessage } from '../../entrypoints/agentSdkTypes.js' +import { EXIT_PLAN_MODE_V2_TOOL_NAME } from '../../tools/ExitPlanModeTool/constants.js' +import { logForDebugging } from '../debug.js' +import { sleep } from '../sleep.js' +import { isTransientNetworkError } from '../teleport/api.js' +import { + type PollRemoteSessionResponse, + pollRemoteSessionEvents, +} from '../teleport.js' + +const POLL_INTERVAL_MS = 3000 +// pollRemoteSessionEvents doesn't retry. A 30min poll makes ~600 calls; +// at any nonzero 5xx rate one blip would kill the run. +const MAX_CONSECUTIVE_FAILURES = 5 + +export type PollFailReason = + | 'terminated' + | 'timeout_pending' + | 'timeout_no_plan' + | 'extract_marker_missing' + | 'network_or_unknown' + | 'stopped' + +export class UltraplanPollError extends Error { + constructor( + message: string, + readonly reason: PollFailReason, + readonly rejectCount: number, + options?: ErrorOptions, + ) { + super(message, options) + this.name = 'UltraplanPollError' + } +} + +// Sentinel string the browser PlanModal includes in the feedback when the user +// clicks "teleport back to terminal". Plan text follows on the next line. +export const ULTRAPLAN_TELEPORT_SENTINEL = '__ULTRAPLAN_TELEPORT_LOCAL__' + +export type ScanResult = + | { kind: 'approved'; plan: string } + | { kind: 'teleport'; plan: string } + | { kind: 'rejected'; id: string } + | { kind: 'pending' } + | { kind: 'terminated'; subtype: string } + | { kind: 'unchanged' } + +/** + * Pill/detail-view state derived from the event stream. Transitions: + * running → (turn ends, no ExitPlanMode) → needs_input + * needs_input → (user replies in browser) → running + * running → (ExitPlanMode emitted, no result yet) → plan_ready + * plan_ready → (rejected) → running + * plan_ready → (approved) → poll resolves, pill removed + */ +export type UltraplanPhase = 'running' | 'needs_input' | 'plan_ready' + +/** + * Pure stateful classifier for the CCR event stream. Ingests SDKMessage[] + * batches (as delivered by pollRemoteSessionEvents) and returns the current + * ExitPlanMode verdict. No I/O, no timers — feed it synthetic or recorded + * events for unit tests and offline replay. + * + * Precedence (approved > terminated > rejected > pending > unchanged): + * pollRemoteSessionEvents paginates up to 50 pages per call, so one ingest + * can span seconds of session activity. A batch may contain both an approved + * tool_result AND a subsequent {type:'result'} (user approved, then remote + * crashed). The approved plan is real and in threadstore — don't drop it. + */ +export class ExitPlanModeScanner { + private exitPlanCalls: string[] = [] + private results = new Map() + private rejectedIds = new Set() + private terminated: { subtype: string } | null = null + private rescanAfterRejection = false + everSeenPending = false + + get rejectCount(): number { + return this.rejectedIds.size + } + + /** + * True when an ExitPlanMode tool_use exists with no tool_result yet — + * the remote is showing the approval dialog in the browser. + */ + get hasPendingPlan(): boolean { + const id = this.exitPlanCalls.findLast(c => !this.rejectedIds.has(c)) + return id !== undefined && !this.results.has(id) + } + + ingest(newEvents: SDKMessage[]): ScanResult { + for (const m of newEvents) { + if (m.type === 'assistant') { + for (const block of m.message.content) { + if (block.type !== 'tool_use') continue + const tu = block as ToolUseBlock + if (tu.name === EXIT_PLAN_MODE_V2_TOOL_NAME) { + this.exitPlanCalls.push(tu.id) + } + } + } else if (m.type === 'user') { + const content = m.message.content + if (!Array.isArray(content)) continue + for (const block of content) { + if (block.type === 'tool_result') { + this.results.set(block.tool_use_id, block) + } + } + } else if (m.type === 'result' && m.subtype !== 'success') { + // result(success) fires after EVERY CCR turn + // If the remote asks a clarifying question (turn ends without + // ExitPlanMode), we must keep polling — the user can reply in + // the browser and reach ExitPlanMode in a later turn. + // Only error subtypes (error_during_execution, error_max_turns, + // etc.) mean the session is actually dead. + this.terminated = { subtype: m.subtype } + } + } + + // Skip-scan when nothing could have moved the target: no new events, no + // rejection last tick. A rejection moves the newest-non-rejected target. + const shouldScan = newEvents.length > 0 || this.rescanAfterRejection + this.rescanAfterRejection = false + + let found: + | { kind: 'approved'; plan: string } + | { kind: 'teleport'; plan: string } + | { kind: 'rejected'; id: string } + | { kind: 'pending' } + | null = null + if (shouldScan) { + for (let i = this.exitPlanCalls.length - 1; i >= 0; i--) { + const id = this.exitPlanCalls[i]! + if (this.rejectedIds.has(id)) continue + const tr = this.results.get(id) + if (!tr) { + found = { kind: 'pending' } + } else if (tr.is_error === true) { + const teleportPlan = extractTeleportPlan(tr.content) + found = + teleportPlan !== null + ? { kind: 'teleport', plan: teleportPlan } + : { kind: 'rejected', id } + } else { + found = { kind: 'approved', plan: extractApprovedPlan(tr.content) } + } + break + } + if (found?.kind === 'approved' || found?.kind === 'teleport') return found + } + + // Bookkeeping before the terminated check — a batch can contain BOTH a + // rejected tool_result and a {type:'result'}; rejectCount must reflect + // the rejection even though terminated takes return precedence. + if (found?.kind === 'rejected') { + this.rejectedIds.add(found.id) + this.rescanAfterRejection = true + } + if (this.terminated) { + return { kind: 'terminated', subtype: this.terminated.subtype } + } + if (found?.kind === 'rejected') { + return found + } + if (found?.kind === 'pending') { + this.everSeenPending = true + return found + } + return { kind: 'unchanged' } + } +} + +export type PollResult = { + plan: string + rejectCount: number + /** 'local' = user clicked teleport (execute here, archive remote). 'remote' = user approved in-CCR execution (don't archive). */ + executionTarget: 'local' | 'remote' +} + +// Returns the approved plan text and where the user wants it executed. +// 'approved' scrapes from the "## Approved Plan:" marker (ExitPlanModeV2Tool +// default branch) — the model writes plan to a file inside CCR and calls +// ExitPlanMode({allowedPrompts}), so input.plan is never in threadstore. +// 'teleport' scrapes from the ULTRAPLAN_TELEPORT_SENTINEL in a deny tool_result — +// browser sends a rejection so the remote stays in plan mode, with the plan +// text embedded in the feedback. Normal rejections (is_error === true, no +// sentinel) are tracked and skipped so the user can iterate in the browser. +export async function pollForApprovedExitPlanMode( + sessionId: string, + timeoutMs: number, + onPhaseChange?: (phase: UltraplanPhase) => void, + shouldStop?: () => boolean, +): Promise { + const deadline = Date.now() + timeoutMs + const scanner = new ExitPlanModeScanner() + let cursor: string | null = null + let failures = 0 + let lastPhase: UltraplanPhase = 'running' + + while (Date.now() < deadline) { + if (shouldStop?.()) { + throw new UltraplanPollError( + 'poll stopped by caller', + 'stopped', + scanner.rejectCount, + ) + } + let newEvents: SDKMessage[] + let sessionStatus: PollRemoteSessionResponse['sessionStatus'] + try { + // Metadata fetch (session_status) is the needs_input signal — + // threadstore doesn't persist result(success) turn-end events, so + // idle status is the only authoritative "remote is waiting" marker. + const resp = await pollRemoteSessionEvents(sessionId, cursor) + newEvents = resp.newEvents + cursor = resp.lastEventId + sessionStatus = resp.sessionStatus + failures = 0 + } catch (e) { + const transient = isTransientNetworkError(e) + if (!transient || ++failures >= MAX_CONSECUTIVE_FAILURES) { + throw new UltraplanPollError( + e instanceof Error ? e.message : String(e), + 'network_or_unknown', + scanner.rejectCount, + { cause: e }, + ) + } + await sleep(POLL_INTERVAL_MS) + continue + } + + let result: ScanResult + try { + result = scanner.ingest(newEvents) + } catch (e) { + throw new UltraplanPollError( + e instanceof Error ? e.message : String(e), + 'extract_marker_missing', + scanner.rejectCount, + ) + } + if (result.kind === 'approved') { + return { + plan: result.plan, + rejectCount: scanner.rejectCount, + executionTarget: 'remote', + } + } + if (result.kind === 'teleport') { + return { + plan: result.plan, + rejectCount: scanner.rejectCount, + executionTarget: 'local', + } + } + if (result.kind === 'terminated') { + throw new UltraplanPollError( + `remote session ended (${result.subtype}) before plan approval`, + 'terminated', + scanner.rejectCount, + ) + } + // plan_ready from the event stream wins; otherwise idle session status + // means the remote asked a question and is waiting for a browser reply. + // requires_action with no pending plan is also needs_input — the remote + // may be blocked on a non-ExitPlanMode permission prompt. + // CCR briefly flips to 'idle' between tool turns (see STABLE_IDLE_POLLS + // in RemoteAgentTask). Only trust idle when no new events arrived — + // events flowing means the session is working regardless of the status + // snapshot. This also makes needs_input → running snap back on the first + // poll that sees the user's reply event, even if session_status lags. + const quietIdle = + (sessionStatus === 'idle' || sessionStatus === 'requires_action') && + newEvents.length === 0 + const phase: UltraplanPhase = scanner.hasPendingPlan + ? 'plan_ready' + : quietIdle + ? 'needs_input' + : 'running' + if (phase !== lastPhase) { + logForDebugging(`[ultraplan] phase ${lastPhase} → ${phase}`) + lastPhase = phase + onPhaseChange?.(phase) + } + await sleep(POLL_INTERVAL_MS) + } + + throw new UltraplanPollError( + scanner.everSeenPending + ? `no approval after ${timeoutMs / 1000}s` + : `ExitPlanMode never reached after ${timeoutMs / 1000}s (the remote container failed to start, or session ID mismatch?)`, + scanner.everSeenPending ? 'timeout_pending' : 'timeout_no_plan', + scanner.rejectCount, + ) +} + +// tool_result content may be string or [{type:'text',text}] depending on +// threadstore encoding. +function contentToText(content: ToolResultBlockParam['content']): string { + return typeof content === 'string' + ? content + : Array.isArray(content) + ? content.map(b => ('text' in b ? b.text : '')).join('') + : '' +} + +// Extracts the plan text after the ULTRAPLAN_TELEPORT_SENTINEL marker. +// Returns null when the sentinel is absent — callers treat null as a normal +// user rejection (scanner falls through to { kind: 'rejected' }). +function extractTeleportPlan( + content: ToolResultBlockParam['content'], +): string | null { + const text = contentToText(content) + const marker = `${ULTRAPLAN_TELEPORT_SENTINEL}\n` + const idx = text.indexOf(marker) + if (idx === -1) return null + return text.slice(idx + marker.length).trimEnd() +} + +// Plan is echoed in tool_result content as "## Approved Plan:\n" or +// "## Approved Plan (edited by user):\n" (ExitPlanModeV2Tool). +function extractApprovedPlan(content: ToolResultBlockParam['content']): string { + const text = contentToText(content) + // Try both markers — edited plans use a different label. + const markers = [ + '## Approved Plan (edited by user):\n', + '## Approved Plan:\n', + ] + for (const marker of markers) { + const idx = text.indexOf(marker) + if (idx !== -1) { + return text.slice(idx + marker.length).trimEnd() + } + } + throw new Error( + `ExitPlanMode approved but tool_result has no "## Approved Plan:" marker — remote may have hit the empty-plan or isAgent branch. Content preview: ${text.slice(0, 200)}`, + ) +} diff --git a/src/utils/ultraplan/keyword.ts b/src/utils/ultraplan/keyword.ts new file mode 100644 index 0000000000000000000000000000000000000000..5e56919addf20c611ff6b5316040397c853bcd6d --- /dev/null +++ b/src/utils/ultraplan/keyword.ts @@ -0,0 +1,127 @@ +type TriggerPosition = { word: string; start: number; end: number } + +const OPEN_TO_CLOSE: Record = { + '`': '`', + '"': '"', + '<': '>', + '{': '}', + '[': ']', + '(': ')', + "'": "'", +} + +/** + * Find keyword positions, skipping occurrences that are clearly not a + * launch directive: + * + * - Inside paired delimiters: backticks, double quotes, angle brackets + * (tag-like only, so `n < 5 ultraplan n > 10` is not a phantom range), + * curly braces, square brackets (innermost — preExpansionInput has + * `[Pasted text #N]` placeholders), parentheses. Single quotes are + * delimiters only when not an apostrophe — the opening quote must be + * preceded by a non-word char (or start) and the closing quote must be + * followed by a non-word char (or end), so "let's ultraplan it's" + * still triggers. + * + * - Path/identifier-like context: immediately preceded or followed by + * `/`, `\`, or `-`, or followed by `.` + word char (file extension). + * `\b` sees a boundary at `-`, so `ultraplan-s` would otherwise + * match. This keeps `src/ultraplan/foo.ts`, `ultraplan.tsx`, and + * `--ultraplan-mode` from triggering while `ultraplan.` at a sentence + * end still does. + * + * - Followed by `?`: a question about the feature shouldn't invoke it. + * Other sentence punctuation (`.`, `,`, `!`) still triggers. + * + * - Slash command input: text starting with `/` is a slash command + * invocation (processUserInput.ts routes it to processSlashCommand, + * not keyword detection), so `/rename ultraplan foo` never triggers. + * Without this, PromptInput would rainbow-highlight the word and show + * the "will launch ultraplan" notification even though submitting the + * input runs /rename, not /ultraplan. + * + * Shape matches findThinkingTriggerPositions (thinking.ts) so + * PromptInput treats both trigger types uniformly. + */ +function findKeywordTriggerPositions( + text: string, + keyword: string, +): TriggerPosition[] { + const re = new RegExp(keyword, 'i') + if (!re.test(text)) return [] + if (text.startsWith('/')) return [] + const quotedRanges: Array<{ start: number; end: number }> = [] + let openQuote: string | null = null + let openAt = 0 + const isWord = (ch: string | undefined) => !!ch && /[\p{L}\p{N}_]/u.test(ch) + for (let i = 0; i < text.length; i++) { + const ch = text[i]! + if (openQuote) { + if (openQuote === '[' && ch === '[') { + openAt = i + continue + } + if (ch !== OPEN_TO_CLOSE[openQuote]) continue + if (openQuote === "'" && isWord(text[i + 1])) continue + quotedRanges.push({ start: openAt, end: i + 1 }) + openQuote = null + } else if ( + (ch === '<' && i + 1 < text.length && /[a-zA-Z/]/.test(text[i + 1]!)) || + (ch === "'" && !isWord(text[i - 1])) || + (ch !== '<' && ch !== "'" && ch in OPEN_TO_CLOSE) + ) { + openQuote = ch + openAt = i + } + } + + const positions: TriggerPosition[] = [] + const wordRe = new RegExp(`\\b${keyword}\\b`, 'gi') + const matches = text.matchAll(wordRe) + for (const match of matches) { + if (match.index === undefined) continue + const start = match.index + const end = start + match[0].length + if (quotedRanges.some(r => start >= r.start && start < r.end)) continue + const before = text[start - 1] + const after = text[end] + if (before === '/' || before === '\\' || before === '-') continue + if (after === '/' || after === '\\' || after === '-' || after === '?') + continue + if (after === '.' && isWord(text[end + 1])) continue + positions.push({ word: match[0], start, end }) + } + return positions +} + +export function findUltraplanTriggerPositions(text: string): TriggerPosition[] { + return findKeywordTriggerPositions(text, 'ultraplan') +} + +export function findUltrareviewTriggerPositions( + text: string, +): TriggerPosition[] { + return findKeywordTriggerPositions(text, 'ultrareview') +} + +export function hasUltraplanKeyword(text: string): boolean { + return findUltraplanTriggerPositions(text).length > 0 +} + +export function hasUltrareviewKeyword(text: string): boolean { + return findUltrareviewTriggerPositions(text).length > 0 +} + +/** + * Replace the first triggerable "ultraplan" with "plan" so the forwarded + * prompt stays grammatical ("please ultraplan this" → "please plan this"). + * Preserves the user's casing of the "plan" suffix. + */ +export function replaceUltraplanKeyword(text: string): string { + const [trigger] = findUltraplanTriggerPositions(text) + if (!trigger) return text + const before = text.slice(0, trigger.start) + const after = text.slice(trigger.end) + if (!(before + after).trim()) return '' + return before + trigger.word.slice('ultra'.length) + after +}