new BackendCrashModal component to notify the user if kcpp crashes at runtime, replace the kcpp tunnel implementation with our own custom one and present its copyable URL in the statusbar

This commit is contained in:
Egor 2025-12-02 18:52:47 -08:00
parent f825cd0d2f
commit c111dfbddc
13 changed files with 447 additions and 86 deletions

View file

@ -12,7 +12,7 @@
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "prettier.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"

View file

@ -44,8 +44,8 @@
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"@types/yauzl": "^2.10.3",
"@typescript-eslint/eslint-plugin": "^8.48.0",
"@typescript-eslint/parser": "^8.48.0",
"@typescript-eslint/eslint-plugin": "^8.48.1",
"@typescript-eslint/parser": "^8.48.1",
"@vitejs/plugin-react": "^5.1.1",
"cross-env": "^10.1.0",
"electron": "^38.7.2",
@ -74,6 +74,7 @@
"@mantine/core": "^8.3.9",
"@mantine/hooks": "^8.3.9",
"@uiw/react-codemirror": "^4.25.3",
"cloudflared": "^0.7.1",
"electron-updater": "^6.6.2",
"execa": "^9.6.1",
"lucide-react": "^0.555.0",

View file

@ -0,0 +1,66 @@
import { Text, Group, Button, Stack } from '@mantine/core';
import { Modal } from '@/components/Modal';
import type { KoboldCrashInfo } from '@/types/ipc';
interface BackendCrashModalProps {
opened: boolean;
onClose: () => void;
crashInfo: KoboldCrashInfo | null;
}
const getCrashDescription = (crashInfo: KoboldCrashInfo) => {
if (crashInfo.errorMessage) {
return crashInfo.errorMessage;
}
if (crashInfo.signal) {
const signalDescriptions: Record<string, string> = {
SIGKILL: 'The process was forcefully terminated',
SIGSEGV: 'Memory access violation (segmentation fault)',
SIGABRT: 'The process aborted unexpectedly',
SIGBUS: 'Bus error (invalid memory access)',
SIGFPE: 'Floating-point exception',
SIGILL: 'Illegal instruction',
SIGTERM: 'The process was terminated',
SIGSTOP: 'The process was stopped',
SIGHUP: 'The process lost its controlling terminal',
};
return (
signalDescriptions[crashInfo.signal] ||
`Terminated by signal ${crashInfo.signal}`
);
}
if (crashInfo.exitCode !== null) {
return `Process exited with error code ${crashInfo.exitCode}`;
}
return 'The process terminated unexpectedly';
};
export const BackendCrashModal = ({
opened,
onClose,
crashInfo,
}: BackendCrashModalProps) => {
if (!crashInfo) return null;
const description = getCrashDescription(crashInfo);
return (
<Modal opened={opened} onClose={onClose} title="Backend Crashed">
<Stack gap="md">
<Text size="sm">{description}</Text>
<Text size="sm" c="dimmed">
Check the terminal output for more details about the crash.
</Text>
<Group justify="flex-end" gap="sm">
<Button onClick={onClose}>Dismiss</Button>
</Group>
</Stack>
</Modal>
);
};

View file

@ -1,8 +1,16 @@
import { useEffect, useState } from 'react';
import { Group, AppShell, ActionIcon, Tooltip } from '@mantine/core';
import { NotepadText } from 'lucide-react';
import { useEffect, useState, useMemo } from 'react';
import {
Group,
AppShell,
ActionIcon,
Tooltip,
CopyButton,
} from '@mantine/core';
import { NotepadText, Globe, Check } from 'lucide-react';
import { usePreferencesStore } from '@/stores/preferences';
import { useNotepadStore } from '@/stores/notepad';
import { useLaunchConfigStore } from '@/stores/launchConfig';
import { getTunnelInterfaceUrl } from '@/utils/interface';
import type {
CpuMetrics,
MemoryMetrics,
@ -20,9 +28,27 @@ export const StatusBar = ({ maxDataPoints = 60 }: StatusBarProps) => {
null
);
const [gpuMetrics, setGpuMetrics] = useState<GpuMetrics | null>(null);
const { resolvedColorScheme: colorScheme, systemMonitoringEnabled } =
usePreferencesStore();
const [tunnelBaseUrl, setTunnelBaseUrl] = useState<string | null>(null);
const {
resolvedColorScheme: colorScheme,
systemMonitoringEnabled,
frontendPreference,
imageGenerationFrontendPreference,
} = usePreferencesStore();
const { isVisible, setVisible } = useNotepadStore();
const { isImageGenerationMode } = useLaunchConfigStore();
const tunnelUrl = useMemo(() => {
if (!tunnelBaseUrl) return null;
if (frontendPreference === 'sillytavern' || frontendPreference === 'openwebui') {
return tunnelBaseUrl;
}
return getTunnelInterfaceUrl(tunnelBaseUrl, {
frontendPreference,
imageGenerationFrontendPreference,
isImageGenerationMode,
});
}, [tunnelBaseUrl, frontendPreference, imageGenerationFrontendPreference, isImageGenerationMode]);
useEffect(() => {
if (!systemMonitoringEnabled) {
@ -63,6 +89,11 @@ export const StatusBar = ({ maxDataPoints = 60 }: StatusBarProps) => {
};
}, [maxDataPoints, systemMonitoringEnabled]);
useEffect(() => {
const cleanup = window.electronAPI.kobold.onTunnelUrlChanged(setTunnelBaseUrl);
return cleanup;
}, []);
const displayCpuMetrics = systemMonitoringEnabled ? cpuMetrics : null;
const displayMemoryMetrics = systemMonitoringEnabled ? memoryMetrics : null;
const displayGpuMetrics = systemMonitoringEnabled ? gpuMetrics : null;
@ -90,6 +121,25 @@ export const StatusBar = ({ maxDataPoints = 60 }: StatusBarProps) => {
<NotepadText size="1.25rem" />
</ActionIcon>
</Tooltip>
{tunnelUrl && (
<CopyButton value={tunnelUrl}>
{({ copied, copy }) => (
<Tooltip
label={copied ? 'Copied!' : 'Copy Tunnel URL'}
position="top"
>
<ActionIcon
variant="subtle"
size="sm"
color={copied ? 'teal' : undefined}
onClick={copy}
>
{copied ? <Check size="1.25rem" /> : <Globe size="1.25rem" />}
</ActionIcon>
</Tooltip>
)}
</CopyButton>
)}
</Group>
<Group gap="xs">

View file

@ -9,6 +9,7 @@ import {
} from '@mantine/core';
import { UpdateAvailableModal } from '@/components/App/UpdateAvailableModal';
import { EjectConfirmModal } from '@/components/App/EjectConfirmModal';
import { BackendCrashModal } from '@/components/App/BackendCrashModal';
import { TitleBar } from '@/components/App/TitleBar';
import { StatusBar } from '@/components/App/StatusBar';
import { ErrorBoundary } from '@/components/App/ErrorBoundary';
@ -21,6 +22,7 @@ import { useLaunchConfigStore } from '@/stores/launchConfig';
import { STATUSBAR_HEIGHT, TITLEBAR_HEIGHT } from '@/constants';
import type { DownloadItem } from '@/types/electron';
import type { InterfaceTab, Screen } from '@/types';
import type { KoboldCrashInfo } from '@/types/ipc';
export const App = () => {
const [currentScreen, setCurrentScreen] = useState<Screen | null>(null);
@ -28,6 +30,7 @@ export const App = () => {
const [activeInterfaceTab, setActiveInterfaceTab] =
useState<InterfaceTab>('terminal');
const [ejectConfirmModalOpen, setEjectConfirmModalOpen] = useState(false);
const [crashInfo, setCrashInfo] = useState<KoboldCrashInfo | null>(null);
const isInterfaceScreen = currentScreen === 'interface';
const { resolvedColorScheme: appColorScheme, systemMonitoringEnabled } =
@ -73,6 +76,18 @@ export const App = () => {
};
}, []);
useEffect(() => {
const crashCleanup = window.electronAPI.kobold.onKoboldCrashed(
(crashData) => {
setCrashInfo(crashData);
}
);
return () => {
crashCleanup();
};
}, []);
const {
updateInfo: binaryUpdateInfo,
showUpdateModal,
@ -245,6 +260,12 @@ export const App = () => {
onConfirm={handleEjectConfirm}
/>
<BackendCrashModal
opened={crashInfo !== null}
onClose={() => setCrashInfo(null)}
crashInfo={crashInfo}
/>
<NotepadContainer />
</AppShell>
);

View file

@ -3,8 +3,9 @@ import { platform } from 'process';
import { terminateProcess } from '@/utils/node/process';
import { logError, safeExecute } from '@/utils/node/logging';
import { sendKoboldOutput } from '@/main/modules/window';
import { sendKoboldOutput, sendToRenderer } from '@/main/modules/window';
import { SERVER_READY_SIGNALS } from '@/constants';
import type { KoboldCrashInfo } from '@/types/ipc';
import { pathExists } from '@/utils/node/fs';
import { parseKoboldConfig } from '@/utils/node/kobold';
import { getCurrentBackend } from '../backend';
@ -17,6 +18,7 @@ import { startFrontend as startSillyTavernFrontend } from '@/main/modules/sillyt
import { startFrontend as startOpenWebUIFrontend } from '@/main/modules/openwebui';
import { patchKliteEmbd, patchKcppSduiEmbd, filterSpam } from './patches';
import { startProxy, stopProxy } from '../proxy';
import { startTunnel, stopTunnel } from '../tunnel';
import { resolveModelPath, abortActiveDownloads } from '../model-download';
import type {
FrontendPreference,
@ -25,6 +27,8 @@ import type {
} from '@/types';
let koboldProcess: ChildProcess | null = null;
let isIntentionalStop = false;
let hasProcessStartedSuccessfully = false;
const preLaunchProcesses = new Set<ChildProcess>();
function spawnPreLaunchCommands(commands: string[]) {
@ -149,6 +153,9 @@ export async function launchKoboldCpp(
await stopKoboldCpp();
}
isIntentionalStop = false;
hasProcessStartedSuccessfully = false;
if (preLaunchCommands.length > 0) {
spawnPreLaunchCommands(preLaunchCommands);
}
@ -172,7 +179,14 @@ export async function launchKoboldCpp(
const binaryDir = currentBackend.path.split(/[/\\]/).slice(0, -1).join('/');
const { isImageMode, isTextMode, debugmode } = parseKoboldConfig(args);
const {
isImageMode,
isTextMode,
debugmode,
remotetunnel,
host: koboldHost,
port: koboldPort,
} = parseKoboldConfig(args);
if (frontendPreference === 'koboldcpp') {
if (isImageMode) {
@ -186,12 +200,12 @@ export async function launchKoboldCpp(
}
const resolvedArgs = await resolveModelPaths(args);
const finalArgs = [...resolvedArgs];
const { host: koboldHost, port: koboldPort } = parseKoboldConfig(args);
const finalArgs = resolvedArgs.filter((arg) => arg !== '--remotetunnel');
await startProxy(koboldHost, koboldPort);
const child = spawn(currentBackend.path, finalArgs, {
cwd: binaryDir,
stdio: ['pipe', 'pipe', 'pipe'],
detached: false,
});
@ -202,6 +216,10 @@ export async function launchKoboldCpp(
sendKoboldOutput(commandLine);
if (remotetunnel) {
startTunnel();
}
let readyResolve:
| ((value: { success: boolean; pid?: number; error?: string }) => void)
| null = null;
@ -217,6 +235,10 @@ export async function launchKoboldCpp(
readyReject = reject;
});
const handleServerReady = () => {
readyResolve?.({ success: true, pid: child.pid });
};
child.stdout?.on('data', (data) => {
const output = data.toString();
const filtered = debugmode ? output : filterSpam(output);
@ -226,7 +248,8 @@ export async function launchKoboldCpp(
if (!isReady && output.includes(SERVER_READY_SIGNALS.KOBOLDCPP)) {
isReady = true;
readyResolve?.({ success: true, pid: child.pid });
hasProcessStartedSuccessfully = true;
handleServerReady();
}
});
@ -239,20 +262,35 @@ export async function launchKoboldCpp(
if (!isReady && output.includes(SERVER_READY_SIGNALS.KOBOLDCPP)) {
isReady = true;
readyResolve?.({ success: true, pid: child.pid });
hasProcessStartedSuccessfully = true;
handleServerReady();
}
});
child.on('exit', (code, signal) => {
const isCrash = signal !== null || (code !== null && code !== 0);
const displayMessage = signal
? `\n[INFO] Process terminated with signal ${signal}`
? `\nProcess terminated with signal ${signal}`
: code === 0
? `\n[INFO] Process exited successfully`
? `\nProcess exited successfully`
: code && (code > 1 || code < 0)
? `\n[ERROR] Process exited with code ${code}`
: `\n[INFO] Process exited with code ${code}`;
? `\nProcess exited with code ${code}`
: `\nProcess exited with code ${code}`;
sendKoboldOutput(displayMessage);
const wasIntentionalStop = isIntentionalStop;
const hadStartedSuccessfully = hasProcessStartedSuccessfully;
koboldProcess = null;
isIntentionalStop = false;
hasProcessStartedSuccessfully = false;
if (isCrash && hadStartedSuccessfully && !wasIntentionalStop) {
const crashInfo: KoboldCrashInfo = {
exitCode: code,
signal,
};
sendToRenderer('kobold-crashed', crashInfo);
}
if (!isReady) {
readyReject?.(
@ -266,9 +304,18 @@ export async function launchKoboldCpp(
child.on('error', (error) => {
logError(`Process error: ${error.message}`, error);
sendKoboldOutput(`\n[ERROR] Process error: ${error.message}\n`);
sendKoboldOutput(`\nProcess error: ${error.message}\n`);
koboldProcess = null;
if (isReady) {
const crashInfo: KoboldCrashInfo = {
exitCode: null,
signal: null,
errorMessage: error.message,
};
sendToRenderer('kobold-crashed', crashInfo);
}
if (!isReady) {
readyReject?.(error);
}
@ -285,7 +332,9 @@ export async function launchKoboldCpp(
export async function stopKoboldCpp() {
abortActiveDownloads();
stopProxy();
stopTunnel();
stopPreLaunchProcesses();
isIntentionalStop = true;
return terminateProcess(koboldProcess);
}

View file

@ -0,0 +1,97 @@
import fs from 'fs';
import { Tunnel, bin, install } from 'cloudflared';
import { logError } from '@/utils/node/logging';
import { sendKoboldOutput, sendToRenderer } from '../window';
import { PROXY } from '@/constants/proxy';
let activeTunnel: Tunnel | null = null;
let tunnelUrl: string | null = null;
export const startTunnel = async () => {
if (activeTunnel) {
return tunnelUrl;
}
try {
sendKoboldOutput('Starting Cloudflare tunnel...');
if (!fs.existsSync(bin)) {
sendKoboldOutput('Installing cloudflared binary...');
await install(bin);
sendKoboldOutput('cloudflared binary installed');
}
const tunnel = Tunnel.quick(`http://${PROXY.HOST}:${PROXY.PORT}`, {
'--no-autoupdate': true,
});
activeTunnel = tunnel;
const url = await new Promise<string>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Tunnel connection timed out'));
}, 30000);
tunnel.once('url', (url) => {
clearTimeout(timeout);
resolve(url);
});
tunnel.once('error', (error) => {
clearTimeout(timeout);
reject(error);
});
});
tunnelUrl = url;
sendKoboldOutput(`Tunnel ready at ${tunnelUrl}`);
sendToRenderer('tunnel-url-changed', tunnelUrl);
tunnel.on('error', (error) => {
logError(`Tunnel error: ${error.message}`, error);
sendKoboldOutput(`[TUNNEL ERROR] ${error.message}`);
});
tunnel.on('exit', (code, signal) => {
sendKoboldOutput(
`Tunnel process exited (code: ${code}, signal: ${signal})`
);
activeTunnel = null;
tunnelUrl = null;
sendToRenderer('tunnel-url-changed', null);
});
return tunnelUrl;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
logError(`Failed to start tunnel: ${errorMessage}`, error as Error);
sendKoboldOutput(`[TUNNEL ERROR] ${errorMessage}`);
activeTunnel = null;
return null;
}
};
export const stopTunnel = () => {
if (!activeTunnel) {
return;
}
try {
sendKoboldOutput('Stopping Cloudflare tunnel...');
activeTunnel.stop();
activeTunnel = null;
tunnelUrl = null;
sendToRenderer('tunnel-url-changed', null);
sendKoboldOutput('Tunnel stopped');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
logError(`Failed to stop tunnel: ${errorMessage}`, error as Error);
activeTunnel = null;
tunnelUrl = null;
}
};
export const getTunnelUrl = () => tunnelUrl;
export const isTunnelActive = () => activeTunnel !== null;

View file

@ -14,6 +14,7 @@ import type {
MemoryMetrics,
GpuMetrics,
} from '@/main/modules/monitoring';
import type { KoboldCrashInfo } from '@/types/ipc';
const koboldAPI: KoboldAPI = {
getInstalledBackends: () => ipcRenderer.invoke('kobold:getInstalledBackends'),
@ -105,6 +106,23 @@ const koboldAPI: KoboldAPI = {
ipcRenderer.removeListener('kobold-output', handler);
};
},
onKoboldCrashed: (callback) => {
const handler = (_: IpcRendererEvent, crashInfo: KoboldCrashInfo) =>
callback(crashInfo);
ipcRenderer.on('kobold-crashed', handler);
return () => {
ipcRenderer.removeListener('kobold-crashed', handler);
};
},
onTunnelUrlChanged: (callback) => {
const handler = (_: IpcRendererEvent, url: string | null) => callback(url);
ipcRenderer.on('tunnel-url-changed', handler);
return () => {
ipcRenderer.removeListener('tunnel-url-changed', handler);
};
},
};
const appAPI: AppAPI = {

View file

@ -18,6 +18,7 @@ import type {
MemoryMetrics,
GpuMetrics,
} from '@/main/modules/monitoring';
import type { KoboldCrashInfo } from '@/types/ipc';
export interface GitHubAsset {
name: string;
@ -176,6 +177,10 @@ export interface KoboldAPI {
onInstallDirChanged: (callback: (newPath: string) => void) => () => void;
onVersionsUpdated: (callback: () => void) => () => void;
onKoboldOutput: (callback: (data: string) => void) => () => void;
onKoboldCrashed: (
callback: (crashInfo: KoboldCrashInfo) => void
) => () => void;
onTunnelUrlChanged: (callback: (url: string | null) => void) => () => void;
}
export interface SystemVersionInfo {

10
src/types/ipc.d.ts vendored
View file

@ -3,15 +3,25 @@ export type IPCChannel =
| 'install-dir-changed'
| 'versions-updated'
| 'kobold-output'
| 'kobold-crashed'
| 'tunnel-url-changed'
| 'window-maximized'
| 'window-unmaximized'
| 'line-numbers-changed';
export interface KoboldCrashInfo {
exitCode: number | null;
signal: string | null;
errorMessage?: string;
}
export interface IPCChannelPayloads {
'download-progress': [progress: number];
'install-dir-changed': [newPath: string];
'versions-updated': [];
'kobold-output': [message: string];
'kobold-crashed': [crashInfo: KoboldCrashInfo];
'tunnel-url-changed': [url: string | null];
'window-maximized': [];
'window-unmaximized': [];
'line-numbers-changed': [showLineNumbers: boolean];

View file

@ -175,3 +175,34 @@ export function getServerInterfaceInfo({
: FRONTENDS.KOBOLDAI_LITE,
};
}
export function getTunnelInterfaceUrl(
tunnelBaseUrl: string,
params: Omit<ServerInterfaceParams, 'frontendPreference'> & {
frontendPreference: Exclude<
FrontendPreference,
'sillytavern' | 'openwebui'
>;
}
) {
const {
frontendPreference,
imageGenerationFrontendPreference,
isImageGenerationMode,
} = params;
if (
isImageGenerationMode &&
imageGenerationFrontendPreference === 'builtin'
) {
return `${tunnelBaseUrl}/sdui`;
}
if (frontendPreference === 'llamacpp') {
return isImageGenerationMode
? `${tunnelBaseUrl}/sdui`
: `${tunnelBaseUrl}/lcpp`;
}
return isImageGenerationMode ? `${tunnelBaseUrl}/sdui` : tunnelBaseUrl;
}

View file

@ -4,6 +4,7 @@ export function parseKoboldConfig(args: string[]) {
let hasSdModel = false;
let hasTextModel = false;
let debugmode = false;
let remotetunnel = false;
for (let i = 0; i < args.length; i++) {
if (
@ -22,11 +23,13 @@ export function parseKoboldConfig(args: string[]) {
hasTextModel = true;
} else if (args[i] === '--debugmode') {
debugmode = true;
} else if (args[i] === '--remotetunnel') {
remotetunnel = true;
}
}
const isImageMode = hasSdModel;
const isTextMode = hasTextModel;
return { host, port, isImageMode, isTextMode, debugmode };
return { host, port, isImageMode, isTextMode, debugmode, remotetunnel };
}

142
yarn.lock
View file

@ -1452,106 +1452,106 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^8.48.0":
version: 8.48.0
resolution: "@typescript-eslint/eslint-plugin@npm:8.48.0"
"@typescript-eslint/eslint-plugin@npm:^8.48.1":
version: 8.48.1
resolution: "@typescript-eslint/eslint-plugin@npm:8.48.1"
dependencies:
"@eslint-community/regexpp": "npm:^4.10.0"
"@typescript-eslint/scope-manager": "npm:8.48.0"
"@typescript-eslint/type-utils": "npm:8.48.0"
"@typescript-eslint/utils": "npm:8.48.0"
"@typescript-eslint/visitor-keys": "npm:8.48.0"
"@typescript-eslint/scope-manager": "npm:8.48.1"
"@typescript-eslint/type-utils": "npm:8.48.1"
"@typescript-eslint/utils": "npm:8.48.1"
"@typescript-eslint/visitor-keys": "npm:8.48.1"
graphemer: "npm:^1.4.0"
ignore: "npm:^7.0.0"
natural-compare: "npm:^1.4.0"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
"@typescript-eslint/parser": ^8.48.0
"@typescript-eslint/parser": ^8.48.1
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/5f4f9ac3ace3f615bac428859026b70fb7fa236666cfe8856fed3add7e4ba73c7113264c2df7a9d68247b679dfcc21b0414488bda7b9b3de1c209b1807ed7842
checksum: 10c0/aeb4692ac27ded73dce5ddba08d46f15d617651f629cdfc5e874dd4ac767eac0523807f1f4e51f6f80675efff78e5937690f1c58740b8cb92b44b87d757a6a1a
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^8.48.0":
version: 8.48.0
resolution: "@typescript-eslint/parser@npm:8.48.0"
"@typescript-eslint/parser@npm:^8.48.1":
version: 8.48.1
resolution: "@typescript-eslint/parser@npm:8.48.1"
dependencies:
"@typescript-eslint/scope-manager": "npm:8.48.0"
"@typescript-eslint/types": "npm:8.48.0"
"@typescript-eslint/typescript-estree": "npm:8.48.0"
"@typescript-eslint/visitor-keys": "npm:8.48.0"
"@typescript-eslint/scope-manager": "npm:8.48.1"
"@typescript-eslint/types": "npm:8.48.1"
"@typescript-eslint/typescript-estree": "npm:8.48.1"
"@typescript-eslint/visitor-keys": "npm:8.48.1"
debug: "npm:^4.3.4"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/180753e1dc55cd5174a236b738d3b0dd6dd6c131797cd417b3b3b8fac344168f3d21bd49eae6c0a075be29ed69b7bc74d97cadd917f1f4d4c113c29e76c1f9cd
checksum: 10c0/54ec22c82cc631f56131bfed9747f8cadf52ab123463a406c5221f258f9533431c4a33ebe21ef178840d50235e69bb370d36aa2fd6a066e7223b38bfa41a1788
languageName: node
linkType: hard
"@typescript-eslint/project-service@npm:8.48.0":
version: 8.48.0
resolution: "@typescript-eslint/project-service@npm:8.48.0"
"@typescript-eslint/project-service@npm:8.48.1":
version: 8.48.1
resolution: "@typescript-eslint/project-service@npm:8.48.1"
dependencies:
"@typescript-eslint/tsconfig-utils": "npm:^8.48.0"
"@typescript-eslint/types": "npm:^8.48.0"
"@typescript-eslint/tsconfig-utils": "npm:^8.48.1"
"@typescript-eslint/types": "npm:^8.48.1"
debug: "npm:^4.3.4"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/6e1d08312fe55a91ba37eb19131af91ad7834bafd15d1cddb83a1e35e5134382e10dc0b14531036ba1c075ce4cba627123625ed6f2e209fb3355f3dda25da0a1
checksum: 10c0/0aeeea5e65d0f837bd9a47265f144f14ca72969d259ee929e63e06526b21f4e990e70c7bafdb2ceb3783373df7d9f5bae32c328a4c6403606f01339bc984b3f5
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:8.48.0":
version: 8.48.0
resolution: "@typescript-eslint/scope-manager@npm:8.48.0"
"@typescript-eslint/scope-manager@npm:8.48.1":
version: 8.48.1
resolution: "@typescript-eslint/scope-manager@npm:8.48.1"
dependencies:
"@typescript-eslint/types": "npm:8.48.0"
"@typescript-eslint/visitor-keys": "npm:8.48.0"
checksum: 10c0/0766e365901a8af9d9e41fa70464254aacf8b4d167734d88b6cdaa0235e86bfdffc57a3e39a20e105929b8df499d252090f64f81f86770f74626ca809afe54b6
"@typescript-eslint/types": "npm:8.48.1"
"@typescript-eslint/visitor-keys": "npm:8.48.1"
checksum: 10c0/16514823784cb598817b87d3d2b4fb618ab8b2378b3401a4c1160a5c914e51e7a925c3c1e7be73e0250e38390f0be70fecb3e0e0bdde7b243d74444933b95d3e
languageName: node
linkType: hard
"@typescript-eslint/tsconfig-utils@npm:8.48.0, @typescript-eslint/tsconfig-utils@npm:^8.48.0":
version: 8.48.0
resolution: "@typescript-eslint/tsconfig-utils@npm:8.48.0"
"@typescript-eslint/tsconfig-utils@npm:8.48.1, @typescript-eslint/tsconfig-utils@npm:^8.48.1":
version: 8.48.1
resolution: "@typescript-eslint/tsconfig-utils@npm:8.48.1"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/52e9ce8ffbaf32f3c6f4b8fa8af6e3901c430411e137a0baf650fcefdd8edf3dcc4569eba726a28424471d4d1d96b815aa4cf7b63aa7b67380efd6a8dd354222
checksum: 10c0/0d540f7ab3018ed1bab8f008c0d30229e0ea12806fdbf1c756572b5cf536a1f2a6c59ca2544c09bcd5b89dcfcf79e5f6be3d765e725492b9c7e4cd64fcecffc6
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:8.48.0":
version: 8.48.0
resolution: "@typescript-eslint/type-utils@npm:8.48.0"
"@typescript-eslint/type-utils@npm:8.48.1":
version: 8.48.1
resolution: "@typescript-eslint/type-utils@npm:8.48.1"
dependencies:
"@typescript-eslint/types": "npm:8.48.0"
"@typescript-eslint/typescript-estree": "npm:8.48.0"
"@typescript-eslint/utils": "npm:8.48.0"
"@typescript-eslint/types": "npm:8.48.1"
"@typescript-eslint/typescript-estree": "npm:8.48.1"
"@typescript-eslint/utils": "npm:8.48.1"
debug: "npm:^4.3.4"
ts-api-utils: "npm:^2.1.0"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/72ab5c7d183b844e4870bfa5dfeb68e2e7ce5f3e1b33c06d5a8e70f0d0a012c9152ad15071d41ba3788266109804a9f4cdb85d664b11df8948bc930e29e0c244
checksum: 10c0/c98a71f7d374be249ecc7c9f20b0a867a73ad4f64e646a6bf9f2c1a5d74f0dc7bd59e9c94a0842068caa366af39ae0c550ede6d653b5c9418a0a587510bbb6d5
languageName: node
linkType: hard
"@typescript-eslint/types@npm:8.48.0, @typescript-eslint/types@npm:^8.48.0":
version: 8.48.0
resolution: "@typescript-eslint/types@npm:8.48.0"
checksum: 10c0/865a8f4ae4a50aa8976f3d7e0f874f1a1c80227ec53ded68644d41011c729a489bb59f70683b29237ab945716ea0258e1d47387163379eab3edaaf5e5cc3b757
"@typescript-eslint/types@npm:8.48.1, @typescript-eslint/types@npm:^8.48.1":
version: 8.48.1
resolution: "@typescript-eslint/types@npm:8.48.1"
checksum: 10c0/366b8140f4c69319f1796b66b33c0c6e16eb6cbe543b9517003104e12ed143b620c1433ccf60d781a629d9433bd509a363c0c9d21fd438c17bb8840733af6caa
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:8.48.0":
version: 8.48.0
resolution: "@typescript-eslint/typescript-estree@npm:8.48.0"
"@typescript-eslint/typescript-estree@npm:8.48.1":
version: 8.48.1
resolution: "@typescript-eslint/typescript-estree@npm:8.48.1"
dependencies:
"@typescript-eslint/project-service": "npm:8.48.0"
"@typescript-eslint/tsconfig-utils": "npm:8.48.0"
"@typescript-eslint/types": "npm:8.48.0"
"@typescript-eslint/visitor-keys": "npm:8.48.0"
"@typescript-eslint/project-service": "npm:8.48.1"
"@typescript-eslint/tsconfig-utils": "npm:8.48.1"
"@typescript-eslint/types": "npm:8.48.1"
"@typescript-eslint/visitor-keys": "npm:8.48.1"
debug: "npm:^4.3.4"
minimatch: "npm:^9.0.4"
semver: "npm:^7.6.0"
@ -1559,32 +1559,32 @@ __metadata:
ts-api-utils: "npm:^2.1.0"
peerDependencies:
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/f17dd35f7b82654fae9fe83c2eb650572464dbce0170d55b3ef94b99e9aae010f2cbadd436089c8e59eef97d41719ace3a2deb4ac3cdfac26d43b36f34df5590
checksum: 10c0/72c0802f74222160f6a13ebbd32b0d504142a2427678c87ea78fc32672c65fd522377d43b31a97c944cbd0aefc36b320bf02f04e47c44f2797d6ccd0a8aa30ec
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:8.48.0":
version: 8.48.0
resolution: "@typescript-eslint/utils@npm:8.48.0"
"@typescript-eslint/utils@npm:8.48.1":
version: 8.48.1
resolution: "@typescript-eslint/utils@npm:8.48.1"
dependencies:
"@eslint-community/eslint-utils": "npm:^4.7.0"
"@typescript-eslint/scope-manager": "npm:8.48.0"
"@typescript-eslint/types": "npm:8.48.0"
"@typescript-eslint/typescript-estree": "npm:8.48.0"
"@typescript-eslint/scope-manager": "npm:8.48.1"
"@typescript-eslint/types": "npm:8.48.1"
"@typescript-eslint/typescript-estree": "npm:8.48.1"
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
typescript: ">=4.8.4 <6.0.0"
checksum: 10c0/56334312d1dc114a5c8b05dac4da191c40a416a5705fa76797ebdc9f6a96d35727fd0993cf8776f5c4411837e5fc2151bfa61d3eecc98b24f5a821a63a4d56f3
checksum: 10c0/1775ac217b578f52d6c1e85258098f8ef764d04830c6ce11043b434860da80f1a5f7cc1b9f2e0a63de161e83b8d876f7ae8362d7644d5d8e636e60ad5eeff4e2
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:8.48.0":
version: 8.48.0
resolution: "@typescript-eslint/visitor-keys@npm:8.48.0"
"@typescript-eslint/visitor-keys@npm:8.48.1":
version: 8.48.1
resolution: "@typescript-eslint/visitor-keys@npm:8.48.1"
dependencies:
"@typescript-eslint/types": "npm:8.48.0"
"@typescript-eslint/types": "npm:8.48.1"
eslint-visitor-keys: "npm:^4.2.1"
checksum: 10c0/20ae9ec255a786de40cdba281b63f634a642dcc34d2a79c5ffc160109f7f6227c28ae2c64be32cbc53dc68dc398c3da715bfcce90422b5024f15f7124a3c1704
checksum: 10c0/ecf4078ce63c296dd340672b516f42bf452534c75af7e7d6c1a3f32b143ff184cb3a4071d7429a9f870371ff9091a790acce28b85ce3c450bfc60554c79d43ca
languageName: node
linkType: hard
@ -2304,6 +2304,15 @@ __metadata:
languageName: node
linkType: hard
"cloudflared@npm:^0.7.1":
version: 0.7.1
resolution: "cloudflared@npm:0.7.1"
bin:
cloudflared: lib/cloudflared.js
checksum: 10c0/e5cb2a44f514c19f80078eed019cf772d545ff58e528359b5d31a96de0885e959085fee37b21d3c99053d93b5a4abeb8c23b5cb66127257ad4dc92ef01912626
languageName: node
linkType: hard
"clsx@npm:^2.1.1":
version: 2.1.1
resolution: "clsx@npm:2.1.1"
@ -3715,10 +3724,11 @@ __metadata:
"@types/react": "npm:^19.2.7"
"@types/react-dom": "npm:^19.2.3"
"@types/yauzl": "npm:^2.10.3"
"@typescript-eslint/eslint-plugin": "npm:^8.48.0"
"@typescript-eslint/parser": "npm:^8.48.0"
"@typescript-eslint/eslint-plugin": "npm:^8.48.1"
"@typescript-eslint/parser": "npm:^8.48.1"
"@uiw/react-codemirror": "npm:^4.25.3"
"@vitejs/plugin-react": "npm:^5.1.1"
cloudflared: "npm:^0.7.1"
cross-env: "npm:^10.1.0"
electron: "npm:^38.7.2"
electron-builder: "npm:^26.0.12"