remote-setup.tsx
commands/remote-setup/remote-setup.tsx
187
Lines
21786
Bytes
1
Exports
12
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file lives in the command layer. It likely turns a user action into concrete program behavior.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with commands, remote-bridge. It contains 187 lines, 12 detected imports, and 1 detected exports.
Important relationships
Detected exports
call
Keywords
resultstatustokengithubondonecasenamelogeventsafestringlogin
Detected imports
execareactreact../../components/CustomSelect/index.js../../components/design-system/Dialog.js../../components/design-system/LoadingState.js../../ink.js../../services/analytics/index.js../../types/command.js../../utils/browser.js../../utils/github/ghAuthStatus.js./api.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
import { execa } from 'execa';
import * as React from 'react';
import { useEffect, useState } from 'react';
import { Select } from '../../components/CustomSelect/index.js';
import { Dialog } from '../../components/design-system/Dialog.js';
import { LoadingState } from '../../components/design-system/LoadingState.js';
import { Box, Text } from '../../ink.js';
import { logEvent, type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS as SafeString } from '../../services/analytics/index.js';
import type { LocalJSXCommandOnDone } from '../../types/command.js';
import { openBrowser } from '../../utils/browser.js';
import { getGhAuthStatus } from '../../utils/github/ghAuthStatus.js';
import { createDefaultEnvironment, getCodeWebUrl, type ImportTokenError, importGithubToken, isSignedIn, RedactedGithubToken } from './api.js';
type CheckResult = {
status: 'not_signed_in';
} | {
status: 'has_gh_token';
token: RedactedGithubToken;
} | {
status: 'gh_not_installed';
} | {
status: 'gh_not_authenticated';
};
async function checkLoginState(): Promise<CheckResult> {
if (!(await isSignedIn())) {
return {
status: 'not_signed_in'
};
}
const ghStatus = await getGhAuthStatus();
if (ghStatus === 'not_installed') {
return {
status: 'gh_not_installed'
};
}
if (ghStatus === 'not_authenticated') {
return {
status: 'gh_not_authenticated'
};
}
// ghStatus === 'authenticated'. getGhAuthStatus spawns with stdout:'ignore'
// (telemetry-safe); spawn once more with stdout:'pipe' to read the token.
const {
stdout
} = await execa('gh', ['auth', 'token'], {
stdout: 'pipe',
stderr: 'ignore',
timeout: 5000,
reject: false
});
const trimmed = stdout.trim();
if (!trimmed) {
return {
status: 'gh_not_authenticated'
};
}
return {
status: 'has_gh_token',
token: new RedactedGithubToken(trimmed)
};
}
function errorMessage(err: ImportTokenError, codeUrl: string): string {
switch (err.kind) {
case 'not_signed_in':
return `Login failed. Please visit ${codeUrl} and login using the GitHub App`;
case 'invalid_token':
return 'GitHub rejected that token. Run `gh auth login` and try again.';
case 'server':
return `Server error (${err.status}). Try again in a moment.`;
case 'network':
return "Couldn't reach the server. Check your connection.";
}
}
type Step = {
name: 'checking';
} | {
name: 'confirm';
token: RedactedGithubToken;
} | {
name: 'uploading';
};
function Web({
onDone
}: {
onDone: LocalJSXCommandOnDone;
}) {
const [step, setStep] = useState<Step>({
name: 'checking'
});
useEffect(() => {
logEvent('tengu_remote_setup_started', {});
void checkLoginState().then(async result => {
switch (result.status) {
case 'not_signed_in':
logEvent('tengu_remote_setup_result', {
result: 'not_signed_in' as SafeString
});
onDone('Not signed in to Claude. Run /login first.');
return;
case 'gh_not_installed':
case 'gh_not_authenticated':
{
const url = `${getCodeWebUrl()}/onboarding?step=alt-auth`;
await openBrowser(url);
logEvent('tengu_remote_setup_result', {
result: result.status as SafeString
});
onDone(result.status === 'gh_not_installed' ? `GitHub CLI not found. Install it via https://cli.github.com/, then run \`gh auth login\`, or connect GitHub on the web: ${url}` : `GitHub CLI not authenticated. Run \`gh auth login\` and try again, or connect GitHub on the web: ${url}`);
return;
}
case 'has_gh_token':
setStep({
name: 'confirm',
token: result.token
});
}
});
// onDone is stable across renders; intentionally not in deps.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleCancel = () => {
logEvent('tengu_remote_setup_result', {
result: 'cancelled' as SafeString
});
onDone();
};
const handleConfirm = async (token: RedactedGithubToken) => {
setStep({
name: 'uploading'
});
const result = await importGithubToken(token);
if (!result.ok) {
logEvent('tengu_remote_setup_result', {
result: 'import_failed' as SafeString,
error_kind: result.error.kind as SafeString
});
onDone(errorMessage(result.error, getCodeWebUrl()));
return;
}
// Token import succeeded. Environment creation is best-effort — if it
// fails, the web state machine routes to env-setup on landing, which is
// one extra click but still better than the OAuth dance.
await createDefaultEnvironment();
const url = getCodeWebUrl();
await openBrowser(url);
logEvent('tengu_remote_setup_result', {
result: 'success' as SafeString
});
onDone(`Connected as ${result.result.github_username}. Opened ${url}`);
};
if (step.name === 'checking') {
return <LoadingState message="Checking login status…" />;
}
if (step.name === 'uploading') {
return <LoadingState message="Connecting GitHub to Claude…" />;
}
const token = step.token;
return <Dialog title="Connect Claude on the web to GitHub?" onCancel={handleCancel} hideInputGuide>
<Box flexDirection="column">
<Text>
Claude on the web requires connecting to your GitHub account to clone
and push code on your behalf.
</Text>
<Text dimColor>
Your local credentials are used to authenticate with GitHub
</Text>
</Box>
<Select options={[{
label: 'Continue',
value: 'send'
}, {
label: 'Cancel',
value: 'cancel'
}]} onChange={value => {
if (value === 'send') {
void handleConfirm(token);
} else {
handleCancel();
}
}} onCancel={handleCancel} />
</Dialog>;
}
export async function call(onDone: LocalJSXCommandOnDone): Promise<React.ReactNode> {
return <Web onDone={onDone} />;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJleGVjYSIsIlJlYWN0IiwidXNlRWZmZWN0IiwidXNlU3RhdGUiLCJTZWxlY3QiLCJEaWFsb2ciLCJMb2FkaW5nU3RhdGUiLCJCb3giLCJUZXh0IiwibG9nRXZlbnQiLCJBbmFseXRpY3NNZXRhZGF0YV9JX1ZFUklGSUVEX1RISVNfSVNfTk9UX0NPREVfT1JfRklMRVBBVEhTIiwiU2FmZVN0cmluZyIsIkxvY2FsSlNYQ29tbWFuZE9uRG9uZSIsIm9wZW5Ccm93c2VyIiwiZ2V0R2hBdXRoU3RhdHVzIiwiY3JlYXRlRGVmYXVsdEVudmlyb25tZW50IiwiZ2V0Q29kZVdlYlVybCIsIkltcG9ydFRva2VuRXJyb3IiLCJpbXBvcnRHaXRodWJUb2tlbiIsImlzU2lnbmVkSW4iLCJSZWRhY3RlZEdpdGh1YlRva2VuIiwiQ2hlY2tSZXN1bHQiLCJzdGF0dXMiLCJ0b2tlbiIsImNoZWNrTG9naW5TdGF0ZSIsIlByb21pc2UiLCJnaFN0YXR1cyIsInN0ZG91dCIsInN0ZGVyciIsInRpbWVvdXQiLCJyZWplY3QiLCJ0cmltbWVkIiwidHJpbSIsImVycm9yTWVzc2FnZSIsImVyciIsImNvZGVVcmwiLCJraW5kIiwiU3RlcCIsIm5hbWUiLCJXZWIiLCJvbkRvbmUiLCJzdGVwIiwic2V0U3RlcCIsInRoZW4iLCJyZXN1bHQiLCJ1cmwiLCJoYW5kbGVDYW5jZWwiLCJoYW5kbGVDb25maXJtIiwib2siLCJlcnJvcl9raW5kIiwiZXJyb3IiLCJnaXRodWJfdXNlcm5hbWUiLCJsYWJlbCIsInZhbHVlIiwiY2FsbCIsIlJlYWN0Tm9kZSJdLCJzb3VyY2VzIjpbInJlbW90ZS1zZXR1cC50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgZXhlY2EgfSBmcm9tICdleGVjYSdcbmltcG9ydCAqIGFzIFJlYWN0IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgdXNlRWZmZWN0LCB1c2VTdGF0ZSB9IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgU2VsZWN0IH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9DdXN0b21TZWxlY3QvaW5kZXguanMnXG5pbXBvcnQgeyBEaWFsb2cgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL2Rlc2lnbi1zeXN0ZW0vRGlhbG9nLmpzJ1xuaW1wb3J0IHsgTG9hZGluZ1N0YXRlIH0gZnJvbSAnLi4vLi4vY29tcG9uZW50cy9kZXNpZ24tc3lzdGVtL0xvYWRpbmdTdGF0ZS5qcydcbmltcG9ydCB7IEJveCwgVGV4dCB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB7XG4gIGxvZ0V2ZW50LFxuICB0eXBlIEFuYWx5dGljc01ldGFkYXRhX0lfVkVSSUZJRURfVEhJU19JU19OT1RfQ09ERV9PUl9GSUxFUEFUSFMgYXMgU2FmZVN0cmluZyxcbn0gZnJvbSAnLi4vLi4vc2VydmljZXMvYW5hbHl0aWNzL2luZGV4LmpzJ1xuaW1wb3J0IHR5cGUgeyBMb2NhbEpTWENvbW1hbmRPbkRvbmUgfSBmcm9tICcuLi8uLi90eXBlcy9jb21tYW5kLmpzJ1xuaW1wb3J0IHsgb3BlbkJyb3dzZXIgfSBmcm9tICcuLi8uLi91dGlscy9icm93c2VyLmpzJ1xuaW1wb3J0IHsgZ2V0R2hBdXRoU3RhdHVzIH0gZnJvbSAnLi4vLi4vdXRpbHMvZ2l0aHViL2doQXV0aFN0YXR1cy5qcydcbmltcG9ydCB7XG4gIGNyZWF0ZURlZmF1bHRFbnZpcm9ubWVudCxcbiAgZ2V0Q29kZVdlYlVybCxcbiAgdHlwZSBJbXBvcnRUb2tlbkVycm9yLFxuICBpbXBvcnRHaXRodWJUb2tlbixcbiAgaXNTaWduZWRJbixcbiAgUmVkYWN0ZWRHaXRodWJUb2tlbixcbn0gZnJvbSAnLi9hcGkuanMnXG5cbnR5cGUgQ2hlY2tSZXN1bHQgPVxuICB8IHsgc3RhdHVzOiAnbm90X3NpZ25lZF9pbicgfVxuICB8IHsgc3RhdHVzOiAnaGFzX2doX3Rva2VuJzsgdG9rZW46IFJlZGFjdGVkR2l0aHViVG9rZW4gfVxuICB8IHsgc3RhdHVzOiAnZ2hfbm90X2luc3RhbGxlZCcgfVxuICB8IHsgc3RhdHVzOiAnZ2hfbm90X2F1dGhlbnRpY2F0ZWQnIH1cblxuYXN5bmMgZnVuY3Rpb24gY2hlY2tMb2dpblN0YXRlKCk6IFByb21pc2U8Q2hlY2tSZXN1bHQ+IHtcbiAgaWYgKCEoYXdhaXQgaXNTaWduZWRJbigpKSkge1xuICAgIHJldHVybiB7IHN0YXR1czogJ25vdF9zaWduZWRfaW4nIH1cbiAgfVxuXG4gIGNvbnN0IGdoU3RhdHVzID0gYXdhaXQgZ2V0R2hBdXRoU3RhdHVzKClcbiAgaWYgKGdoU3RhdHVzID09PSAnbm90X2luc3RhbGxlZCcpIHtcbiAgICByZXR1cm4geyBzdGF0dXM6ICdnaF9ub3RfaW5zdGFsbGVkJyB9XG4gIH1cbiAgaWYgKGdoU3RhdHVzID09PSAnbm90X2F1dGhlbnRpY2F0ZWQnKSB7XG4gICAgcmV0dXJuIHsgc3RhdHVzOiAnZ2hfbm90X2F1dGhlbnRpY2F0ZWQnIH1cbiAgfVxuXG4gIC8vIGdoU3RhdHVzID09PSAnYXV0aGVudGljYXRlZCcuIGdldEdoQXV0aFN0YXR1cyBzcGF3bnMgd2l0aCBzdGRvdXQ6J2lnbm9yZSdcbiAgLy8gKHRlbGVtZXRyeS1zYWZlKTsgc3Bhd24gb25jZSBtb3JlIHdpdGggc3Rkb3V0OidwaXBlJyB0byByZWFkIHRoZSB0b2tlbi5cbiAgY29uc3QgeyBzdGRvdXQgfSA9IGF3YWl0IGV4ZWNhKCdnaCcsIFsnYXV0aCcsICd0b2tlbiddLCB7XG4gICAgc3Rkb3V0OiAncGlwZScsXG4gICAgc3RkZXJyOiAnaWdub3JlJyxcbiAgICB0aW1lb3V0OiA1MDAwLFxuICAgIHJlamVjdDogZmFsc2UsXG4gIH0pXG4gIGNvbnN0IHRyaW1tZWQgPSBzdGRvdXQudHJpbSgpXG4gIGlmICghdHJpbW1lZCkge1xuICAgIHJldHVybiB7IHN0YXR1czogJ2doX25vdF9hdXRoZW50aWNhdGVkJyB9XG4gIH1cbiAgcmV0dXJuIHsgc3RhdHVzOiAnaGFzX2doX3Rva2VuJywgdG9rZW46IG5ldyBSZWRhY3RlZEdpdGh1YlRva2VuKHRyaW1tZWQpIH1cbn1cblxuZnVuY3Rpb24gZXJyb3JNZXNzYWdlKGVycjogSW1wb3J0VG9rZW5FcnJvciwgY29kZVVybDogc3RyaW5nKTogc3RyaW5nIHtcbiAgc3dpdGNoIChlcnIua2luZCkge1xuICAgIGNhc2UgJ25vdF9zaWduZWRfaW4nOlxuICAgICAgcmV0dXJuIGBMb2dpbiBmYWlsZWQuIFBsZWFzZSB2aXNpdCAke2NvZGVVcmx9IGFuZCBsb2dpbiB1c2luZyB0aGUgR2l0SHViIEFwcGBcbiAgICBjYXNlICdpbnZhbGlkX3Rva2VuJzpcbiAgICAgIHJldHVybiAnR2l0SHViIHJlamVjdGVkIHRoYXQgdG9rZW4uIFJ1biBgZ2ggYXV0aCBsb2dpbmAgYW5kIHRyeSBhZ2Fpbi4nXG4gICAgY2FzZSAnc2VydmVyJzpcbiAgICAgIHJldHVybiBgU2VydmVyIGVycm9yICgke2Vyci5zdGF0dXN9KS4gVHJ5IGFnYWluIGluIGEgbW9tZW50LmBcbiAgICBjYXNlICduZXR3b3JrJzpcbiAgICAgIHJldHVybiBcIkNvdWxkbid0IHJlYWNoIHRoZSBzZXJ2ZXIuIENoZWNrIHlvdXIgY29ubmVjdGlvbi5cIlxuICB9XG59XG5cbnR5cGUgU3RlcCA9XG4gIHwgeyBuYW1lOiAnY2hlY2tpbmcnIH1cbiAgfCB7IG5hbWU6ICdjb25maXJtJzsgdG9rZW46IFJlZGFjdGVkR2l0aHViVG9rZW4gfVxuICB8IHsgbmFtZTogJ3VwbG9hZGluZycgfVxuXG5mdW5jdGlvbiBXZWIoeyBvbkRvbmUgfTogeyBvbkRvbmU6IExvY2FsSlNYQ29tbWFuZE9uRG9uZSB9KSB7XG4gIGNvbnN0IFtzdGVwLCBzZXRTdGVwXSA9IHVzZVN0YXRlPFN0ZXA+KHsgbmFtZTogJ2NoZWNraW5nJyB9KVxuXG4gIHVzZUVmZmVjdCgoKSA9PiB7XG4gICAgbG9nRXZlbnQoJ3Rlbmd1X3JlbW90ZV9zZXR1cF9zdGFydGVkJywge30pXG4gICAgdm9pZCBjaGVja0xvZ2luU3RhdGUoKS50aGVuKGFzeW5jIHJlc3VsdCA9PiB7XG4gICAgICBzd2l0Y2ggKHJlc3VsdC5zdGF0dXMpIHtcbiAgICAgICAgY2FzZSAnbm90X3NpZ25lZF9pbic6XG4gICAgICAgICAgbG9nRXZlbnQoJ3Rlbmd1X3JlbW90ZV9zZXR1cF9yZXN1bHQnLCB7XG4gICAgICAgICAgICByZXN1bHQ6ICdub3Rfc2lnbmVkX2luJyBhcyBTYWZlU3RyaW5nLFxuICAgICAgICAgIH0pXG4gICAgICAgICAgb25Eb25lKCdOb3Qgc2lnbmVkIGluIHRvIENsYXVkZS4gUnVuIC9sb2dpbiBmaXJzdC4nKVxuICAgICAgICAgIHJldHVyblxuICAgICAgICBjYXNlICdnaF9ub3RfaW5zdGFsbGVkJzpcbiAgICAgICAgY2FzZSAnZ2hfbm90X2F1dGhlbnRpY2F0ZWQnOiB7XG4gICAgICAgICAgY29uc3QgdXJsID0gYCR7Z2V0Q29kZVdlYlVybCgpfS9vbmJvYXJkaW5nP3N0ZXA9YWx0LWF1dGhgXG4gICAgICAgICAgYXdhaXQgb3BlbkJyb3dzZXIodXJsKVxuICAgICAgICAgIGxvZ0V2ZW50KCd0ZW5ndV9yZW1vdGVfc2V0dXBfcmVzdWx0Jywge1xuICAgICAgICAgICAgcmVzdWx0OiByZXN1bHQuc3RhdHVzIGFzIFNhZmVTdHJpbmcsXG4gICAgICAgICAgfSlcbiAgICAgICAgICBvbkRvbmUoXG4gICAgICAgICAgICByZXN1bHQuc3RhdHVzID09PSAnZ2hfbm90X2luc3RhbGxlZCdcbiAgICAgICAgICAgICAgPyBgR2l0SHViIENMSSBub3QgZm91bmQuIEluc3RhbGwgaXQgdmlhIGh0dHBzOi8vY2xpLmdpdGh1Yi5jb20vLCB0aGVuIHJ1biBcXGBnaCBhdXRoIGxvZ2luXFxgLCBvciBjb25uZWN0IEdpdEh1YiBvbiB0aGUgd2ViOiAke3VybH1gXG4gICAgICAgICAgICAgIDogYEdpdEh1YiBDTEkgbm90IGF1dGhlbnRpY2F0ZWQuIFJ1biBcXGBnaCBhdXRoIGxvZ2luXFxgIGFuZCB0cnkgYWdhaW4sIG9yIGNvbm5lY3QgR2l0SHViIG9uIHRoZSB3ZWI6ICR7dXJsfWAsXG4gICAgICAgICAgKVxuICAgICAgICAgIHJldHVyblxuICAgICAgICB9XG4gICAgICAgIGNhc2UgJ2hhc19naF90b2tlbic6XG4gICAgICAgICAgc2V0U3RlcCh7IG5hbWU6ICdjb25maXJtJywgdG9rZW46IHJlc3VsdC50b2tlbiB9KVxuICAgICAgfVxuICAgIH0pXG4gICAgLy8gb25Eb25lIGlzIHN0YWJsZSBhY3Jvc3MgcmVuZGVyczsgaW50ZW50aW9uYWxseSBub3QgaW4gZGVwcy5cbiAgICAvLyBlc2xpbnQtZGlzYWJsZS1uZXh0LWxpbmUgcmVhY3QtaG9va3MvZXhoYXVzdGl2ZS1kZXBzXG4gIH0sIFtdKVxuXG4gIGNvbnN0IGhhbmRsZUNhbmNlbCA9ICgpID0+IHtcbiAgICBsb2dFdmVudCgndGVuZ3VfcmVtb3RlX3NldHVwX3Jlc3VsdCcsIHtcbiAgICAgIHJlc3VsdDogJ2NhbmNlbGxlZCcgYXMgU2FmZVN0cmluZyxcbiAgICB9KVxuICAgIG9uRG9uZSgpXG4gIH1cblxuICBjb25zdCBoYW5kbGVDb25maXJtID0gYXN5bmMgKHRva2VuOiBSZWRhY3RlZEdpdGh1YlRva2VuKSA9PiB7XG4gICAgc2V0U3RlcCh7IG5hbWU6ICd1cGxvYWRpbmcnIH0pXG5cbiAgICBjb25zdCByZXN1bHQgPSBhd2FpdCBpbXBvcnRHaXRodWJUb2tlbih0b2tlbilcbiAgICBpZiAoIXJlc3VsdC5vaykge1xuICAgICAgbG9nRXZlbnQoJ3Rlbmd1X3JlbW90ZV9zZXR1cF9yZXN1bHQnLCB7XG4gICAgICAgIHJlc3VsdDogJ2ltcG9ydF9mYWlsZWQnIGFzIFNhZmVTdHJpbmcsXG4gICAgICAgIGVycm9yX2tpbmQ6IHJlc3VsdC5lcnJvci5raW5kIGFzIFNhZmVTdHJpbmcsXG4gICAgICB9KVxuICAgICAgb25Eb25lKGVycm9yTWVzc2FnZShyZXN1bHQuZXJyb3IsIGdldENvZGVXZWJVcmwoKSkpXG4gICAgICByZXR1cm5cbiAgICB9XG5cbiAgICAvLyBUb2tlbiBpbXBvcnQgc3VjY2VlZGVkLiBFbnZpcm9ubWVudCBjcmVhdGlvbiBpcyBiZXN0LWVmZm9ydCDigJQgaWYgaXRcbiAgICAvLyBmYWlscywgdGhlIHdlYiBzdGF0ZSBtYWNoaW5lIHJvdXRlcyB0byBlbnYtc2V0dXAgb24gbGFuZGluZywgd2hpY2ggaXNcbiAgICAvLyBvbmUgZXh0cmEgY2xpY2sgYnV0IHN0aWxsIGJldHRlciB0aGFuIHRoZSBPQXV0aCBkYW5jZS5cbiAgICBhd2FpdCBjcmVhdGVEZWZhdWx0RW52aXJvbm1lbnQoKVxuXG4gICAgY29uc3QgdXJsID0gZ2V0Q29kZVdlYlVybCgpXG4gICAgYXdhaXQgb3BlbkJyb3dzZXIodXJsKVxuXG4gICAgbG9nRXZlbnQoJ3Rlbmd1X3JlbW90ZV9zZXR1cF9yZXN1bHQnLCB7XG4gICAgICByZXN1bHQ6ICdzdWNjZXNzJyBhcyBTYWZlU3RyaW5nLFxuICAgIH0pXG4gICAgb25Eb25lKGBDb25uZWN0ZWQgYXMgJHtyZXN1bHQucmVzdWx0LmdpdGh1Yl91c2VybmFtZX0uIE9wZW5lZCAke3VybH1gKVxuICB9XG5cbiAgaWYgKHN0ZXAubmFtZSA9PT0gJ2NoZWNraW5nJykge1xuICAgIHJldHVybiA8TG9hZGluZ1N0YXRlIG1lc3NhZ2U9XCJDaGVja2luZyBsb2dpbiBzdGF0dXPigKZcIiAvPlxuICB9XG5cbiAgaWYgKHN0ZXAubmFtZSA9PT0gJ3VwbG9hZGluZycpIHtcbiAgICByZXR1cm4gPExvYWRpbmdTdGF0ZSBtZXNzYWdlPVwiQ29ubmVjdGluZyBHaXRIdWIgdG8gQ2xhdWRl4oCmXCIgLz5cbiAgfVxuXG4gIGNvbnN0IHRva2VuID0gc3RlcC50b2tlblxuICByZXR1cm4gKFxuICAgIDxEaWFsb2dcbiAgICAgIHRpdGxlPVwiQ29ubmVjdCBDbGF1ZGUgb24gdGhlIHdlYiB0byBHaXRIdWI/XCJcbiAgICAgIG9uQ2FuY2VsPXtoYW5kbGVDYW5jZWx9XG4gICAgICBoaWRlSW5wdXRHdWlkZVxuICAgID5cbiAgICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiPlxuICAgICAgICA8VGV4dD5cbiAgICAgICAgICBDbGF1ZGUgb24gdGhlIHdlYiByZXF1aXJlcyBjb25uZWN0aW5nIHRvIHlvdXIgR2l0SHViIGFjY291bnQgdG8gY2xvbmVcbiAgICAgICAgICBhbmQgcHVzaCBjb2RlIG9uIHlvdXIgYmVoYWxmLlxuICAgICAgICA8L1RleHQ+XG4gICAgICAgIDxUZXh0IGRpbUNvbG9yPlxuICAgICAgICAgIFlvdXIgbG9jYWwgY3JlZGVudGlhbHMgYXJlIHVzZWQgdG8gYXV0aGVudGljYXRlIHdpdGggR2l0SHViXG4gICAgICAgIDwvVGV4dD5cbiAgICAgIDwvQm94PlxuICAgICAgPFNlbGVjdFxuICAgICAgICBvcHRpb25zPXtbXG4gICAgICAgICAgeyBsYWJlbDogJ0NvbnRpbnVlJywgdmFsdWU6ICdzZW5kJyB9LFxuICAgICAgICAgIHsgbGFiZWw6ICdDYW5jZWwnLCB2YWx1ZTogJ2NhbmNlbCcgfSxcbiAgICAgICAgXX1cbiAgICAgICAgb25DaGFuZ2U9e3ZhbHVlID0+IHtcbiAgICAgICAgICBpZiAodmFsdWUgPT09ICdzZW5kJykge1xuICAgICAgICAgICAgdm9pZCBoYW5kbGVDb25maXJtKHRva2VuKVxuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBoYW5kbGVDYW5jZWwoKVxuICAgICAgICAgIH1cbiAgICAgICAgfX1cbiAgICAgICAgb25DYW5jZWw9e2hhbmRsZUNhbmNlbH1cbiAgICAgIC8+XG4gICAgPC9EaWFsb2c+XG4gIClcbn1cblxuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNhbGwoXG4gIG9uRG9uZTogTG9jYWxKU1hDb21tYW5kT25Eb25lLFxuKTogUHJvbWlzZTxSZWFjdC5SZWFjdE5vZGU+IHtcbiAgcmV0dXJuIDxXZWIgb25Eb25lPXtvbkRvbmV9IC8+XG59XG4iXSwibWFwcGluZ3MiOiJBQUFBLFNBQVNBLEtBQUssUUFBUSxPQUFPO0FBQzdCLE9BQU8sS0FBS0MsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0MsU0FBUyxFQUFFQyxRQUFRLFFBQVEsT0FBTztBQUMzQyxTQUFTQyxNQUFNLFFBQVEsd0NBQXdDO0FBQy9ELFNBQVNDLE1BQU0sUUFBUSwwQ0FBMEM7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