mirror of
http://10.0.2.1:3031/sauer/claude-code.git
synced 2026-06-30 11:36:57 +10:00
362 lines
55 KiB
TypeScript
362 lines
55 KiB
TypeScript
|
|
/**
|
|||
|
|
* MCP subcommand handlers — extracted from main.tsx for lazy loading.
|
|||
|
|
* These are dynamically imported only when the corresponding `claude mcp *` command runs.
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
import { stat } from 'fs/promises';
|
|||
|
|
import pMap from 'p-map';
|
|||
|
|
import { cwd } from 'process';
|
|||
|
|
import React from 'react';
|
|||
|
|
import { MCPServerDesktopImportDialog } from '../../components/MCPServerDesktopImportDialog.js';
|
|||
|
|
import { render } from '../../ink.js';
|
|||
|
|
import { KeybindingSetup } from '../../keybindings/KeybindingProviderSetup.js';
|
|||
|
|
import { type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, logEvent } from '../../services/analytics/index.js';
|
|||
|
|
import { clearMcpClientConfig, clearServerTokensFromLocalStorage, getMcpClientConfig, readClientSecret, saveMcpClientSecret } from '../../services/mcp/auth.js';
|
|||
|
|
import { connectToServer, getMcpServerConnectionBatchSize } from '../../services/mcp/client.js';
|
|||
|
|
import { addMcpConfig, getAllMcpConfigs, getMcpConfigByName, getMcpConfigsByScope, removeMcpConfig } from '../../services/mcp/config.js';
|
|||
|
|
import type { ConfigScope, ScopedMcpServerConfig } from '../../services/mcp/types.js';
|
|||
|
|
import { describeMcpConfigFilePath, ensureConfigScope, getScopeLabel } from '../../services/mcp/utils.js';
|
|||
|
|
import { AppStateProvider } from '../../state/AppState.js';
|
|||
|
|
import { getCurrentProjectConfig, getGlobalConfig, saveCurrentProjectConfig } from '../../utils/config.js';
|
|||
|
|
import { isFsInaccessible } from '../../utils/errors.js';
|
|||
|
|
import { gracefulShutdown } from '../../utils/gracefulShutdown.js';
|
|||
|
|
import { safeParseJSON } from '../../utils/json.js';
|
|||
|
|
import { getPlatform } from '../../utils/platform.js';
|
|||
|
|
import { cliError, cliOk } from '../exit.js';
|
|||
|
|
async function checkMcpServerHealth(name: string, server: ScopedMcpServerConfig): Promise<string> {
|
|||
|
|
try {
|
|||
|
|
const result = await connectToServer(name, server);
|
|||
|
|
if (result.type === 'connected') {
|
|||
|
|
return '✓ Connected';
|
|||
|
|
} else if (result.type === 'needs-auth') {
|
|||
|
|
return '! Needs authentication';
|
|||
|
|
} else {
|
|||
|
|
return '✗ Failed to connect';
|
|||
|
|
}
|
|||
|
|
} catch (_error) {
|
|||
|
|
return '✗ Connection error';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// mcp serve (lines 4512–4532)
|
|||
|
|
export async function mcpServeHandler({
|
|||
|
|
debug,
|
|||
|
|
verbose
|
|||
|
|
}: {
|
|||
|
|
debug?: boolean;
|
|||
|
|
verbose?: boolean;
|
|||
|
|
}): Promise<void> {
|
|||
|
|
const providedCwd = cwd();
|
|||
|
|
logEvent('tengu_mcp_start', {});
|
|||
|
|
try {
|
|||
|
|
await stat(providedCwd);
|
|||
|
|
} catch (error) {
|
|||
|
|
if (isFsInaccessible(error)) {
|
|||
|
|
cliError(`Error: Directory ${providedCwd} does not exist`);
|
|||
|
|
}
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
const {
|
|||
|
|
setup
|
|||
|
|
} = await import('../../setup.js');
|
|||
|
|
await setup(providedCwd, 'default', false, false, undefined, false);
|
|||
|
|
const {
|
|||
|
|
startMCPServer
|
|||
|
|
} = await import('../../entrypoints/mcp.js');
|
|||
|
|
await startMCPServer(providedCwd, debug ?? false, verbose ?? false);
|
|||
|
|
} catch (error) {
|
|||
|
|
cliError(`Error: Failed to start MCP server: ${error}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// mcp remove (lines 4545–4635)
|
|||
|
|
export async function mcpRemoveHandler(name: string, options: {
|
|||
|
|
scope?: string;
|
|||
|
|
}): Promise<void> {
|
|||
|
|
// Look up config before removing so we can clean up secure storage
|
|||
|
|
const serverBeforeRemoval = getMcpConfigByName(name);
|
|||
|
|
const cleanupSecureStorage = () => {
|
|||
|
|
if (serverBeforeRemoval && (serverBeforeRemoval.type === 'sse' || serverBeforeRemoval.type === 'http')) {
|
|||
|
|
clearServerTokensFromLocalStorage(name, serverBeforeRemoval);
|
|||
|
|
clearMcpClientConfig(name, serverBeforeRemoval);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
try {
|
|||
|
|
if (options.scope) {
|
|||
|
|
const scope = ensureConfigScope(options.scope);
|
|||
|
|
logEvent('tengu_mcp_delete', {
|
|||
|
|
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
|||
|
|
scope: scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
|||
|
|
});
|
|||
|
|
await removeMcpConfig(name, scope);
|
|||
|
|
cleanupSecureStorage();
|
|||
|
|
process.stdout.write(`Removed MCP server ${name} from ${scope} config\n`);
|
|||
|
|
cliOk(`File modified: ${describeMcpConfigFilePath(scope)}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// If no scope specified, check where the server exists
|
|||
|
|
const projectConfig = getCurrentProjectConfig();
|
|||
|
|
const globalConfig = getGlobalConfig();
|
|||
|
|
|
|||
|
|
// Check if server exists in project scope (.mcp.json)
|
|||
|
|
const {
|
|||
|
|
servers: projectServers
|
|||
|
|
} = getMcpConfigsByScope('project');
|
|||
|
|
const mcpJsonExists = !!projectServers[name];
|
|||
|
|
|
|||
|
|
// Count how many scopes contain this server
|
|||
|
|
const scopes: Array<Exclude<ConfigScope, 'dynamic'>> = [];
|
|||
|
|
if (projectConfig.mcpServers?.[name]) scopes.push('local');
|
|||
|
|
if (mcpJsonExists) scopes.push('project');
|
|||
|
|
if (globalConfig.mcpServers?.[name]) scopes.push('user');
|
|||
|
|
if (scopes.length === 0) {
|
|||
|
|
cliError(`No MCP server found with name: "${name}"`);
|
|||
|
|
} else if (scopes.length === 1) {
|
|||
|
|
// Server exists in only one scope, remove it
|
|||
|
|
const scope = scopes[0]!;
|
|||
|
|
logEvent('tengu_mcp_delete', {
|
|||
|
|
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
|||
|
|
scope: scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
|||
|
|
});
|
|||
|
|
await removeMcpConfig(name, scope);
|
|||
|
|
cleanupSecureStorage();
|
|||
|
|
process.stdout.write(`Removed MCP server "${name}" from ${scope} config\n`);
|
|||
|
|
cliOk(`File modified: ${describeMcpConfigFilePath(scope)}`);
|
|||
|
|
} else {
|
|||
|
|
// Server exists in multiple scopes
|
|||
|
|
process.stderr.write(`MCP server "${name}" exists in multiple scopes:\n`);
|
|||
|
|
scopes.forEach(scope => {
|
|||
|
|
process.stderr.write(` - ${getScopeLabel(scope)} (${describeMcpConfigFilePath(scope)})\n`);
|
|||
|
|
});
|
|||
|
|
process.stderr.write('\nTo remove from a specific scope, use:\n');
|
|||
|
|
scopes.forEach(scope => {
|
|||
|
|
process.stderr.write(` claude mcp remove "${name}" -s ${scope}\n`);
|
|||
|
|
});
|
|||
|
|
cliError();
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
cliError((error as Error).message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// mcp list (lines 4641–4688)
|
|||
|
|
export async function mcpListHandler(): Promise<void> {
|
|||
|
|
logEvent('tengu_mcp_list', {});
|
|||
|
|
const {
|
|||
|
|
servers: configs
|
|||
|
|
} = await getAllMcpConfigs();
|
|||
|
|
if (Object.keys(configs).length === 0) {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log('No MCP servers configured. Use `claude mcp add` to add a server.');
|
|||
|
|
} else {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log('Checking MCP server health...\n');
|
|||
|
|
|
|||
|
|
// Check servers concurrently
|
|||
|
|
const entries = Object.entries(configs);
|
|||
|
|
const results = await pMap(entries, async ([name, server]) => ({
|
|||
|
|
name,
|
|||
|
|
server,
|
|||
|
|
status: await checkMcpServerHealth(name, server)
|
|||
|
|
}), {
|
|||
|
|
concurrency: getMcpServerConnectionBatchSize()
|
|||
|
|
});
|
|||
|
|
for (const {
|
|||
|
|
name,
|
|||
|
|
server,
|
|||
|
|
status
|
|||
|
|
} of results) {
|
|||
|
|
// Intentionally excluding sse-ide servers here since they're internal
|
|||
|
|
if (server.type === 'sse') {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(`${name}: ${server.url} (SSE) - ${status}`);
|
|||
|
|
} else if (server.type === 'http') {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(`${name}: ${server.url} (HTTP) - ${status}`);
|
|||
|
|
} else if (server.type === 'claudeai-proxy') {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(`${name}: ${server.url} - ${status}`);
|
|||
|
|
} else if (!server.type || server.type === 'stdio') {
|
|||
|
|
const args = Array.isArray(server.args) ? server.args : [];
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(`${name}: ${server.command} ${args.join(' ')} - ${status}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// Use gracefulShutdown to properly clean up MCP server connections
|
|||
|
|
// (process.exit bypasses cleanup handlers, leaving child processes orphaned)
|
|||
|
|
await gracefulShutdown(0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// mcp get (lines 4694–4786)
|
|||
|
|
export async function mcpGetHandler(name: string): Promise<void> {
|
|||
|
|
logEvent('tengu_mcp_get', {
|
|||
|
|
name: name as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
|||
|
|
});
|
|||
|
|
const server = getMcpConfigByName(name);
|
|||
|
|
if (!server) {
|
|||
|
|
cliError(`No MCP server found with name: ${name}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(`${name}:`);
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` Scope: ${getScopeLabel(server.scope)}`);
|
|||
|
|
|
|||
|
|
// Check server health
|
|||
|
|
const status = await checkMcpServerHealth(name, server);
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` Status: ${status}`);
|
|||
|
|
|
|||
|
|
// Intentionally excluding sse-ide servers here since they're internal
|
|||
|
|
if (server.type === 'sse') {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` Type: sse`);
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` URL: ${server.url}`);
|
|||
|
|
if (server.headers) {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(' Headers:');
|
|||
|
|
for (const [key, value] of Object.entries(server.headers)) {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` ${key}: ${value}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (server.oauth?.clientId || server.oauth?.callbackPort) {
|
|||
|
|
const parts: string[] = [];
|
|||
|
|
if (server.oauth.clientId) {
|
|||
|
|
parts.push('client_id configured');
|
|||
|
|
const clientConfig = getMcpClientConfig(name, server);
|
|||
|
|
if (clientConfig?.clientSecret) parts.push('client_secret configured');
|
|||
|
|
}
|
|||
|
|
if (server.oauth.callbackPort) parts.push(`callback_port ${server.oauth.callbackPort}`);
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` OAuth: ${parts.join(', ')}`);
|
|||
|
|
}
|
|||
|
|
} else if (server.type === 'http') {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` Type: http`);
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` URL: ${server.url}`);
|
|||
|
|
if (server.headers) {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(' Headers:');
|
|||
|
|
for (const [key, value] of Object.entries(server.headers)) {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` ${key}: ${value}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (server.oauth?.clientId || server.oauth?.callbackPort) {
|
|||
|
|
const parts: string[] = [];
|
|||
|
|
if (server.oauth.clientId) {
|
|||
|
|
parts.push('client_id configured');
|
|||
|
|
const clientConfig = getMcpClientConfig(name, server);
|
|||
|
|
if (clientConfig?.clientSecret) parts.push('client_secret configured');
|
|||
|
|
}
|
|||
|
|
if (server.oauth.callbackPort) parts.push(`callback_port ${server.oauth.callbackPort}`);
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` OAuth: ${parts.join(', ')}`);
|
|||
|
|
}
|
|||
|
|
} else if (server.type === 'stdio') {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` Type: stdio`);
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` Command: ${server.command}`);
|
|||
|
|
const args = Array.isArray(server.args) ? server.args : [];
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` Args: ${args.join(' ')}`);
|
|||
|
|
if (server.env) {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(' Environment:');
|
|||
|
|
for (const [key, value] of Object.entries(server.env)) {
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(` ${key}=${value}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
|||
|
|
console.log(`\nTo remove this server, run: claude mcp remove "${name}" -s ${server.scope}`);
|
|||
|
|
// Use gracefulShutdown to properly clean up MCP server connections
|
|||
|
|
// (process.exit bypasses cleanup handlers, leaving child processes orphaned)
|
|||
|
|
await gracefulShutdown(0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// mcp add-json (lines 4801–4870)
|
|||
|
|
export async function mcpAddJsonHandler(name: string, json: string, options: {
|
|||
|
|
scope?: string;
|
|||
|
|
clientSecret?: true;
|
|||
|
|
}): Promise<void> {
|
|||
|
|
try {
|
|||
|
|
const scope = ensureConfigScope(options.scope);
|
|||
|
|
const parsedJson = safeParseJSON(json);
|
|||
|
|
|
|||
|
|
// Read secret before writing config so cancellation doesn't leave partial state
|
|||
|
|
const needsSecret = options.clientSecret && parsedJson && typeof parsedJson === 'object' && 'type' in parsedJson && (parsedJson.type === 'sse' || parsedJson.type === 'http') && 'url' in parsedJson && typeof parsedJson.url === 'string' && 'oauth' in parsedJson && parsedJson.oauth && typeof parsedJson.oauth === 'object' && 'clientId' in parsedJson.oauth;
|
|||
|
|
const clientSecret = needsSecret ? await readClientSecret() : undefined;
|
|||
|
|
await addMcpConfig(name, parsedJson, scope);
|
|||
|
|
const transportType = parsedJson && typeof parsedJson === 'object' && 'type' in parsedJson ? String(parsedJson.type || 'stdio') : 'stdio';
|
|||
|
|
if (clientSecret && parsedJson && typeof parsedJson === 'object' && 'type' in parsedJson && (parsedJson.type === 'sse' || parsedJson.type === 'http') && 'url' in parsedJson && typeof parsedJson.url === 'string') {
|
|||
|
|
saveMcpClientSecret(name, {
|
|||
|
|
type: parsedJson.type,
|
|||
|
|
url: parsedJson.url
|
|||
|
|
}, clientSecret);
|
|||
|
|
}
|
|||
|
|
logEvent('tengu_mcp_add', {
|
|||
|
|
scope: scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
|||
|
|
source: 'json' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
|||
|
|
type: transportType as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
|||
|
|
});
|
|||
|
|
cliOk(`Added ${transportType} MCP server ${name} to ${scope} config`);
|
|||
|
|
} catch (error) {
|
|||
|
|
cliError((error as Error).message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// mcp add-from-claude-desktop (lines 4881–4927)
|
|||
|
|
export async function mcpAddFromDesktopHandler(options: {
|
|||
|
|
scope?: string;
|
|||
|
|
}): Promise<void> {
|
|||
|
|
try {
|
|||
|
|
const scope = ensureConfigScope(options.scope);
|
|||
|
|
const platform = getPlatform();
|
|||
|
|
logEvent('tengu_mcp_add', {
|
|||
|
|
scope: scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
|||
|
|
platform: platform as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
|
|||
|
|
source: 'desktop' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS
|
|||
|
|
});
|
|||
|
|
const {
|
|||
|
|
readClaudeDesktopMcpServers
|
|||
|
|
} = await import('../../utils/claudeDesktop.js');
|
|||
|
|
const servers = await readClaudeDesktopMcpServers();
|
|||
|
|
if (Object.keys(servers).length === 0) {
|
|||
|
|
cliOk('No MCP servers found in Claude Desktop configuration or configuration file does not exist.');
|
|||
|
|
}
|
|||
|
|
const {
|
|||
|
|
unmount
|
|||
|
|
} = await render(<AppStateProvider>
|
|||
|
|
<KeybindingSetup>
|
|||
|
|
<MCPServerDesktopImportDialog servers={servers} scope={scope} onDone={() => {
|
|||
|
|
unmount();
|
|||
|
|
}} />
|
|||
|
|
</KeybindingSetup>
|
|||
|
|
</AppStateProvider>, {
|
|||
|
|
exitOnCtrlC: true
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
cliError((error as Error).message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// mcp reset-project-choices (lines 4935–4952)
|
|||
|
|
export async function mcpResetChoicesHandler(): Promise<void> {
|
|||
|
|
logEvent('tengu_mcp_reset_mcpjson_choices', {});
|
|||
|
|
saveCurrentProjectConfig(current => ({
|
|||
|
|
...current,
|
|||
|
|
enabledMcpjsonServers: [],
|
|||
|
|
disabledMcpjsonServers: [],
|
|||
|
|
enableAllProjectMcpServers: false
|
|||
|
|
}));
|
|||
|
|
cliOk('All project-scoped (.mcp.json) server approvals and rejections have been reset.\n' + 'You will be prompted for approval next time you start Claude Code.');
|
|||
|
|
}
|
|||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJzdGF0IiwicE1hcCIsImN3ZCIsIlJlYWN0IiwiTUNQU2VydmVyRGVza3RvcEltcG9ydERpYWxvZyIsInJlbmRlciIsIktleWJpbmRpbmdTZXR1cCIsIkFuYWx5dGljc01ldGFkYXRhX0lfVkVSSUZJRURfVEhJU19JU19OT1RfQ09ERV9PUl9GSUxFUEFUSFMiLCJsb2dFdmVudCIsImNsZWFyTWNwQ2xpZW50Q29uZmlnIiwiY2xlYXJTZXJ2ZXJUb2tlbnNGcm9tTG9jYWxTdG9yYWdlIiwiZ2V0TWNwQ2xpZW50Q29uZmlnIiwicmVhZENsaWVudFNlY3JldCIsInNhdmVNY3BDbGllbnRTZWNyZXQiLCJjb25uZWN0VG9TZXJ2ZXIiLCJnZXRNY3BTZXJ2ZXJDb25uZWN0aW9uQmF0Y2hTaXplIiwiYWRkTWNwQ29uZmlnIiwiZ2V0QWxsTWNwQ29uZmlncyIsImdldE1jcENvbmZpZ0J5TmFtZSIsImdldE1jcENvbmZpZ3NCeVNjb3BlIiwicmVtb3ZlTWNwQ29uZmlnIiwiQ29uZmlnU2NvcGUiLCJTY29wZWRNY3BTZXJ2ZXJDb25maWciLCJkZXNjcmliZU1jcENvbmZpZ0ZpbGVQYXRoIiwiZW5zdXJlQ29uZmlnU2NvcGUiLCJnZXRTY29wZUxhYmVsIiwiQXBwU3RhdGVQcm92aWRlciIsImdldEN1cnJlbnRQcm9qZWN0Q29uZmlnIiwiZ2V0R2xvYmFsQ29uZmlnIiwic2F2ZUN1cnJlbnRQcm9qZWN0Q29uZmlnIiwiaXNGc0luYWNjZXNzaWJsZSIsImdyYWNlZnVsU2h1dGRvd24iLCJzYWZlUGFyc2VKU09OIiwiZ2V0UGxhdGZvcm0iLCJjbGlFcnJvciIsImNsaU9rIiwiY2hlY2tNY3BTZXJ2ZXJIZWFsdGgiLCJuYW1lIiwic2VydmVyIiwiUHJvbWlzZSIsInJlc3VsdCIsInR5cGUiLCJfZXJyb3IiLCJtY3BTZXJ2ZUhhbmRsZXIiLCJkZWJ1ZyIsInZlcmJvc2UiLCJwcm92aWRlZEN3ZCIsImVycm9yIiwic2V0dXAiLCJ1bmRlZmluZWQiLCJzdGFydE1DUFNlcnZlciIsIm1jcFJlbW92ZUhhbmRsZXIiLCJvcHRpb25zIiwic2NvcGUiLCJzZXJ2ZXJCZWZvcmVSZW1vdmFsIiwiY2xlYW51cFNlY3VyZVN0b3JhZ2UiLCJwcm9jZXNzIiwic3Rkb3V0Iiwid3JpdGUiLCJwcm9qZWN0Q29uZmlnIiwiZ2xvYmFsQ29uZmlnIiwic2VydmVycyIsInByb2plY3RTZXJ2ZXJzIiwibWNwSnNvbkV4aXN0cyIsInNjb3BlcyIsIkFycmF5IiwiRXhjbHVkZSIsIm1jcFNlcnZlcnMiLCJwdXNoIiwibGVuZ3RoIiwic3RkZXJyIiwiZm9yRWFjaCIsIkVycm9yIiwibWVzc2FnZSIsIm1jcExpc3RIYW5kbGVyIiwiY29uZmlncyIsIk9iamVjdCIsImtleXMiLCJjb25zb2xlIiwibG9nIiwiZW50cmllcyIsInJlc3VsdHMiLCJzdGF0dXMiLCJjb25jdXJyZW5jeSIsInVybCIsImFyZ3MiLCJpc0FycmF5IiwiY29tbWFuZCIsImpvaW4iLCJtY3BHZXRIYW5kbGVyIiwiaGVhZGVycyIsImtleSIsInZhbHVlIiwib2F1dGgiLCJjbGllbnRJZCIsImNhbGxiYWNrUG9ydCIsInBhcnRzIiwiY2xpZW50Q29uZmlnIiwiY2xpZW50U2VjcmV0IiwiZW52IiwibWNwQWRkSnNvbkhhbmRsZXIiLCJqc29uIiwicGFyc2VkSnNvbiIsIm5lZWRzU2VjcmV0IiwidHJhbnNwb3J0VHlwZSIsIlN0cmluZyIsInNvdXJjZSIsIm1jcEFkZEZyb21EZXNrdG9wSGFuZGxlciIsInBsYXRmb3JtIiwicmVhZENsYXVkZURlc2t0b3BNY3BTZXJ2ZXJzIiwidW5tb3VudCIsImV4aXRPbkN0cmxDIiwibWNwUmVzZXRDaG9pY2VzSGFuZGxlciIsImN1cnJlbnQiLCJlbmFibGVkTWNwanNvblNlcnZlcnMiLCJkaXNhYmxlZE1jcGpzb25TZXJ2ZXJzIiwiZW5hYmxlQWxsUHJvamVjdE1jcFNlcnZlcnMiXSwic291cmNlcyI6WyJtY3AudHN4Il0sInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogTUNQIHN1YmNvbW1hbmQgaGFuZGxlcnMg4oCUIGV4dHJhY3RlZCBmcm9tIG1haW4udHN4IGZvciBsYXp5IGxvYWRpbmcuXG4gKiBUaGVzZSBhcmUgZHluYW1pY2FsbHkgaW1wb3J0ZWQgb25seSB3aGVuIHRoZSBjb3JyZXNwb25kaW5nIGBjbGF1ZGUgbWNwICpgIGNvbW1hbmQgcnVucy5cbiAqL1xuXG5pbXBvcnQgeyBzdGF0IH0gZnJvbSAnZnMvcHJvbWlzZXMnXG5pbXBvcnQgcE1hcCBmcm9tICdwLW1hcCdcbmltcG9ydCB7IGN3ZCB9IGZyb20gJ3Byb2Nlc3MnXG5pbXBvcnQgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBNQ1BTZXJ2ZXJEZXNrdG9wSW1wb3J0RGlhbG9nIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9NQ1BTZXJ2ZXJEZXNrdG9wSW1wb3J0RGlhbG9nLmpzJ1xuaW1wb3J0IHsgcmVuZGVyIH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHsgS2V5YmluZGluZ1NldHVwIH0gZnJvbSAnLi4vLi4va2V5YmluZGluZ3MvS2V5YmluZGluZ1Byb3ZpZGVyU2V0dXAuanMnXG5pbXBvcnQge1xuICB0eXBlIEFuYWx5dGljc01ldGFkYXRhX0lfVkVSSUZJRURfVEhJU19JU19OT1RfQ09ERV9PUl9GSUxFUEFUSFMsXG4gIGxvZ0V2ZW50LFxufSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9hbmFseXRpY3MvaW5kZXguanMnXG5pbXBvcnQge1xuICBjbGVhck1jcENsaWVudENvbmZpZyxcbiAgY2xlYXJTZXJ2ZXJUb2tlbnNGcm9tTG9jYWxTdG9yYWdlLFxuICBnZXRNY3BDbGllbnRDb25maWcsXG4gIHJlYWRDbGllbnRTZWNyZXQsXG4gIHNhdmVNY3BDbGllbnRTZWNyZXQsXG59IGZyb20gJy4uLy4uL3NlcnZpY2VzL21jcC9hdXRoLmpzJ1xuaW1wb3J0IHtcbiAgY29ubmVjdFRvU2VydmVyLFxuICBnZXRNY3BTZXJ2ZXJDb25uZWN0aW9uQmF0Y2hTaXplLFxufSBmcm9tICcuLi8uLi9zZXJ2aWNlcy9tY3AvY2xpZW50LmpzJ1xuaW1wb3J0IHtcbiAgYWRkTWNwQ29uZmlnLFxuICBnZXRBbGxNY3BDb25maWdzLFxuICBnZXRNY3BDb25maWdCeU5hbWUsXG4gIGdldE1jcENvbmZpZ3NCeVNjb3BlLFxuICByZW1vdmVNY3BDb25maWcsXG59IGZyb20gJy4uLy4uL3NlcnZpY2VzL21jcC9jb25maWcuanMnXG5pbXBvcnQgdHlwZSB7XG4gIENvbmZpZ1Njb3BlLFxuICBTY29wZWRNY3BTZXJ2ZXJDb25maWcsXG59IGZyb20gJy4uLy4uL3NlcnZpY2VzL21jcC90eXBlcy5qcydcbmltcG9ydCB7XG4gIGRlc2NyaWJlTWNwQ29uZmlnRmlsZVBhdGgsXG4gIGVuc3VyZUNvbmZpZ1Njb3BlLFxuICBnZXRTY29wZUx
|