@nlabs/lex
Version:
312 lines (300 loc) • 35.8 kB
JavaScript
/**
* Copyright (c) 2022-Present, Nitrogen Labs, Inc.
* Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms.
*/ import { existsSync, readFileSync, writeFileSync } from 'fs';
import { resolve as pathResolve } from 'path';
import readline from 'readline';
import { LexConfig } from '../LexConfig.js';
import { createSpinner } from './app.js';
import { log } from './log.js';
// Cursor IDE integration
export const callCursorAI = async (prompt, _options)=>{
try {
// When running within Cursor IDE, we can write the prompt to a temporary file
// that Cursor can use to provide AI assistance
log('Using Cursor IDE for AI fixes...', 'info');
// For now, just log the prompt and return a placeholder
// In a real implementation, Cursor would handle this automatically
log('AI fix requested via Cursor IDE', 'info');
const taskMatch = prompt.match(/^(Generate code according to the following request|Explain the following code|Generate comprehensive unit tests|Analyze the following code|Provide guidance on the following development question):/);
const task = taskMatch ? taskMatch[1] : '';
const isGenerateTask = task.startsWith('Generate code');
const questionMatch = prompt.match(/(?:Generate code according to the following request|Explain the following code|Generate comprehensive unit tests|Analyze the following code|Provide guidance on the following development question):\s*([\s\S]+?)(?:===CONTEXT===|$)/);
const question = questionMatch ? questionMatch[1].trim() : prompt;
if (question.toLowerCase().includes('how many files') && prompt.includes('Project structure:')) {
const projectStructure = prompt.split('Project structure:')[1] || '';
const files = projectStructure.trim().split('\n');
return `Based on the project structure provided, there are ${files.length} files in the project.`;
}
if (isGenerateTask) {
return `
# Code Generation Request: "${question}"
To generate code using Cursor's AI capabilities:
1. **Open your project in Cursor IDE** (https://cursor.sh)
2. Press **Cmd+L** (or Ctrl+L on Windows/Linux) to open the AI chat
3. Type your request: "${question}"
4. Cursor will generate the code directly in your editor
The current CLI integration doesn't have direct access to Cursor's code generation capabilities.
**Alternative options:**
1. **Use OpenAI or Anthropic directly:**
Configure in your lex.config file:
\`\`\`js
export default {
ai: {
provider: 'openai',
apiKey: process.env.OPENAI_API_KEY,
model: 'gpt-4o'
}
}
\`\`\`
2. **Use Cursor's command line tool:**
Install: \`npm install -g @cursor/cli\`
Run: \`cursor ai "${question}"\`
`;
}
return `
To use Cursor's AI capabilities for "${question}", you need to:
1. Open your project in Cursor IDE (https://cursor.sh)
2. Use Cursor's built-in AI features by pressing Cmd+K or Cmd+L
3. Or run the 'cursor' command directly from your terminal
The current integration is limited and doesn't directly access Cursor's AI capabilities.
For the best experience with AI code generation:
- Use Cursor IDE directly
- Or configure OpenAI or Anthropic as your provider in your lex.config file:
\`\`\`js
// lex.config.js (or lex.config.mjs, lex.config.cjs, etc.)
export default {
ai: {
provider: 'openai', // or 'anthropic'
apiKey: process.env.OPENAI_API_KEY, // or ANTHROPIC_API_KEY
model: 'gpt-4o' // or 'claude-3-opus'
}
}
\`\`\`
Then set your API key as an environment variable:
\`\`\`
export OPENAI_API_KEY=your_key_here
\`\`\`
`;
} catch (error) {
throw new Error(`Cursor AI error: ${error.message}`);
}
};
export const callOpenAIAI = async (prompt, options)=>{
try {
const apiKey = options.apiKey || process.env.OPENAI_API_KEY;
if (!apiKey) {
throw new Error('OpenAI API key is required. Set it in your lex.config file or as OPENAI_API_KEY environment variable.');
}
const response = await fetch('https://api.openai.com/v1/chat/completions', {
body: JSON.stringify({
max_tokens: options.maxTokens || 4000,
messages: [
{
content: 'You are a helpful assistant that fixes ESLint errors in code.',
role: 'system'
},
{
content: prompt,
role: 'user'
}
],
model: options.model || 'gpt-4o',
temperature: options.temperature || 0.1
}),
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
method: 'POST'
});
if (!response.ok) {
const error = await response.json();
throw new Error(`OpenAI API error: ${error.error?.message || response.statusText}`);
}
const data = await response.json();
return data.choices[0].message.content;
} catch (error) {
throw new Error(`OpenAI AI error: ${error.message}`);
}
};
export const callAnthropicAI = async (prompt, options)=>{
try {
const apiKey = options.apiKey || process.env.ANTHROPIC_API_KEY;
if (!apiKey) {
throw new Error('Anthropic API key is required. Set it in your lex.config file or as ANTHROPIC_API_KEY environment variable.');
}
const response = await fetch('https://api.anthropic.com/v1/messages', {
body: JSON.stringify({
max_tokens: options.maxTokens || 4000,
messages: [
{
content: prompt,
role: 'user'
}
],
model: options.model || 'claude-3-sonnet-20240229',
temperature: options.temperature || 0.1
}),
headers: {
'Content-Type': 'application/json',
'anthropic-version': '2023-06-01',
'x-api-key': apiKey
},
method: 'POST'
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Anthropic API error: ${error.error?.message || response.statusText}`);
}
const data = await response.json();
return data.content[0].text;
} catch (error) {
throw new Error(`Anthropic AI error: ${error.message}`);
}
};
export const callCopilotAI = async (prompt, _options)=>{
try {
log('GitHub Copilot AI fixes not directly supported. Using manual fix mode.', 'info');
return prompt;
} catch (error) {
throw new Error(`GitHub Copilot AI error: ${error.message}`);
}
};
export const promptForAIProvider = async (_quiet = false)=>{
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve)=>{
log('\nNo AI provider configured. Please choose an AI provider:', 'info');
log('1. Cursor IDE', 'info');
log('2. OpenAI', 'info');
log('3. Anthropic', 'info');
log('4. GitHub Copilot', 'info');
log('5. None (Skip AI features)', 'info');
rl.question('Enter your choice (1-5): ', (answer)=>{
rl.close();
switch(answer){
case '1':
resolve('cursor');
break;
case '2':
resolve('openai');
break;
case '3':
resolve('anthropic');
break;
case '4':
resolve('copilot');
break;
default:
resolve('none');
}
});
});
};
export const promptForAPIKey = async (provider, _quiet = false)=>{
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
return new Promise((resolve)=>{
rl.question(`Please enter your ${provider} API key: `, (answer)=>{
rl.close();
resolve(answer);
});
});
};
export const getAIService = (provider, _options)=>{
switch(provider){
case 'cursor':
return callCursorAI;
case 'openai':
return callOpenAIAI;
case 'anthropic':
return callAnthropicAI;
case 'copilot':
return callCopilotAI;
default:
return async ()=>'No AI provider configured';
}
};
export const callAIService = async (prompt, quiet = false)=>{
const spinner = createSpinner(quiet);
spinner.start('Calling AI service to fix code issues...');
try {
const aiConfig = LexConfig.config.ai || {
provider: 'none'
};
const isInCursorIDE = process.env.CURSOR_IDE === 'true';
if (isInCursorIDE && (aiConfig.provider === 'none' || !aiConfig.provider)) {
log('Detected Cursor IDE environment, using Cursor as AI provider', 'info', quiet);
aiConfig.provider = 'cursor';
}
if (aiConfig.provider === 'none') {
const provider = await promptForAIProvider(quiet);
if (provider === 'none') {
spinner.fail('AI features skipped');
return '';
}
aiConfig.provider = provider;
if (provider !== 'cursor' && provider !== 'copilot' && !process.env[`${provider.toUpperCase()}_API_KEY`]) {
aiConfig.apiKey = await promptForAPIKey(provider, quiet);
}
LexConfig.config.ai = aiConfig;
// Search for config files in multiple formats like LexConfig.parseConfig does
const configFormats = [
'js',
'mjs',
'cjs',
'ts',
'json'
];
const configBaseName = 'lex.config';
let configPath = '';
for (const format of configFormats){
const potentialPath = pathResolve(process.cwd(), `./${configBaseName}.${format}`);
if (existsSync(potentialPath)) {
configPath = potentialPath;
break;
}
}
if (configPath) {
try {
const configContent = readFileSync(configPath, 'utf8');
const updatedConfig = configContent.replace(/ai:.*?[,}]/s, `ai: { provider: '${aiConfig.provider}' },`);
writeFileSync(configPath, updatedConfig);
} catch (_error) {}
}
}
let result = '';
switch(aiConfig.provider){
case 'cursor':
result = await callCursorAI(prompt, aiConfig);
log('Cursor IDE AI integration active', 'info', quiet);
break;
case 'openai':
result = await callOpenAIAI(prompt, aiConfig);
break;
case 'anthropic':
result = await callAnthropicAI(prompt, aiConfig);
break;
case 'copilot':
result = await callCopilotAI(prompt, aiConfig);
break;
default:
spinner.fail('No AI provider configured');
return '';
}
spinner.succeed('AI code fixes generated successfully');
return result;
} catch (error) {
spinner.fail(`AI service error: ${error.message}`);
if (!quiet) {
log(error, 'error');
}
return '';
}
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy91dGlscy9haVNlcnZpY2UudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBDb3B5cmlnaHQgKGMpIDIwMjItUHJlc2VudCwgTml0cm9nZW4gTGFicywgSW5jLlxuICogQ29weXJpZ2h0cyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSB0aGUgYWNjb21wYW55aW5nIExJQ0VOU0UgZmlsZSBmb3IgdGVybXMuXG4gKi9cbmltcG9ydCB7ZXhpc3RzU3luYywgcmVhZEZpbGVTeW5jLCB3cml0ZUZpbGVTeW5jfSBmcm9tICdmcyc7XG5pbXBvcnQge3Jlc29sdmUgYXMgcGF0aFJlc29sdmV9IGZyb20gJ3BhdGgnO1xuaW1wb3J0IHJlYWRsaW5lIGZyb20gJ3JlYWRsaW5lJztcblxuaW1wb3J0IHtBSUNvbmZpZywgTGV4Q29uZmlnfSBmcm9tICcuLi9MZXhDb25maWcuanMnO1xuaW1wb3J0IHtjcmVhdGVTcGlubmVyfSBmcm9tICcuL2FwcC5qcyc7XG5pbXBvcnQge2xvZ30gZnJvbSAnLi9sb2cuanMnO1xuXG4vLyBDdXJzb3IgSURFIGludGVncmF0aW9uXG5leHBvcnQgY29uc3QgY2FsbEN1cnNvckFJID0gYXN5bmMgKHByb21wdDogc3RyaW5nLCBfb3B0aW9uczogQUlDb25maWcpOiBQcm9taXNlPHN0cmluZz4gPT4ge1xuICB0cnkge1xuICAgIC8vIFdoZW4gcnVubmluZyB3aXRoaW4gQ3Vyc29yIElERSwgd2UgY2FuIHdyaXRlIHRoZSBwcm9tcHQgdG8gYSB0ZW1wb3JhcnkgZmlsZVxuICAgIC8vIHRoYXQgQ3Vyc29yIGNhbiB1c2UgdG8gcHJvdmlkZSBBSSBhc3Npc3RhbmNlXG4gICAgbG9nKCdVc2luZyBDdXJzb3IgSURFIGZvciBBSSBmaXhlcy4uLicsICdpbmZvJyk7XG5cbiAgICAvLyBGb3Igbm93LCBqdXN0IGxvZyB0aGUgcHJvbXB0IGFuZCByZXR1cm4gYSBwbGFjZWhvbGRlclxuICAgIC8vIEluIGEgcmVhbCBpbXBsZW1lbnRhdGlvbiwgQ3Vyc29yIHdvdWxkIGhhbmRsZSB0aGlzIGF1dG9tYXRpY2FsbHlcbiAgICBsb2coJ0FJIGZpeCByZXF1ZXN0ZWQgdmlhIEN1cnNvciBJREUnLCAnaW5mbycpO1xuXG4gICAgY29uc3QgdGFza01hdGNoID0gcHJvbXB0Lm1hdGNoKC9eKEdlbmVyYXRlIGNvZGUgYWNjb3JkaW5nIHRvIHRoZSBmb2xsb3dpbmcgcmVxdWVzdHxFeHBsYWluIHRoZSBmb2xsb3dpbmcgY29kZXxHZW5lcmF0ZSBjb21wcmVoZW5zaXZlIHVuaXQgdGVzdHN8QW5hbHl6ZSB0aGUgZm9sbG93aW5nIGNvZGV8UHJvdmlkZSBndWlkYW5jZSBvbiB0aGUgZm9sbG93aW5nIGRldmVsb3BtZW50IHF1ZXN0aW9uKTovKTtcbiAgICBjb25zdCB0YXNrID0gdGFza01hdGNoID8gdGFza01hdGNoWzFdIDogJyc7XG4gICAgY29uc3QgaXNHZW5lcmF0ZVRhc2sgPSB0YXNrLnN0YXJ0c1dpdGgoJ0dlbmVyYXRlIGNvZGUnKTtcblxuICAgIGNvbnN0IHF1ZXN0aW9uTWF0Y2ggPSBwcm9tcHQubWF0Y2goLyg/OkdlbmVyYXRlIGNvZGUgYWNjb3JkaW5nIHRvIHRoZSBmb2xsb3dpbmcgcmVxdWVzdHxFeHBsYWluIHRoZSBmb2xsb3dpbmcgY29kZXxHZW5lcmF0ZSBjb21wcmVoZW5zaXZlIHVuaXQgdGVzdHN8QW5hbHl6ZSB0aGUgZm9sbG93aW5nIGNvZGV8UHJvdmlkZSBndWlkYW5jZSBvbiB0aGUgZm9sbG93aW5nIGRldmVsb3BtZW50IHF1ZXN0aW9uKTpcXHMqKFtcXHNcXFNdKz8pKD86PT09Q09OVEVYVD09PXwkKS8pO1xuICAgIGNvbnN0IHF1ZXN0aW9uID0gcXVlc3Rpb25NYXRjaCA/IHF1ZXN0aW9uTWF0Y2hbMV0udHJpbSgpIDogcHJvbXB0O1xuXG4gICAgaWYocXVlc3Rpb24udG9Mb3dlckNhc2UoKS5pbmNsdWRlcygnaG93IG1hbnkgZmlsZXMnKSAmJiBwcm9tcHQuaW5jbHVkZXMoJ1Byb2plY3Qgc3RydWN0dXJlOicpKSB7XG4gICAgICBjb25zdCBwcm9qZWN0U3RydWN0dXJlID0gcHJvbXB0LnNwbGl0KCdQcm9qZWN0IHN0cnVjdHVyZTonKVsxXSB8fCAnJztcbiAgICAgIGNvbnN0IGZpbGVzID0gcHJvamVjdFN0cnVjdHVyZS50cmltKCkuc3BsaXQoJ1xcbicpO1xuICAgICAgcmV0dXJuIGBCYXNlZCBvbiB0aGUgcHJvamVjdCBzdHJ1Y3R1cmUgcHJvdmlkZWQsIHRoZXJlIGFyZSAke2ZpbGVzLmxlbmd0aH0gZmlsZXMgaW4gdGhlIHByb2plY3QuYDtcbiAgICB9XG5cbiAgICBpZihpc0dlbmVyYXRlVGFzaykge1xuICAgICAgcmV0dXJuIGBcbiMgQ29kZSBHZW5lcmF0aW9uIFJlcXVlc3Q6IFwiJHtxdWVzdGlvbn1cIlxuXG5UbyBnZW5lcmF0ZSBjb2RlIHVzaW5nIEN1cnNvcidzIEFJIGNhcGFiaWxpdGllczpcblxuMS4gKipPcGVuIHlvdXIgcHJvamVjdCBpbiBDdXJzb3IgSURFKiogKGh0dHBzOi8vY3Vyc29yLnNoKVxuMi4gUHJlc3MgKipDbWQrTCoqIChvciBDdHJsK0wgb24gV2luZG93cy9MaW51eCkgdG8gb3BlbiB0aGUgQUkgY2hhdFxuMy4gVHlwZSB5b3VyIHJlcXVlc3Q6IFwiJHtxdWVzdGlvbn1cIlxuNC4gQ3Vyc29yIHdpbGwgZ2VuZXJhdGUgdGhlIGNvZGUgZGlyZWN0bHkgaW4geW91ciBlZGl0b3JcblxuVGhlIGN1cnJlbnQgQ0xJIGludGVncmF0aW9uIGRvZXNuJ3QgaGF2ZSBkaXJlY3QgYWNjZXNzIHRvIEN1cnNvcidzIGNvZGUgZ2VuZXJhdGlvbiBjYXBhYmlsaXRpZXMuXG5cbioqQWx0ZXJuYXRpdmUgb3B0aW9uczoqKlxuXG4xLiAqKlVzZSBPcGVuQUkgb3IgQW50aHJvcGljIGRpcmVjdGx5OioqXG4gICBDb25maWd1cmUgaW4geW91ciBsZXguY29uZmlnIGZpbGU6XG4gICBcXGBcXGBcXGBqc1xuICAgZXhwb3J0IGRlZmF1bHQge1xuICAgICBhaToge1xuICAgICAgIHByb3ZpZGVyOiAnb3BlbmFpJyxcbiAgICAgICBhcGlLZXk6IHByb2Nlc3MuZW52Lk9QRU5BSV9BUElfS0VZLFxuICAgICAgIG1vZGVsOiAnZ3B0LTRvJ1xuICAgICB9XG4gICB9XG4gICBcXGBcXGBcXGBcblxuMi4gKipVc2UgQ3Vyc29yJ3MgY29tbWFuZCBsaW5lIHRvb2w6KipcbiAgIEluc3RhbGw6IFxcYG5wbSBpbnN0YWxsIC1nIEBjdXJzb3IvY2xpXFxgXG4gICBSdW46IFxcYGN1cnNvciBhaSBcIiR7cXVlc3Rpb259XCJcXGBcbmA7XG4gICAgfVxuXG4gICAgcmV0dXJuIGBcblRvIHVzZSBDdXJzb3IncyBBSSBjYXBhYmlsaXRpZXMgZm9yIFwiJHtxdWVzdGlvbn1cIiwgeW91IG5lZWQgdG86XG5cbjEuIE9wZW4geW91ciBwcm9qZWN0IGluIEN1cnNvciBJREUgKGh0dHBzOi8vY3Vyc29yLnNoKVxuMi4gVXNlIEN1cnNvcidzIGJ1aWx0LWluIEFJIGZlYXR1cmVzIGJ5IHByZXNzaW5nIENtZCtLIG9yIENtZCtMXG4zLiBPciBydW4gdGhlICdjdXJzb3InIGNvbW1hbmQgZGlyZWN0bHkgZnJvbSB5b3VyIHRlcm1pbmFsXG5cblRoZSBjdXJyZW50IGludGVncmF0aW9uIGlzIGxpbWl0ZWQgYW5kIGRvZXNuJ3QgZGlyZWN0bHkgYWNjZXNzIEN1cnNvcidzIEFJIGNhcGFiaWxpdGllcy5cblxuRm9yIHRoZSBiZXN0IGV4cGVyaWVuY2Ugd2l0aCBBSSBjb2RlIGdlbmVyYXRpb246XG4tIFVzZSBDdXJzb3IgSURFIGRpcmVjdGx5XG4tIE9yIGNvbmZpZ3VyZSBPcGVuQUkgb3IgQW50aHJvcGljIGFzIHlvdXIgcHJvdmlkZXIgaW4geW91ciBsZXguY29uZmlnIGZpbGU6XG5cblxcYFxcYFxcYGpzXG4vLyBsZXguY29uZmlnLmpzIChvciBsZXguY29uZmlnLm1qcywgbGV4LmNvbmZpZy5janMsIGV0Yy4pXG5leHBvcnQgZGVmYXVsdCB7XG4gIGFpOiB7XG4gICAgcHJvdmlkZXI6ICdvcGVuYWknLCAvLyBvciAnYW50aHJvcGljJ1xuICAgIGFwaUtleTogcHJvY2Vzcy5lbnYuT1BFTkFJX0FQSV9LRVksIC8vIG9yIEFOVEhST1BJQ19BUElfS0VZXG4gICAgbW9kZWw6ICdncHQtNG8nIC8vIG9yICdjbGF1ZGUtMy1vcHVzJ1xuICB9XG59XG5cXGBcXGBcXGBcblxuVGhlbiBzZXQgeW91ciBBUEkga2V5IGFzIGFuIGVudmlyb25tZW50IHZhcmlhYmxlOlxuXFxgXFxgXFxgXG5leHBvcnQgT1BFTkFJX0FQSV9LRVk9eW91cl9rZXlfaGVyZVxuXFxgXFxgXFxgXG5gO1xuICB9IGNhdGNoKGVycm9yKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBDdXJzb3IgQUkgZXJyb3I6ICR7ZXJyb3IubWVzc2FnZX1gKTtcbiAgfVxufTtcblxuZXhwb3J0IGNvbnN0IGNhbGxPcGVuQUlBSSA9IGFzeW5jIChwcm9tcHQ6IHN0cmluZywgb3B0aW9uczogQUlDb25maWcpOiBQcm9taXNlPHN0cmluZz4gPT4ge1xuICB0cnkge1xuICAgIGNvbnN0IGFwaUtleSA9IG9wdGlvbnMuYXBpS2V5IHx8IHByb2Nlc3MuZW52Lk9QRU5BSV9BUElfS0VZO1xuICAgIGlmKCFhcGlLZXkpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignT3BlbkFJIEFQSSBrZXkgaXMgcmVxdWlyZWQuIFNldCBpdCBpbiB5b3VyIGxleC5jb25maWcgZmlsZSBvciBhcyBPUEVOQUlfQVBJX0tFWSBlbnZpcm9ubWVudCB2YXJpYWJsZS4nKTtcbiAgICB9XG5cbiAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IGZldGNoKCdodHRwczovL2FwaS5vcGVuYWkuY29tL3YxL2NoYXQvY29tcGxldGlvbnMnLCB7XG4gICAgICBib2R5OiBKU09OLnN0cmluZ2lmeSh7XG4gICAgICAgIG1heF90b2tlbnM6IG9wdGlvbnMubWF4VG9rZW5zIHx8IDQwMDAsXG4gICAgICAgIG1lc3NhZ2VzOiBbXG4gICAgICAgICAge2NvbnRlbnQ6ICdZb3UgYXJlIGEgaGVscGZ1bCBhc3Npc3RhbnQgdGhhdCBmaXhlcyBFU0xpbnQgZXJyb3JzIGluIGNvZGUuJywgcm9sZTogJ3N5c3RlbSd9LFxuICAgICAgICAgIHtjb250ZW50OiBwcm9tcHQsIHJvbGU6ICd1c2VyJ31cbiAgICAgICAgXSxcbiAgICAgICAgbW9kZWw6IG9wdGlvbnMubW9kZWwgfHwgJ2dwdC00bycsXG4gICAgICAgIHRlbXBlcmF0dXJlOiBvcHRpb25zLnRlbXBlcmF0dXJlIHx8IDAuMVxuICAgICAgfSksXG4gICAgICBoZWFkZXJzOiB7XG4gICAgICAgIEF1dGhvcml6YXRpb246IGBCZWFyZXIgJHthcGlLZXl9YCxcbiAgICAgICAgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9qc29uJ1xuICAgICAgfSxcbiAgICAgIG1ldGhvZDogJ1BPU1QnXG4gICAgfSk7XG5cbiAgICBpZighcmVzcG9uc2Uub2spIHtcbiAgICAgIGNvbnN0IGVycm9yID0gYXdhaXQgcmVzcG9uc2UuanNvbigpO1xuICAgICAgdGhyb3cgbmV3IEVycm9yKGBPcGVuQUkgQVBJIGVycm9yOiAke2Vycm9yLmVycm9yPy5tZXNzYWdlIHx8IHJlc3BvbnNlLnN0YXR1c1RleHR9YCk7XG4gICAgfVxuXG4gICAgY29uc3QgZGF0YSA9IGF3YWl0IHJlc3BvbnNlLmpzb24oKTtcbiAgICByZXR1cm4gZGF0YS5jaG9pY2VzWzBdLm1lc3NhZ2UuY29udGVudDtcbiAgfSBjYXRjaChlcnJvcikge1xuICAgIHRocm93IG5ldyBFcnJvcihgT3BlbkFJIEFJIGVycm9yOiAke2Vycm9yLm1lc3NhZ2V9YCk7XG4gIH1cbn07XG5cbmV4cG9ydCBjb25zdCBjYWxsQW50aHJvcGljQUkgPSBhc3luYyAocHJvbXB0OiBzdHJpbmcsIG9wdGlvbnM6IEFJQ29uZmlnKTogUHJvbWlzZTxzdHJpbmc+ID0+IHtcbiAgdHJ5IHtcbiAgICBjb25zdCBhcGlLZXkgPSBvcHRpb25zLmFwaUtleSB8fCBwcm9jZXNzLmVudi5BTlRIUk9QSUNfQVBJX0tFWTtcbiAgICBpZighYXBpS2V5KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ0FudGhyb3BpYyBBUEkga2V5IGlzIHJlcXVpcmVkLiBTZXQgaXQgaW4geW91ciBsZXguY29uZmlnIGZpbGUgb3IgYXMgQU5USFJPUElDX0FQSV9LRVkgZW52aXJvbm1lbnQgdmFyaWFibGUuJyk7XG4gICAgfVxuXG4gICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaCgnaHR0cHM6Ly9hcGkuYW50aHJvcGljLmNvbS92MS9tZXNzYWdlcycsIHtcbiAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHtcbiAgICAgICAgbWF4X3Rva2Vuczogb3B0aW9ucy5tYXhUb2tlbnMgfHwgNDAwMCxcbiAgICAgICAgbWVzc2FnZXM6IFtcbiAgICAgICAgICB7Y29udGVudDogcHJvbXB0LCByb2xlOiAndXNlcid9XG4gICAgICAgIF0sXG4gICAgICAgIG1vZGVsOiBvcHRpb25zLm1vZGVsIHx8ICdjbGF1ZGUtMy1zb25uZXQtMjAyNDAyMjknLFxuICAgICAgICB0ZW1wZXJhdHVyZTogb3B0aW9ucy50ZW1wZXJhdHVyZSB8fCAwLjFcbiAgICAgIH0pLFxuICAgICAgaGVhZGVyczoge1xuICAgICAgICAnQ29udGVudC1UeXBlJzogJ2FwcGxpY2F0aW9uL2pzb24nLFxuICAgICAgICAnYW50aHJvcGljLXZlcnNpb24nOiAnMjAyMy0wNi0wMScsXG4gICAgICAgICd4LWFwaS1rZXknOiBhcGlLZXlcbiAgICAgIH0sXG4gICAgICBtZXRob2Q6ICdQT1NUJ1xuICAgIH0pO1xuXG4gICAgaWYoIXJlc3BvbnNlLm9rKSB7XG4gICAgICBjb25zdCBlcnJvciA9IGF3YWl0IHJlc3BvbnNlLmpzb24oKTtcbiAgICAgIHRocm93IG5ldyBFcnJvcihgQW50aHJvcGljIEFQSSBlcnJvcjogJHtlcnJvci5lcnJvcj8ubWVzc2FnZSB8fCByZXNwb25zZS5zdGF0dXNUZXh0fWApO1xuICAgIH1cblxuICAgIGNvbnN0IGRhdGEgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgcmV0dXJuIGRhdGEuY29udGVudFswXS50ZXh0O1xuICB9IGNhdGNoKGVycm9yKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBBbnRocm9waWMgQUkgZXJyb3I6ICR7ZXJyb3IubWVzc2FnZX1gKTtcbiAgfVxufTtcblxuZXhwb3J0IGNvbnN0IGNhbGxDb3BpbG90QUkgPSBhc3luYyAocHJvbXB0OiBzdHJpbmcsIF9vcHRpb25zOiBBSUNvbmZpZyk6IFByb21pc2U8c3RyaW5nPiA9PiB7XG4gIHRyeSB7XG4gICAgbG9nKCdHaXRIdWIgQ29waWxvdCBBSSBmaXhlcyBub3QgZGlyZWN0bHkgc3VwcG9ydGVkLiBVc2luZyBtYW51YWwgZml4IG1vZGUuJywgJ2luZm8nKTtcbiAgICByZXR1cm4gcHJvbXB0O1xuICB9IGNhdGNoKGVycm9yKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKGBHaXRIdWIgQ29waWxvdCBBSSBlcnJvcjogJHtlcnJvci5tZXNzYWdlfWApO1xuICB9XG59O1xuXG5leHBvcnQgY29uc3QgcHJvbXB0Rm9yQUlQcm92aWRlciA9IGFzeW5jIChfcXVpZXQgPSBmYWxzZSk6IFByb21pc2U8J2N1cnNvcicgfCAnY29waWxvdCcgfCAnb3BlbmFpJyB8ICdhbnRocm9waWMnIHwgJ25vbmUnPiA9PiB7XG4gIGNvbnN0IHJsID0gcmVhZGxpbmUuY3JlYXRlSW50ZXJmYWNlKHtcbiAgICBpbnB1dDogcHJvY2Vzcy5zdGRpbixcbiAgICBvdXRwdXQ6IHByb2Nlc3Muc3Rkb3V0XG4gIH0pO1xuXG4gIHJldHVybiBuZXcgUHJvbWlzZSgocmVzb2x2ZSkgPT4ge1xuICAgIGxvZygnXFxuTm8gQUkgcHJvdmlkZXIgY29uZmlndXJlZC4gUGxlYXNlIGNob29zZSBhbiBBSSBwcm92aWRlcjonLCAnaW5mbycpO1xuICAgIGxvZygnMS4gQ3Vyc29yIElERScsICdpbmZvJyk7XG4gICAgbG9nKCcyLiBPcGVuQUknLCAnaW5mbycpO1xuICAgIGxvZygnMy4gQW50aHJvcGljJywgJ2luZm8nKTtcbiAgICBsb2coJzQuIEdpdEh1YiBDb3BpbG90JywgJ2luZm8nKTtcbiAgICBsb2coJzUuIE5vbmUgKFNraXAgQUkgZmVhdHVyZXMpJywgJ2luZm8nKTtcblxuICAgIHJsLnF1ZXN0aW9uKCdFbnRlciB5b3VyIGNob2ljZSAoMS01KTogJywgKGFuc3dlcikgPT4ge1xuICAgICAgcmwuY2xvc2UoKTtcblxuICAgICAgc3dpdGNoKGFuc3dlcikge1xuICAgICAgICBjYXNlICcxJzpcbiAgICAgICAgICByZXNvbHZlKCdjdXJzb3InKTtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgY2FzZSAnMic6XG4gICAgICAgICAgcmVzb2x2ZSgnb3BlbmFpJyk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGNhc2UgJzMnOlxuICAgICAgICAgIHJlc29sdmUoJ2FudGhyb3BpYycpO1xuICAgICAgICAgIGJyZWFrO1xuICAgICAgICBjYXNlICc0JzpcbiAgICAgICAgICByZXNvbHZlKCdjb3BpbG90Jyk7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIGRlZmF1bHQ6XG4gICAgICAgICAgcmVzb2x2ZSgnbm9uZScpO1xuICAgICAgfVxuICAgIH0pO1xuICB9KTtcbn07XG5cbmV4cG9ydCBjb25zdCBwcm9tcHRGb3JBUElLZXkgPSBhc3luYyAocHJvdmlkZXI6IHN0cmluZywgX3F1aWV0ID0gZmFsc2UpOiBQcm9taXNlPHN0cmluZz4gPT4ge1xuICBjb25zdCBybCA9IHJlYWRsaW5lLmNyZWF0ZUludGVyZmFjZSh7XG4gICAgaW5wdXQ6IHByb2Nlc3Muc3RkaW4sXG4gICAgb3V0cHV0OiBwcm9jZXNzLnN0ZG91dFxuICB9KTtcblxuICByZXR1cm4gbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHtcbiAgICBybC5xdWVzdGlvbihgUGxlYXNlIGVudGVyIHlvdXIgJHtwcm92aWRlcn0gQVBJIGtleTogYCwgKGFuc3dlcikgPT4ge1xuICAgICAgcmwuY2xvc2UoKTtcbiAgICAgIHJlc29sdmUoYW5zd2VyKTtcbiAgICB9KTtcbiAgfSk7XG59O1xuXG5leHBvcnQgY29uc3QgZ2V0QUlTZXJ2aWNlID0gKFxuICBwcm92aWRlcjogc3RyaW5nLFxuICBfb3B0aW9uczogQUlDb25maWdcbik6IChwcm9tcHQ6IHN0cmluZywgb3B0aW9uczogQUlDb25maWcpPT4gUHJvbWlzZTxzdHJpbmc+ID0+IHtcbiAgc3dpdGNoKHByb3ZpZGVyKSB7XG4gICAgY2FzZSAnY3Vyc29yJzpcbiAgICAgIHJldHVybiBjYWxsQ3Vyc29yQUk7XG4gICAgY2FzZSAnb3BlbmFpJzpcbiAgICAgIHJldHVybiBjYWxsT3BlbkFJQUk7XG4gICAgY2FzZSAnYW50aHJvcGljJzpcbiAgICAgIHJldHVybiBjYWxsQW50aHJvcGljQUk7XG4gICAgY2FzZSAnY29waWxvdCc6XG4gICAgICByZXR1cm4gY2FsbENvcGlsb3RBSTtcbiAgICBkZWZhdWx0OlxuICAgICAgcmV0dXJuIGFzeW5jICgpID0+ICdObyBBSSBwcm92aWRlciBjb25maWd1cmVkJztcbiAgfVxufTtcblxuZXhwb3J0IGNvbnN0IGNhbGxBSVNlcnZpY2UgPSBhc3luYyAocHJvbXB0OiBzdHJpbmcsIHF1aWV0ID0gZmFsc2UpOiBQcm9taXNlPHN0cmluZz4gPT4ge1xuICBjb25zdCBzcGlubmVyID0gY3JlYXRlU3Bpbm5lcihxdWlldCk7XG4gIHNwaW5uZXIuc3RhcnQoJ0NhbGxpbmcgQUkgc2VydmljZSB0byBmaXggY29kZSBpc3N1ZXMuLi4nKTtcblxuICB0cnkge1xuICAgIGNvbnN0IGFpQ29uZmlnID0gTGV4Q29uZmlnLmNvbmZpZy5haSB8fCB7cHJvdmlkZXI6ICdub25lJ307XG5cbiAgICBjb25zdCBpc0luQ3Vyc29ySURFID0gcHJvY2Vzcy5lbnYuQ1VSU09SX0lERSA9PT0gJ3RydWUnO1xuICAgIGlmKGlzSW5DdXJzb3JJREUgJiYgKGFpQ29uZmlnLnByb3ZpZGVyID09PSAnbm9uZScgfHwgIWFpQ29uZmlnLnByb3ZpZGVyKSkge1xuICAgICAgbG9nKCdEZXRlY3RlZCBDdXJzb3IgSURFIGVudmlyb25tZW50LCB1c2luZyBDdXJzb3IgYXMgQUkgcHJvdmlkZXInLCAnaW5mbycsIHF1aWV0KTtcbiAgICAgIGFpQ29uZmlnLnByb3ZpZGVyID0gJ2N1cnNvcic7XG4gICAgfVxuXG4gICAgaWYoYWlDb25maWcucHJvdmlkZXIgPT09ICdub25lJykge1xuICAgICAgY29uc3QgcHJvdmlkZXIgPSBhd2FpdCBwcm9tcHRGb3JBSVByb3ZpZGVyKHF1aWV0KTtcblxuICAgICAgaWYocHJvdmlkZXIgPT09ICdub25lJykge1xuICAgICAgICBzcGlubmVyLmZhaWwoJ0FJIGZlYXR1cmVzIHNraXBwZWQnKTtcbiAgICAgICAgcmV0dXJuICcnO1xuICAgICAgfVxuXG4gICAgICBhaUNvbmZpZy5wcm92aWRlciA9IHByb3ZpZGVyO1xuXG4gICAgICBpZihwcm92aWRlciAhPT0gJ2N1cnNvcicgJiYgcHJvdmlkZXIgIT09ICdjb3BpbG90JyAmJlxuICAgICAgICAhcHJvY2Vzcy5lbnZbYCR7cHJvdmlkZXIudG9VcHBlckNhc2UoKX1fQVBJX0tFWWBdKSB7XG4gICAgICAgIGFpQ29uZmlnLmFwaUtleSA9IGF3YWl0IHByb21wdEZvckFQSUtleShwcm92aWRlciwgcXVpZXQpO1xuICAgICAgfVxuXG4gICAgICBMZXhDb25maWcuY29uZmlnLmFpID0gYWlDb25maWc7XG5cbiAgICAgIC8vIFNlYXJjaCBmb3IgY29uZmlnIGZpbGVzIGluIG11bHRpcGxlIGZvcm1hdHMgbGlrZSBMZXhDb25maWcucGFyc2VDb25maWcgZG9lc1xuICAgICAgY29uc3QgY29uZmlnRm9ybWF0cyA9IFsnanMnLCAnbWpzJywgJ2NqcycsICd0cycsICdqc29uJ107XG4gICAgICBjb25zdCBjb25maWdCYXNlTmFtZSA9ICdsZXguY29uZmlnJztcbiAgICAgIGxldCBjb25maWdQYXRoID0gJyc7XG5cbiAgICAgIGZvcihjb25zdCBmb3JtYXQgb2YgY29uZmlnRm9ybWF0cykge1xuICAgICAgICBjb25zdCBwb3RlbnRpYWxQYXRoID0gcGF0aFJlc29sdmUocHJvY2Vzcy5jd2QoKSwgYC4vJHtjb25maWdCYXNlTmFtZX0uJHtmb3JtYXR9YCk7XG4gICAgICAgIGlmKGV4aXN0c1N5bmMocG90ZW50aWFsUGF0aCkpIHtcbiAgICAgICAgICBjb25maWdQYXRoID0gcG90ZW50aWFsUGF0aDtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBpZihjb25maWdQYXRoKSB7XG4gICAgICAgIHRyeSB7XG4gICAgICAgICAgY29uc3QgY29uZmlnQ29udGVudCA9IHJlYWRGaWxlU3luYyhjb25maWdQYXRoLCAndXRmOCcpO1xuICAgICAgICAgIGNvbnN0IHVwZGF0ZWRDb25maWcgPSBjb25maWdDb250ZW50LnJlcGxhY2UoXG4gICAgICAgICAgICAvYWk6Lio/Wyx9XS9zLFxuICAgICAgICAgICAgYGFpOiB7IHByb3ZpZGVyOiAnJHthaUNvbmZpZy5wcm92aWRlcn0nIH0sYFxuICAgICAgICAgICk7XG4gICAgICAgICAgd3JpdGVGaWxlU3luYyhjb25maWdQYXRoLCB1cGRhdGVkQ29uZmlnKTtcbiAgICAgICAgfSBjYXRjaChfZXJyb3IpIHtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIGxldCByZXN1bHQgPSAnJztcblxuICAgIHN3aXRjaChhaUNvbmZpZy5wcm92aWRlcikge1xuICAgICAgY2FzZSAnY3Vyc29yJzpcbiAgICAgICAgcmVzdWx0ID0gYXdhaXQgY2FsbEN1cnNvckFJKHByb21wdCwgYWlDb25maWcpO1xuICAgICAgICBsb2coJ0N1cnNvciBJREUgQUkgaW50ZWdyYXRpb24gYWN0aXZlJywgJ2luZm8nLCBxdWlldCk7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSAnb3BlbmFpJzpcbiAgICAgICAgcmVzdWx0ID0gYXdhaXQgY2FsbE9wZW5BSUFJKHByb21wdCwgYWlDb25maWcpO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgJ2FudGhyb3BpYyc6XG4gICAgICAgIHJlc3VsdCA9IGF3YWl0IGNhbGxBbnRocm9waWNBSShwcm9tcHQsIGFpQ29uZmlnKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlICdjb3BpbG90JzpcbiAgICAgICAgcmVzdWx0ID0gYXdhaXQgY2FsbENvcGlsb3RBSShwcm9tcHQsIGFpQ29uZmlnKTtcbiAgICAgICAgYnJlYWs7XG4gICAgICBkZWZhdWx0OlxuICAgICAgICBzcGlubmVyLmZhaWwoJ05vIEFJIHByb3ZpZGVyIGNvbmZpZ3VyZWQnKTtcbiAgICAgICAgcmV0dXJuICcnO1xuICAgIH1cblxuICAgIHNwaW5uZXIuc3VjY2VlZCgnQUkgY29kZSBmaXhlcyBnZW5lcmF0ZWQgc3VjY2Vzc2Z1bGx5Jyk7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfSBjYXRjaChlcnJvcikge1xuICAgIHNwaW5uZXIuZmFpbChgQUkgc2VydmljZSBlcnJvcjogJHtlcnJvci5tZXNzYWdlfWApO1xuICAgIGlmKCFxdWlldCkge1xuICAgICAgbG9nKGVycm9yLCAnZXJyb3InKTtcbiAgICB9XG4gICAgcmV0dXJuICcnO1xuICB9XG59OyJdLCJuYW1lcyI6WyJleGlzdHNTeW5jIiwicmVhZEZpbGVTeW5jIiwid3JpdGVGaWxlU3luYyIsInJlc29sdmUiLCJwYXRoUmVzb2x2ZSIsInJlYWRsaW5lIiwiTGV4Q29uZmlnIiwiY3JlYXRlU3Bpbm5lciIsImxvZyIsImNhbGxDdXJzb3JBSSIsInByb21wdCIsIl9vcHRpb25zIiwidGFza01hdGNoIiwibWF0Y2giLCJ0YXNrIiwiaXNHZW5lcmF0ZVRhc2siLCJzdGFydHNXaXRoIiwicXVlc3Rpb25NYXRjaCIsInF1ZXN0aW9uIiwidHJpbSIsInRvTG93ZXJDYXNlIiwiaW5jbHVkZXMiLCJwcm9qZWN0U3RydWN0dXJlIiwic3BsaXQiLCJmaWxlcyIsImxlbmd0aCIsImVycm9yIiwiRXJyb3IiLCJtZXNzYWdlIiwiY2FsbE9wZW5BSUFJIiwib3B0aW9ucyIsImFwaUtleSIsInByb2Nlc3MiLCJlbnYiLCJPUEVOQUlfQVBJX0tFWSIsInJlc3BvbnNlIiwiZmV0Y2giLCJib2R5IiwiSlNPTiIsInN0cmluZ2lmeSIsIm1heF90b2tlbnMiLCJtYXhUb2tlbnMiLCJtZXNzYWdlcyIsImNvbnRlbnQiLCJyb2xlIiwibW9kZWwiLCJ0ZW1wZXJhdHVyZSIsImhlYWRlcnMiLCJBdXRob3JpemF0aW9uIiwibWV0aG9kIiwib2siLCJqc29uIiwic3RhdHVzVGV4dCIsImRhdGEiLCJjaG9pY2VzIiwiY2FsbEFudGhyb3BpY0FJIiwiQU5USFJPUElDX0FQSV9LRVkiLCJ0ZXh0IiwiY2FsbENvcGlsb3RBSSIsInByb21wdEZvckFJUHJvdmlkZXIiLCJfcXVpZXQiLCJybCIsImNyZWF0ZUludGVyZmFjZSIsImlucHV0Iiwic3RkaW4iLCJvdXRwdXQiLCJzdGRvdXQiLCJQcm9taXNlIiwiYW5zd2VyIiwiY2xvc2UiLCJwcm9tcHRGb3JBUElLZXkiLCJwcm92aWRlciIsImdldEFJU2VydmljZSIsImNhbGxBSVNlcnZpY2UiLCJxdWlldCIsInNwaW5uZXIiLCJzdGFydCIsImFpQ29uZmlnIiwiY29uZmlnIiwiYWkiLCJpc0luQ3Vyc29ySURFIiwiQ1VSU09SX0lERSIsImZhaWwiLCJ0b1VwcGVyQ2FzZSIsImNvbmZpZ0Zvcm1hdHMiLCJjb25maWdCYXNlTmFtZSIsImNvbmZpZ1BhdGgiLCJmb3JtYXQiLCJwb3RlbnRpYWxQYXRoIiwiY3dkIiwiY29uZmlnQ29udGVudCIsInVwZGF0ZWRDb25maWciLCJyZXBsYWNlIiwiX2Vycm9yIiwicmVzdWx0Iiwic3VjY2VlZCJdLCJtYXBwaW5ncyI6IkFBQUE7OztDQUdDLEdBQ0QsU0FBUUEsVUFBVSxFQUFFQyxZQUFZLEVBQUVDLGFBQWEsUUFBTyxLQUFLO0FBQzNELFNBQVFDLFdBQVdDLFdBQVcsUUFBTyxPQUFPO0FBQzVDLE9BQU9DLGNBQWMsV0FBVztBQUVoQyxTQUFrQkMsU0FBUyxRQUFPLGtCQUFrQjtBQUNwRCxTQUFRQyxhQUFhLFFBQU8sV0FBVztBQUN2QyxTQUFRQyxHQUFHLFFBQU8sV0FBVztBQUU3Qix5QkFBeUI7QUFDekIsT0FB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