From c111dfbddc80c2f39667dab699ad7f4c71a3aca0 Mon Sep 17 00:00:00 2001 From: Egor Date: Tue, 2 Dec 2025 18:52:47 -0800 Subject: [PATCH] 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 --- .vscode/settings.json | 2 +- package.json | 5 +- src/components/App/BackendCrashModal.tsx | 66 +++++++++ src/components/App/StatusBar.tsx | 60 +++++++- src/components/App/index.tsx | 21 +++ src/main/modules/koboldcpp/launcher/index.ts | 71 ++++++++-- src/main/modules/koboldcpp/tunnel.ts | 97 +++++++++++++ src/preload/index.ts | 18 +++ src/types/electron.d.ts | 5 + src/types/ipc.d.ts | 10 ++ src/utils/interface.ts | 31 ++++ src/utils/node/kobold.ts | 5 +- yarn.lock | 142 ++++++++++--------- 13 files changed, 447 insertions(+), 86 deletions(-) create mode 100644 src/components/App/BackendCrashModal.tsx create mode 100644 src/main/modules/koboldcpp/tunnel.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index c1c6264..cbd6fe4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -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" diff --git a/package.json b/package.json index bf4e5cf..4f208ef 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/components/App/BackendCrashModal.tsx b/src/components/App/BackendCrashModal.tsx new file mode 100644 index 0000000..c0c5973 --- /dev/null +++ b/src/components/App/BackendCrashModal.tsx @@ -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 = { + 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 ( + + + {description} + + + Check the terminal output for more details about the crash. + + + + + + + + ); +}; diff --git a/src/components/App/StatusBar.tsx b/src/components/App/StatusBar.tsx index 40e93ae..13a92ee 100644 --- a/src/components/App/StatusBar.tsx +++ b/src/components/App/StatusBar.tsx @@ -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(null); - const { resolvedColorScheme: colorScheme, systemMonitoringEnabled } = - usePreferencesStore(); + const [tunnelBaseUrl, setTunnelBaseUrl] = useState(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) => { + {tunnelUrl && ( + + {({ copied, copy }) => ( + + + {copied ? : } + + + )} + + )} diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index dc198ca..ae4c79a 100644 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -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(null); @@ -28,6 +30,7 @@ export const App = () => { const [activeInterfaceTab, setActiveInterfaceTab] = useState('terminal'); const [ejectConfirmModalOpen, setEjectConfirmModalOpen] = useState(false); + const [crashInfo, setCrashInfo] = useState(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} /> + setCrashInfo(null)} + crashInfo={crashInfo} + /> + ); diff --git a/src/main/modules/koboldcpp/launcher/index.ts b/src/main/modules/koboldcpp/launcher/index.ts index 523696d..f6f7096 100644 --- a/src/main/modules/koboldcpp/launcher/index.ts +++ b/src/main/modules/koboldcpp/launcher/index.ts @@ -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(); 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); } diff --git a/src/main/modules/koboldcpp/tunnel.ts b/src/main/modules/koboldcpp/tunnel.ts new file mode 100644 index 0000000..a58562f --- /dev/null +++ b/src/main/modules/koboldcpp/tunnel.ts @@ -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((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; diff --git a/src/preload/index.ts b/src/preload/index.ts index 95fb40c..29eb854 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -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 = { diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index 6705cec..cbe7443 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -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 { diff --git a/src/types/ipc.d.ts b/src/types/ipc.d.ts index 658bceb..f984177 100644 --- a/src/types/ipc.d.ts +++ b/src/types/ipc.d.ts @@ -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]; diff --git a/src/utils/interface.ts b/src/utils/interface.ts index a83ca56..1fb2c30 100644 --- a/src/utils/interface.ts +++ b/src/utils/interface.ts @@ -175,3 +175,34 @@ export function getServerInterfaceInfo({ : FRONTENDS.KOBOLDAI_LITE, }; } + +export function getTunnelInterfaceUrl( + tunnelBaseUrl: string, + params: Omit & { + 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; +} diff --git a/src/utils/node/kobold.ts b/src/utils/node/kobold.ts index 757b5eb..a3d5ca9 100644 --- a/src/utils/node/kobold.ts +++ b/src/utils/node/kobold.ts @@ -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 }; } diff --git a/yarn.lock b/yarn.lock index c16229e..9df713c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"