mirror of
http://10.0.2.1:3031/sauer/claude-code.git
synced 2026-06-30 20:26:58 +10:00
362 lines
48 KiB
TypeScript
362 lines
48 KiB
TypeScript
|
|
import chalk from 'chalk';
|
||
|
|
import figures from 'figures';
|
||
|
|
import * as React from 'react';
|
||
|
|
import { color, Text } from '../ink.js';
|
||
|
|
import type { MCPServerConnection } from '../services/mcp/types.js';
|
||
|
|
import { getAccountInformation, isClaudeAISubscriber } from './auth.js';
|
||
|
|
import { getLargeMemoryFiles, getMemoryFiles, MAX_MEMORY_CHARACTER_COUNT } from './claudemd.js';
|
||
|
|
import { getDoctorDiagnostic } from './doctorDiagnostic.js';
|
||
|
|
import { getAWSRegion, getDefaultVertexRegion, isEnvTruthy } from './envUtils.js';
|
||
|
|
import { getDisplayPath } from './file.js';
|
||
|
|
import { formatNumber } from './format.js';
|
||
|
|
import { getIdeClientName, type IDEExtensionInstallationStatus, isJetBrainsIde, toIDEDisplayName } from './ide.js';
|
||
|
|
import { getClaudeAiUserDefaultModelDescription, modelDisplayString } from './model/model.js';
|
||
|
|
import { getAPIProvider } from './model/providers.js';
|
||
|
|
import { getMTLSConfig } from './mtls.js';
|
||
|
|
import { checkInstall } from './nativeInstaller/index.js';
|
||
|
|
import { getProxyUrl } from './proxy.js';
|
||
|
|
import { SandboxManager } from './sandbox/sandbox-adapter.js';
|
||
|
|
import { getSettingsWithAllErrors } from './settings/allErrors.js';
|
||
|
|
import { getEnabledSettingSources, getSettingSourceDisplayNameCapitalized } from './settings/constants.js';
|
||
|
|
import { getManagedFileSettingsPresence, getPolicySettingsOrigin, getSettingsForSource } from './settings/settings.js';
|
||
|
|
import type { ThemeName } from './theme.js';
|
||
|
|
export type Property = {
|
||
|
|
label?: string;
|
||
|
|
value: React.ReactNode | Array<string>;
|
||
|
|
};
|
||
|
|
export type Diagnostic = React.ReactNode;
|
||
|
|
export function buildSandboxProperties(): Property[] {
|
||
|
|
if ("external" !== 'ant') {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
const isSandboxed = SandboxManager.isSandboxingEnabled();
|
||
|
|
return [{
|
||
|
|
label: 'Bash Sandbox',
|
||
|
|
value: isSandboxed ? 'Enabled' : 'Disabled'
|
||
|
|
}];
|
||
|
|
}
|
||
|
|
export function buildIDEProperties(mcpClients: MCPServerConnection[], ideInstallationStatus: IDEExtensionInstallationStatus | null = null, theme: ThemeName): Property[] {
|
||
|
|
const ideClient = mcpClients?.find(client => client.name === 'ide');
|
||
|
|
if (ideInstallationStatus) {
|
||
|
|
const ideName = toIDEDisplayName(ideInstallationStatus.ideType);
|
||
|
|
const pluginOrExtension = isJetBrainsIde(ideInstallationStatus.ideType) ? 'plugin' : 'extension';
|
||
|
|
if (ideInstallationStatus.error) {
|
||
|
|
return [{
|
||
|
|
label: 'IDE',
|
||
|
|
value: <Text>
|
||
|
|
{color('error', theme)(figures.cross)} Error installing {ideName}{' '}
|
||
|
|
{pluginOrExtension}: {ideInstallationStatus.error}
|
||
|
|
{'\n'}Please restart your IDE and try again.
|
||
|
|
</Text>
|
||
|
|
}];
|
||
|
|
}
|
||
|
|
if (ideInstallationStatus.installed) {
|
||
|
|
if (ideClient && ideClient.type === 'connected') {
|
||
|
|
if (ideInstallationStatus.installedVersion !== ideClient.serverInfo?.version) {
|
||
|
|
return [{
|
||
|
|
label: 'IDE',
|
||
|
|
value: `Connected to ${ideName} ${pluginOrExtension} version ${ideInstallationStatus.installedVersion} (server version: ${ideClient.serverInfo?.version})`
|
||
|
|
}];
|
||
|
|
} else {
|
||
|
|
return [{
|
||
|
|
label: 'IDE',
|
||
|
|
value: `Connected to ${ideName} ${pluginOrExtension} version ${ideInstallationStatus.installedVersion}`
|
||
|
|
}];
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
return [{
|
||
|
|
label: 'IDE',
|
||
|
|
value: `Installed ${ideName} ${pluginOrExtension}`
|
||
|
|
}];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else if (ideClient) {
|
||
|
|
const ideName = getIdeClientName(ideClient) ?? 'IDE';
|
||
|
|
if (ideClient.type === 'connected') {
|
||
|
|
return [{
|
||
|
|
label: 'IDE',
|
||
|
|
value: `Connected to ${ideName} extension`
|
||
|
|
}];
|
||
|
|
} else {
|
||
|
|
return [{
|
||
|
|
label: 'IDE',
|
||
|
|
value: `${color('error', theme)(figures.cross)} Not connected to ${ideName}`
|
||
|
|
}];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
export function buildMcpProperties(clients: MCPServerConnection[] = [], theme: ThemeName): Property[] {
|
||
|
|
const servers = clients.filter(client => client.name !== 'ide');
|
||
|
|
if (!servers.length) {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
|
||
|
|
// Summary instead of a full server list — 20+ servers wrapped onto many
|
||
|
|
// rows, dominating the Status pane. Show counts by state + /mcp hint.
|
||
|
|
const byState = {
|
||
|
|
connected: 0,
|
||
|
|
pending: 0,
|
||
|
|
needsAuth: 0,
|
||
|
|
failed: 0
|
||
|
|
};
|
||
|
|
for (const s of servers) {
|
||
|
|
if (s.type === 'connected') byState.connected++;else if (s.type === 'pending') byState.pending++;else if (s.type === 'needs-auth') byState.needsAuth++;else byState.failed++;
|
||
|
|
}
|
||
|
|
const parts: string[] = [];
|
||
|
|
if (byState.connected) parts.push(color('success', theme)(`${byState.connected} connected`));
|
||
|
|
if (byState.needsAuth) parts.push(color('warning', theme)(`${byState.needsAuth} need auth`));
|
||
|
|
if (byState.pending) parts.push(color('inactive', theme)(`${byState.pending} pending`));
|
||
|
|
if (byState.failed) parts.push(color('error', theme)(`${byState.failed} failed`));
|
||
|
|
return [{
|
||
|
|
label: 'MCP servers',
|
||
|
|
value: `${parts.join(', ')} ${color('inactive', theme)('· /mcp')}`
|
||
|
|
}];
|
||
|
|
}
|
||
|
|
export async function buildMemoryDiagnostics(): Promise<Diagnostic[]> {
|
||
|
|
const files = await getMemoryFiles();
|
||
|
|
const largeFiles = getLargeMemoryFiles(files);
|
||
|
|
const diagnostics: Diagnostic[] = [];
|
||
|
|
largeFiles.forEach(file => {
|
||
|
|
const displayPath = getDisplayPath(file.path);
|
||
|
|
diagnostics.push(`Large ${displayPath} will impact performance (${formatNumber(file.content.length)} chars > ${formatNumber(MAX_MEMORY_CHARACTER_COUNT)})`);
|
||
|
|
});
|
||
|
|
return diagnostics;
|
||
|
|
}
|
||
|
|
export function buildSettingSourcesProperties(): Property[] {
|
||
|
|
const enabledSources = getEnabledSettingSources();
|
||
|
|
|
||
|
|
// Filter to only sources that actually have settings loaded
|
||
|
|
const sourcesWithSettings = enabledSources.filter(source => {
|
||
|
|
const settings = getSettingsForSource(source);
|
||
|
|
return settings !== null && Object.keys(settings).length > 0;
|
||
|
|
});
|
||
|
|
|
||
|
|
// Map internal names to user-friendly names
|
||
|
|
// For policySettings, distinguish between remote and local (or skip if neither exists)
|
||
|
|
const sourceNames = sourcesWithSettings.map(source => {
|
||
|
|
if (source === 'policySettings') {
|
||
|
|
const origin = getPolicySettingsOrigin();
|
||
|
|
if (origin === null) {
|
||
|
|
return null; // Skip - no policy settings exist
|
||
|
|
}
|
||
|
|
switch (origin) {
|
||
|
|
case 'remote':
|
||
|
|
return 'Enterprise managed settings (remote)';
|
||
|
|
case 'plist':
|
||
|
|
return 'Enterprise managed settings (plist)';
|
||
|
|
case 'hklm':
|
||
|
|
return 'Enterprise managed settings (HKLM)';
|
||
|
|
case 'file':
|
||
|
|
{
|
||
|
|
const {
|
||
|
|
hasBase,
|
||
|
|
hasDropIns
|
||
|
|
} = getManagedFileSettingsPresence();
|
||
|
|
if (hasBase && hasDropIns) {
|
||
|
|
return 'Enterprise managed settings (file + drop-ins)';
|
||
|
|
}
|
||
|
|
if (hasDropIns) {
|
||
|
|
return 'Enterprise managed settings (drop-ins)';
|
||
|
|
}
|
||
|
|
return 'Enterprise managed settings (file)';
|
||
|
|
}
|
||
|
|
case 'hkcu':
|
||
|
|
return 'Enterprise managed settings (HKCU)';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return getSettingSourceDisplayNameCapitalized(source);
|
||
|
|
}).filter((name): name is string => name !== null);
|
||
|
|
return [{
|
||
|
|
label: 'Setting sources',
|
||
|
|
value: sourceNames
|
||
|
|
}];
|
||
|
|
}
|
||
|
|
export async function buildInstallationDiagnostics(): Promise<Diagnostic[]> {
|
||
|
|
const installWarnings = await checkInstall();
|
||
|
|
return installWarnings.map(warning => warning.message);
|
||
|
|
}
|
||
|
|
export async function buildInstallationHealthDiagnostics(): Promise<Diagnostic[]> {
|
||
|
|
const diagnostic = await getDoctorDiagnostic();
|
||
|
|
const items: Diagnostic[] = [];
|
||
|
|
const {
|
||
|
|
errors: validationErrors
|
||
|
|
} = getSettingsWithAllErrors();
|
||
|
|
if (validationErrors.length > 0) {
|
||
|
|
const invalidFiles = Array.from(new Set(validationErrors.map(error => error.file)));
|
||
|
|
const fileList = invalidFiles.join(', ');
|
||
|
|
items.push(`Found invalid settings files: ${fileList}. They will be ignored.`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add warnings from doctor diagnostic (includes leftover installations, config mismatches, etc.)
|
||
|
|
diagnostic.warnings.forEach(warning => {
|
||
|
|
items.push(warning.issue);
|
||
|
|
});
|
||
|
|
if (diagnostic.hasUpdatePermissions === false) {
|
||
|
|
items.push('No write permissions for auto-updates (requires sudo)');
|
||
|
|
}
|
||
|
|
return items;
|
||
|
|
}
|
||
|
|
export function buildAccountProperties(): Property[] {
|
||
|
|
const accountInfo = getAccountInformation();
|
||
|
|
if (!accountInfo) {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
const properties: Property[] = [];
|
||
|
|
if (accountInfo.subscription) {
|
||
|
|
properties.push({
|
||
|
|
label: 'Login method',
|
||
|
|
value: `${accountInfo.subscription} Account`
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (accountInfo.tokenSource) {
|
||
|
|
properties.push({
|
||
|
|
label: 'Auth token',
|
||
|
|
value: accountInfo.tokenSource
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (accountInfo.apiKeySource) {
|
||
|
|
properties.push({
|
||
|
|
label: 'API key',
|
||
|
|
value: accountInfo.apiKeySource
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Hide sensitive account info in demo mode
|
||
|
|
if (accountInfo.organization && !process.env.IS_DEMO) {
|
||
|
|
properties.push({
|
||
|
|
label: 'Organization',
|
||
|
|
value: accountInfo.organization
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (accountInfo.email && !process.env.IS_DEMO) {
|
||
|
|
properties.push({
|
||
|
|
label: 'Email',
|
||
|
|
value: accountInfo.email
|
||
|
|
});
|
||
|
|
}
|
||
|
|
return properties;
|
||
|
|
}
|
||
|
|
export function buildAPIProviderProperties(): Property[] {
|
||
|
|
const apiProvider = getAPIProvider();
|
||
|
|
const properties: Property[] = [];
|
||
|
|
if (apiProvider !== 'firstParty') {
|
||
|
|
const providerLabel = {
|
||
|
|
bedrock: 'AWS Bedrock',
|
||
|
|
vertex: 'Google Vertex AI',
|
||
|
|
foundry: 'Microsoft Foundry'
|
||
|
|
}[apiProvider];
|
||
|
|
properties.push({
|
||
|
|
label: 'API provider',
|
||
|
|
value: providerLabel
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (apiProvider === 'firstParty') {
|
||
|
|
const anthropicBaseUrl = process.env.ANTHROPIC_BASE_URL;
|
||
|
|
if (anthropicBaseUrl) {
|
||
|
|
properties.push({
|
||
|
|
label: 'Anthropic base URL',
|
||
|
|
value: anthropicBaseUrl
|
||
|
|
});
|
||
|
|
}
|
||
|
|
} else if (apiProvider === 'bedrock') {
|
||
|
|
const bedrockBaseUrl = process.env.BEDROCK_BASE_URL;
|
||
|
|
if (bedrockBaseUrl) {
|
||
|
|
properties.push({
|
||
|
|
label: 'Bedrock base URL',
|
||
|
|
value: bedrockBaseUrl
|
||
|
|
});
|
||
|
|
}
|
||
|
|
properties.push({
|
||
|
|
label: 'AWS region',
|
||
|
|
value: getAWSRegion()
|
||
|
|
});
|
||
|
|
if (isEnvTruthy(process.env.CLAUDE_CODE_SKIP_BEDROCK_AUTH)) {
|
||
|
|
properties.push({
|
||
|
|
value: 'AWS auth skipped'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
} else if (apiProvider === 'vertex') {
|
||
|
|
const vertexBaseUrl = process.env.VERTEX_BASE_URL;
|
||
|
|
if (vertexBaseUrl) {
|
||
|
|
properties.push({
|
||
|
|
label: 'Vertex base URL',
|
||
|
|
value: vertexBaseUrl
|
||
|
|
});
|
||
|
|
}
|
||
|
|
const gcpProject = process.env.ANTHROPIC_VERTEX_PROJECT_ID;
|
||
|
|
if (gcpProject) {
|
||
|
|
properties.push({
|
||
|
|
label: 'GCP project',
|
||
|
|
value: gcpProject
|
||
|
|
});
|
||
|
|
}
|
||
|
|
properties.push({
|
||
|
|
label: 'Default region',
|
||
|
|
value: getDefaultVertexRegion()
|
||
|
|
});
|
||
|
|
if (isEnvTruthy(process.env.CLAUDE_CODE_SKIP_VERTEX_AUTH)) {
|
||
|
|
properties.push({
|
||
|
|
value: 'GCP auth skipped'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
} else if (apiProvider === 'foundry') {
|
||
|
|
const foundryBaseUrl = process.env.ANTHROPIC_FOUNDRY_BASE_URL;
|
||
|
|
if (foundryBaseUrl) {
|
||
|
|
properties.push({
|
||
|
|
label: 'Microsoft Foundry base URL',
|
||
|
|
value: foundryBaseUrl
|
||
|
|
});
|
||
|
|
}
|
||
|
|
const foundryResource = process.env.ANTHROPIC_FOUNDRY_RESOURCE;
|
||
|
|
if (foundryResource) {
|
||
|
|
properties.push({
|
||
|
|
label: 'Microsoft Foundry resource',
|
||
|
|
value: foundryResource
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (isEnvTruthy(process.env.CLAUDE_CODE_SKIP_FOUNDRY_AUTH)) {
|
||
|
|
properties.push({
|
||
|
|
value: 'Microsoft Foundry auth skipped'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
const proxyUrl = getProxyUrl();
|
||
|
|
if (proxyUrl) {
|
||
|
|
properties.push({
|
||
|
|
label: 'Proxy',
|
||
|
|
value: proxyUrl
|
||
|
|
});
|
||
|
|
}
|
||
|
|
const mtlsConfig = getMTLSConfig();
|
||
|
|
if (process.env.NODE_EXTRA_CA_CERTS) {
|
||
|
|
properties.push({
|
||
|
|
label: 'Additional CA cert(s)',
|
||
|
|
value: process.env.NODE_EXTRA_CA_CERTS
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (mtlsConfig) {
|
||
|
|
if (mtlsConfig.cert && process.env.CLAUDE_CODE_CLIENT_CERT) {
|
||
|
|
properties.push({
|
||
|
|
label: 'mTLS client cert',
|
||
|
|
value: process.env.CLAUDE_CODE_CLIENT_CERT
|
||
|
|
});
|
||
|
|
}
|
||
|
|
if (mtlsConfig.key && process.env.CLAUDE_CODE_CLIENT_KEY) {
|
||
|
|
properties.push({
|
||
|
|
label: 'mTLS client key',
|
||
|
|
value: process.env.CLAUDE_CODE_CLIENT_KEY
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return properties;
|
||
|
|
}
|
||
|
|
export function getModelDisplayLabel(mainLoopModel: string | null): string {
|
||
|
|
let modelLabel = modelDisplayString(mainLoopModel);
|
||
|
|
if (mainLoopModel === null && isClaudeAISubscriber()) {
|
||
|
|
const description = getClaudeAiUserDefaultModelDescription();
|
||
|
|
modelLabel = `${chalk.bold('Default')} ${description}`;
|
||
|
|
}
|
||
|
|
return modelLabel;
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJjaGFsayIsImZpZ3VyZXMiLCJSZWFjdCIsImNvbG9yIiwiVGV4dCIsIk1DUFNlcnZlckNvbm5lY3Rpb24iLCJnZXRBY2NvdW50SW5mb3JtYXRpb24iLCJpc0NsYXVkZUFJU3Vic2NyaWJlciIsImdldExhcmdlTWVtb3J5RmlsZXMiLCJnZXRNZW1vcnlGaWxlcyIsIk1BWF9NRU1PUllfQ0hBUkFDVEVSX0NPVU5UIiwiZ2V0RG9jdG9yRGlhZ25vc3RpYyIsImdldEFXU1JlZ2lvbiIsImdldERlZmF1bHRWZXJ0ZXhSZWdpb24iLCJpc0VudlRydXRoeSIsImdldERpc3BsYXlQYXRoIiwiZm9ybWF0TnVtYmVyIiwiZ2V0SWRlQ2xpZW50TmFtZSIsIklERUV4dGVuc2lvbkluc3RhbGxhdGlvblN0YXR1cyIsImlzSmV0QnJhaW5zSWRlIiwidG9JREVEaXNwbGF5TmFtZSIsImdldENsYXVkZUFpVXNlckRlZmF1bHRNb2RlbERlc2NyaXB0aW9uIiwibW9kZWxEaXNwbGF5U3RyaW5nIiwiZ2V0QVBJUHJvdmlkZXIiLCJnZXRNVExTQ29uZmlnIiwiY2hlY2tJbnN0YWxsIiwiZ2V0UHJveHlVcmwiLCJTYW5kYm94TWFuYWdlciIsImdldFNldHRpbmdzV2l0aEFsbEVycm9ycyIsImdldEVuYWJsZWRTZXR0aW5nU291cmNlcyIsImdldFNldHRpbmdTb3VyY2VEaXNwbGF5TmFtZUNhcGl0YWxpemVkIiwiZ2V0TWFuYWdlZEZpbGVTZXR0aW5nc1ByZXNlbmNlIiwiZ2V0UG9saWN5U2V0dGluZ3NPcmlnaW4iLCJnZXRTZXR0aW5nc0ZvclNvdXJjZSIsIlRoZW1lTmFtZSIsIlByb3BlcnR5IiwibGFiZWwiLCJ2YWx1ZSIsIlJlYWN0Tm9kZSIsIkFycmF5IiwiRGlhZ25vc3RpYyIsImJ1aWxkU2FuZGJveFByb3BlcnRpZXMiLCJpc1NhbmRib3hlZCIsImlzU2FuZGJveGluZ0VuYWJsZWQiLCJidWlsZElERVByb3BlcnRpZXMiLCJtY3BDbGllbnRzIiwiaWRlSW5zdGFsbGF0aW9uU3RhdHVzIiwidGhlbWUiLCJpZGVDbGllbnQiLCJmaW5kIiwiY2xpZW50IiwibmFtZSIsImlkZU5hbWUiLCJpZGVUeXBlIiwicGx1Z2luT3JFeHRlbnNpb24iLCJlcnJvciIsImNyb3NzIiwiaW5zdGFsbGVkIiwidHlwZSIsImluc3RhbGxlZFZlcnNpb24iLCJzZXJ2ZXJJbmZvIiwidmVyc2lvbiIsImJ1aWxkTWNwUHJvcGVydGllcyIsImNsaWVudHMiLCJzZXJ2ZXJzIiwiZmlsdGVyIiwibGVuZ3RoIiwiYnlTdGF0ZSIsImNvbm5lY3RlZCIsInBlbmRpbmciLCJuZWVkc0F1dGgiLCJmYWlsZWQiLCJzIiwicGFydHMiLCJwdXNoIiwiam9pbiIsImJ1aWxkTWVtb3J5RGlhZ25vc3RpY3MiLCJQcm9taXNlIiwiZmlsZXMiLCJsYXJnZUZpbGVzIiwiZGlhZ25vc3RpY3MiLCJmb3JFYWNoIiwiZmlsZSIsImRpc3BsYXlQYXRoIiwicGF0aCIsImNvbnRlbnQiLCJidWlsZFNldHRpbmdTb3VyY2VzUHJvcGVydGllcyIsImVuYWJsZWRTb3VyY2VzIiwic291cmNlc1dpdGhTZXR0aW5ncyIsInNvdXJjZSIsInNldHRpbmdzIiwiT2JqZWN0Iiwia2V5cyIsInNvdXJjZU5hbWVzIiwibWFwIiwib3JpZ2luIiwiaGFzQmFzZSIsImhhc0Ryb3BJbnMiLCJidWlsZEluc3RhbGxhdGlvbkRpYWdub3N0aWNzIiwiaW5zdGFsbFdhcm5pbmdzIiwid2FybmluZyIsIm1lc3NhZ2UiLCJidWlsZEluc3RhbGxhdGlvbkhlYWx0aERpYWdub3N0aWNzIiwiZGlhZ25vc3RpYyIsIml0ZW1zIiwiZXJyb3JzIiwidmFsaWRhdGlvbkVycm9ycyIsImludmFsaWRGaWxlcyIsImZyb20iLCJTZXQiLCJmaWxlTGlzdCIsIndhcm5pbmdzIiwiaXNzdWUiLCJoYXNVcGRhdGVQZXJtaXNzaW9ucyIsImJ1aWxkQWNjb3VudFByb3BlcnRpZXMiLCJhY2NvdW50SW5mbyIsInByb3BlcnRpZXMiLCJzdWJzY3JpcHRpb24iLCJ0b2tlblNvdXJjZSIsImFwaUtleVNvdXJjZSIsIm9yZ2FuaXphdGlvbiIsInByb2Nlc3MiLCJlbnYiLCJJU19ERU1PIiwiZW1haWwiLCJidWlsZEFQSVByb3ZpZGVyUHJvcGVydGllcyIsImFwaVByb3ZpZGVyIiwicHJvdmlkZXJMYWJlbCIsImJlZHJvY2siLCJ2ZXJ0ZXgiLCJmb3VuZHJ5IiwiYW50aHJvcGljQmFzZVVybCIsIkFOVEhST1BJQ19CQVNFX1VSTCIsImJlZHJvY2tCYXNlVXJsIiwiQkVEUk9DS19CQVNFX1VSTCIsIkNMQVVERV9DT0RFX1NLSVBfQkVEUk9DS19BVVRIIiwidmVydGV4QmFzZVVybCIsIlZFUlRFWF9CQVNFX1VSTCIsImdjcFByb2plY3QiLCJBTlRIUk9QSUNfVkVSVEVYX1BST0pFQ1RfSUQiLCJDTEFVREVfQ09ERV9TS0lQX1ZFUlRFWF9BVVRIIiwiZm91bmRyeUJhc2VVcmwiLCJBTlRIUk9QSUNfRk9VTkRSWV9CQVNFX1VSTCIsImZvdW5kcnlSZXNvdXJjZSIsIkFOVEhST1BJQ19GT1VORFJZX1JFU09VUkNFIiwiQ0xBVURFX0NPREVfU0tJUF9GT1VORFJZX0FVVEgiLCJwcm94eVVybCIsIm10bHNDb25maWciLCJOT0RFX0VYVFJBX0NBX0NFUlRTIiwiY2VydCIsIkNMQVVERV9DT0RFX0NMSUVOVF9DRVJUIiwia2V5IiwiQ0xBVURFX0NPREVfQ0xJRU5UX0tFWSIsImdldE1vZGVsRGlzcGxheUxhYmVsIiwibWFpbkxvb3BNb2RlbCIsIm1vZGVsTGFiZWwiLCJkZXNjcmlwdGlvbiIsImJvbGQiXSwic291cmNlcyI6WyJzdGF0dXMudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBjaGFsayBmcm9tICdjaGFsaydcbmltcG9ydCBmaWd1cmVzIGZyb20gJ2ZpZ3VyZXMnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IGNvbG9yLCBUZXh0IH0gZnJvbSAnLi4vaW5rLmpzJ1xuaW1wb3J0IHR5cGUgeyBNQ1BTZXJ2ZXJDb25uZWN0aW9uIH0gZnJvbSAnLi4vc2VydmljZXMvbWNwL3R5cGVzLmpzJ1xuaW1wb3J0IHsgZ2V0QWNjb3VudEluZm9ybWF0aW9uLCBpc0NsYXVkZUFJU3Vic2NyaWJlciB9IGZyb20gJy4vYXV0aC5qcydcbmltcG9ydCB7XG4gIGdldExhcmdlTWVtb3J5RmlsZXMsXG4gIGdldE1lbW9yeUZpbGVzLFxuICBNQVhfTUVNT1JZX0NIQVJBQ1RFUl9DT1VOVCxcbn0gZnJvbSAnLi9jbGF1ZGVtZC5qcydcbmltcG9ydCB7IGdldERvY3RvckRpYWdub3N0aWMgfSBmcm9tICcuL2RvY3RvckRpYWdub3N0aWMuanMnXG5pbXBvcnQge1xuICBnZXRBV1NSZWdpb24sXG4gIGdldERlZmF1bHRWZXJ0ZXhSZWdpb24sXG4gIGlzRW52VHJ
|