mirror of
http://10.0.2.1:3031/sauer/claude-code.git
synced 2026-06-30 21:56:58 +10:00
371 lines
41 KiB
TypeScript
371 lines
41 KiB
TypeScript
|
|
import { c as _c } from "react/compiler-runtime";
|
||
|
|
import { mkdir, writeFile } from 'fs/promises';
|
||
|
|
import { marked, type Tokens } from 'marked';
|
||
|
|
import { tmpdir } from 'os';
|
||
|
|
import { join } from 'path';
|
||
|
|
import React, { useRef } from 'react';
|
||
|
|
import type { CommandResultDisplay } from '../../commands.js';
|
||
|
|
import type { OptionWithDescription } from '../../components/CustomSelect/select.js';
|
||
|
|
import { Select } from '../../components/CustomSelect/select.js';
|
||
|
|
import { Byline } from '../../components/design-system/Byline.js';
|
||
|
|
import { KeyboardShortcutHint } from '../../components/design-system/KeyboardShortcutHint.js';
|
||
|
|
import { Pane } from '../../components/design-system/Pane.js';
|
||
|
|
import type { KeyboardEvent } from '../../ink/events/keyboard-event.js';
|
||
|
|
import { stringWidth } from '../../ink/stringWidth.js';
|
||
|
|
import { setClipboard } from '../../ink/termio/osc.js';
|
||
|
|
import { Box, Text } from '../../ink.js';
|
||
|
|
import { logEvent } from '../../services/analytics/index.js';
|
||
|
|
import type { LocalJSXCommandCall } from '../../types/command.js';
|
||
|
|
import type { AssistantMessage, Message } from '../../types/message.js';
|
||
|
|
import { getGlobalConfig, saveGlobalConfig } from '../../utils/config.js';
|
||
|
|
import { extractTextContent, stripPromptXMLTags } from '../../utils/messages.js';
|
||
|
|
import { countCharInString } from '../../utils/stringUtils.js';
|
||
|
|
const COPY_DIR = join(tmpdir(), 'claude');
|
||
|
|
const RESPONSE_FILENAME = 'response.md';
|
||
|
|
const MAX_LOOKBACK = 20;
|
||
|
|
type CodeBlock = {
|
||
|
|
code: string;
|
||
|
|
lang: string | undefined;
|
||
|
|
};
|
||
|
|
function extractCodeBlocks(markdown: string): CodeBlock[] {
|
||
|
|
const tokens = marked.lexer(stripPromptXMLTags(markdown));
|
||
|
|
const blocks: CodeBlock[] = [];
|
||
|
|
for (const token of tokens) {
|
||
|
|
if (token.type === 'code') {
|
||
|
|
const codeToken = token as Tokens.Code;
|
||
|
|
blocks.push({
|
||
|
|
code: codeToken.text,
|
||
|
|
lang: codeToken.lang
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return blocks;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Walk messages newest-first, returning text from assistant messages that
|
||
|
|
* actually said something (skips tool-use-only turns and API errors).
|
||
|
|
* Index 0 = latest, 1 = second-to-latest, etc. Caps at MAX_LOOKBACK.
|
||
|
|
*/
|
||
|
|
export function collectRecentAssistantTexts(messages: Message[]): string[] {
|
||
|
|
const texts: string[] = [];
|
||
|
|
for (let i = messages.length - 1; i >= 0 && texts.length < MAX_LOOKBACK; i--) {
|
||
|
|
const msg = messages[i];
|
||
|
|
if (msg?.type !== 'assistant' || msg.isApiErrorMessage) continue;
|
||
|
|
const content = (msg as AssistantMessage).message.content;
|
||
|
|
if (!Array.isArray(content)) continue;
|
||
|
|
const text = extractTextContent(content, '\n\n');
|
||
|
|
if (text) texts.push(text);
|
||
|
|
}
|
||
|
|
return texts;
|
||
|
|
}
|
||
|
|
export function fileExtension(lang: string | undefined): string {
|
||
|
|
if (lang) {
|
||
|
|
// Sanitize to prevent path traversal (e.g. ```../../etc/passwd)
|
||
|
|
// Language identifiers are alphanumeric: python, tsx, jsonc, etc.
|
||
|
|
const sanitized = lang.replace(/[^a-zA-Z0-9]/g, '');
|
||
|
|
if (sanitized && sanitized !== 'plaintext') {
|
||
|
|
return `.${sanitized}`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return '.txt';
|
||
|
|
}
|
||
|
|
async function writeToFile(text: string, filename: string): Promise<string> {
|
||
|
|
const filePath = join(COPY_DIR, filename);
|
||
|
|
await mkdir(COPY_DIR, {
|
||
|
|
recursive: true
|
||
|
|
});
|
||
|
|
await writeFile(filePath, text, 'utf-8');
|
||
|
|
return filePath;
|
||
|
|
}
|
||
|
|
async function copyOrWriteToFile(text: string, filename: string): Promise<string> {
|
||
|
|
const raw = await setClipboard(text);
|
||
|
|
if (raw) process.stdout.write(raw);
|
||
|
|
const lineCount = countCharInString(text, '\n') + 1;
|
||
|
|
const charCount = text.length;
|
||
|
|
// Also write to a temp file — clipboard paths are best-effort (OSC 52 needs
|
||
|
|
// terminal support), so the file provides a reliable fallback.
|
||
|
|
try {
|
||
|
|
const filePath = await writeToFile(text, filename);
|
||
|
|
return `Copied to clipboard (${charCount} characters, ${lineCount} lines)\nAlso written to ${filePath}`;
|
||
|
|
} catch {
|
||
|
|
return `Copied to clipboard (${charCount} characters, ${lineCount} lines)`;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
function truncateLine(text: string, maxLen: number): string {
|
||
|
|
const firstLine = text.split('\n')[0] ?? '';
|
||
|
|
if (stringWidth(firstLine) <= maxLen) {
|
||
|
|
return firstLine;
|
||
|
|
}
|
||
|
|
let result = '';
|
||
|
|
let width = 0;
|
||
|
|
const targetWidth = maxLen - 1;
|
||
|
|
for (const char of firstLine) {
|
||
|
|
const charWidth = stringWidth(char);
|
||
|
|
if (width + charWidth > targetWidth) break;
|
||
|
|
result += char;
|
||
|
|
width += charWidth;
|
||
|
|
}
|
||
|
|
return result + '\u2026';
|
||
|
|
}
|
||
|
|
type PickerProps = {
|
||
|
|
fullText: string;
|
||
|
|
codeBlocks: CodeBlock[];
|
||
|
|
messageAge: number;
|
||
|
|
onDone: (result?: string, options?: {
|
||
|
|
display?: CommandResultDisplay;
|
||
|
|
}) => void;
|
||
|
|
};
|
||
|
|
type PickerSelection = number | 'full' | 'always';
|
||
|
|
function CopyPicker(t0) {
|
||
|
|
const $ = _c(33);
|
||
|
|
const {
|
||
|
|
fullText,
|
||
|
|
codeBlocks,
|
||
|
|
messageAge,
|
||
|
|
onDone
|
||
|
|
} = t0;
|
||
|
|
const focusedRef = useRef("full");
|
||
|
|
const t1 = `${fullText.length} chars, ${countCharInString(fullText, "\n") + 1} lines`;
|
||
|
|
let t2;
|
||
|
|
if ($[0] !== t1) {
|
||
|
|
t2 = {
|
||
|
|
label: "Full response",
|
||
|
|
value: "full" as const,
|
||
|
|
description: t1
|
||
|
|
};
|
||
|
|
$[0] = t1;
|
||
|
|
$[1] = t2;
|
||
|
|
} else {
|
||
|
|
t2 = $[1];
|
||
|
|
}
|
||
|
|
let t3;
|
||
|
|
if ($[2] !== codeBlocks || $[3] !== t2) {
|
||
|
|
let t4;
|
||
|
|
if ($[5] === Symbol.for("react.memo_cache_sentinel")) {
|
||
|
|
t4 = {
|
||
|
|
label: "Always copy full response",
|
||
|
|
value: "always" as const,
|
||
|
|
description: "Skip this picker in the future (revert via /config)"
|
||
|
|
};
|
||
|
|
$[5] = t4;
|
||
|
|
} else {
|
||
|
|
t4 = $[5];
|
||
|
|
}
|
||
|
|
t3 = [t2, ...codeBlocks.map(_temp), t4];
|
||
|
|
$[2] = codeBlocks;
|
||
|
|
$[3] = t2;
|
||
|
|
$[4] = t3;
|
||
|
|
} else {
|
||
|
|
t3 = $[4];
|
||
|
|
}
|
||
|
|
const options = t3;
|
||
|
|
let t4;
|
||
|
|
if ($[6] !== codeBlocks || $[7] !== fullText) {
|
||
|
|
t4 = function getSelectionContent(selected) {
|
||
|
|
if (selected === "full" || selected === "always") {
|
||
|
|
return {
|
||
|
|
text: fullText,
|
||
|
|
filename: RESPONSE_FILENAME
|
||
|
|
};
|
||
|
|
}
|
||
|
|
const block_0 = codeBlocks[selected];
|
||
|
|
return {
|
||
|
|
text: block_0.code,
|
||
|
|
filename: `copy${fileExtension(block_0.lang)}`,
|
||
|
|
blockIndex: selected
|
||
|
|
};
|
||
|
|
};
|
||
|
|
$[6] = codeBlocks;
|
||
|
|
$[7] = fullText;
|
||
|
|
$[8] = t4;
|
||
|
|
} else {
|
||
|
|
t4 = $[8];
|
||
|
|
}
|
||
|
|
const getSelectionContent = t4;
|
||
|
|
let t5;
|
||
|
|
if ($[9] !== codeBlocks.length || $[10] !== getSelectionContent || $[11] !== messageAge || $[12] !== onDone) {
|
||
|
|
t5 = async function handleSelect(selected_0) {
|
||
|
|
const content = getSelectionContent(selected_0);
|
||
|
|
if (selected_0 === "always") {
|
||
|
|
if (!getGlobalConfig().copyFullResponse) {
|
||
|
|
saveGlobalConfig(_temp2);
|
||
|
|
}
|
||
|
|
logEvent("tengu_copy", {
|
||
|
|
block_count: codeBlocks.length,
|
||
|
|
always: true,
|
||
|
|
message_age: messageAge
|
||
|
|
});
|
||
|
|
const result = await copyOrWriteToFile(content.text, content.filename);
|
||
|
|
onDone(`${result}\nPreference saved. Use /config to change copyFullResponse`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
logEvent("tengu_copy", {
|
||
|
|
selected_block: content.blockIndex,
|
||
|
|
block_count: codeBlocks.length,
|
||
|
|
message_age: messageAge
|
||
|
|
});
|
||
|
|
const result_0 = await copyOrWriteToFile(content.text, content.filename);
|
||
|
|
onDone(result_0);
|
||
|
|
};
|
||
|
|
$[9] = codeBlocks.length;
|
||
|
|
$[10] = getSelectionContent;
|
||
|
|
$[11] = messageAge;
|
||
|
|
$[12] = onDone;
|
||
|
|
$[13] = t5;
|
||
|
|
} else {
|
||
|
|
t5 = $[13];
|
||
|
|
}
|
||
|
|
const handleSelect = t5;
|
||
|
|
let t6;
|
||
|
|
if ($[14] !== codeBlocks.length || $[15] !== getSelectionContent || $[16] !== messageAge || $[17] !== onDone) {
|
||
|
|
const handleWrite = async function handleWrite(selected_1) {
|
||
|
|
const content_0 = getSelectionContent(selected_1);
|
||
|
|
logEvent("tengu_copy", {
|
||
|
|
selected_block: content_0.blockIndex,
|
||
|
|
block_count: codeBlocks.length,
|
||
|
|
message_age: messageAge,
|
||
|
|
write_shortcut: true
|
||
|
|
});
|
||
|
|
;
|
||
|
|
try {
|
||
|
|
const filePath = await writeToFile(content_0.text, content_0.filename);
|
||
|
|
onDone(`Written to ${filePath}`);
|
||
|
|
} catch (t7) {
|
||
|
|
const e = t7;
|
||
|
|
onDone(`Failed to write file: ${e instanceof Error ? e.message : e}`);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
t6 = function handleKeyDown(e_0) {
|
||
|
|
if (e_0.key === "w") {
|
||
|
|
e_0.preventDefault();
|
||
|
|
handleWrite(focusedRef.current);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
$[14] = codeBlocks.length;
|
||
|
|
$[15] = getSelectionContent;
|
||
|
|
$[16] = messageAge;
|
||
|
|
$[17] = onDone;
|
||
|
|
$[18] = t6;
|
||
|
|
} else {
|
||
|
|
t6 = $[18];
|
||
|
|
}
|
||
|
|
const handleKeyDown = t6;
|
||
|
|
let t7;
|
||
|
|
if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
|
||
|
|
t7 = <Text dimColor={true}>Select content to copy:</Text>;
|
||
|
|
$[19] = t7;
|
||
|
|
} else {
|
||
|
|
t7 = $[19];
|
||
|
|
}
|
||
|
|
let t8;
|
||
|
|
if ($[20] === Symbol.for("react.memo_cache_sentinel")) {
|
||
|
|
t8 = value => {
|
||
|
|
focusedRef.current = value;
|
||
|
|
};
|
||
|
|
$[20] = t8;
|
||
|
|
} else {
|
||
|
|
t8 = $[20];
|
||
|
|
}
|
||
|
|
let t9;
|
||
|
|
if ($[21] !== handleSelect) {
|
||
|
|
t9 = selected_2 => {
|
||
|
|
handleSelect(selected_2);
|
||
|
|
};
|
||
|
|
$[21] = handleSelect;
|
||
|
|
$[22] = t9;
|
||
|
|
} else {
|
||
|
|
t9 = $[22];
|
||
|
|
}
|
||
|
|
let t10;
|
||
|
|
if ($[23] !== onDone) {
|
||
|
|
t10 = () => {
|
||
|
|
onDone("Copy cancelled", {
|
||
|
|
display: "system"
|
||
|
|
});
|
||
|
|
};
|
||
|
|
$[23] = onDone;
|
||
|
|
$[24] = t10;
|
||
|
|
} else {
|
||
|
|
t10 = $[24];
|
||
|
|
}
|
||
|
|
let t11;
|
||
|
|
if ($[25] !== options || $[26] !== t10 || $[27] !== t9) {
|
||
|
|
t11 = <Select options={options} hideIndexes={false} onFocus={t8} onChange={t9} onCancel={t10} />;
|
||
|
|
$[25] = options;
|
||
|
|
$[26] = t10;
|
||
|
|
$[27] = t9;
|
||
|
|
$[28] = t11;
|
||
|
|
} else {
|
||
|
|
t11 = $[28];
|
||
|
|
}
|
||
|
|
let t12;
|
||
|
|
if ($[29] === Symbol.for("react.memo_cache_sentinel")) {
|
||
|
|
t12 = <Text dimColor={true}><Byline><KeyboardShortcutHint shortcut="enter" action="copy" /><KeyboardShortcutHint shortcut="w" action="write to file" /><KeyboardShortcutHint shortcut="esc" action="cancel" /></Byline></Text>;
|
||
|
|
$[29] = t12;
|
||
|
|
} else {
|
||
|
|
t12 = $[29];
|
||
|
|
}
|
||
|
|
let t13;
|
||
|
|
if ($[30] !== handleKeyDown || $[31] !== t11) {
|
||
|
|
t13 = <Pane><Box flexDirection="column" gap={1} tabIndex={0} autoFocus={true} onKeyDown={handleKeyDown}>{t7}{t11}{t12}</Box></Pane>;
|
||
|
|
$[30] = handleKeyDown;
|
||
|
|
$[31] = t11;
|
||
|
|
$[32] = t13;
|
||
|
|
} else {
|
||
|
|
t13 = $[32];
|
||
|
|
}
|
||
|
|
return t13;
|
||
|
|
}
|
||
|
|
function _temp2(c) {
|
||
|
|
return {
|
||
|
|
...c,
|
||
|
|
copyFullResponse: true
|
||
|
|
};
|
||
|
|
}
|
||
|
|
function _temp(block, index) {
|
||
|
|
const blockLines = countCharInString(block.code, "\n") + 1;
|
||
|
|
return {
|
||
|
|
label: truncateLine(block.code, 60),
|
||
|
|
value: index,
|
||
|
|
description: [block.lang, blockLines > 1 ? `${blockLines} lines` : undefined].filter(Boolean).join(", ") || undefined
|
||
|
|
};
|
||
|
|
}
|
||
|
|
export const call: LocalJSXCommandCall = async (onDone, context, args) => {
|
||
|
|
const texts = collectRecentAssistantTexts(context.messages);
|
||
|
|
if (texts.length === 0) {
|
||
|
|
onDone('No assistant message to copy');
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
// /copy N reaches back N-1 messages (1 = latest, 2 = second-to-latest, ...)
|
||
|
|
let age = 0;
|
||
|
|
const arg = args?.trim();
|
||
|
|
if (arg) {
|
||
|
|
const n = Number(arg);
|
||
|
|
if (!Number.isInteger(n) || n < 1) {
|
||
|
|
onDone(`Usage: /copy [N] where N is 1 (latest), 2, 3, \u2026 Got: ${arg}`);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
if (n > texts.length) {
|
||
|
|
onDone(`Only ${texts.length} assistant ${texts.length === 1 ? 'message' : 'messages'} available to copy`);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
age = n - 1;
|
||
|
|
}
|
||
|
|
const text = texts[age]!;
|
||
|
|
const codeBlocks = extractCodeBlocks(text);
|
||
|
|
const config = getGlobalConfig();
|
||
|
|
if (codeBlocks.length === 0 || config.copyFullResponse) {
|
||
|
|
logEvent('tengu_copy', {
|
||
|
|
always: config.copyFullResponse,
|
||
|
|
block_count: codeBlocks.length,
|
||
|
|
message_age: age
|
||
|
|
});
|
||
|
|
const result = await copyOrWriteToFile(text, RESPONSE_FILENAME);
|
||
|
|
onDone(result);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return <CopyPicker fullText={text} codeBlocks={codeBlocks} messageAge={age} onDone={onDone} />;
|
||
|
|
};
|
||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJta2RpciIsIndyaXRlRmlsZSIsIm1hcmtlZCIsIlRva2VucyIsInRtcGRpciIsImpvaW4iLCJSZWFjdCIsInVzZVJlZiIsIkNvbW1hbmRSZXN1bHREaXNwbGF5IiwiT3B0aW9uV2l0aERlc2NyaXB0aW9uIiwiU2VsZWN0IiwiQnlsaW5lIiwiS2V5Ym9hcmRTaG9ydGN1dEhpbnQiLCJQYW5lIiwiS2V5Ym9hcmRFdmVudCIsInN0cmluZ1dpZHRoIiwic2V0Q2xpcGJvYXJkIiwiQm94IiwiVGV4dCIsImxvZ0V2ZW50IiwiTG9jYWxKU1hDb21tYW5kQ2FsbCIsIkFzc2lzdGFudE1lc3NhZ2UiLCJNZXNzYWdlIiwiZ2V0R2xvYmFsQ29uZmlnIiwic2F2ZUdsb2JhbENvbmZpZyIsImV4dHJhY3RUZXh0Q29udGVudCIsInN0cmlwUHJvbXB0WE1MVGFncyIsImNvdW50Q2hhckluU3RyaW5nIiwiQ09QWV9ESVIiLCJSRVNQT05TRV9GSUxFTkFNRSIsIk1BWF9MT09LQkFDSyIsIkNvZGVCbG9jayIsImNvZGUiLCJsYW5nIiwiZXh0cmFjdENvZGVCbG9ja3MiLCJtYXJrZG93biIsInRva2VucyIsImxleGVyIiwiYmxvY2tzIiwidG9rZW4iLCJ0eXBlIiwiY29kZVRva2VuIiwiQ29kZSIsInB1c2giLCJ0ZXh0IiwiY29sbGVjdFJlY2VudEFzc2lzdGFudFRleHRzIiwibWVzc2FnZXMiLCJ0ZXh0cyIsImkiLCJsZW5ndGgiLCJtc2ciLCJpc0FwaUVycm9yTWVzc2FnZSIsImNvbnRlbnQiLCJtZXNzYWdlIiwiQXJyYXkiLCJpc0FycmF5IiwiZmlsZUV4dGVuc2lvbiIsInNhbml0aXplZCIsInJlcGxhY2UiLCJ3cml0ZVRvRmlsZSIsImZpbGVuYW1lIiwiUHJvbWlzZSIsImZpbGVQYXRoIiwicmVjdXJzaXZlIiwiY29weU9yV3JpdGVUb0ZpbGUiLCJyYXciLCJwcm9jZXNzIiwic3Rkb3V0Iiwid3JpdGUiLCJsaW5lQ291bnQiLCJjaGFyQ291bnQiLCJ0cnVuY2F0ZUxpbmUiLCJtYXhMZW4iLCJmaXJzdExpbmUiLCJzcGxpdCIsInJlc3VsdCIsIndpZHRoIiwidGFyZ2V0V2lkdGgiLCJjaGFyIiwiY2hhcldpZHRoIiwiUGlja2VyUHJvcHMiLCJmdWxsVGV4dCIsImNvZGVCbG9ja3MiLCJtZXNzYWdlQWdlIiwib25Eb25lIiwib3B0aW9ucyIsImRpc3BsYXkiLCJQaWNrZXJTZWxlY3Rpb24iLCJDb3B5UGlja2VyIiwidDAiLCIkIiwiX2MiLCJmb2N1c2VkUmVmIiwidDEiLCJ0MiIsImxhYmVsIiwidmFsdWUiLCJjb25zdCIsImRlc2NyaXB0aW9uIiwidDMiLCJ0NCIsIlN5bWJvbCIsImZvciIsIm1hcCIsIl90ZW1wIiwiZ2V0U2VsZWN0aW9uQ29udGVudCIsInNlbGVjdGVkIiwiYmxvY2tfMCIsImJsb2NrIiwiYmxvY2tJbmRleCIsInQ1IiwiaGFuZGxlU2VsZWN0Iiwic2VsZWN0ZWRfMCIsImNvcHlGdWxsUmVzcG9uc2UiLCJfdGVtcDIiLCJibG9ja19jb3VudCIsImFsd2F5cyIsIm1lc3NhZ2VfYWdlIiwic2VsZWN0ZWRfYmxvY2siLCJyZXN1bHRfMCIsInQ2IiwiaGFuZGxlV3JpdGUiLCJzZWxlY3RlZF8xIiwiY29udGVudF8wIiwid3JpdGVfc2hvcnRjdXQiLCJ0NyIsImUiLCJFcnJvciIsImhhbmRsZUtleURvd24iLCJlXzAiLCJrZXkiLCJwcmV2ZW50RGVmYXVsdCIsImN1cnJlbnQiLCJ0OCIsInQ5Iiwic2VsZWN0ZWRfMiIsInQxMCIsInQxMSIsInQxMiIsInQxMyIsImMiLCJpbmRleCIsImJsb2NrTGluZXMiLCJ1bmRlZmluZWQiLCJmaWx0ZXIiLCJCb29sZWFuIiwiY2FsbCIsImNvbnRleHQiLCJhcmdzIiwiYWdlIiwiYXJnIiwidHJpbSIsIm4iLCJOdW1iZXIiLCJpc0ludGVnZXIiLCJjb25maWciXSwic291cmNlcyI6WyJjb3B5LnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBta2Rpciwgd3JpdGVGaWxlIH0gZnJvbSAnZnMvcHJvbWlzZXMnXG5pbXBvcnQgeyBtYXJrZWQsIHR5cGUgVG9rZW5zIH0gZnJvbSAnbWFya2VkJ1xuaW1wb3J0IHsgdG1wZGlyIH0gZnJvbSAnb3MnXG5pbXBvcnQgeyBqb2luIH0gZnJvbSAncGF0aCdcbmltcG9ydCBSZWFjdCwgeyB1c2VSZWYgfSBmcm9tICdyZWFjdCdcbmltcG9ydCB0eXBlIHsgQ29tbWFuZFJlc3VsdERpc3BsYXkgfSBmcm9tICcuLi8uLi9jb21tYW5kcy5qcydcbmltcG9ydCB0eXBlIHsgT3B0aW9uV2l0aERlc2NyaXB0aW9uIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9DdXN0b21TZWxlY3Qvc2VsZWN0LmpzJ1xuaW1wb3J0IHsgU2VsZWN0IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9DdXN0b21TZWxlY3Qvc2VsZWN0LmpzJ1xuaW1wb3J0IHsgQnlsaW5lIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL0J5bGluZS5qcydcbmltcG9ydCB7IEtleWJvYXJkU2hvcnRjdXRIaW50IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL0tleWJvYXJkU2hvcnRjdXRIaW50LmpzJ1xuaW1wb3J0IHsgUGFuZSB9IGZyb20gJy4uLy4uL2NvbXBvbmVudHMvZGVzaWduLXN5c3RlbS9QYW5lLmpzJ1xuaW1wb3J0IHR5cGUgeyBLZXlib2FyZEV2ZW50IH0gZnJvbSAnLi4vLi4vaW5rL2V2ZW50cy9rZXlib2FyZC1ldmVudC5qcydcbmltcG9ydCB7IHN0cmluZ1dpZHRoIH0gZnJvbSAnLi4vLi4vaW5rL3N0cmluZ1dpZHRoLmpzJ1xuaW1wb3J0IHsgc2V0Q2xpcGJvYXJkIH0gZnJvbSAnLi4vLi4vaW5rL3Rlcm1pby9vc2MuanMnXG5pbXBvcnQgeyBCb3gsIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgeyBsb2dFdmVudCB9IGZyb20gJy4uLy4uL3NlcnZpY2VzL2FuYWx5dGljcy9pbmRleC5qcydcbmltcG9ydCB0eXBlIHsgTG9jYWxKU1hDb21tYW5kQ2FsbCB9IGZyb20gJy4uLy4uL3R5cGVzL2NvbW1hbmQuanMnXG5pbXBvcnQgdHlwZSB7IEFzc2lzdGFudE1lc3NhZ2UsIE1lc3NhZ2UgfSBmcm9tICcuLi8uLi90eXBlcy9tZXNzYWdlLmpzJ1xuaW1wb3J0IHsgZ2V0R2xvYmFsQ29uZmlnLCBzYXZlR2xvYmFsQ29uZmlnIH0gZnJvbSAnLi4vLi4vdXRpbHMvY29uZmlnLmpzJ1xuaW1wb3J0IHsgZXh0cmFjdFRleHRDb250ZW50LCBzdHJpcFByb21wdFhNTFRhZ3MgfSBmcm9tICcuLi8uLi91dGlscy9tZXNzYWdlcy5qcydcbmltcG9ydCB7IGNvdW50Q2hhckluU3RyaW5nIH0gZnJvbSAnLi4vLi4vdXRpbHMvc3RyaW5nVXRpbHM
|