From bc96f08ee885afa946dbdc21a17ce6ba115a01a8 Mon Sep 17 00:00:00 2001 From: Yogi Date: Sat, 31 Jan 2026 11:24:58 +0700 Subject: [PATCH 01/16] feat: add CLI config detection page and proxy Claude support Add onboarding page for detecting existing Claude CLI configuration and allow users to use their existing API proxy settings. - Add cli-config-detected-page.tsx for showing detected CLI config - Add cliConfigDetectedShownAtom to track if page was shown - Update onboarding flow to check for existing CLI config - Add pnpm workspace configuration --- bun.lock | 56 +- pnpm-lock.yaml | 11227 ++++++++++++++++ pnpm-workspace.yaml | 10 + src/renderer/App.tsx | 191 +- .../onboarding/cli-config-detected-page.tsx | 210 + src/renderer/features/onboarding/index.ts | 9 +- src/renderer/lib/atoms/index.ts | 9 + 7 files changed, 11620 insertions(+), 92 deletions(-) create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 src/renderer/features/onboarding/cli-config-detected-page.tsx diff --git a/bun.lock b/bun.lock index db5831282..5e7ba4fa4 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "21st-desktop", @@ -10,6 +11,7 @@ "@git-diff-view/shiki": "^0.0.36", "@modelcontextprotocol/sdk": "^1.25.3", "@monaco-editor/react": "^4.7.0", + "@pierre/diffs": "^1.0.10", "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-alert-dialog": "^1.1.15", "@radix-ui/react-checkbox": "^1.3.3", @@ -423,6 +425,8 @@ "@opentelemetry/sql-common": ["@opentelemetry/sql-common@0.41.2", "", { "dependencies": { "@opentelemetry/core": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ=="], + "@pierre/diffs": ["@pierre/diffs@1.0.10", "", { "dependencies": { "@shikijs/core": "^3.0.0", "@shikijs/engine-javascript": "^3.0.0", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-ahkpfS30NfaB+PBxnf0/Mc20ySBRTQmM28a7Ojpd0UZixmTyhGhJfBFjvmhX8dSzR22lB3h3OIMMxpB4yYTIOQ=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@posthog/core": ["@posthog/core@1.13.0", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-knjncrk7qRmssFRbGzBl1Tunt21GRpe0Wv+uVelyL0Rh7PdQUsgguulzXFTps8hA6wPwTU4kq85qnbAJ3eH6Wg=="], @@ -611,9 +615,9 @@ "@sentry/opentelemetry": ["@sentry/opentelemetry@10.34.0", "", { "dependencies": { "@sentry/core": "10.34.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0 || ^2.2.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0 || ^2.2.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0 || ^2.2.0", "@opentelemetry/semantic-conventions": "^1.37.0" } }, "sha512-uKuULBOmdVu3bYdD8doMLqKgN0PP3WWtI7Shu11P9PVrhSNT4U9yM9Z6v1aFlQcbrgyg3LynZuXs8lyjt90UbA=="], - "@shikijs/core": ["@shikijs/core@1.29.2", "", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="], + "@shikijs/core": ["@shikijs/core@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA=="], - "@shikijs/engine-javascript": ["@shikijs/engine-javascript@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "oniguruma-to-es": "^2.2.0" } }, "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A=="], + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ=="], "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1" } }, "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA=="], @@ -621,6 +625,8 @@ "@shikijs/themes": ["@shikijs/themes@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g=="], + "@shikijs/transformers": ["@shikijs/transformers@3.22.0", "", { "dependencies": { "@shikijs/core": "3.22.0", "@shikijs/types": "3.22.0" } }, "sha512-E7eRV7mwDBjueLF6852n2oYeJYxBq3NSsDk+uyruYAXONv4U8holGmIrT+mPRJQ1J1SNOH6L8G19KRzmBawrFw=="], + "@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="], "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], @@ -1133,6 +1139,8 @@ "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], + "diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="], + "dir-compare": ["dir-compare@4.2.0", "", { "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ=="], "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], @@ -1531,6 +1539,8 @@ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "lru_map": ["lru_map@0.4.1", "", {}, "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg=="], + "lucide-react": ["lucide-react@0.468.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA=="], "lzma-native": ["lzma-native@8.0.6", "", { "dependencies": { "node-addon-api": "^3.1.0", "node-gyp-build": "^4.2.1", "readable-stream": "^3.6.0" }, "bin": { "lzmajs": "bin/lzmajs" } }, "sha512-09xfg67mkL2Lz20PrrDeNYZxzeW7ADtpYFbwSQh9U8+76RIzx5QsJBMy8qikv3hbUPfpy6hqwxt6FcGK81g9AA=="], @@ -1739,7 +1749,7 @@ "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="], - "oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], + "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], @@ -1895,9 +1905,9 @@ "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], - "regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="], + "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], - "regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="], + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], @@ -2297,6 +2307,8 @@ "@opentelemetry/sdk-metrics/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], + "@pierre/diffs/shiki": ["shiki@3.21.0", "", { "dependencies": { "@shikijs/core": "3.21.0", "@shikijs/engine-javascript": "3.21.0", "@shikijs/engine-oniguruma": "3.21.0", "@shikijs/langs": "3.21.0", "@shikijs/themes": "3.21.0", "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w=="], + "@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], @@ -2321,6 +2333,14 @@ "@sentry/node/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "@shikijs/core/@shikijs/types": ["@shikijs/types@3.21.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA=="], + + "@shikijs/engine-javascript/@shikijs/types": ["@shikijs/types@3.21.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA=="], + + "@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA=="], + + "@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.22.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg=="], + "@tailwindcss/typography/postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], "accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], @@ -2425,6 +2445,10 @@ "roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + "shiki/@shikijs/core": ["@shikijs/core@1.29.2", "", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="], + + "shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "oniguruma-to-es": "^2.2.0" } }, "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A=="], + "socks-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], @@ -2491,10 +2515,6 @@ "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], - "@git-diff-view/shiki/shiki/@shikijs/core": ["@shikijs/core@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA=="], - - "@git-diff-view/shiki/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ=="], - "@git-diff-view/shiki/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ=="], "@git-diff-view/shiki/shiki/@shikijs/langs": ["@shikijs/langs@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA=="], @@ -2509,6 +2529,14 @@ "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + "@pierre/diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ=="], + + "@pierre/diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA=="], + + "@pierre/diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw=="], + + "@pierre/diffs/shiki/@shikijs/types": ["@shikijs/types@3.21.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA=="], + "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "archiver-utils/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], @@ -2543,16 +2571,16 @@ "make-fetch-happen/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + "shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], + "tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - "@git-diff-view/shiki/shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], + "shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="], - "tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="], - "@git-diff-view/shiki/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], - - "@git-diff-view/shiki/shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + "tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 000000000..43e8157b7 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,11227 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@ai-sdk/react': + specifier: ^3.0.14 + version: 3.0.51(react@19.2.1)(zod@3.25.76) + '@anthropic-ai/claude-agent-sdk': + specifier: ^0.2.12 + version: 0.2.19(zod@3.25.76) + '@git-diff-view/react': + specifier: ^0.0.35 + version: 0.0.35(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@git-diff-view/shiki': + specifier: ^0.0.36 + version: 0.0.36 + '@modelcontextprotocol/sdk': + specifier: ^1.25.3 + version: 1.25.3(hono@4.11.5)(zod@3.25.76) + '@radix-ui/react-accordion': + specifier: ^1.2.12 + version: 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-alert-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-checkbox': + specifier: ^1.3.3 + version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-collapsible': + specifier: ^1.1.12 + version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-context-menu': + specifier: ^2.2.16 + version: 2.2.16(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-dialog': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.16 + version: 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-hover-card': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-icons': + specifier: ^1.3.2 + version: 1.3.2(react@19.2.1) + '@radix-ui/react-label': + specifier: ^2.1.8 + version: 2.1.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-progress': + specifier: ^1.1.8 + version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-select': + specifier: ^2.2.6 + version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-switch': + specifier: ^1.2.6 + version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-tabs': + specifier: ^1.1.13 + version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-tooltip': + specifier: ^1.2.8 + version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@sentry/electron': + specifier: ^7.5.0 + version: 7.6.0 + '@tailwindcss/typography': + specifier: ^0.5.19 + version: 0.5.19(tailwindcss@3.4.19) + '@tanstack/react-query': + specifier: ^5.90.10 + version: 5.90.20(react@19.2.1) + '@tanstack/react-virtual': + specifier: ^3.13.18 + version: 3.13.18(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@trpc/client': + specifier: ^11.7.1 + version: 11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3) + '@trpc/react-query': + specifier: ^11.7.1 + version: 11.8.1(@tanstack/react-query@5.90.20(react@19.2.1))(@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.1(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) + '@trpc/server': + specifier: ^11.7.1 + version: 11.8.1(typescript@5.9.3) + '@xterm/addon-canvas': + specifier: ^0.7.0 + version: 0.7.0(@xterm/xterm@5.5.0) + '@xterm/addon-fit': + specifier: ^0.11.0 + version: 0.11.0 + '@xterm/addon-search': + specifier: ^0.16.0 + version: 0.16.0 + '@xterm/addon-serialize': + specifier: ^0.14.0 + version: 0.14.0 + '@xterm/addon-web-links': + specifier: ^0.12.0 + version: 0.12.0 + '@xterm/addon-webgl': + specifier: ^0.19.0 + version: 0.19.0 + ai: + specifier: ^6.0.14 + version: 6.0.49(zod@3.25.76) + better-sqlite3: + specifier: ^11.8.1 + version: 11.10.0 + chokidar: + specifier: ^5.0.0 + version: 5.0.0 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + date-fns: + specifier: ^3.6.0 + version: 3.6.0 + drizzle-orm: + specifier: ^0.45.1 + version: 0.45.1(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.15.6)(better-sqlite3@11.10.0) + electron-log: + specifier: ^5.4.3 + version: 5.4.3 + electron-updater: + specifier: ^6.7.3 + version: 6.7.3 + gray-matter: + specifier: ^4.0.3 + version: 4.0.3 + jotai: + specifier: ^2.11.1 + version: 2.16.2(@babel/core@7.28.6)(@babel/template@7.28.6)(@types/react@19.2.9)(react@19.2.1) + lucide-react: + specifier: ^0.468.0 + version: 0.468.0(react@19.2.1) + mermaid: + specifier: ^11.12.2 + version: 11.12.2 + motion: + specifier: ^11.15.0 + version: 11.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + next-themes: + specifier: ^0.4.4 + version: 0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + node-pty: + specifier: ^1.1.0 + version: 1.1.0 + pidtree: + specifier: ^0.6.0 + version: 0.6.0 + posthog-js: + specifier: ^1.239.1 + version: 1.335.2 + posthog-node: + specifier: ^5.20.0 + version: 5.24.2 + react: + specifier: 19.2.1 + version: 19.2.1 + react-dom: + specifier: 19.2.1 + version: 19.2.1(react@19.2.1) + react-hotkeys-hook: + specifier: ^4.6.1 + version: 4.6.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react-icons: + specifier: ^5.5.0 + version: 5.5.0(react@19.2.1) + react-syntax-highlighter: + specifier: ^16.1.0 + version: 16.1.0(react@19.2.1) + react-zoom-pan-pinch: + specifier: ^3.7.0 + version: 3.7.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + remark-breaks: + specifier: ^4.0.0 + version: 4.0.0 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + shiki: + specifier: ^1.24.4 + version: 1.29.2 + simple-git: + specifier: ^3.28.0 + version: 3.30.0 + sonner: + specifier: ^1.7.1 + version: 1.7.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + streamdown: + specifier: ^2.0.1 + version: 2.1.0(react@19.2.1) + superjson: + specifier: ^2.2.2 + version: 2.2.6 + tailwind-merge: + specifier: ^2.6.0 + version: 2.6.0 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.19) + trpc-electron: + specifier: ^0.1.2 + version: 0.1.2(@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.1(typescript@5.9.3))(electron@33.4.5) + unique-names-generator: + specifier: ^4.7.1 + version: 4.7.1 + xterm: + specifier: ^5.3.0 + version: 5.3.0 + zod: + specifier: ^3.24.1 + version: 3.25.76 + zustand: + specifier: ^5.0.3 + version: 5.0.10(@types/react@19.2.9)(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)) + devDependencies: + '@electron-toolkit/preload': + specifier: ^3.0.1 + version: 3.0.2(electron@33.4.5) + '@electron-toolkit/utils': + specifier: ^4.0.0 + version: 4.0.0(electron@33.4.5) + '@types/better-sqlite3': + specifier: ^7.6.13 + version: 7.6.13 + '@types/node': + specifier: ^20.17.50 + version: 20.19.30 + '@types/react': + specifier: ^19.0.7 + version: 19.2.9 + '@types/react-dom': + specifier: ^19.0.3 + version: 19.2.3(@types/react@19.2.9) + '@types/react-syntax-highlighter': + specifier: ^15.5.13 + version: 15.5.13 + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@6.4.1(@types/node@20.19.30)(jiti@1.21.7)) + '@welldone-software/why-did-you-render': + specifier: ^10.0.1 + version: 10.0.1(react@19.2.1) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.23(postcss@8.5.6) + drizzle-kit: + specifier: ^0.31.8 + version: 0.31.8 + electron: + specifier: 33.4.5 + version: 33.4.5 + electron-builder: + specifier: ^25.1.8 + version: 25.1.8(electron-builder-squirrel-windows@25.1.8) + electron-rebuild: + specifier: ^3.2.9 + version: 3.2.9 + electron-vite: + specifier: ^3.0.0 + version: 3.1.0(vite@6.4.1(@types/node@20.19.30)(jiti@1.21.7)) + postcss: + specifier: ^8.5.1 + version: 8.5.6 + tailwindcss: + specifier: ^3.4.17 + version: 3.4.19 + typescript: + specifier: ^5.4.5 + version: 5.9.3 + vite: + specifier: ^6.3.4 + version: 6.4.1(@types/node@20.19.30)(jiti@1.21.7) + +packages: + + 7zip-bin@5.2.0: + resolution: {integrity: sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==} + + '@ai-sdk/gateway@3.0.22': + resolution: {integrity: sha512-NgnlY73JNuooACHqUIz5uMOEWvqR1MMVbb2soGLMozLY1fgwEIF5iJFDAGa5/YArlzw2ATVU7zQu7HkR/FUjgA==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider-utils@4.0.9': + resolution: {integrity: sha512-bB4r6nfhBOpmoS9mePxjRoCy+LnzP3AfhyMGCkGL4Mn9clVNlqEeKj26zEKEtB6yoSVcT1IQ0Zh9fytwMCDnow==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@3.0.5': + resolution: {integrity: sha512-2Xmoq6DBJqmSl80U6V9z5jJSJP7ehaJJQMy2iFUqTay06wdCqTnPVBBQbtEL8RCChenL+q5DC5H5WzU3vV3v8w==} + engines: {node: '>=18'} + + '@ai-sdk/react@3.0.51': + resolution: {integrity: sha512-7nmCwEJM52NQZB4/ED8qJ4wbDg7EEWh94qJ7K9GSJxD6sWF3GOKrRZ5ivm4qNmKhY+JfCxCAxfghGY5mTKOsxw==} + engines: {node: '>=18'} + peerDependencies: + react: ^18 || ~19.0.1 || ~19.1.2 || ^19.2.1 + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + + '@anthropic-ai/claude-agent-sdk@0.2.19': + resolution: {integrity: sha512-DjaX4t3Swjt5PcsZt6krcp5TfBTRxVuUZhkY6L8WWF8kZBJFuuEd5akNg486XRskTXGuwLmitxp0wHB1hJ9muw==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^4.0.0 + + '@apm-js-collab/code-transformer@0.8.2': + resolution: {integrity: sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA==} + + '@apm-js-collab/tracing-hooks@0.3.1': + resolution: {integrity: sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw==} + + '@babel/code-frame@7.28.6': + resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.6': + resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.6': + resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.6': + resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.6': + resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + engines: {node: '>=6.9.0'} + + '@braintree/sanitize-url@7.1.1': + resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} + + '@chevrotain/cst-dts-gen@11.0.3': + resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + + '@chevrotain/gast@11.0.3': + resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} + + '@chevrotain/regexp-to-ast@11.0.3': + resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} + + '@chevrotain/types@11.0.3': + resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} + + '@chevrotain/utils@11.0.3': + resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + + '@develar/schema-utils@2.6.5': + resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} + engines: {node: '>= 8.9.0'} + + '@drizzle-team/brocli@0.10.2': + resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} + + '@electron-toolkit/preload@3.0.2': + resolution: {integrity: sha512-TWWPToXd8qPRfSXwzf5KVhpXMfONaUuRAZJHsKthKgZR/+LqX1dZVSSClQ8OTAEduvLGdecljCsoT2jSshfoUg==} + peerDependencies: + electron: '>=13.0.0' + + '@electron-toolkit/utils@4.0.0': + resolution: {integrity: sha512-qXSntwEzluSzKl4z5yFNBknmPGjPa3zFhE4mp9+h0cgokY5ornAeP+CJQDBhKsL1S58aOQfcwkD3NwLZCl+64g==} + peerDependencies: + electron: '>=13.0.0' + + '@electron/asar@3.4.1': + resolution: {integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==} + engines: {node: '>=10.12.0'} + hasBin: true + + '@electron/get@2.0.3': + resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} + engines: {node: '>=12'} + + '@electron/notarize@2.5.0': + resolution: {integrity: sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==} + engines: {node: '>= 10.0.0'} + + '@electron/osx-sign@1.3.1': + resolution: {integrity: sha512-BAfviURMHpmb1Yb50YbCxnOY0wfwaLXH5KJ4+80zS0gUkzDX3ec23naTlEqKsN+PwYn+a1cCzM7BJ4Wcd3sGzw==} + engines: {node: '>=12.0.0'} + hasBin: true + + '@electron/rebuild@3.6.1': + resolution: {integrity: sha512-f6596ZHpEq/YskUd8emYvOUne89ij8mQgjYFA5ru25QwbrRO+t1SImofdDv7kKOuWCmVOuU5tvfkbgGxIl3E/w==} + engines: {node: '>=12.13.0'} + hasBin: true + + '@electron/universal@2.0.1': + resolution: {integrity: sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==} + engines: {node: '>=16.4'} + + '@esbuild-kit/core-utils@3.3.2': + resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild-kit/esm-loader@2.6.5': + resolution: {integrity: sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==} + deprecated: 'Merged into tsx: https://tsx.is' + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.18.20': + resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.18.20': + resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.18.20': + resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.18.20': + resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.18.20': + resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.18.20': + resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.18.20': + resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.18.20': + resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.18.20': + resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.18.20': + resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.18.20': + resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.18.20': + resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.18.20': + resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.18.20': + resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.18.20': + resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.18.20': + resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.18.20': + resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.18.20': + resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.18.20': + resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.18.20': + resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.18.20': + resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.18.20': + resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/react-dom@2.1.6': + resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@gar/promisify@1.1.3': + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + + '@git-diff-view/core@0.0.35': + resolution: {integrity: sha512-cdH3BopR6AWUW+6hP78zGyryKxR9JkPgryd1JN78i5k+F9Eo4x/4S23ZF1VZnrpPlGLrSuYfiAZ0ho5m+pTuKg==} + + '@git-diff-view/lowlight@0.0.35': + resolution: {integrity: sha512-MVpOxrNn1oHVOTOWUjxLbbf1W4OtVHjj6CHxwJbBRg9ZWZdShBINjuEgHVMSGB6vZuHKfwruRfXw8XxV3aF8zw==} + + '@git-diff-view/react@0.0.35': + resolution: {integrity: sha512-jIzESQ/oB2FZgEGK1urvAqju89cThv5+enEC4r2atDX15W5xMDNsDBe8FT2U74zBj8JkgROaiGmbF93KIJrWpg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@git-diff-view/shiki@0.0.36': + resolution: {integrity: sha512-t6LGKISEvE0q7u2AR1rq2RLgCcccRttuKE617D1MiHecO0Xd/xvsMhqxW4PgDMkMadhbzWnt6IhBbrdgqBKPwQ==} + + '@hono/node-server@1.19.9': + resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.0': + resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} + + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@kwsites/file-exists@1.1.1': + resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} + + '@kwsites/promise-deferred@1.1.1': + resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + + '@malept/cross-spawn-promise@2.0.0': + resolution: {integrity: sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==} + engines: {node: '>= 12.13.0'} + + '@malept/flatpak-bundler@0.4.0': + resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} + engines: {node: '>= 10.0.0'} + + '@mermaid-js/parser@0.6.3': + resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} + + '@modelcontextprotocol/sdk@1.25.3': + resolution: {integrity: sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@npmcli/fs@2.1.2': + resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + '@npmcli/move-file@2.0.1': + resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This functionality has been moved to @npmcli/fs + + '@opentelemetry/api-logs@0.208.0': + resolution: {integrity: sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@2.5.0': + resolution: {integrity: sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.2.0': + resolution: {integrity: sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@2.5.0': + resolution: {integrity: sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-logs-otlp-http@0.208.0': + resolution: {integrity: sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-amqplib@0.55.0': + resolution: {integrity: sha512-5ULoU8p+tWcQw5PDYZn8rySptGSLZHNX/7srqo2TioPnAAcvTy6sQFQXsNPrAnyRRtYGMetXVyZUy5OaX1+IfA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-connect@0.52.0': + resolution: {integrity: sha512-GXPxfNB5szMbV3I9b7kNWSmQBoBzw7MT0ui6iU/p+NIzVx3a06Ri2cdQO7tG9EKb4aKSLmfX9Cw5cKxXqX6Ohg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-dataloader@0.26.0': + resolution: {integrity: sha512-P2BgnFfTOarZ5OKPmYfbXfDFjQ4P9WkQ1Jji7yH5/WwB6Wm/knynAoA1rxbjWcDlYupFkyT0M1j6XLzDzy0aCA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-express@0.57.0': + resolution: {integrity: sha512-HAdx/o58+8tSR5iW+ru4PHnEejyKrAy9fYFhlEI81o10nYxrGahnMAHWiSjhDC7UQSY3I4gjcPgSKQz4rm/asg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-fs@0.28.0': + resolution: {integrity: sha512-FFvg8fq53RRXVBRHZViP+EMxMR03tqzEGpuq55lHNbVPyFklSVfQBN50syPhK5UYYwaStx0eyCtHtbRreusc5g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-generic-pool@0.52.0': + resolution: {integrity: sha512-ISkNcv5CM2IwvsMVL31Tl61/p2Zm2I2NAsYq5SSBgOsOndT0TjnptjufYVScCnD5ZLD1tpl4T3GEYULLYOdIdQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-graphql@0.56.0': + resolution: {integrity: sha512-IPvNk8AFoVzTAM0Z399t34VDmGDgwT6rIqCUug8P9oAGerl2/PEIYMPOl/rerPGu+q8gSWdmbFSjgg7PDVRd3Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-hapi@0.55.0': + resolution: {integrity: sha512-prqAkRf9e4eEpy4G3UcR32prKE8NLNlA90TdEU1UsghOTg0jUvs40Jz8LQWFEs5NbLbXHYGzB4CYVkCI8eWEVQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-http@0.208.0': + resolution: {integrity: sha512-rhmK46DRWEbQQB77RxmVXGyjs6783crXCnFjYQj+4tDH/Kpv9Rbg3h2kaNyp5Vz2emF1f9HOQQvZoHzwMWOFZQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-ioredis@0.56.0': + resolution: {integrity: sha512-XSWeqsd3rKSsT3WBz/JKJDcZD4QYElZEa0xVdX8f9dh4h4QgXhKRLorVsVkK3uXFbC2sZKAS2Ds+YolGwD83Dg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-kafkajs@0.18.0': + resolution: {integrity: sha512-KCL/1HnZN5zkUMgPyOxfGjLjbXjpd4odDToy+7c+UsthIzVLFf99LnfIBE8YSSrYE4+uS7OwJMhvhg3tWjqMBg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-knex@0.53.0': + resolution: {integrity: sha512-xngn5cH2mVXFmiT1XfQ1aHqq1m4xb5wvU6j9lSgLlihJ1bXzsO543cpDwjrZm2nMrlpddBf55w8+bfS4qDh60g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-koa@0.57.0': + resolution: {integrity: sha512-3JS8PU/D5E3q295mwloU2v7c7/m+DyCqdu62BIzWt+3u9utjxC9QS7v6WmUNuoDN3RM+Q+D1Gpj13ERo+m7CGg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + + '@opentelemetry/instrumentation-lru-memoizer@0.53.0': + resolution: {integrity: sha512-LDwWz5cPkWWr0HBIuZUjslyvijljTwmwiItpMTHujaULZCxcYE9eU44Qf/pbVC8TulT0IhZi+RoGvHKXvNhysw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongodb@0.61.0': + resolution: {integrity: sha512-OV3i2DSoY5M/pmLk+68xr5RvkHU8DRB3DKMzYJdwDdcxeLs62tLbkmRyqJZsYf3Ht7j11rq35pHOWLuLzXL7pQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mongoose@0.55.0': + resolution: {integrity: sha512-5afj0HfF6aM6Nlqgu6/PPHFk8QBfIe3+zF9FGpX76jWPS0/dujoEYn82/XcLSaW5LPUDW8sni+YeK0vTBNri+w==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql2@0.55.0': + resolution: {integrity: sha512-0cs8whQG55aIi20gnK8B7cco6OK6N+enNhW0p5284MvqJ5EPi+I1YlWsWXgzv/V2HFirEejkvKiI4Iw21OqDWg==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-mysql@0.54.0': + resolution: {integrity: sha512-bqC1YhnwAeWmRzy1/Xf9cDqxNG2d/JDkaxnqF5N6iJKN1eVWI+vg7NfDkf52/Nggp3tl1jcC++ptC61BD6738A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-pg@0.61.0': + resolution: {integrity: sha512-UeV7KeTnRSM7ECHa3YscoklhUtTQPs6V6qYpG283AB7xpnPGCUCUfECFT9jFg6/iZOQTt3FHkB1wGTJCNZEvPw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-redis@0.57.0': + resolution: {integrity: sha512-bCxTHQFXzrU3eU1LZnOZQ3s5LURxQPDlU3/upBzlWY77qOI1GZuGofazj3jtzjctMJeBEJhNwIFEgRPBX1kp/Q==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-tedious@0.27.0': + resolution: {integrity: sha512-jRtyUJNZppPBjPae4ZjIQ2eqJbcRaRfJkr0lQLHFmOU/no5A6e9s1OHLd5XZyZoBJ/ymngZitanyRRA5cniseA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-undici@0.19.0': + resolution: {integrity: sha512-Pst/RhR61A2OoZQZkn6OLpdVpXp6qn3Y92wXa6umfJe9rV640r4bc6SWvw4pPN6DiQqPu2c8gnSSZPDtC6JlpQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.7.0 + + '@opentelemetry/instrumentation@0.208.0': + resolution: {integrity: sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.208.0': + resolution: {integrity: sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.208.0': + resolution: {integrity: sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/redis-common@0.38.2': + resolution: {integrity: sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA==} + engines: {node: ^18.19.0 || >=20.6.0} + + '@opentelemetry/resources@2.2.0': + resolution: {integrity: sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/resources@2.5.0': + resolution: {integrity: sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.208.0': + resolution: {integrity: sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@2.2.0': + resolution: {integrity: sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.9.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.2.0': + resolution: {integrity: sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@2.5.0': + resolution: {integrity: sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.39.0': + resolution: {integrity: sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==} + engines: {node: '>=14'} + + '@opentelemetry/sql-common@0.41.2': + resolution: {integrity: sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ==} + engines: {node: ^18.19.0 || >=20.6.0} + peerDependencies: + '@opentelemetry/api': ^1.1.0 + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@posthog/core@1.14.0': + resolution: {integrity: sha512-havjGYHwL8Gy6LXIR911h+M/sYlJLQbepxP/cc1M7Cp3v8F92bzpqkbuvUIUyb7/izkxfGwc9wMqKAo0QxMTrg==} + + '@posthog/types@1.335.2': + resolution: {integrity: sha512-cyl6eFrt0nR7lxb8+oGXyS16wDxQJz6awMWPyDB423lI+MiM64vz0VV5LNABahEc4BuytJzfEOyvyA3LPJ4hOQ==} + + '@prisma/instrumentation@6.19.0': + resolution: {integrity: sha512-QcuYy25pkXM8BJ37wVFBO7Zh34nyRV1GOb2n3lPkkbRYfl4hWl3PTcImP41P0KrzVXfa/45p6eVCos27x3exIg==} + peerDependencies: + '@opentelemetry/api': ^1.8 + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-accordion@1.2.12': + resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-alert-dialog@1.1.15': + resolution: {integrity: sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context-menu@2.2.16': + resolution: {integrity: sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.3': + resolution: {integrity: sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-dropdown-menu@2.1.16': + resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-hover-card@1.1.15': + resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-icons@1.3.2': + resolution: {integrity: sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==} + peerDependencies: + react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-label@2.1.8': + resolution: {integrity: sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-menu@2.1.16': + resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-progress@1.1.8': + resolution: {integrity: sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-switch@1.2.6': + resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.56.0': + resolution: {integrity: sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.56.0': + resolution: {integrity: sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.56.0': + resolution: {integrity: sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.56.0': + resolution: {integrity: sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.56.0': + resolution: {integrity: sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.56.0': + resolution: {integrity: sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.56.0': + resolution: {integrity: sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.56.0': + resolution: {integrity: sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.56.0': + resolution: {integrity: sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.56.0': + resolution: {integrity: sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.56.0': + resolution: {integrity: sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.56.0': + resolution: {integrity: sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.56.0': + resolution: {integrity: sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.56.0': + resolution: {integrity: sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.56.0': + resolution: {integrity: sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.56.0': + resolution: {integrity: sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.56.0': + resolution: {integrity: sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.56.0': + resolution: {integrity: sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.56.0': + resolution: {integrity: sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.56.0': + resolution: {integrity: sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.56.0': + resolution: {integrity: sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.56.0': + resolution: {integrity: sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.56.0': + resolution: {integrity: sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.56.0': + resolution: {integrity: sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.56.0': + resolution: {integrity: sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==} + cpu: [x64] + os: [win32] + + '@sentry-internal/browser-utils@10.34.0': + resolution: {integrity: sha512-0YNr60rGHyedmwkO0lbDBjNx2KAmT3kWamjaqu7Aw+jsESoPLgt+fzaTVvUBvkftBDui2PeTSzXm/nqzssctYg==} + engines: {node: '>=18'} + + '@sentry-internal/feedback@10.34.0': + resolution: {integrity: sha512-wgGnq+iNxsFSOe9WX/FOvtoItSTjgLJJ4dQkVYtcVM6WGBVIg4wgNYfECCnRNztUTPzpZHLjC9r+4Pym451DDQ==} + engines: {node: '>=18'} + + '@sentry-internal/replay-canvas@10.34.0': + resolution: {integrity: sha512-XWH/9njtgMD+LLWjc4KKgBpb+dTCkoUEIFDxcvzG/87d+jirmzf0+r8EfpLwKG+GrqNiiGRV39zIqu0SfPl+cw==} + engines: {node: '>=18'} + + '@sentry-internal/replay@10.34.0': + resolution: {integrity: sha512-Vmea0GcOg57z/S1bVSj3saFcRvDqdLzdy4wd9fQMpMgy5OCbTlo7lxVUndKzbcZnanma6zF6VxwnWER1WuN9RA==} + engines: {node: '>=18'} + + '@sentry/browser@10.34.0': + resolution: {integrity: sha512-8WCsAXli5Z+eIN8dMY8KGQjrS3XgUp1np/pjdeWNrVPVR8q8XpS34qc+f+y/LFrYQC9bs2Of5aIBwRtDCIvRsg==} + engines: {node: '>=18'} + + '@sentry/core@10.34.0': + resolution: {integrity: sha512-4FFpYBMf0VFdPcsr4grDYDOR87mRu6oCfb51oQjU/Pndmty7UgYo0Bst3LEC/8v0SpytBtzXq+Wx/fkwulBesg==} + engines: {node: '>=18'} + + '@sentry/electron@7.6.0': + resolution: {integrity: sha512-ueW3Coa0BtOQFPaf+QaI3mBHMi/t7CkZnuzZ6PNoVpHe6CgYfCtNdE7H1BpMsCpG1FhEAgCLBJtpaMKyQBFdzQ==} + peerDependencies: + '@sentry/node-native': 10.34.0 + peerDependenciesMeta: + '@sentry/node-native': + optional: true + + '@sentry/node-core@10.34.0': + resolution: {integrity: sha512-FrGfC8GzD1cnZDO3zwQ4cjyoY1ZwNHvZbXSvXRYxpjhXidZhvaPurjgLRSB0xGaFgoemmOp1ufsx/w6fQOGA6Q==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/instrumentation': '>=0.57.1 <1' + '@opentelemetry/resources': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/semantic-conventions': ^1.37.0 + + '@sentry/node@10.34.0': + resolution: {integrity: sha512-bEOyH97HuVtWZYAZ5mp0NhYNc+n6QCfiKuLee2P75n2kt4cIPTGvLOSdUwwjllf795uOdKZJuM1IUN0W+YMcVg==} + engines: {node: '>=18'} + + '@sentry/opentelemetry@10.34.0': + resolution: {integrity: sha512-uKuULBOmdVu3bYdD8doMLqKgN0PP3WWtI7Shu11P9PVrhSNT4U9yM9Z6v1aFlQcbrgyg3LynZuXs8lyjt90UbA==} + engines: {node: '>=18'} + peerDependencies: + '@opentelemetry/api': ^1.9.0 + '@opentelemetry/context-async-hooks': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/core': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 || ^2.2.0 + '@opentelemetry/semantic-conventions': ^1.37.0 + + '@shikijs/core@1.29.2': + resolution: {integrity: sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==} + + '@shikijs/core@3.21.0': + resolution: {integrity: sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==} + + '@shikijs/engine-javascript@1.29.2': + resolution: {integrity: sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==} + + '@shikijs/engine-javascript@3.21.0': + resolution: {integrity: sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==} + + '@shikijs/engine-oniguruma@1.29.2': + resolution: {integrity: sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==} + + '@shikijs/engine-oniguruma@3.21.0': + resolution: {integrity: sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==} + + '@shikijs/langs@1.29.2': + resolution: {integrity: sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==} + + '@shikijs/langs@3.21.0': + resolution: {integrity: sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==} + + '@shikijs/themes@1.29.2': + resolution: {integrity: sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==} + + '@shikijs/themes@3.21.0': + resolution: {integrity: sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==} + + '@shikijs/types@1.29.2': + resolution: {integrity: sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==} + + '@shikijs/types@3.21.0': + resolution: {integrity: sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@szmarczak/http-timer@4.0.6': + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + + '@tailwindcss/typography@0.5.19': + resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + + '@tanstack/query-core@5.90.20': + resolution: {integrity: sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==} + + '@tanstack/react-query@5.90.20': + resolution: {integrity: sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-virtual@3.13.18': + resolution: {integrity: sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/virtual-core@3.13.18': + resolution: {integrity: sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==} + + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} + + '@trpc/client@11.8.1': + resolution: {integrity: sha512-L/SJFGanr9xGABmuDoeXR4xAdHJmsXsiF9OuH+apecJ+8sUITzVT1EPeqp0ebqA6lBhEl5pPfg3rngVhi/h60Q==} + peerDependencies: + '@trpc/server': 11.8.1 + typescript: '>=5.7.2' + + '@trpc/react-query@11.8.1': + resolution: {integrity: sha512-0Vu55ld/oINb4U6nIPPi7eZMhxUop6K+4QUK90RVsfSD5r+957sM80M4c8bjh/JBZUxMFv9JOhxxlWcrgHxHow==} + peerDependencies: + '@tanstack/react-query': ^5.80.3 + '@trpc/client': 11.8.1 + '@trpc/server': 11.8.1 + react: '>=18.2.0' + react-dom: '>=18.2.0' + typescript: '>=5.7.2' + + '@trpc/server@11.8.1': + resolution: {integrity: sha512-P4rzZRpEL7zDFgjxK65IdyH0e41FMFfTkQkuq0BA5tKcr7E6v9/v38DEklCpoDN6sPiB1Sigy/PUEzHENhswDA==} + peerDependencies: + typescript: '>=5.7.2' + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/better-sqlite3@7.6.13': + resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} + + '@types/cacheable-request@6.0.3': + resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/fs-extra@9.0.13': + resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/http-cache-semantics@4.0.4': + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} + + '@types/keyv@3.1.4': + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/mysql@2.15.27': + resolution: {integrity: sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA==} + + '@types/node@20.19.30': + resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==} + + '@types/pg-pool@2.0.6': + resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} + + '@types/pg@8.15.6': + resolution: {integrity: sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==} + + '@types/plist@3.0.5': + resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} + + '@types/prismjs@1.26.5': + resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react-syntax-highlighter@15.5.13': + resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} + + '@types/react@19.2.9': + resolution: {integrity: sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==} + + '@types/responselike@1.0.3': + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + + '@types/tedious@4.0.14': + resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/verror@1.10.11': + resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vercel/oidc@3.1.0': + resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==} + engines: {node: '>= 20'} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vue/reactivity@3.5.27': + resolution: {integrity: sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==} + + '@vue/shared@3.5.27': + resolution: {integrity: sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==} + + '@welldone-software/why-did-you-render@10.0.1': + resolution: {integrity: sha512-tMgGkt30iVYeLMUKExNmtm019QgyjLtA7lwB0QAizYNEuihlCG2eoAWBBaz/bDeI7LeqAJ9msC6hY3vX+JB97g==} + peerDependencies: + react: ^19 + + '@xmldom/xmldom@0.8.11': + resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} + engines: {node: '>=10.0.0'} + + '@xterm/addon-canvas@0.7.0': + resolution: {integrity: sha512-LF5LYcfvefJuJ7QotNRdRSPc9YASAVDeoT5uyXS/nZshZXjYplGXRECBGiznwvhNL2I8bq1Lf5MzRwstsYQ2Iw==} + peerDependencies: + '@xterm/xterm': ^5.0.0 + + '@xterm/addon-fit@0.11.0': + resolution: {integrity: sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==} + + '@xterm/addon-search@0.16.0': + resolution: {integrity: sha512-9OeuBFu0/uZJPu+9AHKY6g/w0Czyb/Ut0A5t79I4ULoU4IfU5BEpPFVGQxP4zTTMdfZEYkVIRYbHBX1xWwjeSA==} + + '@xterm/addon-serialize@0.14.0': + resolution: {integrity: sha512-uteyTU1EkrQa2Ux6P/uFl2fzmXI46jy5uoQMKEOM0fKTyiW7cSn0WrFenHm5vO5uEXX/GpwW/FgILvv3r0WbkA==} + + '@xterm/addon-web-links@0.12.0': + resolution: {integrity: sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw==} + + '@xterm/addon-webgl@0.19.0': + resolution: {integrity: sha512-b3fMOsyLVuCeNJWxolACEUED0vm7qC0cy4wRvf3oURSzDTYVQiGPhTnhWZwIHdvC48Y+oLhvYXnY4XDXPoJo6A==} + + '@xterm/xterm@5.5.0': + resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==} + + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ai@6.0.49: + resolution: {integrity: sha512-LABniBX/0R6Tv+iUK5keUZhZLaZUe4YjP5M2rZ4wAdZ8iKV3EfTAoJxuL1aaWTSJKIilKa9QUEkCgnp89/32bw==} + engines: {node: '>=18'} + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + app-builder-bin@5.0.0-alpha.10: + resolution: {integrity: sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==} + + app-builder-lib@25.1.8: + resolution: {integrity: sha512-pCqe7dfsQFBABC1jeKZXQWhGcCPF3rPCXDdfqVKjIeWBcXzyC1iOWZdfFhGl+S9MyE/k//DFmC6FzuGAUudNDg==} + engines: {node: '>=14.0.0'} + peerDependencies: + dmg-builder: 25.1.8 + electron-builder-squirrel-windows: 25.1.8 + + aproba@2.1.0: + resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} + + archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + + archiver-utils@3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} + engines: {node: '>= 10'} + + archiver@5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} + engines: {node: '>= 10'} + + are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + async-exit-hook@2.0.1: + resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} + engines: {node: '>=0.12.0'} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + autoprefixer@10.4.23: + resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.9.18: + resolution: {integrity: sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==} + hasBin: true + + better-sqlite3@11.10.0: + resolution: {integrity: sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bluebird-lst@1.0.9: + resolution: {integrity: sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==} + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + builder-util-runtime@9.2.10: + resolution: {integrity: sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==} + engines: {node: '>=12.0.0'} + + builder-util-runtime@9.5.1: + resolution: {integrity: sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==} + engines: {node: '>=12.0.0'} + + builder-util@25.1.7: + resolution: {integrity: sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + cacache@16.1.3: + resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + cacheable-lookup@5.0.4: + resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} + engines: {node: '>=10.6.0'} + + cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001766: + resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chevrotain-allstar@0.3.1: + resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} + peerDependencies: + chevrotain: ^11.0.0 + + chevrotain@11.0.3: + resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + chromium-pickle-js@0.2.0: + resolution: {integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-truncate@2.1.0: + resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} + engines: {node: '>=8'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + compare-version@0.1.2: + resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} + engines: {node: '>=0.10.0'} + + compress-commons@4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} + engines: {node: '>= 10'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + config-file-ts@0.2.8-rc1: + resolution: {integrity: sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==} + + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + + content-disposition@1.0.1: + resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + + core-js@3.48.0: + resolution: {integrity: sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==} + + core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} + engines: {node: '>= 10'} + + crc@3.8.0: + resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.1: + resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.13: + resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + + date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dir-compare@4.2.0: + resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dmg-builder@25.1.8: + resolution: {integrity: sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==} + + dmg-license@1.0.11: + resolution: {integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==} + engines: {node: '>=8'} + os: [darwin] + hasBin: true + + dompurify@3.3.1: + resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + + dotenv-expand@11.0.7: + resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} + engines: {node: '>=12'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + drizzle-kit@0.31.8: + resolution: {integrity: sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==} + hasBin: true + + drizzle-orm@0.45.1: + resolution: {integrity: sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA==} + peerDependencies: + '@aws-sdk/client-rds-data': '>=3' + '@cloudflare/workers-types': '>=4' + '@electric-sql/pglite': '>=0.2.0' + '@libsql/client': '>=0.10.0' + '@libsql/client-wasm': '>=0.10.0' + '@neondatabase/serverless': '>=0.10.0' + '@op-engineering/op-sqlite': '>=2' + '@opentelemetry/api': ^1.4.1 + '@planetscale/database': '>=1.13' + '@prisma/client': '*' + '@tidbcloud/serverless': '*' + '@types/better-sqlite3': '*' + '@types/pg': '*' + '@types/sql.js': '*' + '@upstash/redis': '>=1.34.7' + '@vercel/postgres': '>=0.8.0' + '@xata.io/client': '*' + better-sqlite3: '>=7' + bun-types: '*' + expo-sqlite: '>=14.0.0' + gel: '>=2' + knex: '*' + kysely: '*' + mysql2: '>=2' + pg: '>=8' + postgres: '>=3' + prisma: '*' + sql.js: '>=1' + sqlite3: '>=5' + peerDependenciesMeta: + '@aws-sdk/client-rds-data': + optional: true + '@cloudflare/workers-types': + optional: true + '@electric-sql/pglite': + optional: true + '@libsql/client': + optional: true + '@libsql/client-wasm': + optional: true + '@neondatabase/serverless': + optional: true + '@op-engineering/op-sqlite': + optional: true + '@opentelemetry/api': + optional: true + '@planetscale/database': + optional: true + '@prisma/client': + optional: true + '@tidbcloud/serverless': + optional: true + '@types/better-sqlite3': + optional: true + '@types/pg': + optional: true + '@types/sql.js': + optional: true + '@upstash/redis': + optional: true + '@vercel/postgres': + optional: true + '@xata.io/client': + optional: true + better-sqlite3: + optional: true + bun-types: + optional: true + expo-sqlite: + optional: true + gel: + optional: true + knex: + optional: true + kysely: + optional: true + mysql2: + optional: true + pg: + optional: true + postgres: + optional: true + prisma: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + electron-builder-squirrel-windows@25.1.8: + resolution: {integrity: sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==} + + electron-builder@25.1.8: + resolution: {integrity: sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==} + engines: {node: '>=14.0.0'} + hasBin: true + + electron-log@5.4.3: + resolution: {integrity: sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ==} + engines: {node: '>= 14'} + + electron-publish@25.1.7: + resolution: {integrity: sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==} + + electron-rebuild@3.2.9: + resolution: {integrity: sha512-FkEZNFViUem3P0RLYbZkUjC8LUFIK+wKq09GHoOITSJjfDAVQv964hwaNseTTWt58sITQX3/5fHNYcTefqaCWw==} + engines: {node: '>=12.13.0'} + deprecated: Please use @electron/rebuild moving forward. There is no API change, just a package name change + hasBin: true + + electron-to-chromium@1.5.278: + resolution: {integrity: sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==} + + electron-updater@6.7.3: + resolution: {integrity: sha512-EgkT8Z9noqXKbwc3u5FkJA+r48jwZ5DTUiOkJMOTEEH//n5Am6wfQGz7nvSFEA2oIAMv9jRzn5JKTyWeSKOPgg==} + + electron-vite@3.1.0: + resolution: {integrity: sha512-M7aAzaRvSl5VO+6KN4neJCYLHLpF/iWo5ztchI/+wMxIieDZQqpbCYfaEHHHPH6eupEzfvZdLYdPdmvGqoVe0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@swc/core': ^1.0.0 + vite: ^4.0.0 || ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + '@swc/core': + optional: true + + electron@33.4.5: + resolution: {integrity: sha512-rbDc4QOqfMT1uopUG+KcaMKzKgFAXAzN3wNIdgErnB1tUnpgTxwFv1BDN/exCl1vaVWBeM9YtbO5PgbGZeq7xw==} + engines: {node: '>= 12.20.55'} + hasBin: true + + emoji-regex-xs@1.0.0: + resolution: {integrity: sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + + esbuild-register@3.6.0: + resolution: {integrity: sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==} + peerDependencies: + esbuild: '>=0.12 <1' + + esbuild@0.18.20: + resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.0.6: + resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + + express-rate-limit@7.5.1: + resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + extsprintf@1.4.1: + resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==} + engines: {'0': node >=0.6.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.4.8: + resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + + forwarded-parse@2.1.2: + resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + framer-motion@11.18.2: + resolution: {integrity: sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@11.3.3: + resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} + engines: {node: '>=14.14'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + + global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + got@11.8.6: + resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} + engines: {node: '>=10.19.0'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-sanitize@5.0.2: + resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + highlight.js@11.11.1: + resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} + engines: {node: '>=12.0.0'} + + highlightjs-vue@1.0.0: + resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} + + hono@4.11.5: + resolution: {integrity: sha512-WemPi9/WfyMwZs+ZUXdiwcCh9Y+m7L+8vki9MzDw3jJ+W9Lc+12HGsd368Qc1vZi1xwW8BWMMsnK5efYKPdt4g==} + engines: {node: '>=16.9.0'} + + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + http2-wrapper@1.0.3: + resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + engines: {node: '>=10.19.0'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + iconv-corefoundation@1.1.7: + resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==} + engines: {node: ^8.11.2 || >=10} + os: [darwin] + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + import-in-the-middle@2.0.5: + resolution: {integrity: sha512-0InH9/4oDCBRzWXhpOqusspLBrVfK1vPvbn9Wxl8DAQ8yyx5fWJRETICSwkiAMaYntjJAMBP1R4B6cQnEUYVEA==} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-ci@3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isbinaryfile@4.0.10: + resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} + engines: {node: '>= 8.0.0'} + + isbinaryfile@5.0.7: + resolution: {integrity: sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==} + engines: {node: '>= 18.0.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + jose@6.1.3: + resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==} + + jotai@2.16.2: + resolution: {integrity: sha512-DH0lBiTXvewsxtqqwjDW6Hg9JPTDnq9LcOsXSFWCAUEt+qj5ohl9iRVX9zQXPPHKLXCdH+5mGvM28fsXMl17/g==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@babel/core': '>=7.0.0' + '@babel/template': '>=7.0.0' + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@babel/core': + optional: true + '@babel/template': + optional: true + '@types/react': + optional: true + react: + optional: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + json-schema@0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + katex@0.16.28: + resolution: {integrity: sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg==} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + langium@3.3.1: + resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} + engines: {node: '>=16.0.0'} + + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + + lazy-val@1.0.5: + resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash-es@4.17.23: + resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} + + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + + lowlight@1.20.0: + resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + + lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + lucide-react@0.468.0: + resolution: {integrity: sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc + + lzma-native@8.0.6: + resolution: {integrity: sha512-09xfg67mkL2Lz20PrrDeNYZxzeW7ADtpYFbwSQh9U8+76RIzx5QsJBMy8qikv3hbUPfpy6hqwxt6FcGK81g9AA==} + engines: {node: '>=10.0.0'} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + make-fetch-happen@10.2.1: + resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + + marked@17.0.1: + resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==} + engines: {node: '>= 20'} + hasBin: true + + matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-newline-to-break@2.0.0: + resolution: {integrity: sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + mermaid@11.12.2: + resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@10.1.1: + resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + + minipass-fetch@2.1.2: + resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + + motion-dom@11.18.1: + resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==} + + motion-utils@11.18.1: + resolution: {integrity: sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==} + + motion@11.18.2: + resolution: {integrity: sha512-JLjvFDuFr42NFtcVoMAyC2sEjnpA8xpy6qWPyzQvCloznAyQ8FIXioxWfHiLtgYhoVpfUqSWpn1h9++skj9+Wg==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + + node-abi@3.87.0: + resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==} + engines: {node: '>=10'} + + node-addon-api@1.7.2: + resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} + + node-addon-api@3.2.1: + resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + + node-api-version@0.1.4: + resolution: {integrity: sha512-KGXihXdUChwJAOHO53bv9/vXcLmdUsZ6jIptbvYvkpKfth+r7jw44JkVxQFA3kX5nQjzjmGu1uAu/xNNLNlI5g==} + + node-api-version@0.2.1: + resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==} + + node-gyp-build@4.8.4: + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} + hasBin: true + + node-gyp@9.4.1: + resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==} + engines: {node: ^12.13 || ^14.13 || >=16} + hasBin: true + + node-pty@1.1.0: + resolution: {integrity: sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + nopt@6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@2.3.0: + resolution: {integrity: sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==} + + oniguruma-to-es@4.3.4: + resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@8.3.0: + resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pe-library@0.4.1: + resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==} + engines: {node: '>=12', npm: '>=6'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-protocol@1.11.0: + resolution: {integrity: sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + plist@3.1.0: + resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} + engines: {node: '>=10.4.0'} + + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.1: + resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + posthog-js@1.335.2: + resolution: {integrity: sha512-xiPh9eXqNiNiFZjVe+HMcuEeqhbMJuL+bOVUM6ywGAfxUe71av71q6hK/zlzIiPNsPxhV6PL08LC6yPooStQxA==} + + posthog-node@5.24.2: + resolution: {integrity: sha512-cywIUYtSIC9BilgLlZd1R2xNk6omKL6tywG/SCPmUJKeG2jhjvJHSrHXYx4x3uQsUjn8aB9UVI8km+W326Zm8g==} + engines: {node: ^20.20.0 || >=22.22.0} + + preact@10.28.2: + resolution: {integrity: sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qs@6.14.1: + resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} + engines: {node: '>=0.6'} + + query-selector-shadow-dom@1.0.1: + resolution: {integrity: sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + react-dom@19.2.1: + resolution: {integrity: sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==} + peerDependencies: + react: ^19.2.1 + + react-hotkeys-hook@4.6.2: + resolution: {integrity: sha512-FmP+ZriY3EG59Ug/lxNfrObCnW9xQShgk7Nb83+CkpfkcCpfS95ydv+E9JuXA5cp8KtskU7LGlIARpkc92X22Q==} + peerDependencies: + react: '>=16.8.1' + react-dom: '>=16.8.1' + + react-icons@5.5.0: + resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} + peerDependencies: + react: '*' + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-syntax-highlighter@16.1.0: + resolution: {integrity: sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==} + engines: {node: '>= 16.20.2'} + peerDependencies: + react: '>= 0.14.0' + + react-zoom-pan-pinch@3.7.0: + resolution: {integrity: sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA==} + engines: {node: '>=8', npm: '>=5'} + peerDependencies: + react: '*' + react-dom: '*' + + react@19.2.1: + resolution: {integrity: sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==} + engines: {node: '>=0.10.0'} + + reactivity-store@0.3.12: + resolution: {integrity: sha512-Idz9EL4dFUtQbHySZQzckWOTUfqjdYpUtNW0iOysC32mG7IjiUGB77QrsyR5eAWBkRiS9JscF6A3fuQAIy+LrQ==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + read-binary-file-arch@1.0.6: + resolution: {integrity: sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==} + hasBin: true + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + refractor@5.0.0: + resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} + + regex-recursion@5.1.1: + resolution: {integrity: sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@5.1.1: + resolution: {integrity: sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + rehype-harden@1.1.7: + resolution: {integrity: sha512-j5DY0YSK2YavvNGV+qBHma15J9m0WZmRe8posT5AtKDS6TNWtMVTo6RiqF8SidfcASYz8f3k2J/1RWmq5zTXUw==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-sanitize@6.0.0: + resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} + + remark-breaks@4.0.0: + resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + remend@1.1.0: + resolution: {integrity: sha512-JENGyuIhTwzUfCarW43X4r9cehoqTo9QyYxfNDZSud2AmqeuWjZ5pfybasTa4q0dxTJAj5m8NB+wR+YueAFpxQ==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + require-in-the-middle@8.0.1: + resolution: {integrity: sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ==} + engines: {node: '>=9.3.0 || >=8.10.0 <9.0.0'} + + resedit@1.7.2: + resolution: {integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==} + engines: {node: '>=12', npm: '>=6'} + + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + + rollup@4.56.0: + resolution: {integrity: sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sanitize-filename@1.6.3: + resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==} + + sax@1.4.4: + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} + engines: {node: '>=11.0.0'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shiki@1.29.2: + resolution: {integrity: sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==} + + shiki@3.21.0: + resolution: {integrity: sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + simple-git@3.30.0: + resolution: {integrity: sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg==} + + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + + slice-ansi@3.0.0: + resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + engines: {node: '>=8'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@7.0.0: + resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} + engines: {node: '>= 10'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + sonner@1.7.4: + resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + ssri@9.0.1: + resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + stat-mode@1.0.0: + resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==} + engines: {node: '>= 6'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + streamdown@2.1.0: + resolution: {integrity: sha512-u9gWd0AmjKg1d+74P44XaPlGrMeC21oDOSIhjGNEYMAttDMzCzlJO6lpTyJ9JkSinQQF65YcK4eOd3q9iTvULw==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + sumchecker@3.0.1: + resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} + engines: {node: '>= 8.0'} + + superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + swr@2.3.8: + resolution: {integrity: sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + tailwind-merge@2.6.0: + resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==} + + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me + + temp-file@3.4.0: + resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + throttleit@2.1.0: + resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} + engines: {node: '>=18'} + + tiny-typed-emitter@2.1.0: + resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tmp-promise@3.0.3: + resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} + + tmp@0.2.5: + resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} + engines: {node: '>=14.14'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + trpc-electron@0.1.2: + resolution: {integrity: sha512-sQpWBwQWzsgrERugjzUpPqY/+/n8NxkUq6YssQ5+5rALkvGCWq45T5Dreiwm2kh91dZMFlALTyMd8PhB0vgbIg==} + peerDependencies: + '@trpc/client': '>=11.0.0' + '@trpc/server': '>=11.0.0' + electron: '>19.0.0' + + truncate-utf8-bytes@1.0.2: + resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} + + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + + type-is@2.0.1: + resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} + engines: {node: '>= 0.6'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unique-filename@2.0.1: + resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + unique-names-generator@4.7.1: + resolution: {integrity: sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==} + engines: {node: '>=8'} + + unique-slug@3.0.0: + resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + utf8-byte-length@1.0.5: + resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + verror@1.10.1: + resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} + engines: {node: '>=0.6.0'} + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + web-vitals@5.1.0: + resolution: {integrity: sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + xterm@5.3.0: + resolution: {integrity: sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg==} + deprecated: This package is now deprecated. Move to @xterm/xterm instead. + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zip-stream@4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} + engines: {node: '>= 10'} + + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zustand@5.0.10: + resolution: {integrity: sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + 7zip-bin@5.2.0: {} + + '@ai-sdk/gateway@3.0.22(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 3.0.5 + '@ai-sdk/provider-utils': 4.0.9(zod@3.25.76) + '@vercel/oidc': 3.1.0 + zod: 3.25.76 + + '@ai-sdk/provider-utils@4.0.9(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 3.0.5 + '@standard-schema/spec': 1.1.0 + eventsource-parser: 3.0.6 + zod: 3.25.76 + + '@ai-sdk/provider@3.0.5': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@3.0.51(react@19.2.1)(zod@3.25.76)': + dependencies: + '@ai-sdk/provider-utils': 4.0.9(zod@3.25.76) + ai: 6.0.49(zod@3.25.76) + react: 19.2.1 + swr: 2.3.8(react@19.2.1) + throttleit: 2.1.0 + transitivePeerDependencies: + - zod + + '@alloc/quick-lru@5.2.0': {} + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.0.2 + + '@anthropic-ai/claude-agent-sdk@0.2.19(zod@3.25.76)': + dependencies: + zod: 3.25.76 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + + '@apm-js-collab/code-transformer@0.8.2': {} + + '@apm-js-collab/tracing-hooks@0.3.1': + dependencies: + '@apm-js-collab/code-transformer': 0.8.2 + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + + '@babel/code-frame@7.28.6': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.6': {} + + '@babel/core@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.6': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/runtime@7.28.6': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/traverse@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@braintree/sanitize-url@7.1.1': {} + + '@chevrotain/cst-dts-gen@11.0.3': + dependencies: + '@chevrotain/gast': 11.0.3 + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/gast@11.0.3': + dependencies: + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/regexp-to-ast@11.0.3': {} + + '@chevrotain/types@11.0.3': {} + + '@chevrotain/utils@11.0.3': {} + + '@develar/schema-utils@2.6.5': + dependencies: + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + + '@drizzle-team/brocli@0.10.2': {} + + '@electron-toolkit/preload@3.0.2(electron@33.4.5)': + dependencies: + electron: 33.4.5 + + '@electron-toolkit/utils@4.0.0(electron@33.4.5)': + dependencies: + electron: 33.4.5 + + '@electron/asar@3.4.1': + dependencies: + commander: 5.1.0 + glob: 7.2.3 + minimatch: 3.1.2 + + '@electron/get@2.0.3': + dependencies: + debug: 4.4.3 + env-paths: 2.2.1 + fs-extra: 8.1.0 + got: 11.8.6 + progress: 2.0.3 + semver: 6.3.1 + sumchecker: 3.0.1 + optionalDependencies: + global-agent: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@electron/notarize@2.5.0': + dependencies: + debug: 4.4.3 + fs-extra: 9.1.0 + promise-retry: 2.0.1 + transitivePeerDependencies: + - supports-color + + '@electron/osx-sign@1.3.1': + dependencies: + compare-version: 0.1.2 + debug: 4.4.3 + fs-extra: 10.1.0 + isbinaryfile: 4.0.10 + minimist: 1.2.8 + plist: 3.1.0 + transitivePeerDependencies: + - supports-color + + '@electron/rebuild@3.6.1': + dependencies: + '@malept/cross-spawn-promise': 2.0.0 + chalk: 4.1.2 + debug: 4.4.3 + detect-libc: 2.1.2 + fs-extra: 10.1.0 + got: 11.8.6 + node-abi: 3.87.0 + node-api-version: 0.2.1 + node-gyp: 9.4.1 + ora: 5.4.1 + read-binary-file-arch: 1.0.6 + semver: 7.7.3 + tar: 6.2.1 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - supports-color + + '@electron/universal@2.0.1': + dependencies: + '@electron/asar': 3.4.1 + '@malept/cross-spawn-promise': 2.0.0 + debug: 4.4.3 + dir-compare: 4.2.0 + fs-extra: 11.3.3 + minimatch: 9.0.5 + plist: 3.1.0 + transitivePeerDependencies: + - supports-color + + '@esbuild-kit/core-utils@3.3.2': + dependencies: + esbuild: 0.18.20 + source-map-support: 0.5.21 + + '@esbuild-kit/esm-loader@2.6.5': + dependencies: + '@esbuild-kit/core-utils': 3.3.2 + get-tsconfig: 4.13.0 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.18.20': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.18.20': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.18.20': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.18.20': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.18.20': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.18.20': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.18.20': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.18.20': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.18.20': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.18.20': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.18.20': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.18.20': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.18.20': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.18.20': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.18.20': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.18.20': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.18.20': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.18.20': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.18.20': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.18.20': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.18.20': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.18.20': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@floating-ui/dom': 1.7.4 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + + '@floating-ui/utils@0.2.10': {} + + '@gar/promisify@1.1.3': {} + + '@git-diff-view/core@0.0.35': + dependencies: + '@git-diff-view/lowlight': 0.0.35 + fast-diff: 1.3.0 + highlight.js: 11.11.1 + lowlight: 3.3.0 + + '@git-diff-view/lowlight@0.0.35': + dependencies: + '@types/hast': 3.0.4 + highlight.js: 11.11.1 + lowlight: 3.3.0 + + '@git-diff-view/react@0.0.35(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@git-diff-view/core': 0.0.35 + '@types/hast': 3.0.4 + fast-diff: 1.3.0 + highlight.js: 11.11.1 + lowlight: 3.3.0 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + reactivity-store: 0.3.12(react@19.2.1) + use-sync-external-store: 1.6.0(react@19.2.1) + + '@git-diff-view/shiki@0.0.36': + dependencies: + '@types/hast': 3.0.4 + shiki: 3.21.0 + + '@hono/node-server@1.19.9(hono@4.11.5)': + dependencies: + hono: 4.11.5 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.0': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + mlly: 1.8.0 + + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@kwsites/file-exists@1.1.1': + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@kwsites/promise-deferred@1.1.1': {} + + '@malept/cross-spawn-promise@2.0.0': + dependencies: + cross-spawn: 7.0.6 + + '@malept/flatpak-bundler@0.4.0': + dependencies: + debug: 4.4.3 + fs-extra: 9.1.0 + lodash: 4.17.23 + tmp-promise: 3.0.3 + transitivePeerDependencies: + - supports-color + + '@mermaid-js/parser@0.6.3': + dependencies: + langium: 3.3.1 + + '@modelcontextprotocol/sdk@1.25.3(hono@4.11.5)(zod@3.25.76)': + dependencies: + '@hono/node-server': 1.19.9(hono@4.11.5) + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.0.6 + express: 5.2.1 + express-rate-limit: 7.5.1(express@5.2.1) + jose: 6.1.3 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 3.25.76 + zod-to-json-schema: 3.25.1(zod@3.25.76) + transitivePeerDependencies: + - hono + - supports-color + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@npmcli/fs@2.1.2': + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.7.3 + + '@npmcli/move-file@2.0.1': + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + + '@opentelemetry/api-logs@0.208.0': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/exporter-logs-otlp-http@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/instrumentation-amqplib@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-connect@0.52.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@types/connect': 3.4.38 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-dataloader@0.26.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-express@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-fs@0.28.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-generic-pool@0.52.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-graphql@0.56.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-hapi@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-http@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + forwarded-parse: 2.1.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-ioredis@0.56.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-kafkajs@0.18.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-knex@0.53.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-koa@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-lru-memoizer@0.53.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongodb@0.61.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongoose@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql2@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mysql@0.54.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@types/mysql': 2.15.27 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-pg@0.61.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@opentelemetry/sql-common': 0.41.2(@opentelemetry/api@1.9.0) + '@types/pg': 8.15.6 + '@types/pg-pool': 2.0.6 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-redis@0.57.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/redis-common': 0.38.2 + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-tedious@0.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@types/tedious': 4.0.14 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-undici@0.19.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + import-in-the-middle: 2.0.5 + require-in-the-middle: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.208.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.2.0(@opentelemetry/api@1.9.0) + protobufjs: 7.5.4 + + '@opentelemetry/redis-common@0.38.2': {} + + '@opentelemetry/resources@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/resources@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/sdk-logs@0.208.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-trace-base@2.2.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.2.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + + '@opentelemetry/semantic-conventions@1.39.0': {} + + '@opentelemetry/sql-common@0.41.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@posthog/core@1.14.0': + dependencies: + cross-spawn: 7.0.6 + + '@posthog/types@1.335.2': {} + + '@prisma/instrumentation@6.19.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-alert-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.9)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-context-menu@2.2.16(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-context@1.1.2(@types/react@19.2.9)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-context@1.1.3(@types/react@19.2.9)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + aria-hidden: 1.2.6 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.9)(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-direction@1.1.1(@types/react@19.2.9)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.9)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-icons@1.3.2(react@19.2.1)': + dependencies: + react: 19.2.1 + + '@radix-ui/react-id@1.1.1(@types/react@19.2.9)(react@19.2.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-label@2.1.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.9)(react@19.2.1) + aria-hidden: 1.2.6 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.9)(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + aria-hidden: 1.2.6 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.9)(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/rect': 1.1.1 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-progress@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-context': 1.1.3(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + aria-hidden: 1.2.6 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + react-remove-scroll: 2.7.2(@types/react@19.2.9)(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-slot@1.2.3(@types/react@19.2.9)(react@19.2.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-slot@1.2.4(@types/react@19.2.9)(react@19.2.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.9)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.9)(react@19.2.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.9)(react@19.2.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.9)(react@19.2.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.9)(react@19.2.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.9)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.9)(react@19.2.1)': + dependencies: + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.9)(react@19.2.1)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.9)(react@19.2.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.9)(react@19.2.1) + react: 19.2.1 + optionalDependencies: + '@types/react': 19.2.9 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + + '@radix-ui/rect@1.1.1': {} + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.56.0': + optional: true + + '@rollup/rollup-android-arm64@4.56.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.56.0': + optional: true + + '@rollup/rollup-darwin-x64@4.56.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.56.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.56.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.56.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.56.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.56.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.56.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.56.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.56.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.56.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.56.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.56.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.56.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.56.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.56.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.56.0': + optional: true + + '@sentry-internal/browser-utils@10.34.0': + dependencies: + '@sentry/core': 10.34.0 + + '@sentry-internal/feedback@10.34.0': + dependencies: + '@sentry/core': 10.34.0 + + '@sentry-internal/replay-canvas@10.34.0': + dependencies: + '@sentry-internal/replay': 10.34.0 + '@sentry/core': 10.34.0 + + '@sentry-internal/replay@10.34.0': + dependencies: + '@sentry-internal/browser-utils': 10.34.0 + '@sentry/core': 10.34.0 + + '@sentry/browser@10.34.0': + dependencies: + '@sentry-internal/browser-utils': 10.34.0 + '@sentry-internal/feedback': 10.34.0 + '@sentry-internal/replay': 10.34.0 + '@sentry-internal/replay-canvas': 10.34.0 + '@sentry/core': 10.34.0 + + '@sentry/core@10.34.0': {} + + '@sentry/electron@7.6.0': + dependencies: + '@sentry/browser': 10.34.0 + '@sentry/core': 10.34.0 + '@sentry/node': 10.34.0 + transitivePeerDependencies: + - supports-color + + '@sentry/node-core@10.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': + dependencies: + '@apm-js-collab/tracing-hooks': 0.3.1 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@sentry/core': 10.34.0 + '@sentry/opentelemetry': 10.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) + import-in-the-middle: 2.0.5 + transitivePeerDependencies: + - supports-color + + '@sentry/node@10.34.0': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-amqplib': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.52.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dataloader': 0.26.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fs': 0.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-generic-pool': 0.52.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-graphql': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-hapi': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-ioredis': 0.56.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-kafkajs': 0.18.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-knex': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-koa': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-lru-memoizer': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongodb': 0.61.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongoose': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql': 0.54.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql2': 0.55.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pg': 0.61.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-redis': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-tedious': 0.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-undici': 0.19.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@prisma/instrumentation': 6.19.0(@opentelemetry/api@1.9.0) + '@sentry/core': 10.34.0 + '@sentry/node-core': 10.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.208.0(@opentelemetry/api@1.9.0))(@opentelemetry/resources@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) + '@sentry/opentelemetry': 10.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0) + import-in-the-middle: 2.0.5 + minimatch: 9.0.5 + transitivePeerDependencies: + - supports-color + + '@sentry/opentelemetry@10.34.0(@opentelemetry/api@1.9.0)(@opentelemetry/context-async-hooks@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/core@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@2.5.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.39.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.39.0 + '@sentry/core': 10.34.0 + + '@shikijs/core@1.29.2': + dependencies: + '@shikijs/engine-javascript': 1.29.2 + '@shikijs/engine-oniguruma': 1.29.2 + '@shikijs/types': 1.29.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/core@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@1.29.2': + dependencies: + '@shikijs/types': 1.29.2 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 2.3.0 + + '@shikijs/engine-javascript@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.4 + + '@shikijs/engine-oniguruma@1.29.2': + dependencies: + '@shikijs/types': 1.29.2 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/engine-oniguruma@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@1.29.2': + dependencies: + '@shikijs/types': 1.29.2 + + '@shikijs/langs@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + + '@shikijs/themes@1.29.2': + dependencies: + '@shikijs/types': 1.29.2 + + '@shikijs/themes@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + + '@shikijs/types@1.29.2': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/types@3.21.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@sindresorhus/is@4.6.0': {} + + '@standard-schema/spec@1.1.0': {} + + '@szmarczak/http-timer@4.0.6': + dependencies: + defer-to-connect: 2.0.1 + + '@tailwindcss/typography@0.5.19(tailwindcss@3.4.19)': + dependencies: + postcss-selector-parser: 6.0.10 + tailwindcss: 3.4.19 + + '@tanstack/query-core@5.90.20': {} + + '@tanstack/react-query@5.90.20(react@19.2.1)': + dependencies: + '@tanstack/query-core': 5.90.20 + react: 19.2.1 + + '@tanstack/react-virtual@3.13.18(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@tanstack/virtual-core': 3.13.18 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + + '@tanstack/virtual-core@3.13.18': {} + + '@tootallnate/once@2.0.0': {} + + '@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3)': + dependencies: + '@trpc/server': 11.8.1(typescript@5.9.3) + typescript: 5.9.3 + + '@trpc/react-query@11.8.1(@tanstack/react-query@5.90.20(react@19.2.1))(@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.1(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3)': + dependencies: + '@tanstack/react-query': 5.90.20(react@19.2.1) + '@trpc/client': 11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3) + '@trpc/server': 11.8.1(typescript@5.9.3) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + typescript: 5.9.3 + + '@trpc/server@11.8.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/better-sqlite3@7.6.13': + dependencies: + '@types/node': 20.19.30 + + '@types/cacheable-request@6.0.3': + dependencies: + '@types/http-cache-semantics': 4.0.4 + '@types/keyv': 3.1.4 + '@types/node': 20.19.30 + '@types/responselike': 1.0.3 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.19.30 + + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + + '@types/estree@1.0.8': {} + + '@types/fs-extra@9.0.13': + dependencies: + '@types/node': 20.19.30 + + '@types/geojson@7946.0.16': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/http-cache-semantics@4.0.4': {} + + '@types/keyv@3.1.4': + dependencies: + '@types/node': 20.19.30 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/ms@2.1.0': {} + + '@types/mysql@2.15.27': + dependencies: + '@types/node': 20.19.30 + + '@types/node@20.19.30': + dependencies: + undici-types: 6.21.0 + + '@types/pg-pool@2.0.6': + dependencies: + '@types/pg': 8.15.6 + + '@types/pg@8.15.6': + dependencies: + '@types/node': 20.19.30 + pg-protocol: 1.11.0 + pg-types: 2.2.0 + + '@types/plist@3.0.5': + dependencies: + '@types/node': 20.19.30 + xmlbuilder: 15.1.1 + optional: true + + '@types/prismjs@1.26.5': {} + + '@types/react-dom@19.2.3(@types/react@19.2.9)': + dependencies: + '@types/react': 19.2.9 + + '@types/react-syntax-highlighter@15.5.13': + dependencies: + '@types/react': 19.2.9 + + '@types/react@19.2.9': + dependencies: + csstype: 3.2.3 + + '@types/responselike@1.0.3': + dependencies: + '@types/node': 20.19.30 + + '@types/tedious@4.0.14': + dependencies: + '@types/node': 20.19.30 + + '@types/trusted-types@2.0.7': + optional: true + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@types/verror@1.10.11': + optional: true + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 20.19.30 + optional: true + + '@ungap/structured-clone@1.3.0': {} + + '@vercel/oidc@3.1.0': {} + + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@20.19.30)(jiti@1.21.7))': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.1(@types/node@20.19.30)(jiti@1.21.7) + transitivePeerDependencies: + - supports-color + + '@vue/reactivity@3.5.27': + dependencies: + '@vue/shared': 3.5.27 + + '@vue/shared@3.5.27': {} + + '@welldone-software/why-did-you-render@10.0.1(react@19.2.1)': + dependencies: + lodash: 4.17.23 + react: 19.2.1 + + '@xmldom/xmldom@0.8.11': {} + + '@xterm/addon-canvas@0.7.0(@xterm/xterm@5.5.0)': + dependencies: + '@xterm/xterm': 5.5.0 + + '@xterm/addon-fit@0.11.0': {} + + '@xterm/addon-search@0.16.0': {} + + '@xterm/addon-serialize@0.14.0': {} + + '@xterm/addon-web-links@0.12.0': {} + + '@xterm/addon-webgl@0.19.0': {} + + '@xterm/xterm@5.5.0': {} + + abbrev@1.1.1: {} + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + agent-base@7.1.4: {} + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ai@6.0.49(zod@3.25.76): + dependencies: + '@ai-sdk/gateway': 3.0.22(zod@3.25.76) + '@ai-sdk/provider': 3.0.5 + '@ai-sdk/provider-utils': 4.0.9(zod@3.25.76) + '@opentelemetry/api': 1.9.0 + zod: 3.25.76 + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv-keywords@3.5.2(ajv@6.12.6): + dependencies: + ajv: 6.12.6 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + app-builder-bin@5.0.0-alpha.10: {} + + app-builder-lib@25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8): + dependencies: + '@develar/schema-utils': 2.6.5 + '@electron/notarize': 2.5.0 + '@electron/osx-sign': 1.3.1 + '@electron/rebuild': 3.6.1 + '@electron/universal': 2.0.1 + '@malept/flatpak-bundler': 0.4.0 + '@types/fs-extra': 9.0.13 + async-exit-hook: 2.0.1 + bluebird-lst: 1.0.9 + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + chromium-pickle-js: 0.2.0 + config-file-ts: 0.2.8-rc1 + debug: 4.4.3 + dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8) + dotenv: 16.6.1 + dotenv-expand: 11.0.7 + ejs: 3.1.10 + electron-builder-squirrel-windows: 25.1.8(dmg-builder@25.1.8) + electron-publish: 25.1.7 + form-data: 4.0.5 + fs-extra: 10.1.0 + hosted-git-info: 4.1.0 + is-ci: 3.0.1 + isbinaryfile: 5.0.7 + js-yaml: 4.1.1 + json5: 2.2.3 + lazy-val: 1.0.5 + minimatch: 10.1.1 + resedit: 1.7.2 + sanitize-filename: 1.6.3 + semver: 7.7.3 + tar: 6.2.1 + temp-file: 3.4.0 + transitivePeerDependencies: + - bluebird + - supports-color + + aproba@2.1.0: {} + + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + + are-we-there-yet@3.0.1: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + + arg@5.0.2: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + assert-plus@1.0.0: + optional: true + + astral-regex@2.0.0: + optional: true + + async-exit-hook@2.0.1: {} + + async@3.2.6: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + autoprefixer@10.4.23(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001766 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + bail@2.0.2: {} + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.9.18: {} + + better-sqlite3@11.10.0: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + binary-extensions@2.3.0: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bluebird-lst@1.0.9: + dependencies: + bluebird: 3.7.2 + + bluebird@3.7.2: {} + + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.14.1 + raw-body: 3.0.2 + type-is: 2.0.1 + transitivePeerDependencies: + - supports-color + + boolean@3.2.0: + optional: true + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.18 + caniuse-lite: 1.0.30001766 + electron-to-chromium: 1.5.278 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + buffer-crc32@0.2.13: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + builder-util-runtime@9.2.10: + dependencies: + debug: 4.4.3 + sax: 1.4.4 + transitivePeerDependencies: + - supports-color + + builder-util-runtime@9.5.1: + dependencies: + debug: 4.4.3 + sax: 1.4.4 + transitivePeerDependencies: + - supports-color + + builder-util@25.1.7: + dependencies: + 7zip-bin: 5.2.0 + '@types/debug': 4.1.12 + app-builder-bin: 5.0.0-alpha.10 + bluebird-lst: 1.0.9 + builder-util-runtime: 9.2.10 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + fs-extra: 10.1.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-ci: 3.0.1 + js-yaml: 4.1.1 + source-map-support: 0.5.21 + stat-mode: 1.0.0 + temp-file: 3.4.0 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + + cac@6.7.14: {} + + cacache@16.1.3: + dependencies: + '@npmcli/fs': 2.1.2 + '@npmcli/move-file': 2.0.1 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 8.1.0 + infer-owner: 1.0.4 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1 + rimraf: 3.0.2 + ssri: 9.0.1 + tar: 6.2.1 + unique-filename: 2.0.1 + transitivePeerDependencies: + - bluebird + + cacheable-lookup@5.0.4: {} + + cacheable-request@7.0.4: + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001766: {} + + ccount@2.0.1: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chevrotain-allstar@0.3.1(chevrotain@11.0.3): + dependencies: + chevrotain: 11.0.3 + lodash-es: 4.17.23 + + chevrotain@11.0.3: + dependencies: + '@chevrotain/cst-dts-gen': 11.0.3 + '@chevrotain/gast': 11.0.3 + '@chevrotain/regexp-to-ast': 11.0.3 + '@chevrotain/types': 11.0.3 + '@chevrotain/utils': 11.0.3 + lodash-es: 4.17.21 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + chownr@1.1.4: {} + + chownr@2.0.0: {} + + chromium-pickle-js@0.2.0: {} + + ci-info@3.9.0: {} + + cjs-module-lexer@2.2.0: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clean-stack@2.2.0: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-spinners@2.9.2: {} + + cli-truncate@2.1.0: + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + optional: true + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-response@1.0.3: + dependencies: + mimic-response: 1.0.1 + + clone@1.0.4: {} + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-support@1.1.3: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + comma-separated-tokens@2.0.3: {} + + commander@4.1.1: {} + + commander@5.1.0: {} + + commander@7.2.0: {} + + commander@8.3.0: {} + + compare-version@0.1.2: {} + + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + config-file-ts@0.2.8-rc1: + dependencies: + glob: 10.5.0 + typescript: 5.9.3 + + console-control-strings@1.1.0: {} + + content-disposition@1.0.1: {} + + content-type@1.0.5: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + + core-js@3.48.0: {} + + core-util-is@1.0.2: + optional: true + + core-util-is@1.0.3: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + + crc-32@1.2.2: {} + + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + + crc@3.8.0: + dependencies: + buffer: 5.7.1 + optional: true + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.2.3: {} + + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.1 + + cytoscape-fcose@2.2.0(cytoscape@3.33.1): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.33.1 + + cytoscape@3.33.1: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.13: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.23 + + date-fns@3.6.0: {} + + dayjs@1.11.19: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + defer-to-connect@2.0.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + optional: true + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + optional: true + + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + + delayed-stream@1.0.0: {} + + delegates@1.0.0: {} + + depd@2.0.0: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + detect-node@2.1.0: + optional: true + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + didyoumean@1.2.2: {} + + dir-compare@4.2.0: + dependencies: + minimatch: 3.1.2 + p-limit: 3.1.0 + + dlv@1.1.3: {} + + dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8): + dependencies: + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + fs-extra: 10.1.0 + iconv-lite: 0.6.3 + js-yaml: 4.1.1 + optionalDependencies: + dmg-license: 1.0.11 + transitivePeerDependencies: + - bluebird + - electron-builder-squirrel-windows + - supports-color + + dmg-license@1.0.11: + dependencies: + '@types/plist': 3.0.5 + '@types/verror': 1.10.11 + ajv: 6.12.6 + crc: 3.8.0 + iconv-corefoundation: 1.1.7 + plist: 3.1.0 + smart-buffer: 4.2.0 + verror: 1.10.1 + optional: true + + dompurify@3.3.1: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + dotenv-expand@11.0.7: + dependencies: + dotenv: 16.6.1 + + dotenv@16.6.1: {} + + drizzle-kit@0.31.8: + dependencies: + '@drizzle-team/brocli': 0.10.2 + '@esbuild-kit/esm-loader': 2.6.5 + esbuild: 0.25.12 + esbuild-register: 3.6.0(esbuild@0.25.12) + transitivePeerDependencies: + - supports-color + + drizzle-orm@0.45.1(@opentelemetry/api@1.9.0)(@types/better-sqlite3@7.6.13)(@types/pg@8.15.6)(better-sqlite3@11.10.0): + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/better-sqlite3': 7.6.13 + '@types/pg': 8.15.6 + better-sqlite3: 11.10.0 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + ejs@3.1.10: + dependencies: + jake: 10.9.4 + + electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8): + dependencies: + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + archiver: 5.3.2 + builder-util: 25.1.7 + fs-extra: 10.1.0 + transitivePeerDependencies: + - bluebird + - dmg-builder + - supports-color + + electron-builder@25.1.8(electron-builder-squirrel-windows@25.1.8): + dependencies: + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + chalk: 4.1.2 + dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8) + fs-extra: 10.1.0 + is-ci: 3.0.1 + lazy-val: 1.0.5 + simple-update-notifier: 2.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - electron-builder-squirrel-windows + - supports-color + + electron-log@5.4.3: {} + + electron-publish@25.1.7: + dependencies: + '@types/fs-extra': 9.0.13 + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + chalk: 4.1.2 + fs-extra: 10.1.0 + lazy-val: 1.0.5 + mime: 2.6.0 + transitivePeerDependencies: + - supports-color + + electron-rebuild@3.2.9: + dependencies: + '@malept/cross-spawn-promise': 2.0.0 + chalk: 4.1.2 + debug: 4.4.3 + detect-libc: 2.1.2 + fs-extra: 10.1.0 + got: 11.8.6 + lzma-native: 8.0.6 + node-abi: 3.87.0 + node-api-version: 0.1.4 + node-gyp: 9.4.1 + ora: 5.4.1 + semver: 7.7.3 + tar: 6.2.1 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - supports-color + + electron-to-chromium@1.5.278: {} + + electron-updater@6.7.3: + dependencies: + builder-util-runtime: 9.5.1 + fs-extra: 10.1.0 + js-yaml: 4.1.1 + lazy-val: 1.0.5 + lodash.escaperegexp: 4.1.2 + lodash.isequal: 4.5.0 + semver: 7.7.3 + tiny-typed-emitter: 2.1.0 + transitivePeerDependencies: + - supports-color + + electron-vite@3.1.0(vite@6.4.1(@types/node@20.19.30)(jiti@1.21.7)): + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.6) + cac: 6.7.14 + esbuild: 0.25.12 + magic-string: 0.30.21 + picocolors: 1.1.1 + vite: 6.4.1(@types/node@20.19.30)(jiti@1.21.7) + transitivePeerDependencies: + - supports-color + + electron@33.4.5: + dependencies: + '@electron/get': 2.0.3 + '@types/node': 20.19.30 + extract-zip: 2.0.1 + transitivePeerDependencies: + - supports-color + + emoji-regex-xs@1.0.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@2.0.0: {} + + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + entities@6.0.1: {} + + env-paths@2.2.1: {} + + err-code@2.0.3: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es6-error@4.1.1: + optional: true + + esbuild-register@3.6.0(esbuild@0.25.12): + dependencies: + debug: 4.4.3 + esbuild: 0.25.12 + transitivePeerDependencies: + - supports-color + + esbuild@0.18.20: + optionalDependencies: + '@esbuild/android-arm': 0.18.20 + '@esbuild/android-arm64': 0.18.20 + '@esbuild/android-x64': 0.18.20 + '@esbuild/darwin-arm64': 0.18.20 + '@esbuild/darwin-x64': 0.18.20 + '@esbuild/freebsd-arm64': 0.18.20 + '@esbuild/freebsd-x64': 0.18.20 + '@esbuild/linux-arm': 0.18.20 + '@esbuild/linux-arm64': 0.18.20 + '@esbuild/linux-ia32': 0.18.20 + '@esbuild/linux-loong64': 0.18.20 + '@esbuild/linux-mips64el': 0.18.20 + '@esbuild/linux-ppc64': 0.18.20 + '@esbuild/linux-riscv64': 0.18.20 + '@esbuild/linux-s390x': 0.18.20 + '@esbuild/linux-x64': 0.18.20 + '@esbuild/netbsd-x64': 0.18.20 + '@esbuild/openbsd-x64': 0.18.20 + '@esbuild/sunos-x64': 0.18.20 + '@esbuild/win32-arm64': 0.18.20 + '@esbuild/win32-ia32': 0.18.20 + '@esbuild/win32-x64': 0.18.20 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: + optional: true + + escape-string-regexp@5.0.0: {} + + esprima@4.0.1: {} + + estree-util-is-identifier-name@3.0.0: {} + + etag@1.8.1: {} + + eventsource-parser@3.0.6: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.0.6 + + expand-template@2.0.3: {} + + exponential-backoff@3.1.3: {} + + express-rate-limit@7.5.1(express@5.2.1): + dependencies: + express: 5.2.1 + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.2.2 + content-disposition: 1.0.1 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.1 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extend@3.0.2: {} + + extract-zip@2.0.1: + dependencies: + debug: 4.4.3 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + extsprintf@1.4.1: + optional: true + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-uri@3.1.0: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fault@1.0.4: + dependencies: + format: 0.2.2 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fflate@0.4.8: {} + + file-uri-to-path@1.0.0: {} + + filelist@1.0.4: + dependencies: + minimatch: 5.1.6 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + format@0.2.2: {} + + forwarded-parse@2.1.2: {} + + forwarded@0.2.0: {} + + fraction.js@5.3.4: {} + + framer-motion@11.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + dependencies: + motion-dom: 11.18.1 + motion-utils: 11.18.1 + tslib: 2.8.1 + optionalDependencies: + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + + fresh@2.0.0: {} + + fs-constants@1.0.0: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@11.3.3: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + gauge@4.0.4: + dependencies: + aproba: 2.1.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-nonce@1.0.1: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@5.2.0: + dependencies: + pump: 3.0.3 + + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-from-package@0.0.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + + global-agent@3.0.0: + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.7.3 + serialize-error: 7.0.1 + optional: true + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + optional: true + + gopd@1.2.0: {} + + got@11.8.6: + dependencies: + '@sindresorhus/is': 4.6.0 + '@szmarczak/http-timer': 4.0.6 + '@types/cacheable-request': 6.0.3 + '@types/responselike': 1.0.3 + cacheable-lookup: 5.0.4 + cacheable-request: 7.0.4 + decompress-response: 6.0.0 + http2-wrapper: 1.0.3 + lowercase-keys: 2.0.0 + p-cancelable: 2.1.1 + responselike: 2.0.1 + + graceful-fs@4.2.11: {} + + gray-matter@4.0.3: + dependencies: + js-yaml: 3.14.2 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 + + hachure-fill@0.5.2: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + optional: true + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + has-unicode@2.0.1: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-sanitize@5.0.2: + dependencies: + '@types/hast': 3.0.4 + '@ungap/structured-clone': 1.3.0 + unist-util-position: 5.0.0 + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + highlight.js@10.7.3: {} + + highlight.js@11.11.1: {} + + highlightjs-vue@1.0.0: {} + + hono@4.11.5: {} + + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + + html-url-attributes@3.0.1: {} + + html-void-elements@3.0.0: {} + + http-cache-semantics@4.2.0: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + http2-wrapper@1.0.3: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + iconv-corefoundation@1.1.7: + dependencies: + cli-truncate: 2.1.0 + node-addon-api: 1.7.2 + optional: true + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + import-in-the-middle@2.0.5: + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 2.2.0 + module-details-from-path: 1.0.4 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + infer-owner@1.0.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + ini@1.3.8: {} + + inline-style-parser@0.2.7: {} + + internmap@1.0.1: {} + + internmap@2.0.3: {} + + ip-address@10.1.0: {} + + ipaddr.js@1.9.1: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-ci@3.0.1: + dependencies: + ci-info: 3.9.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-decimal@2.0.1: {} + + is-extendable@0.1.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hexadecimal@2.0.1: {} + + is-interactive@1.0.0: {} + + is-lambda@1.0.1: {} + + is-number@7.0.0: {} + + is-plain-obj@4.1.0: {} + + is-promise@4.0.0: {} + + is-unicode-supported@0.1.0: {} + + is-what@5.5.0: {} + + isarray@1.0.0: {} + + isbinaryfile@4.0.10: {} + + isbinaryfile@5.0.7: {} + + isexe@2.0.0: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.4 + picocolors: 1.1.1 + + jiti@1.21.7: {} + + jose@6.1.3: {} + + jotai@2.16.2(@babel/core@7.28.6)(@babel/template@7.28.6)(@types/react@19.2.9)(react@19.2.1): + optionalDependencies: + '@babel/core': 7.28.6 + '@babel/template': 7.28.6 + '@types/react': 19.2.9 + react: 19.2.1 + + js-tokens@4.0.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + json-schema@0.4.0: {} + + json-stringify-safe@5.0.1: + optional: true + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + katex@0.16.28: + dependencies: + commander: 8.3.0 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + khroma@2.1.0: {} + + kind-of@6.0.3: {} + + langium@3.3.1: + dependencies: + chevrotain: 11.0.3 + chevrotain-allstar: 0.3.1(chevrotain@11.0.3) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 + + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + + lazy-val@1.0.5: {} + + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lodash-es@4.17.21: {} + + lodash-es@4.17.23: {} + + lodash.defaults@4.2.0: {} + + lodash.difference@4.5.0: {} + + lodash.escaperegexp@4.1.2: {} + + lodash.flatten@4.4.0: {} + + lodash.isequal@4.5.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.union@4.6.0: {} + + lodash@4.17.23: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + long@5.3.2: {} + + longest-streak@3.1.0: {} + + lowercase-keys@2.0.0: {} + + lowlight@1.20.0: + dependencies: + fault: 1.0.4 + highlight.js: 10.7.3 + + lowlight@3.3.0: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + highlight.js: 11.11.1 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lru-cache@7.18.3: {} + + lucide-react@0.468.0(react@19.2.1): + dependencies: + react: 19.2.1 + + lzma-native@8.0.6: + dependencies: + node-addon-api: 3.2.1 + node-gyp-build: 4.8.4 + readable-stream: 3.6.2 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-fetch-happen@10.2.1: + dependencies: + agentkeepalive: 4.6.0 + cacache: 16.1.3 + http-cache-semantics: 4.2.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 2.1.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.4 + promise-retry: 2.0.1 + socks-proxy-agent: 7.0.0 + ssri: 9.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + + markdown-table@3.0.4: {} + + marked@16.4.2: {} + + marked@17.0.1: {} + + matcher@3.0.0: + dependencies: + escape-string-regexp: 4.0.0 + optional: true + + math-intrinsics@1.1.0: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-newline-to-break@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-find-and-replace: 3.0.2 + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + merge2@1.4.1: {} + + mermaid@11.12.2: + dependencies: + '@braintree/sanitize-url': 7.1.1 + '@iconify/utils': 3.1.0 + '@mermaid-js/parser': 0.6.3 + '@types/d3': 7.4.3 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.13 + dayjs: 1.11.19 + dompurify: 3.3.1 + katex: 0.16.28 + khroma: 2.1.0 + lodash-es: 4.17.23 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 11.1.0 + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + + mimic-response@1.0.1: {} + + mimic-response@3.1.0: {} + + minimatch@10.1.1: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass-collect@1.0.2: + dependencies: + minipass: 3.3.6 + + minipass-fetch@2.1.2: + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.2: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp-classic@0.5.3: {} + + mkdirp@1.0.4: {} + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + module-details-from-path@1.0.4: {} + + motion-dom@11.18.1: + dependencies: + motion-utils: 11.18.1 + + motion-utils@11.18.1: {} + + motion@11.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + dependencies: + framer-motion: 11.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + tslib: 2.8.1 + optionalDependencies: + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + napi-build-utils@2.0.0: {} + + negotiator@0.6.4: {} + + negotiator@1.0.0: {} + + next-themes@0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + dependencies: + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + + node-abi@3.87.0: + dependencies: + semver: 7.7.3 + + node-addon-api@1.7.2: + optional: true + + node-addon-api@3.2.1: {} + + node-addon-api@7.1.1: {} + + node-api-version@0.1.4: + dependencies: + semver: 7.7.3 + + node-api-version@0.2.1: + dependencies: + semver: 7.7.3 + + node-gyp-build@4.8.4: {} + + node-gyp@9.4.1: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.3 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 10.2.1 + nopt: 6.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.7.3 + tar: 6.2.1 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + + node-pty@1.1.0: + dependencies: + node-addon-api: 7.1.1 + + node-releases@2.0.27: {} + + nopt@6.0.0: + dependencies: + abbrev: 1.1.1 + + normalize-path@3.0.0: {} + + normalize-url@6.1.0: {} + + npmlog@6.0.2: + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: + optional: true + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@2.3.0: + dependencies: + emoji-regex-xs: 1.0.0 + regex: 5.1.1 + regex-recursion: 5.1.1 + + oniguruma-to-es@4.3.4: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.1.0 + regex-recursion: 6.0.2 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + p-cancelable@2.1.1: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + package-json-from-dist@1.0.1: {} + + package-manager-detector@1.6.0: {} + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + parseurl@1.3.3: {} + + path-data-parser@0.1.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-to-regexp@8.3.0: {} + + pathe@2.0.3: {} + + pe-library@0.4.1: {} + + pend@1.2.0: {} + + pg-int8@1.0.1: {} + + pg-protocol@1.11.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.1 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.6.0: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + pkce-challenge@5.0.1: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + plist@3.1.0: + dependencies: + '@xmldom/xmldom': 0.8.11 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + + postcss-import@15.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.6): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.6 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.6 + + postcss-nested@6.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postgres-array@2.0.0: {} + + postgres-bytea@1.0.1: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + posthog-js@1.335.2: + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.208.0 + '@opentelemetry/exporter-logs-otlp-http': 0.208.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.208.0(@opentelemetry/api@1.9.0) + '@posthog/core': 1.14.0 + '@posthog/types': 1.335.2 + core-js: 3.48.0 + dompurify: 3.3.1 + fflate: 0.4.8 + preact: 10.28.2 + query-selector-shadow-dom: 1.0.1 + web-vitals: 5.1.0 + + posthog-node@5.24.2: + dependencies: + '@posthog/core': 1.14.0 + + preact@10.28.2: {} + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.87.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + + prismjs@1.30.0: {} + + process-nextick-args@2.0.1: {} + + progress@2.0.3: {} + + promise-inflight@1.0.1: {} + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + + property-information@7.1.0: {} + + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 20.19.30 + long: 5.3.2 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + qs@6.14.1: + dependencies: + side-channel: 1.1.0 + + query-selector-shadow-dom@1.0.1: {} + + queue-microtask@1.2.3: {} + + quick-lru@5.1.1: {} + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + react-dom@19.2.1(react@19.2.1): + dependencies: + react: 19.2.1 + scheduler: 0.27.0 + + react-hotkeys-hook@4.6.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + dependencies: + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + + react-icons@5.5.0(react@19.2.1): + dependencies: + react: 19.2.1 + + react-refresh@0.17.0: {} + + react-remove-scroll-bar@2.3.8(@types/react@19.2.9)(react@19.2.1): + dependencies: + react: 19.2.1 + react-style-singleton: 2.2.3(@types/react@19.2.9)(react@19.2.1) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.9 + + react-remove-scroll@2.7.2(@types/react@19.2.9)(react@19.2.1): + dependencies: + react: 19.2.1 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.9)(react@19.2.1) + react-style-singleton: 2.2.3(@types/react@19.2.9)(react@19.2.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.9)(react@19.2.1) + use-sidecar: 1.1.3(@types/react@19.2.9)(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + + react-style-singleton@2.2.3(@types/react@19.2.9)(react@19.2.1): + dependencies: + get-nonce: 1.0.1 + react: 19.2.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.9 + + react-syntax-highlighter@16.1.0(react@19.2.1): + dependencies: + '@babel/runtime': 7.28.6 + highlight.js: 10.7.3 + highlightjs-vue: 1.0.0 + lowlight: 1.20.0 + prismjs: 1.30.0 + react: 19.2.1 + refractor: 5.0.0 + + react-zoom-pan-pinch@3.7.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + dependencies: + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + + react@19.2.1: {} + + reactivity-store@0.3.12(react@19.2.1): + dependencies: + '@vue/reactivity': 3.5.27 + '@vue/shared': 3.5.27 + react: 19.2.1 + use-sync-external-store: 1.6.0(react@19.2.1) + + read-binary-file-arch@1.0.6: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + readdirp@5.0.0: {} + + refractor@5.0.0: + dependencies: + '@types/hast': 3.0.4 + '@types/prismjs': 1.26.5 + hastscript: 9.0.1 + parse-entities: 4.0.2 + + regex-recursion@5.1.1: + dependencies: + regex: 5.1.1 + regex-utilities: 2.3.0 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@5.1.1: + dependencies: + regex-utilities: 2.3.0 + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rehype-harden@1.1.7: + dependencies: + unist-util-visit: 5.1.0 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-sanitize@6.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-sanitize: 5.0.2 + + remark-breaks@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-newline-to-break: 2.0.0 + unified: 11.0.5 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + remend@1.1.0: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + require-in-the-middle@8.0.1: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + transitivePeerDependencies: + - supports-color + + resedit@1.7.2: + dependencies: + pe-library: 0.4.1 + + resolve-alpn@1.2.1: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + responselike@2.0.1: + dependencies: + lowercase-keys: 2.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + retry@0.12.0: {} + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + roarr@2.15.4: + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.4 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + optional: true + + robust-predicates@3.0.2: {} + + rollup@4.56.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.56.0 + '@rollup/rollup-android-arm64': 4.56.0 + '@rollup/rollup-darwin-arm64': 4.56.0 + '@rollup/rollup-darwin-x64': 4.56.0 + '@rollup/rollup-freebsd-arm64': 4.56.0 + '@rollup/rollup-freebsd-x64': 4.56.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.56.0 + '@rollup/rollup-linux-arm-musleabihf': 4.56.0 + '@rollup/rollup-linux-arm64-gnu': 4.56.0 + '@rollup/rollup-linux-arm64-musl': 4.56.0 + '@rollup/rollup-linux-loong64-gnu': 4.56.0 + '@rollup/rollup-linux-loong64-musl': 4.56.0 + '@rollup/rollup-linux-ppc64-gnu': 4.56.0 + '@rollup/rollup-linux-ppc64-musl': 4.56.0 + '@rollup/rollup-linux-riscv64-gnu': 4.56.0 + '@rollup/rollup-linux-riscv64-musl': 4.56.0 + '@rollup/rollup-linux-s390x-gnu': 4.56.0 + '@rollup/rollup-linux-x64-gnu': 4.56.0 + '@rollup/rollup-linux-x64-musl': 4.56.0 + '@rollup/rollup-openbsd-x64': 4.56.0 + '@rollup/rollup-openharmony-arm64': 4.56.0 + '@rollup/rollup-win32-arm64-msvc': 4.56.0 + '@rollup/rollup-win32-ia32-msvc': 4.56.0 + '@rollup/rollup-win32-x64-gnu': 4.56.0 + '@rollup/rollup-win32-x64-msvc': 4.56.0 + fsevents: 2.3.3 + + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.3.0 + transitivePeerDependencies: + - supports-color + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rw@1.3.3: {} + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + sanitize-filename@1.6.3: + dependencies: + truncate-utf8-bytes: 1.0.2 + + sax@1.4.4: {} + + scheduler@0.27.0: {} + + section-matter@1.0.0: + dependencies: + extend-shallow: 2.0.1 + kind-of: 6.0.3 + + semver-compare@1.0.0: + optional: true + + semver@6.3.1: {} + + semver@7.7.3: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + optional: true + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + set-blocking@2.0.0: {} + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shiki@1.29.2: + dependencies: + '@shikijs/core': 1.29.2 + '@shikijs/engine-javascript': 1.29.2 + '@shikijs/engine-oniguruma': 1.29.2 + '@shikijs/langs': 1.29.2 + '@shikijs/themes': 1.29.2 + '@shikijs/types': 1.29.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + shiki@3.21.0: + dependencies: + '@shikijs/core': 3.21.0 + '@shikijs/engine-javascript': 3.21.0 + '@shikijs/engine-oniguruma': 3.21.0 + '@shikijs/langs': 3.21.0 + '@shikijs/themes': 3.21.0 + '@shikijs/types': 3.21.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + simple-git@3.30.0: + dependencies: + '@kwsites/file-exists': 1.1.1 + '@kwsites/promise-deferred': 1.1.1 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.7.3 + + slice-ansi@3.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + optional: true + + smart-buffer@4.2.0: {} + + socks-proxy-agent@7.0.0: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + + socks@2.8.7: + dependencies: + ip-address: 10.1.0 + smart-buffer: 4.2.0 + + sonner@1.7.4(react-dom@19.2.1(react@19.2.1))(react@19.2.1): + dependencies: + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + space-separated-tokens@2.0.2: {} + + sprintf-js@1.0.3: {} + + sprintf-js@1.1.3: + optional: true + + ssri@9.0.1: + dependencies: + minipass: 3.3.6 + + stat-mode@1.0.0: {} + + statuses@2.0.2: {} + + streamdown@2.1.0(react@19.2.1): + dependencies: + clsx: 2.1.1 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + marked: 17.0.1 + react: 19.2.1 + rehype-harden: 1.1.7 + rehype-raw: 7.0.0 + rehype-sanitize: 6.0.0 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remend: 1.1.0 + tailwind-merge: 3.4.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + transitivePeerDependencies: + - supports-color + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-bom-string@1.0.0: {} + + strip-json-comments@2.0.1: {} + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + stylis@4.3.6: {} + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + sumchecker@3.0.1: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + superjson@2.2.6: + dependencies: + copy-anything: 4.0.5 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + swr@2.3.8(react@19.2.1): + dependencies: + dequal: 2.0.3 + react: 19.2.1 + use-sync-external-store: 1.6.0(react@19.2.1) + + tailwind-merge@2.6.0: {} + + tailwind-merge@3.4.0: {} + + tailwindcss-animate@1.0.7(tailwindcss@3.4.19): + dependencies: + tailwindcss: 3.4.19 + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.1.0(postcss@8.5.6) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6) + postcss-nested: 6.2.0(postcss@8.5.6) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + temp-file@3.4.0: + dependencies: + async-exit-hook: 2.0.1 + fs-extra: 10.1.0 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + throttleit@2.1.0: {} + + tiny-typed-emitter@2.1.0: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tmp-promise@3.0.3: + dependencies: + tmp: 0.2.5 + + tmp@0.2.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + trpc-electron@0.1.2(@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3))(@trpc/server@11.8.1(typescript@5.9.3))(electron@33.4.5): + dependencies: + '@trpc/client': 11.8.1(@trpc/server@11.8.1(typescript@5.9.3))(typescript@5.9.3) + '@trpc/server': 11.8.1(typescript@5.9.3) + electron: 33.4.5 + + truncate-utf8-bytes@1.0.2: + dependencies: + utf8-byte-length: 1.0.5 + + ts-dedent@2.2.0: {} + + ts-interface-checker@0.1.13: {} + + tslib@2.8.1: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + type-fest@0.13.1: + optional: true + + type-is@2.0.1: + dependencies: + content-type: 1.0.5 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + undici-types@6.21.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unique-filename@2.0.1: + dependencies: + unique-slug: 3.0.0 + + unique-names-generator@4.7.1: {} + + unique-slug@3.0.0: + dependencies: + imurmurhash: 0.1.4 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-callback-ref@1.3.3(@types/react@19.2.9)(react@19.2.1): + dependencies: + react: 19.2.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.9 + + use-sidecar@1.1.3(@types/react@19.2.9)(react@19.2.1): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.9 + + use-sync-external-store@1.6.0(react@19.2.1): + dependencies: + react: 19.2.1 + + utf8-byte-length@1.0.5: {} + + util-deprecate@1.0.2: {} + + uuid@11.1.0: {} + + vary@1.1.2: {} + + verror@1.10.1: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.4.1 + optional: true + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@6.4.1(@types/node@20.19.30)(jiti@1.21.7): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.56.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 20.19.30 + fsevents: 2.3.3 + jiti: 1.21.7 + + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-uri@3.0.8: {} + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + web-namespaces@2.0.1: {} + + web-vitals@5.1.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + xmlbuilder@15.1.1: {} + + xtend@4.0.2: {} + + xterm@5.3.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yocto-queue@0.1.0: {} + + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 + + zod-to-json-schema@3.25.1(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod@3.25.76: {} + + zustand@5.0.10(@types/react@19.2.9)(react@19.2.1)(use-sync-external-store@1.6.0(react@19.2.1)): + optionalDependencies: + '@types/react': 19.2.9 + react: 19.2.1 + use-sync-external-store: 1.6.0(react@19.2.1) + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 000000000..5a3dd5920 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,10 @@ +ignoredBuiltDependencies: + - better-sqlite3 + - core-js + - esbuild + - lzma-native + - node-pty + - protobufjs + +onlyBuiltDependencies: + - electron diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 9e3c90106..ac2072fa9 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,33 +1,42 @@ -import { Provider as JotaiProvider, useAtomValue, useSetAtom } from "jotai" -import { ThemeProvider, useTheme } from "next-themes" -import { useEffect, useMemo } from "react" -import { Toaster } from "sonner" -import { TooltipProvider } from "./components/ui/tooltip" -import { TRPCProvider } from "./contexts/TRPCProvider" -import { WindowProvider, getInitialWindowParams } from "./contexts/WindowContext" -import { selectedProjectAtom, selectedAgentChatIdAtom } from "./features/agents/atoms" -import { useAgentSubChatStore } from "./features/agents/stores/sub-chat-store" -import { AgentsLayout } from "./features/layout/agents-layout" +import { Provider as JotaiProvider, useAtomValue, useSetAtom } from "jotai"; +import { ThemeProvider, useTheme } from "next-themes"; +import { useEffect, useMemo } from "react"; +import { Toaster } from "sonner"; +import { TooltipProvider } from "./components/ui/tooltip"; +import { TRPCProvider } from "./contexts/TRPCProvider"; +import { + WindowProvider, + getInitialWindowParams, +} from "./contexts/WindowContext"; +import { + selectedProjectAtom, + selectedAgentChatIdAtom, +} from "./features/agents/atoms"; +import { useAgentSubChatStore } from "./features/agents/stores/sub-chat-store"; +import { AgentsLayout } from "./features/layout/agents-layout"; import { AnthropicOnboardingPage, ApiKeyOnboardingPage, BillingMethodPage, + CliConfigDetectedPage, SelectRepoPage, -} from "./features/onboarding" -import { identify, initAnalytics, shutdown } from "./lib/analytics" +} from "./features/onboarding"; +import { identify, initAnalytics, shutdown } from "./lib/analytics"; import { - anthropicOnboardingCompletedAtom, apiKeyOnboardingCompletedAtom, - billingMethodAtom -} from "./lib/atoms" -import { appStore } from "./lib/jotai-store" -import { VSCodeThemeProvider } from "./lib/themes/theme-provider" -import { trpc } from "./lib/trpc" + anthropicOnboardingCompletedAtom, + apiKeyOnboardingCompletedAtom, + billingMethodAtom, + cliConfigDetectedShownAtom, +} from "./lib/atoms"; +import { appStore } from "./lib/jotai-store"; +import { VSCodeThemeProvider } from "./lib/themes/theme-provider"; +import { trpc } from "./lib/trpc"; /** * Custom Toaster that adapts to theme */ function ThemedToaster() { - const { resolvedTheme } = useTheme() + const { resolvedTheme } = useTheme(); return ( - ) + ); } /** * Main content router - decides which page to show based on onboarding state */ function AppContent() { - const billingMethod = useAtomValue(billingMethodAtom) - const setBillingMethod = useSetAtom(billingMethodAtom) + const billingMethod = useAtomValue(billingMethodAtom); + const setBillingMethod = useSetAtom(billingMethodAtom); const anthropicOnboardingCompleted = useAtomValue( - anthropicOnboardingCompletedAtom - ) - const setAnthropicOnboardingCompleted = useSetAtom(anthropicOnboardingCompletedAtom) - const apiKeyOnboardingCompleted = useAtomValue(apiKeyOnboardingCompletedAtom) - const setApiKeyOnboardingCompleted = useSetAtom(apiKeyOnboardingCompletedAtom) - const selectedProject = useAtomValue(selectedProjectAtom) - const setSelectedChatId = useSetAtom(selectedAgentChatIdAtom) - const { setActiveSubChat, addToOpenSubChats, setChatId } = useAgentSubChatStore() + anthropicOnboardingCompletedAtom, + ); + const setAnthropicOnboardingCompleted = useSetAtom( + anthropicOnboardingCompletedAtom, + ); + const apiKeyOnboardingCompleted = useAtomValue(apiKeyOnboardingCompletedAtom); + const setApiKeyOnboardingCompleted = useSetAtom( + apiKeyOnboardingCompletedAtom, + ); + const cliConfigDetectedShown = useAtomValue(cliConfigDetectedShownAtom); + const setCliConfigDetectedShown = useSetAtom(cliConfigDetectedShownAtom); + const selectedProject = useAtomValue(selectedProjectAtom); + const setSelectedChatId = useSetAtom(selectedAgentChatIdAtom); + const { setActiveSubChat, addToOpenSubChats, setChatId } = + useAgentSubChatStore(); // Apply initial window params (chatId/subChatId) when opening via "Open in new window" useEffect(() => { - const params = getInitialWindowParams() + const params = getInitialWindowParams(); if (params.chatId) { - console.log("[App] Opening chat from window params:", params.chatId, params.subChatId) - setSelectedChatId(params.chatId) - setChatId(params.chatId) + console.log( + "[App] Opening chat from window params:", + params.chatId, + params.subChatId, + ); + setSelectedChatId(params.chatId); + setChatId(params.chatId); if (params.subChatId) { - addToOpenSubChats(params.subChatId) - setActiveSubChat(params.subChatId) + addToOpenSubChats(params.subChatId); + setActiveSubChat(params.subChatId); } } - }, [setSelectedChatId, setChatId, addToOpenSubChats, setActiveSubChat]) + }, [setSelectedChatId, setChatId, addToOpenSubChats, setActiveSubChat]); // Check if user has existing CLI config (API key or proxy) // Based on PR #29 by @sa4hnd const { data: cliConfig, isLoading: isLoadingCliConfig } = - trpc.claudeCode.hasExistingCliConfig.useQuery() + trpc.claudeCode.hasExistingCliConfig.useQuery(); // Migration: If user already completed Anthropic onboarding but has no billing method set, // automatically set it to "claude-subscription" (legacy users before billing method was added) useEffect(() => { if (!billingMethod && anthropicOnboardingCompleted) { - setBillingMethod("claude-subscription") + setBillingMethod("claude-subscription"); } - }, [billingMethod, anthropicOnboardingCompleted, setBillingMethod]) + }, [billingMethod, anthropicOnboardingCompleted, setBillingMethod]); // Auto-skip onboarding if user has existing CLI config (API key or proxy) // This allows users with ANTHROPIC_API_KEY to use the app without OAuth + // Only auto-skip if the user has already seen the CLI config detected page useEffect(() => { - if (cliConfig?.hasConfig && !billingMethod) { - console.log("[App] Detected existing CLI config, auto-completing onboarding") - setBillingMethod("api-key") - setApiKeyOnboardingCompleted(true) + if (cliConfig?.hasConfig && !billingMethod && cliConfigDetectedShown) { + console.log( + "[App] Detected existing CLI config, auto-completing onboarding", + ); + setBillingMethod("api-key"); + setApiKeyOnboardingCompleted(true); } - }, [cliConfig?.hasConfig, billingMethod, setBillingMethod, setApiKeyOnboardingCompleted]) + }, [ + cliConfig?.hasConfig, + billingMethod, + cliConfigDetectedShown, + setBillingMethod, + setApiKeyOnboardingCompleted, + ]); // Fetch projects to validate selectedProject exists const { data: projects, isLoading: isLoadingProjects } = - trpc.projects.list.useQuery() + trpc.projects.list.useQuery(); // Validated project - only valid if exists in DB const validatedProject = useMemo(() => { - if (!selectedProject) return null + if (!selectedProject) return null; // While loading, trust localStorage value to prevent flicker - if (isLoadingProjects) return selectedProject + if (isLoadingProjects) return selectedProject; // After loading, validate against DB - if (!projects) return null - const exists = projects.some((p) => p.id === selectedProject.id) - return exists ? selectedProject : null - }, [selectedProject, projects, isLoadingProjects]) + if (!projects) return null; + const exists = projects.some((p) => p.id === selectedProject.id); + return exists ? selectedProject : null; + }, [selectedProject, projects, isLoadingProjects]); // Determine which page to show: - // 1. No billing method selected -> BillingMethodPage + // 1. No billing method selected -> BillingMethodPage OR CliConfigDetectedPage // 2. Claude subscription selected but not completed -> AnthropicOnboardingPage // 3. API key or custom model selected but not completed -> ApiKeyOnboardingPage // 4. No valid project selected -> SelectRepoPage // 5. Otherwise -> AgentsLayout + + // Show CLI config detected page if config exists and user hasn't seen it yet + if ( + !billingMethod && + cliConfig?.hasConfig && + !cliConfigDetectedShown && + !isLoadingCliConfig + ) { + return ; + } + if (!billingMethod) { - return + return ; } - if (billingMethod === "claude-subscription" && !anthropicOnboardingCompleted) { - return + if ( + billingMethod === "claude-subscription" && + !anthropicOnboardingCompleted + ) { + return ; } if ( (billingMethod === "api-key" || billingMethod === "custom-model") && !apiKeyOnboardingCompleted ) { - return + return ; } if (!validatedProject && !isLoadingProjects) { - return + return ; } - return + return ; } export function App() { // Initialize analytics on mount useEffect(() => { - initAnalytics() + initAnalytics(); // Sync analytics opt-out status to main process const syncOptOutStatus = async () => { try { const optOut = - localStorage.getItem("preferences:analytics-opt-out") === "true" - await window.desktopApi?.setAnalyticsOptOut(optOut) + localStorage.getItem("preferences:analytics-opt-out") === "true"; + await window.desktopApi?.setAnalyticsOptOut(optOut); } catch (error) { - console.warn("[Analytics] Failed to sync opt-out status:", error) + console.warn("[Analytics] Failed to sync opt-out status:", error); } - } - syncOptOutStatus() + }; + syncOptOutStatus(); // Identify user if already authenticated const identifyUser = async () => { try { - const user = await window.desktopApi?.getUser() + const user = await window.desktopApi?.getUser(); if (user?.id) { - identify(user.id, { email: user.email, name: user.name }) + identify(user.id, { email: user.email, name: user.name }); } } catch (error) { - console.warn("[Analytics] Failed to identify user:", error) + console.warn("[Analytics] Failed to identify user:", error); } - } - identifyUser() + }; + identifyUser(); // Cleanup on unmount return () => { - shutdown() - } - }, []) + shutdown(); + }; + }, []); return ( @@ -190,5 +233,5 @@ export function App() { - ) + ); } diff --git a/src/renderer/features/onboarding/cli-config-detected-page.tsx b/src/renderer/features/onboarding/cli-config-detected-page.tsx new file mode 100644 index 000000000..9229f8895 --- /dev/null +++ b/src/renderer/features/onboarding/cli-config-detected-page.tsx @@ -0,0 +1,210 @@ +"use client"; + +import { useSetAtom } from "jotai"; +import { useState } from "react"; +import { ChevronLeft, Check, ExternalLink } from "lucide-react"; + +import { ClaudeCodeIcon, KeyFilledIcon } from "../../components/ui/icons"; +import { + apiKeyOnboardingCompletedAtom, + billingMethodAtom, + cliConfigDetectedShownAtom, +} from "../../lib/atoms"; +import { trpc } from "../../lib/trpc"; +import { cn } from "../../lib/utils"; + +export function CliConfigDetectedPage() { + const setBillingMethod = useSetAtom(billingMethodAtom); + const setApiKeyOnboardingCompleted = useSetAtom( + apiKeyOnboardingCompletedAtom, + ); + const setCliConfigDetectedShown = useSetAtom(cliConfigDetectedShownAtom); + const [isContinuing, setIsContinuing] = useState(false); + const [showOAuthFlow, setShowOAuthFlow] = useState(false); + + // Check for existing CLI config + const { data: cliConfig, isLoading } = + trpc.claudeCode.hasExistingCliConfig.useQuery(); + + const handleUseExistingConfig = () => { + setIsContinuing(true); + // Mark CLI config detected as shown so we don't show this page again + setCliConfigDetectedShown(true); + // Set billing method to api-key since CLI config is being used + setBillingMethod("api-key"); + // Mark API key onboarding as complete + setApiKeyOnboardingCompleted(true); + }; + + const handleUseOAuth = () => { + setShowOAuthFlow(true); + setBillingMethod("claude-subscription"); + }; + + const handleBack = () => { + setBillingMethod(null); + }; + + if (isLoading) { + return ( +
+
+ Checking configuration... +
+
+ ); + } + + // If user chose OAuth, show loading while redirecting + if (showOAuthFlow) { + return ( +
+
+ +
+

+ Connecting to Claude... +

+

+ Opening browser for authentication +

+
+
+
+ ); + } + + const hasApiKey = cliConfig?.hasApiKey; + const hasBaseUrl = cliConfig?.baseUrl !== null; + + return ( +
+ {/* Draggable title bar area */} +
+ + {/* Back button - fixed in top left corner below traffic lights */} + + +
+ {/* Header */} +
+
+
+ +
+
+ +
+
+
+

+ Claude Configuration Detected +

+

+ We found an existing Claude CLI configuration in your shell + environment. +

+
+
+ + {/* Configuration Status */} +
+
+
+ + API Key:{" "} + {hasApiKey ? ( + + Configured + + ) : ( + Not set + )} + +
+ +
+
+ + API Proxy:{" "} + {hasBaseUrl ? ( + + {cliConfig?.baseUrl} + + ) : ( + + Default (api.anthropic.com) + + )} + +
+ +
+

+ Your ANTHROPIC_API_KEY + {hasBaseUrl && " and "} + {hasBaseUrl && ( + ANTHROPIC_BASE_URL + )}{" "} + environment variables were detected. +

+
+
+ + {/* Primary Action - Use Existing Config */} + + + {/* Secondary Action - OAuth */} +
+ +

+ Use your Claude Pro/Max subscription with OAuth authentication +

+
+
+
+ ); +} diff --git a/src/renderer/features/onboarding/index.ts b/src/renderer/features/onboarding/index.ts index 9cb588fd2..9bfac611e 100644 --- a/src/renderer/features/onboarding/index.ts +++ b/src/renderer/features/onboarding/index.ts @@ -1,4 +1,5 @@ -export { AnthropicOnboardingPage } from "./anthropic-onboarding-page" -export { ApiKeyOnboardingPage } from "./api-key-onboarding-page" -export { BillingMethodPage } from "./billing-method-page" -export { SelectRepoPage } from "./select-repo-page" +export { AnthropicOnboardingPage } from "./anthropic-onboarding-page"; +export { ApiKeyOnboardingPage } from "./api-key-onboarding-page"; +export { BillingMethodPage } from "./billing-method-page"; +export { CliConfigDetectedPage } from "./cli-config-detected-page"; +export { SelectRepoPage } from "./select-repo-page"; diff --git a/src/renderer/lib/atoms/index.ts b/src/renderer/lib/atoms/index.ts index 35dcf34a1..d0005ae9f 100644 --- a/src/renderer/lib/atoms/index.ts +++ b/src/renderer/lib/atoms/index.ts @@ -743,6 +743,15 @@ export const apiKeyOnboardingCompletedAtom = atomWithStorage( { getOnInit: true }, ) +// Whether user has seen the CLI config detected page +// This tracks if the user was shown the "Configuration Detected" screen +export const cliConfigDetectedShownAtom = atomWithStorage( + "onboarding:cli-config-detected-shown", + false, + undefined, + { getOnInit: true }, +) + // ============================================ // SESSION INFO ATOMS (MCP, Plugins, Tools) // ============================================ From 70f4dd69b336c9dc01874e00f983acf313dc1a5c Mon Sep 17 00:00:00 2001 From: Yogi Date: Sat, 31 Jan 2026 18:07:22 +0700 Subject: [PATCH 02/16] feat: standardize UI and refactor profile system - Detect and merge CLI config from ~/.claude/settings.json - Standardize new-chat-form model selector to match chat-input-area - Reorder new-chat-form buttons (Model -> Thinking -> Plan) - Remove legacy 'Migrated Config' creation - Remove 'System (No Profile)' option to enforce profile usage - Rename fallback display label to 'System' - Fix React object child error in conditional rendering --- bun.lock | 2586 ----------------- bun.lockb | Bin 473965 -> 0 bytes ...25-01-31-multiple-model-profiles-design.md | 188 ++ ...-multiple-model-profiles-implementation.md | 751 +++++ .../2025-01-31-unified-model-selector.md | 86 + package.json | 1 + pnpm-lock.yaml | 252 +- src/main/lib/claude/env.ts | 127 +- src/main/lib/claude/offline-handler.ts | 5 + src/main/lib/trpc/routers/claude-code.ts | 6 + src/main/lib/trpc/routers/claude.ts | 76 +- .../settings-tabs/agents-models-tab.tsx | 137 +- .../settings-tabs/model-profile-form.tsx | 201 ++ .../settings-tabs/model-profile-row.tsx | 104 + .../settings-tabs/model-profiles-section.tsx | 144 + src/renderer/features/agents/atoms/index.ts | 6 + .../features/agents/lib/ipc-chat-transport.ts | 10 +- src/renderer/features/agents/lib/models.ts | 15 +- .../features/agents/main/active-chat.tsx | 48 + .../features/agents/main/chat-input-area.tsx | 278 +- .../features/agents/main/new-chat-form.tsx | 236 +- .../onboarding/api-key-onboarding-page.tsx | 161 +- .../onboarding/cli-config-detected-page.tsx | 35 +- src/renderer/lib/atoms/index.ts | 8 +- 24 files changed, 2175 insertions(+), 3286 deletions(-) delete mode 100644 bun.lock delete mode 100755 bun.lockb create mode 100644 docs/plans/2025-01-31-multiple-model-profiles-design.md create mode 100644 docs/plans/2025-01-31-multiple-model-profiles-implementation.md create mode 100644 docs/plans/2025-01-31-unified-model-selector.md create mode 100644 src/renderer/components/dialogs/settings-tabs/model-profile-form.tsx create mode 100644 src/renderer/components/dialogs/settings-tabs/model-profile-row.tsx create mode 100644 src/renderer/components/dialogs/settings-tabs/model-profiles-section.tsx diff --git a/bun.lock b/bun.lock deleted file mode 100644 index 5e7ba4fa4..000000000 --- a/bun.lock +++ /dev/null @@ -1,2586 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 0, - "workspaces": { - "": { - "name": "21st-desktop", - "dependencies": { - "@ai-sdk/react": "^3.0.14", - "@anthropic-ai/claude-agent-sdk": "0.2.25", - "@git-diff-view/react": "^0.0.35", - "@git-diff-view/shiki": "^0.0.36", - "@modelcontextprotocol/sdk": "^1.25.3", - "@monaco-editor/react": "^4.7.0", - "@pierre/diffs": "^1.0.10", - "@radix-ui/react-accordion": "^1.2.12", - "@radix-ui/react-alert-dialog": "^1.1.15", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-collapsible": "^1.1.12", - "@radix-ui/react-context-menu": "^2.2.16", - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-hover-card": "^1.1.15", - "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.8", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-progress": "^1.1.8", - "@radix-ui/react-select": "^2.2.6", - "@radix-ui/react-slot": "^1.2.4", - "@radix-ui/react-switch": "^1.2.6", - "@radix-ui/react-tabs": "^1.1.13", - "@radix-ui/react-tooltip": "^1.2.8", - "@sentry/electron": "^7.5.0", - "@tailwindcss/typography": "^0.5.19", - "@tanstack/react-query": "^5.90.10", - "@tanstack/react-virtual": "^3.13.18", - "@trpc/client": "^11.7.1", - "@trpc/react-query": "^11.7.1", - "@trpc/server": "^11.7.1", - "@xterm/addon-canvas": "^0.7.0", - "@xterm/addon-fit": "^0.11.0", - "@xterm/addon-search": "^0.16.0", - "@xterm/addon-serialize": "^0.14.0", - "@xterm/addon-web-links": "^0.12.0", - "@xterm/addon-webgl": "^0.19.0", - "ai": "^6.0.14", - "async-mutex": "^0.5.0", - "better-sqlite3": "^11.8.1", - "chokidar": "^5.0.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "date-fns": "^3.6.0", - "drizzle-orm": "^0.45.1", - "electron-log": "^5.4.3", - "electron-updater": "^6.7.3", - "gray-matter": "^4.0.3", - "jotai": "^2.11.1", - "jsonc-parser": "^3.3.1", - "lucide-react": "^0.468.0", - "mermaid": "^11.12.2", - "monaco-editor": "^0.55.1", - "motion": "^11.15.0", - "next-themes": "^0.4.4", - "node-pty": "^1.1.0", - "pidtree": "^0.6.0", - "posthog-js": "^1.239.1", - "posthog-node": "^5.20.0", - "react": "19.2.1", - "react-dom": "19.2.1", - "react-hotkeys-hook": "^4.6.1", - "react-icons": "^5.5.0", - "react-zoom-pan-pinch": "^3.7.0", - "remark-breaks": "^4.0.0", - "remark-gfm": "^4.0.1", - "shiki": "^1.24.4", - "simple-git": "^3.28.0", - "sonner": "^1.7.1", - "streamdown": "^2.0.1", - "superjson": "^2.2.2", - "tailwind-merge": "^2.6.0", - "tailwindcss-animate": "^1.0.7", - "trpc-electron": "^0.1.2", - "unique-names-generator": "^4.7.1", - "xterm": "^5.3.0", - "zod": "^3.24.1", - "zustand": "^5.0.3", - }, - "devDependencies": { - "@electron-toolkit/preload": "^3.0.1", - "@electron-toolkit/utils": "^4.0.0", - "@types/better-sqlite3": "^7.6.13", - "@types/node": "^20.17.50", - "@types/react": "^19.0.7", - "@types/react-dom": "^19.0.3", - "@vitejs/plugin-react": "^4.3.4", - "@welldone-software/why-did-you-render": "^10.0.1", - "autoprefixer": "^10.4.20", - "drizzle-kit": "^0.31.8", - "electron": "33.4.5", - "electron-builder": "^25.1.8", - "electron-rebuild": "^3.2.9", - "electron-vite": "^3.0.0", - "postcss": "^8.5.1", - "tailwindcss": "^3.4.17", - "typescript": "^5.4.5", - "vite": "^6.3.4", - }, - }, - }, - "packages": { - "7zip-bin": ["7zip-bin@5.2.0", "", {}, "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A=="], - - "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.4", "@ai-sdk/provider-utils": "4.0.8", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-WOHGHclkcDN3VYJ3xBafa38uhmW+xRV8Hbe9LtEJKej3+qU3Hy1SUcptxsqfVACo1H1Nt5xRX7rEvoCHlwC8tw=="], - - "@ai-sdk/provider": ["@ai-sdk/provider@3.0.4", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5KXyBOSEX+l67elrEa+wqo/LSsSTtrPj9Uoh3zMbe/ceQX4ucHI3b9nUEfNkGF3Ry1svv90widAt+aiKdIJasQ=="], - - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.8", "", { "dependencies": { "@ai-sdk/provider": "3.0.4", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ns9gN7MmpI8vTRandzgz+KK/zNMLzhrriiKECMt4euLtQFSBgNfydtagPOX4j4pS1/3KvHF6RivhT3gNQgBZsg=="], - - "@ai-sdk/react": ["@ai-sdk/react@3.0.47", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.8", "ai": "6.0.45", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-H+RKDWrJCybang8RxC4WkEzdgaXD/KrlmRKq6gfJ5fNv0pmcGSjIHi3PJpEPa5zOymblesDWxh8KM0u/ToMo7w=="], - - "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - - "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], - - "@anthropic-ai/claude-agent-sdk": ["@anthropic-ai/claude-agent-sdk@0.2.25", "", { "optionalDependencies": { "@img/sharp-darwin-arm64": "^0.33.5", "@img/sharp-darwin-x64": "^0.33.5", "@img/sharp-linux-arm": "^0.33.5", "@img/sharp-linux-arm64": "^0.33.5", "@img/sharp-linux-x64": "^0.33.5", "@img/sharp-linuxmusl-arm64": "^0.33.5", "@img/sharp-linuxmusl-x64": "^0.33.5", "@img/sharp-win32-x64": "^0.33.5" }, "peerDependencies": { "zod": "^4.0.0" } }, "sha512-YIP3I40+XSkC3zE1Z8KRQY02VA7UfofFamF1cFrLe7FbtCnjpslyDl9coGBh2DAi9xj2yQcKZZf751jEWpB+dQ=="], - - "@apm-js-collab/code-transformer": ["@apm-js-collab/code-transformer@0.8.2", "", {}, "sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA=="], - - "@apm-js-collab/tracing-hooks": ["@apm-js-collab/tracing-hooks@0.3.1", "", { "dependencies": { "@apm-js-collab/code-transformer": "^0.8.0", "debug": "^4.4.1", "module-details-from-path": "^1.0.4" } }, "sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw=="], - - "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], - - "@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], - - "@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="], - - "@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], - - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], - - "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], - - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], - - "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - - "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], - - "@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], - - "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="], - - "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], - - "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], - - "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], - - "@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], - - "@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], - - "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.1", "", {}, "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw=="], - - "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.0.3", "", { "dependencies": { "@chevrotain/gast": "11.0.3", "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ=="], - - "@chevrotain/gast": ["@chevrotain/gast@11.0.3", "", { "dependencies": { "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q=="], - - "@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@11.0.3", "", {}, "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA=="], - - "@chevrotain/types": ["@chevrotain/types@11.0.3", "", {}, "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ=="], - - "@chevrotain/utils": ["@chevrotain/utils@11.0.3", "", {}, "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ=="], - - "@develar/schema-utils": ["@develar/schema-utils@2.6.5", "", { "dependencies": { "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" } }, "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig=="], - - "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], - - "@electron-toolkit/preload": ["@electron-toolkit/preload@3.0.2", "", { "peerDependencies": { "electron": ">=13.0.0" } }, "sha512-TWWPToXd8qPRfSXwzf5KVhpXMfONaUuRAZJHsKthKgZR/+LqX1dZVSSClQ8OTAEduvLGdecljCsoT2jSshfoUg=="], - - "@electron-toolkit/utils": ["@electron-toolkit/utils@4.0.0", "", { "peerDependencies": { "electron": ">=13.0.0" } }, "sha512-qXSntwEzluSzKl4z5yFNBknmPGjPa3zFhE4mp9+h0cgokY5ornAeP+CJQDBhKsL1S58aOQfcwkD3NwLZCl+64g=="], - - "@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="], - - "@electron/get": ["@electron/get@2.0.3", "", { "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ=="], - - "@electron/notarize": ["@electron/notarize@2.5.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.1", "promise-retry": "^2.0.1" } }, "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A=="], - - "@electron/osx-sign": ["@electron/osx-sign@1.3.1", "", { "dependencies": { "compare-version": "^0.1.2", "debug": "^4.3.4", "fs-extra": "^10.0.0", "isbinaryfile": "^4.0.8", "minimist": "^1.2.6", "plist": "^3.0.5" }, "bin": { "electron-osx-flat": "bin/electron-osx-flat.js", "electron-osx-sign": "bin/electron-osx-sign.js" } }, "sha512-BAfviURMHpmb1Yb50YbCxnOY0wfwaLXH5KJ4+80zS0gUkzDX3ec23naTlEqKsN+PwYn+a1cCzM7BJ4Wcd3sGzw=="], - - "@electron/rebuild": ["@electron/rebuild@3.6.1", "", { "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "chalk": "^4.0.0", "debug": "^4.1.1", "detect-libc": "^2.0.1", "fs-extra": "^10.0.0", "got": "^11.7.0", "node-abi": "^3.45.0", "node-api-version": "^0.2.0", "node-gyp": "^9.0.0", "ora": "^5.1.0", "read-binary-file-arch": "^1.0.6", "semver": "^7.3.5", "tar": "^6.0.5", "yargs": "^17.0.1" }, "bin": { "electron-rebuild": "lib/cli.js" } }, "sha512-f6596ZHpEq/YskUd8emYvOUne89ij8mQgjYFA5ru25QwbrRO+t1SImofdDv7kKOuWCmVOuU5tvfkbgGxIl3E/w=="], - - "@electron/universal": ["@electron/universal@2.0.1", "", { "dependencies": { "@electron/asar": "^3.2.7", "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.3.1", "dir-compare": "^4.2.0", "fs-extra": "^11.1.1", "minimatch": "^9.0.3", "plist": "^3.1.0" } }, "sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA=="], - - "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], - - "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="], - - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], - - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], - - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], - - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - - "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], - - "@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], - - "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.6", "", { "dependencies": { "@floating-ui/dom": "^1.7.4" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw=="], - - "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], - - "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], - - "@git-diff-view/core": ["@git-diff-view/core@0.0.35", "", { "dependencies": { "@git-diff-view/lowlight": "^0.0.35", "fast-diff": "^1.3.0", "highlight.js": "^11.11.0", "lowlight": "^3.3.0" } }, "sha512-cdH3BopR6AWUW+6hP78zGyryKxR9JkPgryd1JN78i5k+F9Eo4x/4S23ZF1VZnrpPlGLrSuYfiAZ0ho5m+pTuKg=="], - - "@git-diff-view/lowlight": ["@git-diff-view/lowlight@0.0.35", "", { "dependencies": { "@types/hast": "^3.0.0", "highlight.js": "^11.11.0", "lowlight": "^3.3.0" } }, "sha512-MVpOxrNn1oHVOTOWUjxLbbf1W4OtVHjj6CHxwJbBRg9ZWZdShBINjuEgHVMSGB6vZuHKfwruRfXw8XxV3aF8zw=="], - - "@git-diff-view/react": ["@git-diff-view/react@0.0.35", "", { "dependencies": { "@git-diff-view/core": "^0.0.35", "@types/hast": "^3.0.0", "fast-diff": "^1.3.0", "highlight.js": "^11.11.0", "lowlight": "^3.3.0", "reactivity-store": "^0.3.12", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-jIzESQ/oB2FZgEGK1urvAqju89cThv5+enEC4r2atDX15W5xMDNsDBe8FT2U74zBj8JkgROaiGmbF93KIJrWpg=="], - - "@git-diff-view/shiki": ["@git-diff-view/shiki@0.0.36", "", { "dependencies": { "@types/hast": "^3.0.0", "shiki": "^3.9.2" } }, "sha512-t6LGKISEvE0q7u2AR1rq2RLgCcccRttuKE617D1MiHecO0Xd/xvsMhqxW4PgDMkMadhbzWnt6IhBbrdgqBKPwQ=="], - - "@hono/node-server": ["@hono/node-server@1.19.9", "", { "peerDependencies": { "hono": "^4" } }, "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw=="], - - "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], - - "@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="], - - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="], - - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="], - - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="], - - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="], - - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="], - - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="], - - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="], - - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="], - - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="], - - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="], - - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="], - - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="], - - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="], - - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="], - - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="], - - "@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="], - - "@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="], - - "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@kwsites/file-exists": ["@kwsites/file-exists@1.1.1", "", { "dependencies": { "debug": "^4.1.1" } }, "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw=="], - - "@kwsites/promise-deferred": ["@kwsites/promise-deferred@1.1.1", "", {}, "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="], - - "@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@2.0.0", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg=="], - - "@malept/flatpak-bundler": ["@malept/flatpak-bundler@0.4.0", "", { "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "tmp-promise": "^3.0.2" } }, "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q=="], - - "@mermaid-js/parser": ["@mermaid-js/parser@0.6.3", "", { "dependencies": { "langium": "3.3.1" } }, "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA=="], - - "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.3", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ=="], - - "@monaco-editor/loader": ["@monaco-editor/loader@1.7.0", "", { "dependencies": { "state-local": "^1.0.6" } }, "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA=="], - - "@monaco-editor/react": ["@monaco-editor/react@4.7.0", "", { "dependencies": { "@monaco-editor/loader": "^1.5.0" }, "peerDependencies": { "monaco-editor": ">= 0.25.0 < 1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA=="], - - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - - "@npmcli/fs": ["@npmcli/fs@2.1.2", "", { "dependencies": { "@gar/promisify": "^1.1.3", "semver": "^7.3.5" } }, "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ=="], - - "@npmcli/move-file": ["@npmcli/move-file@2.0.1", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ=="], - - "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - - "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg=="], - - "@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@2.5.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw=="], - - "@opentelemetry/core": ["@opentelemetry/core@2.5.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ=="], - - "@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-exporter-base": "0.208.0", "@opentelemetry/otlp-transformer": "0.208.0", "@opentelemetry/sdk-logs": "0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg=="], - - "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "import-in-the-middle": "^2.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Eju0L4qWcQS+oXxi6pgh7zvE2byogAkcsVv0OjHF/97iOz1N/aKE6etSGowYkie+YA1uo6DNwdSxaaNnLvcRlA=="], - - "@opentelemetry/instrumentation-amqplib": ["@opentelemetry/instrumentation-amqplib@0.55.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-5ULoU8p+tWcQw5PDYZn8rySptGSLZHNX/7srqo2TioPnAAcvTy6sQFQXsNPrAnyRRtYGMetXVyZUy5OaX1+IfA=="], - - "@opentelemetry/instrumentation-connect": ["@opentelemetry/instrumentation-connect@0.52.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/semantic-conventions": "^1.27.0", "@types/connect": "3.4.38" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-GXPxfNB5szMbV3I9b7kNWSmQBoBzw7MT0ui6iU/p+NIzVx3a06Ri2cdQO7tG9EKb4aKSLmfX9Cw5cKxXqX6Ohg=="], - - "@opentelemetry/instrumentation-dataloader": ["@opentelemetry/instrumentation-dataloader@0.26.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-P2BgnFfTOarZ5OKPmYfbXfDFjQ4P9WkQ1Jji7yH5/WwB6Wm/knynAoA1rxbjWcDlYupFkyT0M1j6XLzDzy0aCA=="], - - "@opentelemetry/instrumentation-express": ["@opentelemetry/instrumentation-express@0.57.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HAdx/o58+8tSR5iW+ru4PHnEejyKrAy9fYFhlEI81o10nYxrGahnMAHWiSjhDC7UQSY3I4gjcPgSKQz4rm/asg=="], - - "@opentelemetry/instrumentation-fs": ["@opentelemetry/instrumentation-fs@0.28.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FFvg8fq53RRXVBRHZViP+EMxMR03tqzEGpuq55lHNbVPyFklSVfQBN50syPhK5UYYwaStx0eyCtHtbRreusc5g=="], - - "@opentelemetry/instrumentation-generic-pool": ["@opentelemetry/instrumentation-generic-pool@0.52.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ISkNcv5CM2IwvsMVL31Tl61/p2Zm2I2NAsYq5SSBgOsOndT0TjnptjufYVScCnD5ZLD1tpl4T3GEYULLYOdIdQ=="], - - "@opentelemetry/instrumentation-graphql": ["@opentelemetry/instrumentation-graphql@0.56.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-IPvNk8AFoVzTAM0Z399t34VDmGDgwT6rIqCUug8P9oAGerl2/PEIYMPOl/rerPGu+q8gSWdmbFSjgg7PDVRd3Q=="], - - "@opentelemetry/instrumentation-hapi": ["@opentelemetry/instrumentation-hapi@0.55.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-prqAkRf9e4eEpy4G3UcR32prKE8NLNlA90TdEU1UsghOTg0jUvs40Jz8LQWFEs5NbLbXHYGzB4CYVkCI8eWEVQ=="], - - "@opentelemetry/instrumentation-http": ["@opentelemetry/instrumentation-http@0.208.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/instrumentation": "0.208.0", "@opentelemetry/semantic-conventions": "^1.29.0", "forwarded-parse": "2.1.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-rhmK46DRWEbQQB77RxmVXGyjs6783crXCnFjYQj+4tDH/Kpv9Rbg3h2kaNyp5Vz2emF1f9HOQQvZoHzwMWOFZQ=="], - - "@opentelemetry/instrumentation-ioredis": ["@opentelemetry/instrumentation-ioredis@0.56.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/redis-common": "^0.38.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-XSWeqsd3rKSsT3WBz/JKJDcZD4QYElZEa0xVdX8f9dh4h4QgXhKRLorVsVkK3uXFbC2sZKAS2Ds+YolGwD83Dg=="], - - "@opentelemetry/instrumentation-kafkajs": ["@opentelemetry/instrumentation-kafkajs@0.18.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/semantic-conventions": "^1.30.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-KCL/1HnZN5zkUMgPyOxfGjLjbXjpd4odDToy+7c+UsthIzVLFf99LnfIBE8YSSrYE4+uS7OwJMhvhg3tWjqMBg=="], - - "@opentelemetry/instrumentation-knex": ["@opentelemetry/instrumentation-knex@0.53.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/semantic-conventions": "^1.33.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-xngn5cH2mVXFmiT1XfQ1aHqq1m4xb5wvU6j9lSgLlihJ1bXzsO543cpDwjrZm2nMrlpddBf55w8+bfS4qDh60g=="], - - "@opentelemetry/instrumentation-koa": ["@opentelemetry/instrumentation-koa@0.57.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/semantic-conventions": "^1.36.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" } }, "sha512-3JS8PU/D5E3q295mwloU2v7c7/m+DyCqdu62BIzWt+3u9utjxC9QS7v6WmUNuoDN3RM+Q+D1Gpj13ERo+m7CGg=="], - - "@opentelemetry/instrumentation-lru-memoizer": ["@opentelemetry/instrumentation-lru-memoizer@0.53.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-LDwWz5cPkWWr0HBIuZUjslyvijljTwmwiItpMTHujaULZCxcYE9eU44Qf/pbVC8TulT0IhZi+RoGvHKXvNhysw=="], - - "@opentelemetry/instrumentation-mongodb": ["@opentelemetry/instrumentation-mongodb@0.61.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-OV3i2DSoY5M/pmLk+68xr5RvkHU8DRB3DKMzYJdwDdcxeLs62tLbkmRyqJZsYf3Ht7j11rq35pHOWLuLzXL7pQ=="], - - "@opentelemetry/instrumentation-mongoose": ["@opentelemetry/instrumentation-mongoose@0.55.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-5afj0HfF6aM6Nlqgu6/PPHFk8QBfIe3+zF9FGpX76jWPS0/dujoEYn82/XcLSaW5LPUDW8sni+YeK0vTBNri+w=="], - - "@opentelemetry/instrumentation-mysql": ["@opentelemetry/instrumentation-mysql@0.54.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0", "@types/mysql": "2.15.27" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-bqC1YhnwAeWmRzy1/Xf9cDqxNG2d/JDkaxnqF5N6iJKN1eVWI+vg7NfDkf52/Nggp3tl1jcC++ptC61BD6738A=="], - - "@opentelemetry/instrumentation-mysql2": ["@opentelemetry/instrumentation-mysql2@0.55.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/semantic-conventions": "^1.33.0", "@opentelemetry/sql-common": "^0.41.2" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-0cs8whQG55aIi20gnK8B7cco6OK6N+enNhW0p5284MvqJ5EPi+I1YlWsWXgzv/V2HFirEejkvKiI4Iw21OqDWg=="], - - "@opentelemetry/instrumentation-pg": ["@opentelemetry/instrumentation-pg@0.61.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/semantic-conventions": "^1.34.0", "@opentelemetry/sql-common": "^0.41.2", "@types/pg": "8.15.6", "@types/pg-pool": "2.0.6" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-UeV7KeTnRSM7ECHa3YscoklhUtTQPs6V6qYpG283AB7xpnPGCUCUfECFT9jFg6/iZOQTt3FHkB1wGTJCNZEvPw=="], - - "@opentelemetry/instrumentation-redis": ["@opentelemetry/instrumentation-redis@0.57.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/redis-common": "^0.38.2", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-bCxTHQFXzrU3eU1LZnOZQ3s5LURxQPDlU3/upBzlWY77qOI1GZuGofazj3jtzjctMJeBEJhNwIFEgRPBX1kp/Q=="], - - "@opentelemetry/instrumentation-tedious": ["@opentelemetry/instrumentation-tedious@0.27.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.208.0", "@types/tedious": "^4.0.14" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-jRtyUJNZppPBjPae4ZjIQ2eqJbcRaRfJkr0lQLHFmOU/no5A6e9s1OHLd5XZyZoBJ/ymngZitanyRRA5cniseA=="], - - "@opentelemetry/instrumentation-undici": ["@opentelemetry/instrumentation-undici@0.19.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/semantic-conventions": "^1.24.0" }, "peerDependencies": { "@opentelemetry/api": "^1.7.0" } }, "sha512-Pst/RhR61A2OoZQZkn6OLpdVpXp6qn3Y92wXa6umfJe9rV640r4bc6SWvw4pPN6DiQqPu2c8gnSSZPDtC6JlpQ=="], - - "@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.208.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/otlp-transformer": "0.208.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA=="], - - "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/sdk-logs": "0.208.0", "@opentelemetry/sdk-metrics": "2.2.0", "@opentelemetry/sdk-trace-base": "2.2.0", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ=="], - - "@opentelemetry/redis-common": ["@opentelemetry/redis-common@0.38.2", "", {}, "sha512-1BCcU93iwSRZvDAgwUxC/DV4T/406SkMfxGqu5ojc3AvNI+I9GhV7v0J1HljsczuuhcnFLYqD5VmwVXfCGHzxA=="], - - "@opentelemetry/resources": ["@opentelemetry/resources@2.5.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g=="], - - "@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.208.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.208.0", "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA=="], - - "@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw=="], - - "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.5.0", "", { "dependencies": { "@opentelemetry/core": "2.5.0", "@opentelemetry/resources": "2.5.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ=="], - - "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.39.0", "", {}, "sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg=="], - - "@opentelemetry/sql-common": ["@opentelemetry/sql-common@0.41.2", "", { "dependencies": { "@opentelemetry/core": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0" } }, "sha512-4mhWm3Z8z+i508zQJ7r6Xi7y4mmoJpdvH0fZPFRkWrdp5fq7hhZ2HhYokEOLkfqSMgPR4Z9EyB3DBkbKGOqZiQ=="], - - "@pierre/diffs": ["@pierre/diffs@1.0.10", "", { "dependencies": { "@shikijs/core": "^3.0.0", "@shikijs/engine-javascript": "^3.0.0", "@shikijs/transformers": "^3.0.0", "diff": "8.0.3", "hast-util-to-html": "9.0.5", "lru_map": "0.4.1", "shiki": "^3.0.0" }, "peerDependencies": { "react": "^18.3.1 || ^19.0.0", "react-dom": "^18.3.1 || ^19.0.0" } }, "sha512-ahkpfS30NfaB+PBxnf0/Mc20ySBRTQmM28a7Ojpd0UZixmTyhGhJfBFjvmhX8dSzR22lB3h3OIMMxpB4yYTIOQ=="], - - "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - - "@posthog/core": ["@posthog/core@1.13.0", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-knjncrk7qRmssFRbGzBl1Tunt21GRpe0Wv+uVelyL0Rh7PdQUsgguulzXFTps8hA6wPwTU4kq85qnbAJ3eH6Wg=="], - - "@posthog/types": ["@posthog/types@1.333.0", "", {}, "sha512-9Wg/2ez+EZh6NmtOjhtYSkBHz/yIq8WMS0QSIizUoggh35hHVg4BTMXl3rz/tPearJNKU/8oRjEyuZ0OYTEDOA=="], - - "@prisma/instrumentation": ["@prisma/instrumentation@6.19.0", "", { "dependencies": { "@opentelemetry/instrumentation": ">=0.52.0 <1" }, "peerDependencies": { "@opentelemetry/api": "^1.8" } }, "sha512-QcuYy25pkXM8BJ37wVFBO7Zh34nyRV1GOb2n3lPkkbRYfl4hWl3PTcImP41P0KrzVXfa/45p6eVCos27x3exIg=="], - - "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], - - "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], - - "@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], - - "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], - - "@protobufjs/fetch": ["@protobufjs/fetch@1.1.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" } }, "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ=="], - - "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="], - - "@protobufjs/inquire": ["@protobufjs/inquire@1.1.0", "", {}, "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="], - - "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="], - - "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="], - - "@protobufjs/utf8": ["@protobufjs/utf8@1.1.0", "", {}, "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="], - - "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], - - "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], - - "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA=="], - - "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], - - "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], - - "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="], - - "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], - - "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], - - "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], - - "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], - - "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="], - - "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], - - "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], - - "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], - - "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="], - - "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], - - "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], - - "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg=="], - - "@radix-ui/react-icons": ["@radix-ui/react-icons@1.3.2", "", { "peerDependencies": { "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" } }, "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g=="], - - "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], - - "@radix-ui/react-label": ["@radix-ui/react-label@2.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A=="], - - "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], - - "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="], - - "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], - - "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], - - "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], - - "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - - "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.8", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA=="], - - "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], - - "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], - - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], - - "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="], - - "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], - - "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], - - "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], - - "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], - - "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], - - "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], - - "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], - - "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], - - "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], - - "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], - - "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], - - "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], - - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], - - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.56.0", "", { "os": "android", "cpu": "arm" }, "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.56.0", "", { "os": "android", "cpu": "arm64" }, "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.56.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.56.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.56.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.56.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg=="], - - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.56.0", "", { "os": "linux", "cpu": "arm" }, "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A=="], - - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.56.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw=="], - - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.56.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ=="], - - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.56.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA=="], - - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg=="], - - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA=="], - - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.56.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw=="], - - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.56.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg=="], - - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew=="], - - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ=="], - - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.56.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ=="], - - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.56.0", "", { "os": "linux", "cpu": "x64" }, "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw=="], - - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.56.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA=="], - - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.56.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA=="], - - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.56.0", "", { "os": "none", "cpu": "arm64" }, "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.56.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.56.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg=="], - - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g=="], - - "@sentry-internal/browser-utils": ["@sentry-internal/browser-utils@10.34.0", "", { "dependencies": { "@sentry/core": "10.34.0" } }, "sha512-0YNr60rGHyedmwkO0lbDBjNx2KAmT3kWamjaqu7Aw+jsESoPLgt+fzaTVvUBvkftBDui2PeTSzXm/nqzssctYg=="], - - "@sentry-internal/feedback": ["@sentry-internal/feedback@10.34.0", "", { "dependencies": { "@sentry/core": "10.34.0" } }, "sha512-wgGnq+iNxsFSOe9WX/FOvtoItSTjgLJJ4dQkVYtcVM6WGBVIg4wgNYfECCnRNztUTPzpZHLjC9r+4Pym451DDQ=="], - - "@sentry-internal/replay": ["@sentry-internal/replay@10.34.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.34.0", "@sentry/core": "10.34.0" } }, "sha512-Vmea0GcOg57z/S1bVSj3saFcRvDqdLzdy4wd9fQMpMgy5OCbTlo7lxVUndKzbcZnanma6zF6VxwnWER1WuN9RA=="], - - "@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.34.0", "", { "dependencies": { "@sentry-internal/replay": "10.34.0", "@sentry/core": "10.34.0" } }, "sha512-XWH/9njtgMD+LLWjc4KKgBpb+dTCkoUEIFDxcvzG/87d+jirmzf0+r8EfpLwKG+GrqNiiGRV39zIqu0SfPl+cw=="], - - "@sentry/browser": ["@sentry/browser@10.34.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.34.0", "@sentry-internal/feedback": "10.34.0", "@sentry-internal/replay": "10.34.0", "@sentry-internal/replay-canvas": "10.34.0", "@sentry/core": "10.34.0" } }, "sha512-8WCsAXli5Z+eIN8dMY8KGQjrS3XgUp1np/pjdeWNrVPVR8q8XpS34qc+f+y/LFrYQC9bs2Of5aIBwRtDCIvRsg=="], - - "@sentry/core": ["@sentry/core@10.34.0", "", {}, "sha512-4FFpYBMf0VFdPcsr4grDYDOR87mRu6oCfb51oQjU/Pndmty7UgYo0Bst3LEC/8v0SpytBtzXq+Wx/fkwulBesg=="], - - "@sentry/electron": ["@sentry/electron@7.6.0", "", { "dependencies": { "@sentry/browser": "10.34.0", "@sentry/core": "10.34.0", "@sentry/node": "10.34.0" }, "peerDependencies": { "@sentry/node-native": "10.34.0" }, "optionalPeers": ["@sentry/node-native"] }, "sha512-ueW3Coa0BtOQFPaf+QaI3mBHMi/t7CkZnuzZ6PNoVpHe6CgYfCtNdE7H1BpMsCpG1FhEAgCLBJtpaMKyQBFdzQ=="], - - "@sentry/node": ["@sentry/node@10.34.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^2.2.0", "@opentelemetry/core": "^2.2.0", "@opentelemetry/instrumentation": "^0.208.0", "@opentelemetry/instrumentation-amqplib": "0.55.0", "@opentelemetry/instrumentation-connect": "0.52.0", "@opentelemetry/instrumentation-dataloader": "0.26.0", "@opentelemetry/instrumentation-express": "0.57.0", "@opentelemetry/instrumentation-fs": "0.28.0", "@opentelemetry/instrumentation-generic-pool": "0.52.0", "@opentelemetry/instrumentation-graphql": "0.56.0", "@opentelemetry/instrumentation-hapi": "0.55.0", "@opentelemetry/instrumentation-http": "0.208.0", "@opentelemetry/instrumentation-ioredis": "0.56.0", "@opentelemetry/instrumentation-kafkajs": "0.18.0", "@opentelemetry/instrumentation-knex": "0.53.0", "@opentelemetry/instrumentation-koa": "0.57.0", "@opentelemetry/instrumentation-lru-memoizer": "0.53.0", "@opentelemetry/instrumentation-mongodb": "0.61.0", "@opentelemetry/instrumentation-mongoose": "0.55.0", "@opentelemetry/instrumentation-mysql": "0.54.0", "@opentelemetry/instrumentation-mysql2": "0.55.0", "@opentelemetry/instrumentation-pg": "0.61.0", "@opentelemetry/instrumentation-redis": "0.57.0", "@opentelemetry/instrumentation-tedious": "0.27.0", "@opentelemetry/instrumentation-undici": "0.19.0", "@opentelemetry/resources": "^2.2.0", "@opentelemetry/sdk-trace-base": "^2.2.0", "@opentelemetry/semantic-conventions": "^1.37.0", "@prisma/instrumentation": "6.19.0", "@sentry/core": "10.34.0", "@sentry/node-core": "10.34.0", "@sentry/opentelemetry": "10.34.0", "import-in-the-middle": "^2.0.1", "minimatch": "^9.0.0" } }, "sha512-bEOyH97HuVtWZYAZ5mp0NhYNc+n6QCfiKuLee2P75n2kt4cIPTGvLOSdUwwjllf795uOdKZJuM1IUN0W+YMcVg=="], - - "@sentry/node-core": ["@sentry/node-core@10.34.0", "", { "dependencies": { "@apm-js-collab/tracing-hooks": "^0.3.1", "@sentry/core": "10.34.0", "@sentry/opentelemetry": "10.34.0", "import-in-the-middle": "^2.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0 || ^2.2.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0 || ^2.2.0", "@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/resources": "^1.30.1 || ^2.1.0 || ^2.2.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0 || ^2.2.0", "@opentelemetry/semantic-conventions": "^1.37.0" } }, "sha512-FrGfC8GzD1cnZDO3zwQ4cjyoY1ZwNHvZbXSvXRYxpjhXidZhvaPurjgLRSB0xGaFgoemmOp1ufsx/w6fQOGA6Q=="], - - "@sentry/opentelemetry": ["@sentry/opentelemetry@10.34.0", "", { "dependencies": { "@sentry/core": "10.34.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^1.30.1 || ^2.1.0 || ^2.2.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0 || ^2.2.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0 || ^2.2.0", "@opentelemetry/semantic-conventions": "^1.37.0" } }, "sha512-uKuULBOmdVu3bYdD8doMLqKgN0PP3WWtI7Shu11P9PVrhSNT4U9yM9Z6v1aFlQcbrgyg3LynZuXs8lyjt90UbA=="], - - "@shikijs/core": ["@shikijs/core@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA=="], - - "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ=="], - - "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1" } }, "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA=="], - - "@shikijs/langs": ["@shikijs/langs@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ=="], - - "@shikijs/themes": ["@shikijs/themes@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2" } }, "sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g=="], - - "@shikijs/transformers": ["@shikijs/transformers@3.22.0", "", { "dependencies": { "@shikijs/core": "3.22.0", "@shikijs/types": "3.22.0" } }, "sha512-E7eRV7mwDBjueLF6852n2oYeJYxBq3NSsDk+uyruYAXONv4U8holGmIrT+mPRJQ1J1SNOH6L8G19KRzmBawrFw=="], - - "@shikijs/types": ["@shikijs/types@1.29.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw=="], - - "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], - - "@sindresorhus/is": ["@sindresorhus/is@4.6.0", "", {}, "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw=="], - - "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@szmarczak/http-timer": ["@szmarczak/http-timer@4.0.6", "", { "dependencies": { "defer-to-connect": "^2.0.0" } }, "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w=="], - - "@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="], - - "@tanstack/query-core": ["@tanstack/query-core@5.90.19", "", {}, "sha512-GLW5sjPVIvH491VV1ufddnfldyVB+teCnpPIvweEfkpRx7CfUmUGhoh9cdcUKBh/KwVxk22aNEDxeTsvmyB/WA=="], - - "@tanstack/react-query": ["@tanstack/react-query@5.90.19", "", { "dependencies": { "@tanstack/query-core": "5.90.19" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-qTZRZ4QyTzQc+M0IzrbKHxSeISUmRB3RPGmao5bT+sI6ayxSRhn0FXEnT5Hg3as8SBFcRosrXXRFB+yAcxVxJQ=="], - - "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.18", "", { "dependencies": { "@tanstack/virtual-core": "3.13.18" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A=="], - - "@tanstack/virtual-core": ["@tanstack/virtual-core@3.13.18", "", {}, "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg=="], - - "@tootallnate/once": ["@tootallnate/once@2.0.0", "", {}, "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A=="], - - "@trpc/client": ["@trpc/client@11.8.1", "", { "peerDependencies": { "@trpc/server": "11.8.1", "typescript": ">=5.7.2" } }, "sha512-L/SJFGanr9xGABmuDoeXR4xAdHJmsXsiF9OuH+apecJ+8sUITzVT1EPeqp0ebqA6lBhEl5pPfg3rngVhi/h60Q=="], - - "@trpc/react-query": ["@trpc/react-query@11.8.1", "", { "peerDependencies": { "@tanstack/react-query": "^5.80.3", "@trpc/client": "11.8.1", "@trpc/server": "11.8.1", "react": ">=18.2.0", "react-dom": ">=18.2.0", "typescript": ">=5.7.2" } }, "sha512-0Vu55ld/oINb4U6nIPPi7eZMhxUop6K+4QUK90RVsfSD5r+957sM80M4c8bjh/JBZUxMFv9JOhxxlWcrgHxHow=="], - - "@trpc/server": ["@trpc/server@11.8.1", "", { "peerDependencies": { "typescript": ">=5.7.2" } }, "sha512-P4rzZRpEL7zDFgjxK65IdyH0e41FMFfTkQkuq0BA5tKcr7E6v9/v38DEklCpoDN6sPiB1Sigy/PUEzHENhswDA=="], - - "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], - - "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], - - "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], - - "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - - "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="], - - "@types/cacheable-request": ["@types/cacheable-request@6.0.3", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "^3.1.4", "@types/node": "*", "@types/responselike": "^1.0.0" } }, "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw=="], - - "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], - - "@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], - - "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], - - "@types/d3-axis": ["@types/d3-axis@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="], - - "@types/d3-brush": ["@types/d3-brush@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="], - - "@types/d3-chord": ["@types/d3-chord@3.0.6", "", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="], - - "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], - - "@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="], - - "@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="], - - "@types/d3-dispatch": ["@types/d3-dispatch@3.0.7", "", {}, "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA=="], - - "@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="], - - "@types/d3-dsv": ["@types/d3-dsv@3.0.7", "", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="], - - "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], - - "@types/d3-fetch": ["@types/d3-fetch@3.0.7", "", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="], - - "@types/d3-force": ["@types/d3-force@3.0.10", "", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="], - - "@types/d3-format": ["@types/d3-format@3.0.4", "", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="], - - "@types/d3-geo": ["@types/d3-geo@3.1.0", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ=="], - - "@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="], - - "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], - - "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], - - "@types/d3-polygon": ["@types/d3-polygon@3.0.2", "", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="], - - "@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="], - - "@types/d3-random": ["@types/d3-random@3.0.3", "", {}, "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ=="], - - "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], - - "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="], - - "@types/d3-selection": ["@types/d3-selection@3.0.11", "", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="], - - "@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="], - - "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], - - "@types/d3-time-format": ["@types/d3-time-format@4.0.3", "", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="], - - "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], - - "@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="], - - "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="], - - "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], - - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - - "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], - - "@types/fs-extra": ["@types/fs-extra@9.0.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA=="], - - "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], - - "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], - - "@types/http-cache-semantics": ["@types/http-cache-semantics@4.0.4", "", {}, "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA=="], - - "@types/keyv": ["@types/keyv@3.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg=="], - - "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], - - "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - - "@types/mysql": ["@types/mysql@2.15.27", "", { "dependencies": { "@types/node": "*" } }, "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA=="], - - "@types/node": ["@types/node@20.19.30", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g=="], - - "@types/pg": ["@types/pg@8.15.6", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ=="], - - "@types/pg-pool": ["@types/pg-pool@2.0.6", "", { "dependencies": { "@types/pg": "*" } }, "sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ=="], - - "@types/plist": ["@types/plist@3.0.5", "", { "dependencies": { "@types/node": "*", "xmlbuilder": ">=11.0.1" } }, "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA=="], - - "@types/react": ["@types/react@19.2.9", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA=="], - - "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - - "@types/responselike": ["@types/responselike@1.0.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw=="], - - "@types/tedious": ["@types/tedious@4.0.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw=="], - - "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], - - "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], - - "@types/verror": ["@types/verror@1.10.11", "", {}, "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg=="], - - "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], - - "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], - - "@vercel/oidc": ["@vercel/oidc@3.1.0", "", {}, "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w=="], - - "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], - - "@vue/reactivity": ["@vue/reactivity@3.5.27", "", { "dependencies": { "@vue/shared": "3.5.27" } }, "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ=="], - - "@vue/shared": ["@vue/shared@3.5.27", "", {}, "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ=="], - - "@welldone-software/why-did-you-render": ["@welldone-software/why-did-you-render@10.0.1", "", { "dependencies": { "lodash": "^4" }, "peerDependencies": { "react": "^19" } }, "sha512-tMgGkt30iVYeLMUKExNmtm019QgyjLtA7lwB0QAizYNEuihlCG2eoAWBBaz/bDeI7LeqAJ9msC6hY3vX+JB97g=="], - - "@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="], - - "@xterm/addon-canvas": ["@xterm/addon-canvas@0.7.0", "", { "peerDependencies": { "@xterm/xterm": "^5.0.0" } }, "sha512-LF5LYcfvefJuJ7QotNRdRSPc9YASAVDeoT5uyXS/nZshZXjYplGXRECBGiznwvhNL2I8bq1Lf5MzRwstsYQ2Iw=="], - - "@xterm/addon-fit": ["@xterm/addon-fit@0.11.0", "", {}, "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g=="], - - "@xterm/addon-search": ["@xterm/addon-search@0.16.0", "", {}, "sha512-9OeuBFu0/uZJPu+9AHKY6g/w0Czyb/Ut0A5t79I4ULoU4IfU5BEpPFVGQxP4zTTMdfZEYkVIRYbHBX1xWwjeSA=="], - - "@xterm/addon-serialize": ["@xterm/addon-serialize@0.14.0", "", {}, "sha512-uteyTU1EkrQa2Ux6P/uFl2fzmXI46jy5uoQMKEOM0fKTyiW7cSn0WrFenHm5vO5uEXX/GpwW/FgILvv3r0WbkA=="], - - "@xterm/addon-web-links": ["@xterm/addon-web-links@0.12.0", "", {}, "sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw=="], - - "@xterm/addon-webgl": ["@xterm/addon-webgl@0.19.0", "", {}, "sha512-b3fMOsyLVuCeNJWxolACEUED0vm7qC0cy4wRvf3oURSzDTYVQiGPhTnhWZwIHdvC48Y+oLhvYXnY4XDXPoJo6A=="], - - "@xterm/xterm": ["@xterm/xterm@5.5.0", "", {}, "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A=="], - - "abbrev": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="], - - "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], - - "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], - - "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], - - "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], - - "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], - - "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="], - - "ai": ["ai@6.0.45", "", { "dependencies": { "@ai-sdk/gateway": "3.0.19", "@ai-sdk/provider": "3.0.4", "@ai-sdk/provider-utils": "4.0.8", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8QFU1cFY25Z7M72gpw4pzkvyEXK4X1oO3ZRUQYofU4hs742rt+NMAmgNn2uZo/cMtEH5Dt1Vlu/fMQXmLnRoZQ=="], - - "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], - - "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], - - "ajv-keywords": ["ajv-keywords@3.5.2", "", { "peerDependencies": { "ajv": "^6.9.1" } }, "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ=="], - - "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - - "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], - - "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], - - "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], - - "app-builder-bin": ["app-builder-bin@5.0.0-alpha.10", "", {}, "sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw=="], - - "app-builder-lib": ["app-builder-lib@25.1.8", "", { "dependencies": { "@develar/schema-utils": "~2.6.5", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.1", "@electron/rebuild": "3.6.1", "@electron/universal": "2.0.1", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "bluebird-lst": "^1.0.9", "builder-util": "25.1.7", "builder-util-runtime": "9.2.10", "chromium-pickle-js": "^0.2.0", "config-file-ts": "0.2.8-rc1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "25.1.7", "form-data": "^4.0.0", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "is-ci": "^3.0.0", "isbinaryfile": "^5.0.0", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.0.0", "resedit": "^1.7.0", "sanitize-filename": "^1.6.3", "semver": "^7.3.8", "tar": "^6.1.12", "temp-file": "^3.4.0" }, "peerDependencies": { "dmg-builder": "25.1.8", "electron-builder-squirrel-windows": "25.1.8" } }, "sha512-pCqe7dfsQFBABC1jeKZXQWhGcCPF3rPCXDdfqVKjIeWBcXzyC1iOWZdfFhGl+S9MyE/k//DFmC6FzuGAUudNDg=="], - - "aproba": ["aproba@2.1.0", "", {}, "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew=="], - - "archiver": ["archiver@5.3.2", "", { "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", "buffer-crc32": "^0.2.1", "readable-stream": "^3.6.0", "readdir-glob": "^1.1.2", "tar-stream": "^2.2.0", "zip-stream": "^4.1.0" } }, "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw=="], - - "archiver-utils": ["archiver-utils@2.1.0", "", { "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^2.0.0" } }, "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw=="], - - "are-we-there-yet": ["are-we-there-yet@3.0.1", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg=="], - - "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], - - "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - - "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], - - "assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="], - - "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], - - "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], - - "async-exit-hook": ["async-exit-hook@2.0.1", "", {}, "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw=="], - - "async-mutex": ["async-mutex@0.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA=="], - - "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], - - "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="], - - "autoprefixer": ["autoprefixer@10.4.23", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-lite": "^1.0.30001760", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA=="], - - "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], - - "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - - "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - - "baseline-browser-mapping": ["baseline-browser-mapping@2.9.17", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ=="], - - "better-sqlite3": ["better-sqlite3@11.10.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ=="], - - "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], - - "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], - - "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], - - "bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="], - - "bluebird-lst": ["bluebird-lst@1.0.9", "", { "dependencies": { "bluebird": "^3.5.5" } }, "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw=="], - - "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], - - "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], - - "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], - - "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - - "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], - - "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], - - "builder-util": ["builder-util@25.1.7", "", { "dependencies": { "7zip-bin": "~5.2.0", "@types/debug": "^4.1.6", "app-builder-bin": "5.0.0-alpha.10", "bluebird-lst": "^1.0.9", "builder-util-runtime": "9.2.10", "chalk": "^4.1.2", "cross-spawn": "^7.0.3", "debug": "^4.3.4", "fs-extra": "^10.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "is-ci": "^3.0.0", "js-yaml": "^4.1.0", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0" } }, "sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww=="], - - "builder-util-runtime": ["builder-util-runtime@9.2.10", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw=="], - - "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], - - "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], - - "cacache": ["cacache@16.1.3", "", { "dependencies": { "@npmcli/fs": "^2.1.0", "@npmcli/move-file": "^2.0.0", "chownr": "^2.0.0", "fs-minipass": "^2.1.0", "glob": "^8.0.1", "infer-owner": "^1.0.4", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "mkdirp": "^1.0.4", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^9.0.0", "tar": "^6.1.11", "unique-filename": "^2.0.0" } }, "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ=="], - - "cacheable-lookup": ["cacheable-lookup@5.0.4", "", {}, "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="], - - "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="], - - "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], - - "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], - - "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001765", "", {}, "sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ=="], - - "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], - - "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - - "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], - - "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], - - "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], - - "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], - - "chevrotain": ["chevrotain@11.0.3", "", { "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", "@chevrotain/regexp-to-ast": "11.0.3", "@chevrotain/types": "11.0.3", "@chevrotain/utils": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw=="], - - "chevrotain-allstar": ["chevrotain-allstar@0.3.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="], - - "chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], - - "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], - - "chromium-pickle-js": ["chromium-pickle-js@0.2.0", "", {}, "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw=="], - - "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], - - "cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="], - - "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], - - "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], - - "cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="], - - "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], - - "cli-truncate": ["cli-truncate@2.1.0", "", { "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" } }, "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg=="], - - "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - - "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="], - - "clone-response": ["clone-response@1.0.3", "", { "dependencies": { "mimic-response": "^1.0.0" } }, "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA=="], - - "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], - - "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], - - "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], - - "color-support": ["color-support@1.1.3", "", { "bin": { "color-support": "bin.js" } }, "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="], - - "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], - - "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], - - "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], - - "compare-version": ["compare-version@0.1.2", "", {}, "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A=="], - - "compress-commons": ["compress-commons@4.1.2", "", { "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg=="], - - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - - "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], - - "config-file-ts": ["config-file-ts@0.2.8-rc1", "", { "dependencies": { "glob": "^10.3.12", "typescript": "^5.4.3" } }, "sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg=="], - - "console-control-strings": ["console-control-strings@1.1.0", "", {}, "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="], - - "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], - - "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], - - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - - "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], - - "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], - - "copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="], - - "core-js": ["core-js@3.48.0", "", {}, "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ=="], - - "core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="], - - "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], - - "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], - - "crc": ["crc@3.8.0", "", { "dependencies": { "buffer": "^5.1.0" } }, "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ=="], - - "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], - - "crc32-stream": ["crc32-stream@4.0.3", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" } }, "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw=="], - - "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], - - "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="], - - "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], - - "cytoscape-fcose": ["cytoscape-fcose@2.2.0", "", { "dependencies": { "cose-base": "^2.2.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ=="], - - "d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="], - - "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], - - "d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="], - - "d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="], - - "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], - - "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], - - "d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="], - - "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], - - "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], - - "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], - - "d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], - - "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], - - "d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="], - - "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], - - "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], - - "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], - - "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], - - "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], - - "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], - - "d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="], - - "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], - - "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], - - "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="], - - "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], - - "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], - - "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], - - "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], - - "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], - - "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], - - "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], - - "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], - - "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], - - "dagre-d3-es": ["dagre-d3-es@7.0.13", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q=="], - - "date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], - - "dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], - - "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], - - "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], - - "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="], - - "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], - - "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], - - "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], - - "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="], - - "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], - - "delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="], - - "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], - - "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], - - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - - "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], - - "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], - - "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], - - "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="], - - "diff": ["diff@8.0.3", "", {}, "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ=="], - - "dir-compare": ["dir-compare@4.2.0", "", { "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ=="], - - "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="], - - "dmg-builder": ["dmg-builder@25.1.8", "", { "dependencies": { "app-builder-lib": "25.1.8", "builder-util": "25.1.7", "builder-util-runtime": "9.2.10", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" }, "optionalDependencies": { "dmg-license": "^1.0.11" } }, "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ=="], - - "dmg-license": ["dmg-license@1.0.11", "", { "dependencies": { "@types/plist": "^3.0.1", "@types/verror": "^1.10.3", "ajv": "^6.10.0", "crc": "^3.8.0", "iconv-corefoundation": "^1.1.7", "plist": "^3.0.4", "smart-buffer": "^4.0.2", "verror": "^1.10.0" }, "os": "darwin", "bin": { "dmg-license": "bin/dmg-license.js" } }, "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q=="], - - "dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="], - - "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], - - "dotenv-expand": ["dotenv-expand@11.0.7", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA=="], - - "drizzle-kit": ["drizzle-kit@0.31.8", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg=="], - - "drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="], - - "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - - "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - - "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - - "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="], - - "electron": ["electron@33.4.5", "", { "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^20.9.0", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" } }, "sha512-rbDc4QOqfMT1uopUG+KcaMKzKgFAXAzN3wNIdgErnB1tUnpgTxwFv1BDN/exCl1vaVWBeM9YtbO5PgbGZeq7xw=="], - - "electron-builder": ["electron-builder@25.1.8", "", { "dependencies": { "app-builder-lib": "25.1.8", "builder-util": "25.1.7", "builder-util-runtime": "9.2.10", "chalk": "^4.1.2", "dmg-builder": "25.1.8", "fs-extra": "^10.1.0", "is-ci": "^3.0.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "cli.js", "install-app-deps": "install-app-deps.js" } }, "sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig=="], - - "electron-builder-squirrel-windows": ["electron-builder-squirrel-windows@25.1.8", "", { "dependencies": { "app-builder-lib": "25.1.8", "archiver": "^5.3.1", "builder-util": "25.1.7", "fs-extra": "^10.1.0" } }, "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg=="], - - "electron-log": ["electron-log@5.4.3", "", {}, "sha512-sOUsM3LjZdugatazSQ/XTyNcw8dfvH1SYhXWiJyfYodAAKOZdHs0txPiLDXFzOZbhXgAgshQkshH2ccq0feyLQ=="], - - "electron-publish": ["electron-publish@25.1.7", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "builder-util": "25.1.7", "builder-util-runtime": "9.2.10", "chalk": "^4.1.2", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg=="], - - "electron-rebuild": ["electron-rebuild@3.2.9", "", { "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "chalk": "^4.0.0", "debug": "^4.1.1", "detect-libc": "^2.0.1", "fs-extra": "^10.0.0", "got": "^11.7.0", "lzma-native": "^8.0.5", "node-abi": "^3.0.0", "node-api-version": "^0.1.4", "node-gyp": "^9.0.0", "ora": "^5.1.0", "semver": "^7.3.5", "tar": "^6.0.5", "yargs": "^17.0.1" }, "bin": { "electron-rebuild": "lib/src/cli.js" } }, "sha512-FkEZNFViUem3P0RLYbZkUjC8LUFIK+wKq09GHoOITSJjfDAVQv964hwaNseTTWt58sITQX3/5fHNYcTefqaCWw=="], - - "electron-to-chromium": ["electron-to-chromium@1.5.277", "", {}, "sha512-wKXFZw4erWmmOz5N/grBoJ2XrNJGDFMu2+W5ACHza5rHtvsqrK4gb6rnLC7XxKB9WlJ+RmyQatuEXmtm86xbnw=="], - - "electron-updater": ["electron-updater@6.7.3", "", { "dependencies": { "builder-util-runtime": "9.5.1", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", "semver": "~7.7.3", "tiny-typed-emitter": "^2.1.0" } }, "sha512-EgkT8Z9noqXKbwc3u5FkJA+r48jwZ5DTUiOkJMOTEEH//n5Am6wfQGz7nvSFEA2oIAMv9jRzn5JKTyWeSKOPgg=="], - - "electron-vite": ["electron-vite@3.1.0", "", { "dependencies": { "@babel/core": "^7.26.10", "@babel/plugin-transform-arrow-functions": "^7.25.9", "cac": "^6.7.14", "esbuild": "^0.25.1", "magic-string": "^0.30.17", "picocolors": "^1.1.1" }, "peerDependencies": { "@swc/core": "^1.0.0", "vite": "^4.0.0 || ^5.0.0 || ^6.0.0" }, "optionalPeers": ["@swc/core"], "bin": { "electron-vite": "bin/electron-vite.js" } }, "sha512-M7aAzaRvSl5VO+6KN4neJCYLHLpF/iWo5ztchI/+wMxIieDZQqpbCYfaEHHHPH6eupEzfvZdLYdPdmvGqoVe0Q=="], - - "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], - - "emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="], - - "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - - "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], - - "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], - - "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - - "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], - - "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], - - "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], - - "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], - - "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], - - "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], - - "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], - - "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], - - "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], - - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], - - "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], - - "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="], - - "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], - - "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], - - "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], - - "eventsource-parser": ["eventsource-parser@3.0.6", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="], - - "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], - - "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="], - - "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], - - "express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="], - - "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], - - "extend-shallow": ["extend-shallow@2.0.1", "", { "dependencies": { "is-extendable": "^0.1.0" } }, "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug=="], - - "extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="], - - "extsprintf": ["extsprintf@1.4.1", "", {}, "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA=="], - - "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - - "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], - - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], - - "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], - - "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], - - "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], - - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], - - "fflate": ["fflate@0.4.8", "", {}, "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="], - - "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], - - "filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="], - - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - - "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], - - "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], - - "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], - - "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], - - "forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="], - - "fraction.js": ["fraction.js@5.3.4", "", {}, "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ=="], - - "framer-motion": ["framer-motion@11.18.2", "", { "dependencies": { "motion-dom": "^11.18.1", "motion-utils": "^11.18.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w=="], - - "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], - - "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], - - "fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], - - "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], - - "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], - - "gauge": ["gauge@4.0.4", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^3.0.7", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg=="], - - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - - "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], - - "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], - - "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], - - "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - - "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], - - "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], - - "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], - - "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - - "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], - - "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], - - "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], - - "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], - - "got": ["got@11.8.6", "", { "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" } }, "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g=="], - - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - - "gray-matter": ["gray-matter@4.0.3", "", { "dependencies": { "js-yaml": "^3.13.1", "kind-of": "^6.0.2", "section-matter": "^1.0.0", "strip-bom-string": "^1.0.0" } }, "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q=="], - - "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], - - "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], - - "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], - - "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], - - "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], - - "has-unicode": ["has-unicode@2.0.1", "", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="], - - "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - - "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="], - - "hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], - - "hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], - - "hast-util-sanitize": ["hast-util-sanitize@5.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "unist-util-position": "^5.0.0" } }, "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg=="], - - "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], - - "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], - - "hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="], - - "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], - - "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], - - "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], - - "hono": ["hono@4.11.5", "", {}, "sha512-WemPi9/WfyMwZs+ZUXdiwcCh9Y+m7L+8vki9MzDw3jJ+W9Lc+12HGsd368Qc1vZi1xwW8BWMMsnK5efYKPdt4g=="], - - "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], - - "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], - - "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], - - "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], - - "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], - - "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], - - "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], - - "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], - - "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], - - "iconv-corefoundation": ["iconv-corefoundation@1.1.7", "", { "dependencies": { "cli-truncate": "^2.1.0", "node-addon-api": "^1.6.3" }, "os": "darwin" }, "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ=="], - - "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], - - "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "import-in-the-middle": ["import-in-the-middle@2.0.5", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-0InH9/4oDCBRzWXhpOqusspLBrVfK1vPvbn9Wxl8DAQ8yyx5fWJRETICSwkiAMaYntjJAMBP1R4B6cQnEUYVEA=="], - - "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], - - "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], - - "infer-owner": ["infer-owner@1.0.4", "", {}, "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="], - - "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], - - "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - - "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], - - "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], - - "internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], - - "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], - - "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], - - "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], - - "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], - - "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], - - "is-ci": ["is-ci@3.0.1", "", { "dependencies": { "ci-info": "^3.2.0" }, "bin": { "is-ci": "bin.js" } }, "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ=="], - - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], - - "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], - - "is-extendable": ["is-extendable@0.1.1", "", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="], - - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], - - "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - - "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], - - "is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], - - "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], - - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - - "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], - - "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], - - "is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], - - "is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="], - - "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], - - "isbinaryfile": ["isbinaryfile@5.0.7", "", {}, "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ=="], - - "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], - - "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="], - - "jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="], - - "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], - - "jotai": ["jotai@2.16.2", "", { "peerDependencies": { "@babel/core": ">=7.0.0", "@babel/template": ">=7.0.0", "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@babel/core", "@babel/template", "@types/react", "react"] }, "sha512-DH0lBiTXvewsxtqqwjDW6Hg9JPTDnq9LcOsXSFWCAUEt+qj5ohl9iRVX9zQXPPHKLXCdH+5mGvM28fsXMl17/g=="], - - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - - "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], - - "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - - "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], - - "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], - - "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], - - "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], - - "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - - "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], - - "jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - - "katex": ["katex@0.16.27", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw=="], - - "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], - - "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], - - "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], - - "langium": ["langium@3.3.1", "", { "dependencies": { "chevrotain": "~11.0.3", "chevrotain-allstar": "~0.3.0", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.0.8" } }, "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w=="], - - "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], - - "lazy-val": ["lazy-val@1.0.5", "", {}, "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q=="], - - "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], - - "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="], - - "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="], - - "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], - - "lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="], - - "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], - - "lodash.difference": ["lodash.difference@4.5.0", "", {}, "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="], - - "lodash.escaperegexp": ["lodash.escaperegexp@4.1.2", "", {}, "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw=="], - - "lodash.flatten": ["lodash.flatten@4.4.0", "", {}, "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="], - - "lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="], - - "lodash.isplainobject": ["lodash.isplainobject@4.0.6", "", {}, "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="], - - "lodash.union": ["lodash.union@4.6.0", "", {}, "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="], - - "log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], - - "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], - - "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], - - "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], - - "lowlight": ["lowlight@3.3.0", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.0.0", "highlight.js": "~11.11.0" } }, "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ=="], - - "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - - "lru_map": ["lru_map@0.4.1", "", {}, "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg=="], - - "lucide-react": ["lucide-react@0.468.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA=="], - - "lzma-native": ["lzma-native@8.0.6", "", { "dependencies": { "node-addon-api": "^3.1.0", "node-gyp-build": "^4.2.1", "readable-stream": "^3.6.0" }, "bin": { "lzmajs": "bin/lzmajs" } }, "sha512-09xfg67mkL2Lz20PrrDeNYZxzeW7ADtpYFbwSQh9U8+76RIzx5QsJBMy8qikv3hbUPfpy6hqwxt6FcGK81g9AA=="], - - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "make-fetch-happen": ["make-fetch-happen@10.2.1", "", { "dependencies": { "agentkeepalive": "^4.2.1", "cacache": "^16.1.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^7.7.1", "minipass": "^3.1.6", "minipass-collect": "^1.0.2", "minipass-fetch": "^2.0.3", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", "promise-retry": "^2.0.1", "socks-proxy-agent": "^7.0.0", "ssri": "^9.0.0" } }, "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w=="], - - "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], - - "marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], - - "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], - - "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], - - "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], - - "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], - - "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], - - "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], - - "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], - - "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], - - "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], - - "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], - - "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], - - "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="], - - "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], - - "mdast-util-newline-to-break": ["mdast-util-newline-to-break@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-find-and-replace": "^3.0.0" } }, "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog=="], - - "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], - - "mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="], - - "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], - - "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], - - "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], - - "merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], - - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - - "mermaid": ["mermaid@11.12.2", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "^0.6.3", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.13", "dayjs": "^1.11.18", "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.21", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w=="], - - "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], - - "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], - - "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], - - "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], - - "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], - - "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], - - "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], - - "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], - - "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], - - "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], - - "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], - - "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], - - "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], - - "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], - - "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], - - "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], - - "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], - - "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], - - "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], - - "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], - - "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], - - "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], - - "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], - - "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], - - "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], - - "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], - - "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], - - "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], - - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - - "mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], - - "mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], - - "mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], - - "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], - - "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], - - "minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="], - - "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - - "minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], - - "minipass-collect": ["minipass-collect@1.0.2", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="], - - "minipass-fetch": ["minipass-fetch@2.1.2", "", { "dependencies": { "minipass": "^3.1.6", "minipass-sized": "^1.0.3", "minizlib": "^2.1.2" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA=="], - - "minipass-flush": ["minipass-flush@1.0.5", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw=="], - - "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], - - "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], - - "minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], - - "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], - - "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], - - "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], - - "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], - - "monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="], - - "motion": ["motion@11.18.2", "", { "dependencies": { "framer-motion": "^11.18.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-JLjvFDuFr42NFtcVoMAyC2sEjnpA8xpy6qWPyzQvCloznAyQ8FIXioxWfHiLtgYhoVpfUqSWpn1h9++skj9+Wg=="], - - "motion-dom": ["motion-dom@11.18.1", "", { "dependencies": { "motion-utils": "^11.18.1" } }, "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw=="], - - "motion-utils": ["motion-utils@11.18.1", "", {}, "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], - - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], - - "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], - - "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], - - "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="], - - "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], - - "node-api-version": ["node-api-version@0.1.4", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-KGXihXdUChwJAOHO53bv9/vXcLmdUsZ6jIptbvYvkpKfth+r7jw44JkVxQFA3kX5nQjzjmGu1uAu/xNNLNlI5g=="], - - "node-gyp": ["node-gyp@9.4.1", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "glob": "^7.1.4", "graceful-fs": "^4.2.6", "make-fetch-happen": "^10.0.3", "nopt": "^6.0.0", "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.2", "which": "^2.0.2" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ=="], - - "node-gyp-build": ["node-gyp-build@4.8.4", "", { "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", "node-gyp-build-test": "build-test.js" } }, "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ=="], - - "node-pty": ["node-pty@1.1.0", "", { "dependencies": { "node-addon-api": "^7.1.0" } }, "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg=="], - - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], - - "nopt": ["nopt@6.0.0", "", { "dependencies": { "abbrev": "^1.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g=="], - - "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], - - "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="], - - "npmlog": ["npmlog@6.0.2", "", { "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", "gauge": "^4.0.3", "set-blocking": "^2.0.0" } }, "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg=="], - - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - - "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="], - - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], - - "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], - - "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], - - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - - "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], - - "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="], - - "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], - - "ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], - - "p-cancelable": ["p-cancelable@2.1.1", "", {}, "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg=="], - - "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - - "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], - - "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], - - "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], - - "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], - - "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - - "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], - - "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], - - "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], - - "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], - - "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], - - "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - - "path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], - - "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], - - "pe-library": ["pe-library@0.4.1", "", {}, "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw=="], - - "pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="], - - "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="], - - "pg-protocol": ["pg-protocol@1.11.0", "", {}, "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g=="], - - "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], - - "pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], - - "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], - - "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="], - - "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], - - "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], - - "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], - - "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], - - "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], - - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - - "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="], - - "postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="], - - "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], - - "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="], - - "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], - - "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], - - "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="], - - "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="], - - "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="], - - "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], - - "posthog-js": ["posthog-js@1.333.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.208.0", "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", "@opentelemetry/resources": "^2.2.0", "@opentelemetry/sdk-logs": "^0.208.0", "@posthog/core": "1.13.0", "@posthog/types": "1.333.0", "core-js": "^3.38.1", "dompurify": "^3.3.1", "fflate": "^0.4.8", "preact": "^10.28.0", "query-selector-shadow-dom": "^1.0.1", "web-vitals": "^4.2.4" } }, "sha512-c7vquERMedjuGE2GnaDDJW/V1BIMMQG7BlYKrH0z8O7fc3WpEsQ/IyQ+9aD9+DLxlDCFpzrwgoxVDWi9K37mdA=="], - - "posthog-node": ["posthog-node@5.24.1", "", { "dependencies": { "@posthog/core": "1.13.0" } }, "sha512-1+wsosb5fjuor9zpp3h2uq0xKYY7rDz8gpw/10Scz8Ob/uVNrsHSwGy76D9rgt4cfyaEgpJwyYv+hPi2+YjWtw=="], - - "preact": ["preact@10.28.2", "", {}, "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA=="], - - "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], - - "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], - - "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="], - - "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="], - - "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], - - "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], - - "protobufjs": ["protobufjs@7.5.4", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg=="], - - "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], - - "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], - - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - - "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="], - - "query-selector-shadow-dom": ["query-selector-shadow-dom@1.0.1", "", {}, "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw=="], - - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - - "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], - - "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], - - "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], - - "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], - - "react": ["react@19.2.1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="], - - "react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="], - - "react-hotkeys-hook": ["react-hotkeys-hook@4.6.2", "", { "peerDependencies": { "react": ">=16.8.1", "react-dom": ">=16.8.1" } }, "sha512-FmP+ZriY3EG59Ug/lxNfrObCnW9xQShgk7Nb83+CkpfkcCpfS95ydv+E9JuXA5cp8KtskU7LGlIARpkc92X22Q=="], - - "react-icons": ["react-icons@5.5.0", "", { "peerDependencies": { "react": "*" } }, "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw=="], - - "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], - - "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], - - "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], - - "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], - - "react-zoom-pan-pinch": ["react-zoom-pan-pinch@3.7.0", "", { "peerDependencies": { "react": "*", "react-dom": "*" } }, "sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA=="], - - "reactivity-store": ["reactivity-store@0.3.12", "", { "dependencies": { "@vue/reactivity": "~3.5.22", "@vue/shared": "~3.5.22", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Idz9EL4dFUtQbHySZQzckWOTUfqjdYpUtNW0iOysC32mG7IjiUGB77QrsyR5eAWBkRiS9JscF6A3fuQAIy+LrQ=="], - - "read-binary-file-arch": ["read-binary-file-arch@1.0.6", "", { "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg=="], - - "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], - - "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - - "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="], - - "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], - - "regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="], - - "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], - - "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], - - "rehype-harden": ["rehype-harden@1.1.7", "", { "dependencies": { "unist-util-visit": "^5.0.0" } }, "sha512-j5DY0YSK2YavvNGV+qBHma15J9m0WZmRe8posT5AtKDS6TNWtMVTo6RiqF8SidfcASYz8f3k2J/1RWmq5zTXUw=="], - - "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], - - "rehype-sanitize": ["rehype-sanitize@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-sanitize": "^5.0.0" } }, "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg=="], - - "remark-breaks": ["remark-breaks@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-newline-to-break": "^2.0.0", "unified": "^11.0.0" } }, "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ=="], - - "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], - - "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], - - "remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], - - "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], - - "remend": ["remend@1.1.0", "", {}, "sha512-JENGyuIhTwzUfCarW43X4r9cehoqTo9QyYxfNDZSud2AmqeuWjZ5pfybasTa4q0dxTJAj5m8NB+wR+YueAFpxQ=="], - - "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], - - "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], - - "require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="], - - "resedit": ["resedit@1.7.2", "", { "dependencies": { "pe-library": "^0.4.1" } }, "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA=="], - - "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], - - "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], - - "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], - - "responselike": ["responselike@2.0.1", "", { "dependencies": { "lowercase-keys": "^2.0.0" } }, "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw=="], - - "restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="], - - "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], - - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - - "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], - - "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], - - "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], - - "rollup": ["rollup@4.56.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.56.0", "@rollup/rollup-android-arm64": "4.56.0", "@rollup/rollup-darwin-arm64": "4.56.0", "@rollup/rollup-darwin-x64": "4.56.0", "@rollup/rollup-freebsd-arm64": "4.56.0", "@rollup/rollup-freebsd-x64": "4.56.0", "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", "@rollup/rollup-linux-arm-musleabihf": "4.56.0", "@rollup/rollup-linux-arm64-gnu": "4.56.0", "@rollup/rollup-linux-arm64-musl": "4.56.0", "@rollup/rollup-linux-loong64-gnu": "4.56.0", "@rollup/rollup-linux-loong64-musl": "4.56.0", "@rollup/rollup-linux-ppc64-gnu": "4.56.0", "@rollup/rollup-linux-ppc64-musl": "4.56.0", "@rollup/rollup-linux-riscv64-gnu": "4.56.0", "@rollup/rollup-linux-riscv64-musl": "4.56.0", "@rollup/rollup-linux-s390x-gnu": "4.56.0", "@rollup/rollup-linux-x64-gnu": "4.56.0", "@rollup/rollup-linux-x64-musl": "4.56.0", "@rollup/rollup-openbsd-x64": "4.56.0", "@rollup/rollup-openharmony-arm64": "4.56.0", "@rollup/rollup-win32-arm64-msvc": "4.56.0", "@rollup/rollup-win32-ia32-msvc": "4.56.0", "@rollup/rollup-win32-x64-gnu": "4.56.0", "@rollup/rollup-win32-x64-msvc": "4.56.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg=="], - - "roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], - - "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], - - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - - "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], - - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - - "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - - "sanitize-filename": ["sanitize-filename@1.6.3", "", { "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg=="], - - "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], - - "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "section-matter": ["section-matter@1.0.0", "", { "dependencies": { "extend-shallow": "^2.0.1", "kind-of": "^6.0.0" } }, "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA=="], - - "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - - "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], - - "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], - - "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], - - "serve-static": ["serve-static@2.2.1", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], - - "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], - - "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], - - "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], - - "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], - - "shiki": ["shiki@1.29.2", "", { "dependencies": { "@shikijs/core": "1.29.2", "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/langs": "1.29.2", "@shikijs/themes": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4" } }, "sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg=="], - - "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], - - "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], - - "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], - - "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], - - "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - - "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], - - "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], - - "simple-git": ["simple-git@3.30.0", "", { "dependencies": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", "debug": "^4.4.0" } }, "sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg=="], - - "simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="], - - "slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="], - - "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], - - "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], - - "socks-proxy-agent": ["socks-proxy-agent@7.0.0", "", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww=="], - - "sonner": ["sonner@1.7.4", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw=="], - - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], - - "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], - - "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="], - - "ssri": ["ssri@9.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q=="], - - "stat-mode": ["stat-mode@1.0.0", "", {}, "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg=="], - - "state-local": ["state-local@1.0.7", "", {}, "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="], - - "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], - - "streamdown": ["streamdown@2.1.0", "", { "dependencies": { "clsx": "^2.1.1", "hast-util-to-jsx-runtime": "^2.3.6", "html-url-attributes": "^3.0.1", "marked": "^17.0.1", "rehype-harden": "^1.1.7", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remend": "1.1.0", "tailwind-merge": "^3.4.0", "unified": "^11.0.5", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-u9gWd0AmjKg1d+74P44XaPlGrMeC21oDOSIhjGNEYMAttDMzCzlJO6lpTyJ9JkSinQQF65YcK4eOd3q9iTvULw=="], - - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], - - "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], - - "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], - - "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - - "strip-bom-string": ["strip-bom-string@1.0.0", "", {}, "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g=="], - - "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], - - "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], - - "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], - - "stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="], - - "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], - - "sumchecker": ["sumchecker@3.0.1", "", { "dependencies": { "debug": "^4.1.0" } }, "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg=="], - - "superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="], - - "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - - "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - - "swr": ["swr@2.3.8", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w=="], - - "tailwind-merge": ["tailwind-merge@2.6.0", "", {}, "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="], - - "tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="], - - "tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="], - - "tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], - - "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], - - "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], - - "temp-file": ["temp-file@3.4.0", "", { "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" } }, "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg=="], - - "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="], - - "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="], - - "throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="], - - "tiny-typed-emitter": ["tiny-typed-emitter@2.1.0", "", {}, "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="], - - "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], - - "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], - - "tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="], - - "tmp-promise": ["tmp-promise@3.0.3", "", { "dependencies": { "tmp": "^0.2.0" } }, "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ=="], - - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - - "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], - - "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], - - "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], - - "trpc-electron": ["trpc-electron@0.1.2", "", { "peerDependencies": { "@trpc/client": ">=11.0.0", "@trpc/server": ">=11.0.0", "electron": ">19.0.0" } }, "sha512-sQpWBwQWzsgrERugjzUpPqY/+/n8NxkUq6YssQ5+5rALkvGCWq45T5Dreiwm2kh91dZMFlALTyMd8PhB0vgbIg=="], - - "truncate-utf8-bytes": ["truncate-utf8-bytes@1.0.2", "", { "dependencies": { "utf8-byte-length": "^1.0.1" } }, "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ=="], - - "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], - - "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], - - "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], - - "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], - - "unique-filename": ["unique-filename@2.0.1", "", { "dependencies": { "unique-slug": "^3.0.0" } }, "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A=="], - - "unique-names-generator": ["unique-names-generator@4.7.1", "", {}, "sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow=="], - - "unique-slug": ["unique-slug@3.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w=="], - - "unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="], - - "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], - - "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], - - "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], - - "unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="], - - "universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - - "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], - - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], - - "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - - "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], - - "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], - - "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - - "utf8-byte-length": ["utf8-byte-length@1.0.5", "", {}, "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA=="], - - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - - "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], - - "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], - - "verror": ["verror@1.10.1", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg=="], - - "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], - - "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], - - "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - - "vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], - - "vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="], - - "vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="], - - "vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="], - - "vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="], - - "vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], - - "vscode-uri": ["vscode-uri@3.0.8", "", {}, "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="], - - "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="], - - "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], - - "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="], - - "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], - - "wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="], - - "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - - "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], - - "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], - - "xterm": ["xterm@5.3.0", "", {}, "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg=="], - - "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - - "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - - "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], - - "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - - "yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], - - "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], - - "zip-stream": ["zip-stream@4.1.1", "", { "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", "readable-stream": "^3.6.0" } }, "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ=="], - - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], - - "zustand": ["zustand@5.0.10", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg=="], - - "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - - "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@chevrotain/cst-dts-gen/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], - - "@chevrotain/gast/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], - - "@develar/schema-utils/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], - - "@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], - - "@electron/asar/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "@electron/get/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], - - "@electron/get/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@electron/notarize/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], - - "@electron/osx-sign/isbinaryfile": ["isbinaryfile@4.0.10", "", {}, "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw=="], - - "@electron/rebuild/node-api-version": ["node-api-version@0.2.1", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q=="], - - "@electron/universal/fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="], - - "@electron/universal/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], - - "@git-diff-view/shiki/shiki": ["shiki@3.21.0", "", { "dependencies": { "@shikijs/core": "3.21.0", "@shikijs/engine-javascript": "3.21.0", "@shikijs/engine-oniguruma": "3.21.0", "@shikijs/langs": "3.21.0", "@shikijs/themes": "3.21.0", "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w=="], - - "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], - - "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "@malept/flatpak-bundler/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], - - "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="], - - "@opentelemetry/instrumentation-http/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="], - - "@opentelemetry/otlp-exporter-base/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="], - - "@opentelemetry/otlp-transformer/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="], - - "@opentelemetry/otlp-transformer/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], - - "@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="], - - "@opentelemetry/sdk-logs/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="], - - "@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], - - "@opentelemetry/sdk-metrics/@opentelemetry/core": ["@opentelemetry/core@2.2.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw=="], - - "@opentelemetry/sdk-metrics/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], - - "@pierre/diffs/shiki": ["shiki@3.21.0", "", { "dependencies": { "@shikijs/core": "3.21.0", "@shikijs/engine-javascript": "3.21.0", "@shikijs/engine-oniguruma": "3.21.0", "@shikijs/langs": "3.21.0", "@shikijs/themes": "3.21.0", "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w=="], - - "@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-label/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], - - "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-progress/@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="], - - "@radix-ui/react-progress/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], - - "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - - "@sentry/node/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "@shikijs/core/@shikijs/types": ["@shikijs/types@3.21.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA=="], - - "@shikijs/engine-javascript/@shikijs/types": ["@shikijs/types@3.21.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA=="], - - "@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.22.0", "", { "dependencies": { "@shikijs/types": "3.22.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA=="], - - "@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.22.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg=="], - - "@tailwindcss/typography/postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], - - "accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], - - "ajv-keywords/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], - - "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - - "archiver-utils/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - - "body-parser/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], - - "cacache/glob": ["glob@8.1.0", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^5.0.1", "once": "^1.3.0" } }, "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ=="], - - "cacache/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], - - "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], - - "clone-response/mimic-response": ["mimic-response@1.0.1", "", {}, "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="], - - "config-file-ts/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], - - "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], - - "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], - - "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], - - "dir-compare/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "dmg-license/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], - - "electron-updater/builder-util-runtime": ["builder-util-runtime@9.5.1", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ=="], - - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], - - "foreground-child/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], - - "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], - - "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], - - "gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], - - "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - - "iconv-corefoundation/node-addon-api": ["node-addon-api@1.7.2", "", {}, "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg=="], - - "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], - - "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - - "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - - "lzma-native/node-addon-api": ["node-addon-api@3.2.1", "", {}, "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A=="], - - "make-fetch-happen/http-proxy-agent": ["http-proxy-agent@5.0.0", "", { "dependencies": { "@tootallnate/once": "2", "agent-base": "6", "debug": "4" } }, "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w=="], - - "make-fetch-happen/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], - - "make-fetch-happen/lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], - - "make-fetch-happen/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "matcher/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], - - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - - "minipass-collect/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "minipass-fetch/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "monaco-editor/dompurify": ["dompurify@3.2.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw=="], - - "monaco-editor/marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="], - - "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], - - "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - - "path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - - "raw-body/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], - - "readdir-glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], - - "roarr/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], - - "shiki/@shikijs/core": ["@shikijs/core@1.29.2", "", { "dependencies": { "@shikijs/engine-javascript": "1.29.2", "@shikijs/engine-oniguruma": "1.29.2", "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ=="], - - "shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1", "oniguruma-to-es": "^2.2.0" } }, "sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A=="], - - "socks-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - - "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - - "streamdown/marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="], - - "streamdown/tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], - - "tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], - - "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], - - "zip-stream/archiver-utils": ["archiver-utils@3.0.4", "", { "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", "lazystream": "^1.0.0", "lodash.defaults": "^4.2.0", "lodash.difference": "^4.5.0", "lodash.flatten": "^4.4.0", "lodash.isplainobject": "^4.0.6", "lodash.union": "^4.6.0", "normalize-path": "^3.0.0", "readable-stream": "^3.6.0" } }, "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw=="], - - "@develar/schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "@electron/asar/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - - "@electron/get/fs-extra/jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], - - "@electron/get/fs-extra/universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="], - - "@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="], - - "@git-diff-view/shiki/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ=="], - - "@git-diff-view/shiki/shiki/@shikijs/langs": ["@shikijs/langs@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA=="], - - "@git-diff-view/shiki/shiki/@shikijs/themes": ["@shikijs/themes@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw=="], - - "@git-diff-view/shiki/shiki/@shikijs/types": ["@shikijs/types@3.21.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA=="], - - "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - - "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - - "@pierre/diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ=="], - - "@pierre/diffs/shiki/@shikijs/langs": ["@shikijs/langs@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA=="], - - "@pierre/diffs/shiki/@shikijs/themes": ["@shikijs/themes@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0" } }, "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw=="], - - "@pierre/diffs/shiki/@shikijs/types": ["@shikijs/types@3.21.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA=="], - - "ajv-keywords/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "archiver-utils/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - - "archiver-utils/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - - "cacache/glob/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], - - "config-file-ts/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - - "config-file-ts/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], - - "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], - - "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], - - "dir-compare/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - - "dmg-license/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], - - "form-data/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], - - "glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - - "gray-matter/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - - "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - - "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - - "make-fetch-happen/http-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - - "make-fetch-happen/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], - - "shiki/@shikijs/engine-javascript/oniguruma-to-es": ["oniguruma-to-es@2.3.0", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^5.1.1", "regex-recursion": "^5.1.1" } }, "sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g=="], - - "tailwindcss/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - - "tailwindcss/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], - - "shiki/@shikijs/engine-javascript/oniguruma-to-es/regex": ["regex@5.1.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw=="], - - "shiki/@shikijs/engine-javascript/oniguruma-to-es/regex-recursion": ["regex-recursion@5.1.1", "", { "dependencies": { "regex": "^5.1.1", "regex-utilities": "^2.3.0" } }, "sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w=="], - - "tailwindcss/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - } -} diff --git a/bun.lockb b/bun.lockb deleted file mode 100755 index b1069a4211972eed8f5427d76d3d99b492545ba7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 473965 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 VCKRn;!4gB%D^6nn@&ELn{{?DGCeHu> diff --git a/docs/plans/2025-01-31-multiple-model-profiles-design.md b/docs/plans/2025-01-31-multiple-model-profiles-design.md new file mode 100644 index 000000000..a03db1ad1 --- /dev/null +++ b/docs/plans/2025-01-31-multiple-model-profiles-design.md @@ -0,0 +1,188 @@ +# Multiple Model Profiles - Design Document + +**Date:** 2025-01-31 +**Status:** Approved +**Author:** AI Brainstorming Session + +## Overview + +Enable users to create and manage multiple Claude API proxy configurations (profiles), each with their own `ANTHROPIC_BASE_URL`, `ANTHROPIC_API_KEY`, and model name. Users can quickly switch between profiles from the settings UI and directly from the active chat interface. + +## Problem Statement + +Currently, the app supports only a single "Override Model" configuration. Users with multiple proxy configurations (e.g., local proxy, cloud proxy, different API keys) must manually edit these settings each time they want to switch. This is cumbersome and error-prone. + +## Solution + +Replace the single "Override Model" card with a profile list UI that supports: +- Creating, editing, deleting, and switching between multiple profiles +- Quick profile selector in the chat header for fast switching +- Auto-detection of CLI config to suggest creating a profile +- Legacy migration from existing single config + +## Section 1: UI Layout + +### Model Profiles List + +The "Override Model" section in [agents-models-tab.tsx](../src/renderer/components/dialogs/settings-tabs/agents-models-tab.tsx) will be replaced with a list of profile cards. + +**ProfileRow Component Structure:** + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Profile Name [Active] [⋮] │ +│ Model: claude-3-7-sonnet-20250219 │ +│ Proxy: http://localhost:8080 │ +└─────────────────────────────────────────────────────────────────┘ +``` + +- **Profile Name**: User-defined name (e.g., "Local Proxy", "Cloud Gateway") +- **Active Badge**: Shows which profile is currently active +- **Actions Menu (⋮)**: Edit, Delete, Set Active + +**Empty State:** +- When no profiles exist (excluding offline), show "Add your first proxy configuration" +- Button to open the add form + +## Section 2: Add/Edit Form & CRUD Operations + +### Inline Add/Edit Form + +When user clicks "Add Profile" or edits an existing profile, an inline form expands: + +```tsx +interface ProfileFormState { + mode: 'add' | 'edit' + profileId?: string // set when editing + fields: { + name: string + model: string + token: string + baseUrl: string + } +} +``` + +**Form Fields:** +- Name (text input, required) +- Model (text input, placeholder: "claude-3-7-sonnet-20250219") +- API Token (password input, placeholder: "sk-ant-...") +- Base URL (text input, placeholder: "https://api.anthropic.com") + +**Validation:** +- All fields required +- Token should start with "sk-ant-" (warning only, not blocking) +- Base URL should be a valid URL format + +### CRUD Operations Table + +| Operation | Action | Atoms Updated | +|-----------|--------|---------------| +| **Add** | Push new profile to `modelProfilesAtom` | `modelProfilesAtom`, `activeProfileIdAtom` (if first) | +| **Edit** | Find by ID and update in `modelProfilesAtom` | `modelProfilesAtom` | +| **Delete** | Filter out by ID from `modelProfilesAtom` | `modelProfilesAtom`, `activeProfileIdAtom` (if deleting active) | +| **Set Active** | Update `activeProfileIdAtom` | `activeProfileIdAtom` | + +**Delete Behavior:** +- If deleting the active profile, auto-select the next available profile +- If no profiles remain, set `activeProfileIdAtom` to `null` (use default) +- Show confirmation dialog for delete + +## Section 3: Integration + +### Existing System Integration + +The `activeConfigAtom` in [atoms/index.ts](../src/renderer/lib/atoms/index.ts) already handles the priority chain: + +```typescript +// Priority chain: +// 1. Auto-offline (if enabled and no internet) +// 2. Active profile (if selected) +// 3. Legacy single config (backwards compat) +// 4. None (use Claude Code default) +``` + +**No changes needed to `activeConfigAtom`** - it already reads from `activeProfileIdAtom` and `modelProfilesAtom`. + +### CLI Config Auto-Detection + +The existing [cli-config-detected-page.tsx](../src/renderer/features/onboarding/cli-config-detected-page.tsx) detects environment variables. Extend it to offer creating a profile: + +**Flow:** +1. User has `ANTHROPIC_BASE_URL` and/or `ANTHROPIC_API_KEY` in env +2. App shows "Configuration Detected" page +3. User clicks "Use Existing Configuration" +4. **NEW**: Prompt "Save as a profile?" with Yes/No +5. If Yes, open inline form with detected values pre-filled + +### Legacy Migration + +On app startup, check if `customClaudeConfigAtom` has values: + +```typescript +// One-time migration +useEffect(() => { + const legacyConfig = get(customClaudeConfigAtom) + const profiles = get(modelProfilesAtom) + + // Only migrate if legacy has values and profiles is default (only offline) + if (normalizeCustomClaudeConfig(legacyConfig) && profiles.length === 1) { + const migratedProfile: ModelProfile = { + id: crypto.randomUUID(), + name: "Migrated Config", + config: legacyConfig, + } + set(modelProfilesAtom, [OFFLINE_PROFILE, migratedProfile]) + set(activeProfileIdAtom, migratedProfile.id) + // Clear legacy + set(customClaudeConfigAtom, EMPTY_CONFIG) + } +}, []) +``` + +### Quick Selector in Chat + +Add a dropdown in the chat header ([active-chat.tsx](../src/renderer/features/agents/main/active-chat.tsx)) for fast profile switching: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ [New Chat] [Profile: Local Proxy ▼] [Settings ⚙] │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**Implementation:** +- Use `DropdownMenu` with `RadioGroup` for selection +- Show active profile with checkmark +- clicking updates `activeProfileIdAtom` +- Profile change takes effect on next message + +## Files to Modify + +1. **[agents-models-tab.tsx](../src/renderer/components/dialogs/settings-tabs/agents-models-tab.tsx)** - Replace Override Model section with profile list +2. **[cli-config-detected-page.tsx](../src/renderer/features/onboarding/cli-config-detected-page.tsx)** - Add "Save as profile" option +3. **[active-chat.tsx](../src/renderer/features/agents/main/active-chat.tsx)** - Add quick profile selector to header +4. **[atoms/index.ts](../src/renderer/lib/atoms/index.ts)** - Add migration logic (atoms already in place) + +## Data Types (Already Defined) + +```typescript +export type ModelProfile = { + id: string + name: string + config: CustomClaudeConfig + isOffline?: boolean +} + +export type CustomClaudeConfig = { + model: string + token: string + baseUrl: string +} +``` + +## Future Enhancements (Out of Scope) + +- Per-chat profile selection +- Profile import/export +- Connection testing before saving +- Profile templates (presets for common proxies) diff --git a/docs/plans/2025-01-31-multiple-model-profiles-implementation.md b/docs/plans/2025-01-31-multiple-model-profiles-implementation.md new file mode 100644 index 000000000..438680b63 --- /dev/null +++ b/docs/plans/2025-01-31-multiple-model-profiles-implementation.md @@ -0,0 +1,751 @@ +# Multiple Model Profiles - Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Enable users to create, manage, and switch between multiple Claude API proxy configurations (profiles) with custom base URLs, API keys, and model names. + +**Architecture:** Replace the single "Override Model" form with a profile list UI. Use existing `modelProfilesAtom` and `activeProfileIdAtom` for state. The `activeConfigAtom` already handles the priority chain - no changes needed there. + +**Tech Stack:** React 19, Jotai atoms, TypeScript, Tailwind CSS, Radix UI components, sonner toasts + +--- + +## Task 1: Create ProfileRow Component + +**Files:** +- Create: `src/renderer/components/dialogs/settings-tabs/model-profile-row.tsx` + +**Step 1: Write the component** + +```tsx +import { Check, MoreHorizontal, Pencil, Trash2 } from "lucide-react" +import { type ModelProfile } from "../../../../lib/atoms" +import { Badge } from "../../ui/badge" +import { Button } from "../../ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../../ui/dropdown-menu" + +interface ProfileRowProps { + profile: ModelProfile + isActive: boolean + onActivate: () => void + onEdit: () => void + onDelete: () => void +} + +export function ModelProfileRow({ + profile, + isActive, + onActivate, + onEdit, + onDelete, +}: ProfileRowProps) { + return ( +
+
+
+
+ {profile.name} + {isActive && ( + + Active + + )} +
+
+ Model: {profile.config.model || "Not set"} +
+
+ Proxy: {profile.config.baseUrl || "Default (api.anthropic.com)"} +
+
+
+ +
+ {!isActive && ( + + )} + + + + + + + + Edit + + + + Delete + + + +
+
+ ) +} +``` + +**Step 2: Commit** + +```bash +git add src/renderer/components/dialogs/settings-tabs/model-profile-row.tsx +git commit -m "feat: add ModelProfileRow component for profile list" +``` + +--- + +## Task 2: Create ProfileForm Component (Inline Add/Edit) + +**Files:** +- Create: `src/renderer/components/dialogs/settings-tabs/model-profile-form.tsx` + +**Step 1: Write the component** + +```tsx +import { X } from "lucide-react" +import { useEffect, useState } from "react" +import { type CustomClaudeConfig, type ModelProfile } from "../../../../lib/atoms" +import { Button } from "../../ui/button" +import { Input } from "../../ui/input" +import { Label } from "../../ui/label" + +const EMPTY_CONFIG: CustomClaudeConfig = { + model: "", + token: "", + baseUrl: "", +} + +interface ProfileFormProps { + mode: "add" | "edit" + existingProfile?: ModelProfile + onSave: (profile: Omit) => void + onCancel: () => void +} + +export function ModelProfileForm({ + mode, + existingProfile, + onSave, + onCancel, +}: ProfileFormProps) { + const [name, setName] = useState(existingProfile?.name || "") + const [model, setModel] = useState(existingProfile?.config.model || "") + const [token, setToken] = useState(existingProfile?.config.token || "") + const [baseUrl, setBaseUrl] = useState(existingProfile?.config.baseUrl || "") + + const isValid = name.trim() && model.trim() && token.trim() && baseUrl.trim() + + const handleSave = () => { + if (!isValid) return + + onSave({ + name: name.trim(), + config: { + model: model.trim(), + token: token.trim(), + baseUrl: baseUrl.trim(), + }, + }) + } + + return ( +
+
+

+ {mode === "add" ? "Add Profile" : "Edit Profile"} +

+ +
+ +
+
+ + setName(e.target.value)} + placeholder="e.g., Local Proxy, Cloud Gateway" + className="h-8" + /> +
+ +
+ + setModel(e.target.value)} + placeholder="claude-3-7-sonnet-20250219" + className="h-8" + /> +
+ +
+ + setToken(e.target.value)} + placeholder="sk-ant-..." + className="h-8" + /> +
+ +
+ + setBaseUrl(e.target.value)} + placeholder="https://api.anthropic.com" + className="h-8" + /> +
+ + +
+
+ ) +} +``` + +**Step 2: Commit** + +```bash +git add src/renderer/components/dialogs/settings-tabs/model-profile-form.tsx +git commit -m "feat: add ModelProfileForm component for add/edit" +``` + +--- + +## Task 3: Create ModelProfilesSection Component + +**Files:** +- Create: `src/renderer/components/dialogs/settings-tabs/model-profiles-section.tsx` + +**Step 1: Write the component** + +```tsx +import { useAtom } from "jotai" +import { Plus } from "lucide-react" +import { useState } from "react" +import { toast } from "sonner" +import { + activeProfileIdAtom, + modelProfilesAtom, + type ModelProfile, +} from "../../../../lib/atoms" +import { Button } from "../../ui/button" +import { ModelProfileForm } from "./model-profile-form" +import { ModelProfileRow } from "./model-profile-row" + +type FormState = { + mode: "add" | "edit" | null + editingProfile?: ModelProfile +} + +export function ModelProfilesSection() { + const [profiles, setProfiles] = useAtom(modelProfilesAtom) + const [activeProfileId, setActiveProfileId] = useAtom(activeProfileIdAtom) + const [formState, setFormState] = useState({ mode: null }) + + // Filter out offline profile from display + const customProfiles = profiles.filter((p) => !p.isOffline) + + const handleAddProfile = (profileData: Omit) => { + const newProfile: ModelProfile = { + ...profileData, + id: crypto.randomUUID(), + } + + setProfiles([...profiles, newProfile]) + + // If this is the first custom profile, auto-activate it + if (customProfiles.length === 0) { + setActiveProfileId(newProfile.id) + } + + setFormState({ mode: null }) + toast.success("Profile added") + } + + const handleEditProfile = (profileData: Omit) => { + if (!formState.editingProfile) return + + setProfiles( + profiles.map((p) => + p.id === formState.editingProfile!.id + ? { ...profileData, id: p.id } + : p, + ), + ) + + setFormState({ mode: null }) + toast.success("Profile updated") + } + + const handleDeleteProfile = (profileId: string) => { + const confirmed = window.confirm( + "Are you sure you want to delete this profile?", + ) + if (!confirmed) return + + const newProfiles = profiles.filter((p) => p.id !== profileId) + setProfiles(newProfiles) + + // If we deleted the active profile, clear the active ID + if (activeProfileId === profileId) { + const remainingCustom = newProfiles.filter((p) => !p.isOffline) + setActiveProfileId(remainingCustom[0]?.id || null) + } + + toast.success("Profile deleted") + } + + const handleStartEdit = (profile: ModelProfile) => { + setFormState({ mode: "edit", editingProfile: profile }) + } + + const handleCancelForm = () => { + setFormState({ mode: null }) + } + + return ( +
+ {customProfiles.length === 0 && !formState.mode ? ( +
+

+ No custom profiles yet. Add your first proxy configuration. +

+ +
+ ) : ( + <> + {customProfiles.map((profile) => ( + setActiveProfileId(profile.id)} + onEdit={() => handleStartEdit(profile)} + onDelete={() => handleDeleteProfile(profile.id)} + /> + ))} + + {formState.mode && ( + + )} + + {!formState.mode && ( +
+ +
+ )} + + )} +
+ ) +} +``` + +**Step 2: Commit** + +```bash +git add src/renderer/components/dialogs/settings-tabs/model-profiles-section.tsx +git commit -m "feat: add ModelProfilesSection with CRUD operations" +``` + +--- + +## Task 4: Integrate ModelProfilesSection into AgentsModelsTab + +**Files:** +- Modify: `src/renderer/components/dialogs/settings-tabs/agents-models-tab.tsx:463-599` + +**Step 1: Replace the Override Model section with ModelProfilesSection** + +Find the existing "Override Model" section (around line 463) and replace it with: + +```tsx + {/* Model Profiles */} +
+
+
+

+ Model Profiles +

+

+ Manage your proxy configurations +

+
+
+ + +
+``` + +**Step 2: Add the import at the top of the file** + +Add to imports section: +```tsx +import { ModelProfilesSection } from "./model-profiles-section" +``` + +**Step 3: Remove the old Override Model UI code** + +Delete the entire "Override Model" section div that contains: +- The header with "Override Model" title and Reset button +- The form with Model name, API token, and Base URL inputs +- All associated state (model, baseUrl, token, handleBlurSave, handleReset, etc.) + +**Step 4: Clean up unused code** + +Remove these unused imports and state from the component: +- `storedConfig, setStoredConfig, model, baseUrl, token` state variables +- `handleBlurSave`, `handleReset` handlers +- `savedConfigRef` ref +- `useEffect` hooks for syncing config state + +**Step 5: Commit** + +```bash +git add src/renderer/components/dialogs/settings-tabs/agents-models-tab.tsx +git commit -m "feat: integrate ModelProfilesSection into settings tab" +``` + +--- + +## Task 5: Add Quick Profile Selector to Chat Header + +**Files:** +- Modify: `src/renderer/features/agents/main/active-chat.tsx` + +**Step 1: Find the chat header section** + +Locate the header area in active-chat.tsx that contains the chat title and controls. Look for elements like the model badge or settings button. + +**Step 2: Add the profile selector dropdown** + +Insert this component near the header controls: + +```tsx +import { useAtom } from "jotai" +import { ChevronDown } from "lucide-react" +import { activeProfileIdAtom, modelProfilesAtom } from "../../../../lib/atoms" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../../../components/ui/dropdown-menu" +import { Badge } from "../../../components/ui/badge" + +// Inside the component, add: +function ModelProfileSelector() { + const [profiles] = useAtom(modelProfilesAtom) + const [activeProfileId, setActiveProfileId] = useAtom(activeProfileIdAtom) + + const activeProfile = profiles.find((p) => p.id === activeProfileId) + const customProfiles = profiles.filter((p) => !p.isOffline) + + if (customProfiles.length === 0) return null + + return ( + + + + + + setActiveProfileId(null)}> + Default (no profile) + {!activeProfileId && Active} + + {customProfiles.map((profile) => ( + setActiveProfileId(profile.id)} + > + {profile.name} + {activeProfileId === profile.id && ( + Active + )} + + ))} + + + ) +} +``` + +**Step 3: Place the selector in the header** + +Add `` in the header, likely next to the settings button or model indicator. + +**Step 4: Commit** + +```bash +git add src/renderer/features/agents/main/active-chat.tsx +git commit -m "feat: add quick profile selector to chat header" +``` + +--- + +## Task 6: Add CLI Config "Save as Profile" Prompt + +**Files:** +- Modify: `src/renderer/features/onboarding/cli-config-detected-page.tsx:29-37` + +**Step 1: Modify handleUseExistingConfig to prompt for saving** + +Replace the existing `handleUseExistingConfig` function with: + +```tsx + const handleUseExistingConfig = () => { + const cliConfigData = cliConfig + if (!cliConfigData) return + + // Prompt user to save as profile + const shouldSave = window.confirm( + "Would you like to save this configuration as a profile for easy switching later?", + ) + + if (shouldSave) { + // Get profiles from atom + const profiles = get(modelProfilesAtom) + const setProfiles = set(modelProfilesAtom) + const setActiveProfileId = set(activeProfileIdAtom) + + const newProfile: ModelProfile = { + id: crypto.randomUUID(), + name: "Detected CLI Config", + config: { + model: cliConfigData.model || "claude-3-7-sonnet-20250219", + token: cliConfigData.apiKey || "", + baseUrl: cliConfigData.baseUrl || "https://api.anthropic.com", + }, + } + + setProfiles([...profiles, newProfile]) + setActiveProfileId(newProfile.id) + } + + // Mark CLI config detected as shown + setCliConfigDetectedShown(true) + setBillingMethod("api-key") + setApiKeyOnboardingCompleted(true) + } +``` + +**Step 2: Add necessary imports** + +Add to imports: +```tsx +import { modelProfilesAtom, activeProfileIdAtom, type ModelProfile } from "../../lib/atoms" +import { useSetAtom, useAtom } from "jotai" +``` + +**Step 3: Commit** + +```bash +git add src/renderer/features/onboarding/cli-config-detected-page.tsx +git commit -m "feat: add 'save as profile' option to CLI config detection" +``` + +--- + +## Task 7: Add Legacy Config Migration + +**Files:** +- Modify: `src/renderer/lib/atoms/index.ts` + +**Step 1: Add migration logic after atom definitions** + +Add this code after the `customClaudeConfigAtom` definition (around line 271): + +```tsx +// Migration: convert legacy single config to profile +// This runs once when the module loads +if (typeof window !== "undefined") { + const legacyKey = "agents:claude-custom-config" + const migrationFlagKey = "agents:legacy-config-migrated" + + const legacyValue = localStorage.getItem(legacyKey) + const hasMigrated = localStorage.getItem(migrationFlagKey) + + if (legacyValue && !hasMigrated) { + try { + const legacyConfig: CustomClaudeConfig = JSON.parse(legacyValue) + + // Only migrate if has actual values + if ( + legacyConfig.model?.trim() || + legacyConfig.token?.trim() || + legacyConfig.baseUrl?.trim() + ) { + const profilesKey = "agents:model-profiles" + const activeProfileKey = "agents:active-profile-id" + + const existingProfiles = localStorage.getItem(profilesKey) + const profiles: ModelProfile[] = existingProfiles + ? JSON.parse(existingProfiles) + : [OFFLINE_PROFILE] + + // Create migrated profile + const migratedProfile: ModelProfile = { + id: crypto.randomUUID(), + name: "Migrated Config", + config: { + model: legacyConfig.model || "", + token: legacyConfig.token || "", + baseUrl: legacyConfig.baseUrl || "", + }, + } + + // Add profile and set as active + const updatedProfiles = [...profiles, migratedProfile] + localStorage.setItem(profilesKey, JSON.stringify(updatedProfiles)) + localStorage.setItem(activeProfileKey, JSON.stringify(migratedProfile.id)) + + // Clear legacy config + localStorage.setItem( + legacyKey, + JSON.stringify({ model: "", token: "", baseUrl: "" }), + ) + + // Mark as migrated + localStorage.setItem(migrationFlagKey, "true") + console.log("[atoms] Migrated legacy config to profile system") + } + } catch (e) { + console.error("[atoms] Failed to migrate legacy config:", e) + } + } +} +``` + +**Step 2: Commit** + +```bash +git add src/renderer/lib/atoms/index.ts +git commit -m "feat: add legacy config migration to profile system" +``` + +--- + +## Testing Checklist + +After completing all tasks, verify: + +1. **Settings UI:** + - [ ] Can add a new profile with all fields + - [ ] Can edit an existing profile + - [ ] Can delete a profile + - [ ] Active profile shows badge + - [ ] Clicking "Use" activates a profile + - [ ] Empty state shows "Add Profile" button + +2. **Chat Header:** + - [ ] Profile selector appears when profiles exist + - [ ] Can switch profiles from dropdown + - [ ] Active profile is highlighted + - [ ] "Default" option clears active profile + +3. **CLI Config Detection:** + - [ ] Prompt appears when using existing config + - [ ] Choosing "Yes" creates a profile + - [ ] Profile is auto-activated + +4. **Legacy Migration:** + - [ ] Existing `customClaudeConfigAtom` values migrate to profile + - [ ] Migrated profile is set as active + - [ ] Legacy config is cleared after migration + +5. **Integration:** + - [ ] Active profile config is used by `activeConfigAtom` + - [ ] Switching profiles affects next message + - [ ] Offline profile still works for fallback + +--- + +## Files Modified Summary + +| File | Action | +|------|--------| +| `src/renderer/components/dialogs/settings-tabs/model-profile-row.tsx` | Create | +| `src/renderer/components/dialogs/settings-tabs/model-profile-form.tsx` | Create | +| `src/renderer/components/dialogs/settings-tabs/model-profiles-section.tsx` | Create | +| `src/renderer/components/dialogs/settings-tabs/agents-models-tab.tsx` | Modify | +| `src/renderer/features/agents/main/active-chat.tsx` | Modify | +| `src/renderer/features/onboarding/cli-config-detected-page.tsx` | Modify | +| `src/renderer/lib/atoms/index.ts` | Modify | + +--- + +## Related Docs + +- Design: [docs/plans/2025-01-31-multiple-model-profiles-design.md](./2025-01-31-multiple-model-profiles-design.md) +- Atoms reference: `src/renderer/lib/atoms/index.ts:222-368` diff --git a/docs/plans/2025-01-31-unified-model-selector.md b/docs/plans/2025-01-31-unified-model-selector.md new file mode 100644 index 000000000..2dcd74f7c --- /dev/null +++ b/docs/plans/2025-01-31-unified-model-selector.md @@ -0,0 +1,86 @@ +# Unified Model Selector with Custom Profile Support + +## Problem +When a custom model profile is active, the model selector in the chat input is disabled and shows "Custom Model". Users cannot switch between Default/Opus/Sonnet/Haiku options. + +## Goal +Keep the model selector (Default, Opus, Sonnet, Haiku) always enabled. When a custom profile is active, these selections map to the profile's configured models: + +| UI Selection | Without Profile | With Profile | +|--------------|-----------------|--------------| +| Default | claude-3-7-sonnet | profile.model (ANTHROPIC_MODEL) | +| Opus | opus | profile.defaultOpusModel | +| Sonnet | sonnet | profile.defaultSonnetModel | +| Haiku | haiku | profile.defaultHaikuModel | + +## Implementation + +### 1. Update `chat-input-area.tsx` + +**File:** `src/renderer/features/agents/main/chat-input-area.tsx` + +Changes: +- Remove `disabled={hasCustomClaudeConfig}` from the model dropdown (line 1370) +- Remove condition that forces dropdown closed: `open={hasCustomClaudeConfig ? false : isModelDropdownOpen}` → `open={isModelDropdownOpen}` (line 1361) +- Add "Default" option to the model list +- Show profile name in parentheses when profile is active (e.g., "Sonnet (Z.AI Proxy)") + +### 2. Update `models.ts` + +**File:** `src/renderer/features/agents/lib/models.ts` + +Add "default" model option: +```typescript +export const CLAUDE_MODELS = [ + { id: "default", name: "Default" }, + { id: "opus", name: "Opus" }, + { id: "sonnet", name: "Sonnet" }, + { id: "haiku", name: "Haiku" }, +] +``` + +### 3. Update `atoms/index.ts` (agents feature) + +**File:** `src/renderer/features/agents/atoms/index.ts` + +Update MODEL_ID_MAP to include "default": +```typescript +export const MODEL_ID_MAP: Record = { + default: "default", // Will use profile's main model + opus: "opus", + sonnet: "sonnet", + haiku: "haiku", +} +``` + +### 4. Backend already supports this + +The tRPC schema in `claude.ts` already accepts: +- `model: z.string().optional()` - the selected model (opus/sonnet/haiku/default) +- `customConfig.defaultOpusModel`, `customConfig.defaultSonnetModel`, `customConfig.defaultHaikuModel` + +The Claude SDK uses these as environment variables: +- ANTHROPIC_MODEL +- ANTHROPIC_DEFAULT_OPUS_MODEL +- ANTHROPIC_DEFAULT_SONNET_MODEL +- ANTHROPIC_DEFAULT_HAIKU_MODEL + +## Files to Modify + +1. `src/renderer/features/agents/main/chat-input-area.tsx` - Enable dropdown, add Default option +2. `src/renderer/features/agents/lib/models.ts` - Add "default" to CLAUDE_MODELS +3. `src/renderer/features/agents/atoms/index.ts` - Add "default" to MODEL_ID_MAP + +## Testing + +1. Create a custom profile with: + - Model: glm-4.7 + - Default Opus Model: glm-4.7 + - Default Sonnet Model: glm-4.7 + - Default Haiku Model: glm-4.5-air + +2. Set profile as active + +3. Verify model selector shows Default/Opus/Sonnet/Haiku options + +4. Select each option and verify the correct model is used in the request diff --git a/package.json b/package.json index d676d1aa4..d5b94213c 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", "@radix-ui/react-tooltip": "^1.2.8", + "@radix-ui/react-visually-hidden": "^1.2.4", "@sentry/electron": "^7.5.0", "@tailwindcss/typography": "^0.5.19", "@tanstack/react-query": "^5.90.10", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43e8157b7..13835d6e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^3.0.14 version: 3.0.51(react@19.2.1)(zod@3.25.76) '@anthropic-ai/claude-agent-sdk': - specifier: ^0.2.12 - version: 0.2.19(zod@3.25.76) + specifier: 0.2.25 + version: 0.2.25(zod@3.25.76) '@git-diff-view/react': specifier: ^0.0.35 version: 0.0.35(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -23,6 +23,12 @@ importers: '@modelcontextprotocol/sdk': specifier: ^1.25.3 version: 1.25.3(hono@4.11.5)(zod@3.25.76) + '@monaco-editor/react': + specifier: ^4.7.0 + version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@pierre/diffs': + specifier: ^1.0.10 + version: 1.0.10(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@radix-ui/react-accordion': specifier: ^1.2.12 version: 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -74,6 +80,9 @@ importers: '@radix-ui/react-tooltip': specifier: ^1.2.8 version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + '@radix-ui/react-visually-hidden': + specifier: ^1.2.4 + version: 1.2.4(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) '@sentry/electron': specifier: ^7.5.0 version: 7.6.0 @@ -116,6 +125,9 @@ importers: ai: specifier: ^6.0.14 version: 6.0.49(zod@3.25.76) + async-mutex: + specifier: ^0.5.0 + version: 0.5.0 better-sqlite3: specifier: ^11.8.1 version: 11.10.0 @@ -146,12 +158,18 @@ importers: jotai: specifier: ^2.11.1 version: 2.16.2(@babel/core@7.28.6)(@babel/template@7.28.6)(@types/react@19.2.9)(react@19.2.1) + jsonc-parser: + specifier: ^3.3.1 + version: 3.3.1 lucide-react: specifier: ^0.468.0 version: 0.468.0(react@19.2.1) mermaid: specifier: ^11.12.2 version: 11.12.2 + monaco-editor: + specifier: ^0.55.1 + version: 0.55.1 motion: specifier: ^11.15.0 version: 11.18.2(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -182,9 +200,6 @@ importers: react-icons: specifier: ^5.5.0 version: 5.5.0(react@19.2.1) - react-syntax-highlighter: - specifier: ^16.1.0 - version: 16.1.0(react@19.2.1) react-zoom-pan-pinch: specifier: ^3.7.0 version: 3.7.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1) @@ -249,9 +264,6 @@ importers: '@types/react-dom': specifier: ^19.0.3 version: 19.2.3(@types/react@19.2.9) - '@types/react-syntax-highlighter': - specifier: ^15.5.13 - version: 15.5.13 '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.7.0(vite@6.4.1(@types/node@20.19.30)(jiti@1.21.7)) @@ -323,8 +335,8 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@anthropic-ai/claude-agent-sdk@0.2.19': - resolution: {integrity: sha512-DjaX4t3Swjt5PcsZt6krcp5TfBTRxVuUZhkY6L8WWF8kZBJFuuEd5akNg486XRskTXGuwLmitxp0wHB1hJ9muw==} + '@anthropic-ai/claude-agent-sdk@0.2.25': + resolution: {integrity: sha512-YIP3I40+XSkC3zE1Z8KRQY02VA7UfofFamF1cFrLe7FbtCnjpslyDl9coGBh2DAi9xj2yQcKZZf751jEWpB+dQ==} engines: {node: '>=18.0.0'} peerDependencies: zod: ^4.0.0 @@ -412,10 +424,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} - engines: {node: '>=6.9.0'} - '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -969,6 +977,16 @@ packages: '@cfworker/json-schema': optional: true + '@monaco-editor/loader@1.7.0': + resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} + + '@monaco-editor/react@4.7.0': + resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1222,6 +1240,12 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 + '@pierre/diffs@1.0.10': + resolution: {integrity: sha512-ahkpfS30NfaB+PBxnf0/Mc20ySBRTQmM28a7Ojpd0UZixmTyhGhJfBFjvmhX8dSzR22lB3h3OIMMxpB4yYTIOQ==} + peerDependencies: + react: ^18.3.1 || ^19.0.0 + react-dom: ^18.3.1 || ^19.0.0 + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1773,6 +1797,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-visually-hidden@1.2.4': + resolution: {integrity: sha512-kaeiyGCe844dkb9AVF+rb4yTyb1LiLN/e3es3nLiRyN4dC8AduBYPMnnNlDjX2VDOcvDEiPnRNMJeWCfsX0txg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} @@ -1968,6 +2005,9 @@ packages: '@shikijs/core@3.21.0': resolution: {integrity: sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==} + '@shikijs/core@3.22.0': + resolution: {integrity: sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==} + '@shikijs/engine-javascript@1.29.2': resolution: {integrity: sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==} @@ -1992,12 +2032,18 @@ packages: '@shikijs/themes@3.21.0': resolution: {integrity: sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==} + '@shikijs/transformers@3.22.0': + resolution: {integrity: sha512-E7eRV7mwDBjueLF6852n2oYeJYxBq3NSsDk+uyruYAXONv4U8holGmIrT+mPRJQ1J1SNOH6L8G19KRzmBawrFw==} + '@shikijs/types@1.29.2': resolution: {integrity: sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==} '@shikijs/types@3.21.0': resolution: {integrity: sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==} + '@shikijs/types@3.22.0': + resolution: {integrity: sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==} + '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -2218,17 +2264,11 @@ packages: '@types/plist@3.0.5': resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} - '@types/prismjs@1.26.5': - resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} - '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 - '@types/react-syntax-highlighter@15.5.13': - resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==} - '@types/react@19.2.9': resolution: {integrity: sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==} @@ -2440,6 +2480,9 @@ packages: resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} engines: {node: '>=0.12.0'} + async-mutex@0.5.0: + resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} + async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -3004,6 +3047,10 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff@8.0.3: + resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} + engines: {node: '>=0.3.1'} + dir-compare@4.2.0: resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} @@ -3019,6 +3066,9 @@ packages: os: [darwin] hasBin: true + dompurify@3.2.7: + resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} + dompurify@3.3.1: resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} @@ -3335,9 +3385,6 @@ packages: fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - fault@1.0.4: - resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} - fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} @@ -3375,10 +3422,6 @@ packages: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} - format@0.2.2: - resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} - engines: {node: '>=0.4.x'} - forwarded-parse@2.1.2: resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} @@ -3572,16 +3615,10 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} - highlight.js@10.7.3: - resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} - highlight.js@11.11.1: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} - highlightjs-vue@1.0.0: - resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} - hono@4.11.5: resolution: {integrity: sha512-WemPi9/WfyMwZs+ZUXdiwcCh9Y+m7L+8vki9MzDw3jJ+W9Lc+12HGsd368Qc1vZi1xwW8BWMMsnK5efYKPdt4g==} engines: {node: '>=16.9.0'} @@ -3836,6 +3873,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -3925,9 +3965,6 @@ packages: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} - lowlight@1.20.0: - resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} - lowlight@3.3.0: resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} @@ -3945,6 +3982,9 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} + lru_map@0.4.1: + resolution: {integrity: sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==} + lucide-react@0.468.0: resolution: {integrity: sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==} peerDependencies: @@ -3965,6 +4005,11 @@ packages: markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} + engines: {node: '>= 18'} + hasBin: true + marked@16.4.2: resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} engines: {node: '>= 20'} @@ -4235,6 +4280,9 @@ packages: module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + monaco-editor@0.55.1: + resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} + motion-dom@11.18.1: resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==} @@ -4568,10 +4616,6 @@ packages: engines: {node: '>=10'} hasBin: true - prismjs@1.30.0: - resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} - engines: {node: '>=6'} - process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -4685,12 +4729,6 @@ packages: '@types/react': optional: true - react-syntax-highlighter@16.1.0: - resolution: {integrity: sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==} - engines: {node: '>= 16.20.2'} - peerDependencies: - react: '>= 0.14.0' - react-zoom-pan-pinch@3.7.0: resolution: {integrity: sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA==} engines: {node: '>=8', npm: '>=5'} @@ -4732,9 +4770,6 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} - refractor@5.0.0: - resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} - regex-recursion@5.1.1: resolution: {integrity: sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==} @@ -5002,6 +5037,9 @@ packages: resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==} engines: {node: '>= 6'} + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} @@ -5495,7 +5533,7 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.0.2 - '@anthropic-ai/claude-agent-sdk@0.2.19(zod@3.25.76)': + '@anthropic-ai/claude-agent-sdk@0.2.25(zod@3.25.76)': dependencies: zod: 3.25.76 optionalDependencies: @@ -5612,8 +5650,6 @@ snapshots: '@babel/core': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/runtime@7.28.6': {} - '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.28.6 @@ -6097,6 +6133,17 @@ snapshots: - hono - supports-color + '@monaco-editor/loader@1.7.0': + dependencies: + state-local: 1.0.7 + + '@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@monaco-editor/loader': 1.7.0 + monaco-editor: 0.55.1 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -6407,6 +6454,18 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.5.0(@opentelemetry/api@1.9.0) + '@pierre/diffs@1.0.10(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@shikijs/core': 3.21.0 + '@shikijs/engine-javascript': 3.21.0 + '@shikijs/transformers': 3.22.0 + diff: 8.0.3 + hast-util-to-html: 9.0.5 + lru_map: 0.4.1 + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + shiki: 3.21.0 + '@pkgjs/parseargs@0.11.0': optional: true @@ -6965,6 +7024,15 @@ snapshots: '@types/react': 19.2.9 '@types/react-dom': 19.2.3(@types/react@19.2.9) + '@radix-ui/react-visually-hidden@1.2.4(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.9))(@types/react@19.2.9)(react-dom@19.2.1(react@19.2.1))(react@19.2.1) + react: 19.2.1 + react-dom: 19.2.1(react@19.2.1) + optionalDependencies: + '@types/react': 19.2.9 + '@types/react-dom': 19.2.3(@types/react@19.2.9) + '@radix-ui/rect@1.1.1': {} '@rolldown/pluginutils@1.0.0-beta.27': {} @@ -7161,6 +7229,13 @@ snapshots: '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 + '@shikijs/core@3.22.0': + dependencies: + '@shikijs/types': 3.22.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + '@shikijs/engine-javascript@1.29.2': dependencies: '@shikijs/types': 1.29.2 @@ -7199,6 +7274,11 @@ snapshots: dependencies: '@shikijs/types': 3.21.0 + '@shikijs/transformers@3.22.0': + dependencies: + '@shikijs/core': 3.22.0 + '@shikijs/types': 3.22.0 + '@shikijs/types@1.29.2': dependencies: '@shikijs/vscode-textmate': 10.0.2 @@ -7209,6 +7289,11 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 + '@shikijs/types@3.22.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + '@shikijs/vscode-textmate@10.0.2': {} '@sindresorhus/is@4.6.0': {} @@ -7468,16 +7553,10 @@ snapshots: xmlbuilder: 15.1.1 optional: true - '@types/prismjs@1.26.5': {} - '@types/react-dom@19.2.3(@types/react@19.2.9)': dependencies: '@types/react': 19.2.9 - '@types/react-syntax-highlighter@15.5.13': - dependencies: - '@types/react': 19.2.9 - '@types/react@19.2.9': dependencies: csstype: 3.2.3 @@ -7732,6 +7811,10 @@ snapshots: async-exit-hook@2.0.1: {} + async-mutex@0.5.0: + dependencies: + tslib: 2.8.1 + async@3.2.6: {} asynckit@0.4.0: {} @@ -8345,6 +8428,8 @@ snapshots: didyoumean@1.2.2: {} + diff@8.0.3: {} + dir-compare@4.2.0: dependencies: minimatch: 3.1.2 @@ -8379,6 +8464,10 @@ snapshots: verror: 1.10.1 optional: true + dompurify@3.2.7: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dompurify@3.3.1: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -8719,10 +8808,6 @@ snapshots: dependencies: reusify: 1.1.0 - fault@1.0.4: - dependencies: - format: 0.2.2 - fd-slicer@1.1.0: dependencies: pend: 1.2.0 @@ -8767,8 +8852,6 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 - format@0.2.2: {} - forwarded-parse@2.1.2: {} forwarded@0.2.0: {} @@ -9058,12 +9141,8 @@ snapshots: property-information: 7.1.0 space-separated-tokens: 2.0.2 - highlight.js@10.7.3: {} - highlight.js@11.11.1: {} - highlightjs-vue@1.0.0: {} - hono@4.11.5: {} hosted-git-info@4.1.0: @@ -9276,6 +9355,8 @@ snapshots: json5@2.2.3: {} + jsonc-parser@3.3.1: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -9351,11 +9432,6 @@ snapshots: lowercase-keys@2.0.0: {} - lowlight@1.20.0: - dependencies: - fault: 1.0.4 - highlight.js: 10.7.3 - lowlight@3.3.0: dependencies: '@types/hast': 3.0.4 @@ -9374,6 +9450,8 @@ snapshots: lru-cache@7.18.3: {} + lru_map@0.4.1: {} + lucide-react@0.468.0(react@19.2.1): dependencies: react: 19.2.1 @@ -9412,6 +9490,8 @@ snapshots: markdown-table@3.0.4: {} + marked@14.0.0: {} + marked@16.4.2: {} marked@17.0.1: {} @@ -9894,6 +9974,11 @@ snapshots: module-details-from-path@1.0.4: {} + monaco-editor@0.55.1: + dependencies: + dompurify: 3.2.7 + marked: 14.0.0 + motion-dom@11.18.1: dependencies: motion-utils: 11.18.1 @@ -10220,8 +10305,6 @@ snapshots: tar-fs: 2.1.4 tunnel-agent: 0.6.0 - prismjs@1.30.0: {} - process-nextick-args@2.0.1: {} progress@2.0.3: {} @@ -10331,16 +10414,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.9 - react-syntax-highlighter@16.1.0(react@19.2.1): - dependencies: - '@babel/runtime': 7.28.6 - highlight.js: 10.7.3 - highlightjs-vue: 1.0.0 - lowlight: 1.20.0 - prismjs: 1.30.0 - react: 19.2.1 - refractor: 5.0.0 - react-zoom-pan-pinch@3.7.0(react-dom@19.2.1(react@19.2.1))(react@19.2.1): dependencies: react: 19.2.1 @@ -10391,13 +10464,6 @@ snapshots: readdirp@5.0.0: {} - refractor@5.0.0: - dependencies: - '@types/hast': 3.0.4 - '@types/prismjs': 1.26.5 - hastscript: 9.0.1 - parse-entities: 4.0.2 - regex-recursion@5.1.1: dependencies: regex: 5.1.1 @@ -10771,6 +10837,8 @@ snapshots: stat-mode@1.0.0: {} + state-local@1.0.7: {} + statuses@2.0.2: {} streamdown@2.1.0(react@19.2.1): diff --git a/src/main/lib/claude/env.ts b/src/main/lib/claude/env.ts index e181e7895..53097fea2 100644 --- a/src/main/lib/claude/env.ts +++ b/src/main/lib/claude/env.ts @@ -137,6 +137,35 @@ function stripSensitiveKeys(env: Record): void { } } +/** + * Load Claude Code CLI settings from disk (~/.claude/settings.json) + * This is needed because some users configure Claude Code via 'claude config' + * which writes to this file, and these settings might not be in the shell env. + */ +function loadClaudeCliSettings(): Record { + try { + const homeDir = os.homedir() + const settingsPath = path.join(homeDir, ".claude", "settings.json") + + if (!fs.existsSync(settingsPath)) { + return {} + } + + const content = fs.readFileSync(settingsPath, 'utf-8') + const settings = JSON.parse(content) + + // Config values are stored in the 'env' object within settings.json + if (settings && typeof settings === 'object' && settings.env && typeof settings.env === 'object') { + console.log(`[claude-env] Loaded ${Object.keys(settings.env).length} vars from ${settingsPath}`) + return settings.env as Record + } + } catch (error) { + console.warn("[claude-env] Failed to load settings.json:", error) + } + + return {} +} + /** * Load full shell environment. * - Windows: Derives PATH from process.env + common install locations (no shell spawn) @@ -148,62 +177,72 @@ export function getClaudeShellEnvironment(): Record { return { ...cachedShellEnv } } - // Windows: use platform provider to build environment + let env: Record = {} + + // 1. Load settings from ~/.claude/settings.json + // These are the lowest priority of the "external" configs, but higher than defaults. + // We start with these, then overlay shell env on top. + const fileSettings = loadClaudeCliSettings() + Object.assign(env, fileSettings) + + // 2. Load shell environment if (isWindows()) { console.log( "[claude-env] Windows detected, deriving PATH without shell invocation" ) // Use platform provider to build environment - const env = platform.buildEnvironment() - - // Strip sensitive keys - stripSensitiveKeys(env) + const platformEnv = platform.buildEnvironment() + stripSensitiveKeys(platformEnv) + + Object.assign(env, platformEnv) console.log( `[claude-env] Built Windows environment with ${Object.keys(env).length} vars` ) - cachedShellEnv = env - return { ...env } + } else { + // macOS/Linux: spawn interactive login shell to get full environment + const shell = getDefaultShell() + const command = `echo -n "${DELIMITER}"; env; echo -n "${DELIMITER}"; exit` + + try { + const output = execSync(`${shell} -ilc '${command}'`, { + encoding: "utf8", + timeout: 5000, + env: { + // Prevent Oh My Zsh from blocking with auto-update prompts + DISABLE_AUTO_UPDATE: "true", + // Minimal env to bootstrap the shell + HOME: os.homedir(), + USER: os.userInfo().username, + SHELL: shell, + }, + }) + + const shellEnv = parseEnvOutput(output) + stripSensitiveKeys(shellEnv) + + console.log( + `[claude-env] Loaded ${Object.keys(shellEnv).length} environment variables from shell` + ) + + // Overlay shell env on top of file settings + Object.assign(env, shellEnv) + } catch (error) { + console.error("[claude-env] Failed to load shell environment:", error) + + // Fallback: use platform provider + const platformEnv = platform.buildEnvironment() + stripSensitiveKeys(platformEnv) + + console.log("[claude-env] Using fallback environment from platform provider") + Object.assign(env, platformEnv) + } } - // macOS/Linux: spawn interactive login shell to get full environment - const shell = getDefaultShell() - const command = `echo -n "${DELIMITER}"; env; echo -n "${DELIMITER}"; exit` - - try { - const output = execSync(`${shell} -ilc '${command}'`, { - encoding: "utf8", - timeout: 5000, - env: { - // Prevent Oh My Zsh from blocking with auto-update prompts - DISABLE_AUTO_UPDATE: "true", - // Minimal env to bootstrap the shell - HOME: os.homedir(), - USER: os.userInfo().username, - SHELL: shell, - }, - }) - - const env = parseEnvOutput(output) - stripSensitiveKeys(env) - - console.log( - `[claude-env] Loaded ${Object.keys(env).length} environment variables from shell` - ) - cachedShellEnv = env - return { ...env } - } catch (error) { - console.error("[claude-env] Failed to load shell environment:", error) - - // Fallback: use platform provider - const env = platform.buildEnvironment() - stripSensitiveKeys(env) - - console.log("[claude-env] Using fallback environment from platform provider") - cachedShellEnv = env - return { ...env } - } + // 3. Cache and return + cachedShellEnv = env + return { ...env } } /** diff --git a/src/main/lib/claude/offline-handler.ts b/src/main/lib/claude/offline-handler.ts index 292279ac2..5254ecd2a 100644 --- a/src/main/lib/claude/offline-handler.ts +++ b/src/main/lib/claude/offline-handler.ts @@ -8,6 +8,11 @@ export type CustomClaudeConfig = { model: string token: string baseUrl: string + // Additional model configuration (optional) + defaultOpusModel?: string // ANTHROPIC_DEFAULT_OPUS_MODEL + defaultSonnetModel?: string // ANTHROPIC_DEFAULT_SONNET_MODEL + defaultHaikuModel?: string // ANTHROPIC_DEFAULT_HAIKU_MODEL + subagentModel?: string // CLAUDE_CODE_SUBAGENT_MODEL } export type OfflineCheckResult = { diff --git a/src/main/lib/trpc/routers/claude-code.ts b/src/main/lib/trpc/routers/claude-code.ts index 78766a88b..174b795d5 100644 --- a/src/main/lib/trpc/routers/claude-code.ts +++ b/src/main/lib/trpc/routers/claude-code.ts @@ -118,7 +118,13 @@ export const claudeCodeRouter = router({ return { hasConfig, hasApiKey: !!shellEnv.ANTHROPIC_API_KEY, + apiKey: shellEnv.ANTHROPIC_API_KEY || shellEnv.ANTHROPIC_AUTH_TOKEN || null, baseUrl: shellEnv.ANTHROPIC_BASE_URL || null, + model: shellEnv.ANTHROPIC_MODEL || null, + defaultOpusModel: shellEnv.ANTHROPIC_DEFAULT_OPUS_MODEL || null, + defaultSonnetModel: shellEnv.ANTHROPIC_DEFAULT_SONNET_MODEL || null, + defaultHaikuModel: shellEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL || null, + subagentModel: shellEnv.CLAUDE_CODE_SUBAGENT_MODEL || null, } }), diff --git a/src/main/lib/trpc/routers/claude.ts b/src/main/lib/trpc/routers/claude.ts index 6b883f585..767a0c6ff 100644 --- a/src/main/lib/trpc/routers/claude.ts +++ b/src/main/lib/trpc/routers/claude.ts @@ -474,6 +474,11 @@ export const claudeRouter = router({ model: z.string().min(1), token: z.string().min(1), baseUrl: z.string().min(1), + // Additional model configuration (optional) + defaultOpusModel: z.string().optional(), + defaultSonnetModel: z.string().optional(), + defaultHaikuModel: z.string().optional(), + subagentModel: z.string().optional(), }) .optional(), maxThinkingTokens: z.number().optional(), // Enable extended thinking @@ -747,6 +752,12 @@ export const claudeRouter = router({ customEnv: { ANTHROPIC_AUTH_TOKEN: finalCustomConfig.token, ANTHROPIC_BASE_URL: finalCustomConfig.baseUrl, + // Additional model configuration from profile + ...(finalCustomConfig.model && { ANTHROPIC_MODEL: finalCustomConfig.model }), + ...(finalCustomConfig.defaultOpusModel && { ANTHROPIC_DEFAULT_OPUS_MODEL: finalCustomConfig.defaultOpusModel }), + ...(finalCustomConfig.defaultSonnetModel && { ANTHROPIC_DEFAULT_SONNET_MODEL: finalCustomConfig.defaultSonnetModel }), + ...(finalCustomConfig.defaultHaikuModel && { ANTHROPIC_DEFAULT_HAIKU_MODEL: finalCustomConfig.defaultHaikuModel }), + ...(finalCustomConfig.subagentModel && { CLAUDE_CODE_SUBAGENT_MODEL: finalCustomConfig.subagentModel }), }, }), enableTasks: input.enableTasks ?? true, @@ -810,6 +821,64 @@ export const claudeRouter = router({ symlinksCreated.add(cacheKey) } + // Write settings.json to isolated config dir with profile's env settings + // This ensures the CLI uses the selected profile instead of global ~/.claude/settings.json + try { + const isolatedSettingsPath = path.join(isolatedConfigDir, "settings.json") + const homeSettingsPath = path.join(os.homedir(), ".claude", "settings.json") + + // Read existing global settings (for non-env settings like permissions, plugins, etc.) + let baseSettings: Record = {} + try { + const globalSettings = await fs.readFile(homeSettingsPath, "utf-8") + baseSettings = JSON.parse(globalSettings) + } catch { + // No global settings file, start fresh + } + + // Build env section from profile config (overrides global env) + const profileEnv: Record = {} + if (finalCustomConfig) { + if (finalCustomConfig.token) { + profileEnv.ANTHROPIC_AUTH_TOKEN = finalCustomConfig.token + } + if (finalCustomConfig.baseUrl) { + profileEnv.ANTHROPIC_BASE_URL = finalCustomConfig.baseUrl + } + if (finalCustomConfig.model) { + profileEnv.ANTHROPIC_MODEL = finalCustomConfig.model + } + if (finalCustomConfig.defaultOpusModel) { + profileEnv.ANTHROPIC_DEFAULT_OPUS_MODEL = finalCustomConfig.defaultOpusModel + } + if (finalCustomConfig.defaultSonnetModel) { + profileEnv.ANTHROPIC_DEFAULT_SONNET_MODEL = finalCustomConfig.defaultSonnetModel + } + if (finalCustomConfig.defaultHaikuModel) { + profileEnv.ANTHROPIC_DEFAULT_HAIKU_MODEL = finalCustomConfig.defaultHaikuModel + } + if (finalCustomConfig.subagentModel) { + profileEnv.CLAUDE_CODE_SUBAGENT_MODEL = finalCustomConfig.subagentModel + } + } + + // Merge: keep global non-env settings, but replace env with profile settings + const isolatedSettings = { + ...baseSettings, + env: Object.keys(profileEnv).length > 0 ? profileEnv : undefined, + } + + // Remove env key entirely if no profile config (use OAuth) + if (!isolatedSettings.env) { + delete isolatedSettings.env + } + + await fs.writeFile(isolatedSettingsPath, JSON.stringify(isolatedSettings, null, 2)) + console.log(`[claude] Wrote profile settings to ${isolatedSettingsPath}`) + } catch (settingsErr) { + console.error(`[claude] Failed to write isolated settings.json:`, settingsErr) + } + // Read MCP servers from ~/.claude.json for the original project path // These will be passed directly to the SDK via options.mcpServers // OPTIMIZATION: Cache MCP config by file mtime to avoid re-parsing on every message @@ -965,13 +1034,14 @@ export const claudeRouter = router({ // Log SDK configuration for debugging if (isUsingOllama) { + const envRecord = finalEnv as Record console.log('[Ollama Debug] SDK Configuration:', { model: resolvedModel, - baseUrl: finalEnv.ANTHROPIC_BASE_URL, + baseUrl: envRecord.ANTHROPIC_BASE_URL, cwd: input.cwd, configDir: isolatedConfigDir, - hasAuthToken: !!finalEnv.ANTHROPIC_AUTH_TOKEN, - tokenPreview: finalEnv.ANTHROPIC_AUTH_TOKEN?.slice(0, 10) + '...', + hasAuthToken: !!envRecord.ANTHROPIC_AUTH_TOKEN, + tokenPreview: envRecord.ANTHROPIC_AUTH_TOKEN?.slice(0, 10) + '...', }) console.log('[Ollama Debug] Session settings:', { resumeSessionId: resumeSessionId || 'none (first message)', diff --git a/src/renderer/components/dialogs/settings-tabs/agents-models-tab.tsx b/src/renderer/components/dialogs/settings-tabs/agents-models-tab.tsx index 66ffebb4b..4180f4b04 100644 --- a/src/renderer/components/dialogs/settings-tabs/agents-models-tab.tsx +++ b/src/renderer/components/dialogs/settings-tabs/agents-models-tab.tsx @@ -1,13 +1,11 @@ import { useAtom, useSetAtom } from "jotai" import { MoreHorizontal, Plus } from "lucide-react" -import { useCallback, useEffect, useRef, useState } from "react" +import { useEffect, useState } from "react" import { toast } from "sonner" import { agentsSettingsDialogOpenAtom, anthropicOnboardingCompletedAtom, - customClaudeConfigAtom, openaiApiKeyAtom, - type CustomClaudeConfig, } from "../../../lib/atoms" import { trpc } from "../../../lib/trpc" import { Badge } from "../../ui/badge" @@ -20,6 +18,7 @@ import { } from "../../ui/dropdown-menu" import { Input } from "../../ui/input" import { Label } from "../../ui/label" +import { ModelProfilesSection } from "./model-profiles-section" // Hook to detect narrow screen function useIsNarrowScreen(): boolean { @@ -38,12 +37,6 @@ function useIsNarrowScreen(): boolean { return isNarrow } -const EMPTY_CONFIG: CustomClaudeConfig = { - model: "", - token: "", - baseUrl: "", -} - // Account row component function AccountRow({ account, @@ -247,10 +240,6 @@ function AnthropicAccountsSection() { } export function AgentsModelsTab() { - const [storedConfig, setStoredConfig] = useAtom(customClaudeConfigAtom) - const [model, setModel] = useState(storedConfig.model) - const [baseUrl, setBaseUrl] = useState(storedConfig.baseUrl) - const [token, setToken] = useState(storedConfig.token) const setAnthropicOnboardingCompleted = useSetAtom( anthropicOnboardingCompletedAtom, ) @@ -266,58 +255,10 @@ export function AgentsModelsTab() { const setOpenAIKeyMutation = trpc.voice.setOpenAIKey.useMutation() const trpcUtils = trpc.useUtils() - useEffect(() => { - setModel(storedConfig.model) - setBaseUrl(storedConfig.baseUrl) - setToken(storedConfig.token) - }, [storedConfig.model, storedConfig.baseUrl, storedConfig.token]) - useEffect(() => { setOpenaiKey(storedOpenAIKey) }, [storedOpenAIKey]) - const savedConfigRef = useRef(storedConfig) - - const handleBlurSave = useCallback(() => { - const trimmedModel = model.trim() - const trimmedBaseUrl = baseUrl.trim() - const trimmedToken = token.trim() - - // Only save if all fields are filled - if (trimmedModel && trimmedBaseUrl && trimmedToken) { - const next: CustomClaudeConfig = { - model: trimmedModel, - token: trimmedToken, - baseUrl: trimmedBaseUrl, - } - if ( - next.model !== savedConfigRef.current.model || - next.token !== savedConfigRef.current.token || - next.baseUrl !== savedConfigRef.current.baseUrl - ) { - setStoredConfig(next) - savedConfigRef.current = next - } - } else if (!trimmedModel && !trimmedBaseUrl && !trimmedToken) { - // All cleared — reset - if (savedConfigRef.current.model || savedConfigRef.current.token || savedConfigRef.current.baseUrl) { - setStoredConfig(EMPTY_CONFIG) - savedConfigRef.current = EMPTY_CONFIG - } - } - }, [model, baseUrl, token, setStoredConfig]) - - const handleReset = () => { - setStoredConfig(EMPTY_CONFIG) - savedConfigRef.current = EMPTY_CONFIG - setModel("") - setBaseUrl("") - setToken("") - toast.success("Model settings reset") - } - - const canReset = Boolean(model.trim() || baseUrl.trim() || token.trim()) - const handleClaudeCodeSetup = () => { // Don't disconnect - just open onboarding to add a new account // The previous code was calling disconnectClaudeCode.mutate() which @@ -398,74 +339,20 @@ export function AgentsModelsTab() {
+ {/* Model Profiles */}
-

- Override Model -

- {canReset && ( - - )} -
-
-
-
- -

- Model identifier to use for requests -

-
-
- setModel(e.target.value)} - onBlur={handleBlurSave} - className="w-full" - placeholder="claude-3-7-sonnet-20250219" - /> -
-
- -
-
- -

- ANTHROPIC_AUTH_TOKEN env -

-
-
- setToken(e.target.value)} - onBlur={handleBlurSave} - className="w-full" - placeholder="sk-ant-..." - /> -
-
- -
-
- -

- ANTHROPIC_BASE_URL env -

-
-
- setBaseUrl(e.target.value)} - onBlur={handleBlurSave} - className="w-full" - placeholder="https://api.anthropic.com" - /> -
+
+

+ Model Profiles +

+

+ Manage your proxy configurations +

-
+ +
{/* OpenAI API Key for Voice Input */} diff --git a/src/renderer/components/dialogs/settings-tabs/model-profile-form.tsx b/src/renderer/components/dialogs/settings-tabs/model-profile-form.tsx new file mode 100644 index 000000000..c7644d497 --- /dev/null +++ b/src/renderer/components/dialogs/settings-tabs/model-profile-form.tsx @@ -0,0 +1,201 @@ +import { X } from "lucide-react" +import { useState } from "react" +import { type CustomClaudeConfig, type ModelProfile } from "../../../lib/atoms" +import { Button } from "../../ui/button" +import { Input } from "../../ui/input" +import { Label } from "../../ui/label" + +const EMPTY_CONFIG: CustomClaudeConfig = { + model: "", + token: "", + baseUrl: "", + defaultOpusModel: "", + defaultSonnetModel: "", + defaultHaikuModel: "", + subagentModel: "", +} + +interface ProfileFormProps { + mode: "add" | "edit" + existingProfile?: ModelProfile + onSave: (profile: Omit) => void + onCancel: () => void +} + +export function ModelProfileForm({ + mode, + existingProfile, + onSave, + onCancel, +}: ProfileFormProps) { + const [name, setName] = useState(existingProfile?.name || "") + const [model, setModel] = useState(existingProfile?.config.model || "") + const [token, setToken] = useState(existingProfile?.config.token || "") + const [baseUrl, setBaseUrl] = useState(existingProfile?.config.baseUrl || "") + const [defaultOpusModel, setDefaultOpusModel] = useState(existingProfile?.config.defaultOpusModel || "") + const [defaultSonnetModel, setDefaultSonnetModel] = useState(existingProfile?.config.defaultSonnetModel || "") + const [defaultHaikuModel, setDefaultHaikuModel] = useState(existingProfile?.config.defaultHaikuModel || "") + const [subagentModel, setSubagentModel] = useState(existingProfile?.config.subagentModel || "") + + const isValid = name.trim() && model.trim() && token.trim() && baseUrl.trim() + + const handleSave = () => { + if (!isValid) return + + onSave({ + name: name.trim(), + config: { + model: model.trim(), + token: token.trim(), + baseUrl: baseUrl.trim(), + defaultOpusModel: defaultOpusModel.trim() || undefined, + defaultSonnetModel: defaultSonnetModel.trim() || undefined, + defaultHaikuModel: defaultHaikuModel.trim() || undefined, + subagentModel: subagentModel.trim() || undefined, + }, + }) + } + + return ( +
+
+

+ {mode === "add" ? "Add Profile" : "Edit Profile"} +

+ +
+ +
+
+ + setName(e.target.value)} + placeholder="e.g., Local Proxy, Cloud Gateway" + className="h-8" + /> +
+ +
+ + setBaseUrl(e.target.value)} + placeholder="https://api.anthropic.com" + className="h-8" + /> +
+ +
+ + setToken(e.target.value)} + placeholder="sk-ant-..." + className="h-8" + /> +
+ +
+ + setModel(e.target.value)} + placeholder="claude-sonnet-4-5-thinking" + className="h-8" + /> +
+ +
+

+ Additional Model Configuration (Optional) +

+
+ + {/* 2-column grid for optional model fields */} +
+
+ + setDefaultOpusModel(e.target.value)} + placeholder="claude-opus-4-5-thinking" + className="h-8" + /> +
+ +
+ + setDefaultSonnetModel(e.target.value)} + placeholder="claude-sonnet-4-5-thinking" + className="h-8" + /> +
+ +
+ + setDefaultHaikuModel(e.target.value)} + placeholder="claude-sonnet-4-5" + className="h-8" + /> +
+ +
+ + setSubagentModel(e.target.value)} + placeholder="claude-sonnet-4-5-thinking" + className="h-8" + /> +
+
+ + +
+
+ ) +} diff --git a/src/renderer/components/dialogs/settings-tabs/model-profile-row.tsx b/src/renderer/components/dialogs/settings-tabs/model-profile-row.tsx new file mode 100644 index 000000000..d2f2cd3c3 --- /dev/null +++ b/src/renderer/components/dialogs/settings-tabs/model-profile-row.tsx @@ -0,0 +1,104 @@ +import { Check, MoreHorizontal, Pencil, Trash2 } from "lucide-react" +import { type ModelProfile } from "../../../lib/atoms" +import { Badge } from "../../ui/badge" +import { Button } from "../../ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../../ui/dropdown-menu" + +interface ProfileRowProps { + profile: ModelProfile + isActive: boolean + onActivate: () => void + onEdit: () => void + onDelete: () => void +} + +export function ModelProfileRow({ + profile, + isActive, + onActivate, + onEdit, + onDelete, +}: ProfileRowProps) { + return ( +
+
+
+
+ {profile.name} + {isActive && ( + + Active + + )} +
+
+ Model: {profile.config.model || "Not set"} +
+
+ Proxy: {profile.config.baseUrl || "Default (api.anthropic.com)"} +
+ {(profile.config.defaultOpusModel || + profile.config.defaultSonnetModel || + profile.config.defaultHaikuModel || + profile.config.subagentModel) && ( +
+ {profile.config.defaultOpusModel && ( + + OPUS + + )} + {profile.config.defaultSonnetModel && ( + + SONNET + + )} + {profile.config.defaultHaikuModel && ( + + HAIKU + + )} + {profile.config.subagentModel && ( + + SUBAGENT + + )} +
+ )} +
+
+ +
+ {!isActive && ( + + )} + + + + + + + + Edit + + + + Delete + + + +
+
+ ) +} diff --git a/src/renderer/components/dialogs/settings-tabs/model-profiles-section.tsx b/src/renderer/components/dialogs/settings-tabs/model-profiles-section.tsx new file mode 100644 index 000000000..0a20caa13 --- /dev/null +++ b/src/renderer/components/dialogs/settings-tabs/model-profiles-section.tsx @@ -0,0 +1,144 @@ +import { useAtom } from "jotai" +import { Plus } from "lucide-react" +import { useState } from "react" +import { toast } from "sonner" +import { + activeProfileIdAtom, + modelProfilesAtom, + type ModelProfile, +} from "../../../lib/atoms" +import { Button } from "../../ui/button" +import { ModelProfileForm } from "./model-profile-form" +import { ModelProfileRow } from "./model-profile-row" + +type FormState = { + mode: "add" | "edit" | null + editingProfile?: ModelProfile +} + +export function ModelProfilesSection() { + const [profiles, setProfiles] = useAtom(modelProfilesAtom) + const [activeProfileId, setActiveProfileId] = useAtom(activeProfileIdAtom) + const [formState, setFormState] = useState({ mode: null }) + + // Filter out offline profile from display + const customProfiles = profiles.filter((p) => !p.isOffline) + + const handleAddProfile = (profileData: Omit) => { + const newProfile: ModelProfile = { + ...profileData, + id: crypto.randomUUID(), + } + + setProfiles([...profiles, newProfile]) + + // If this is the first custom profile, auto-activate it + if (customProfiles.length === 0) { + setActiveProfileId(newProfile.id) + } + + setFormState({ mode: null }) + toast.success("Profile added") + } + + const handleEditProfile = (profileData: Omit) => { + if (!formState.editingProfile) return + + setProfiles( + profiles.map((p) => + p.id === formState.editingProfile!.id + ? { ...profileData, id: p.id } + : p, + ), + ) + + setFormState({ mode: null }) + toast.success("Profile updated") + } + + const handleDeleteProfile = (profileId: string) => { + const confirmed = window.confirm( + "Are you sure you want to delete this profile?", + ) + if (!confirmed) return + + const newProfiles = profiles.filter((p) => p.id !== profileId) + setProfiles(newProfiles) + + // If we deleted the active profile, clear the active ID + if (activeProfileId === profileId) { + const remainingCustom = newProfiles.filter((p) => !p.isOffline) + setActiveProfileId(remainingCustom[0]?.id || null) + } + + toast.success("Profile deleted") + } + + const handleStartEdit = (profile: ModelProfile) => { + setFormState({ mode: "edit", editingProfile: profile }) + } + + const handleCancelForm = () => { + setFormState({ mode: null }) + } + + return ( +
+ {customProfiles.length === 0 && !formState.mode ? ( +
+

+ No custom profiles yet. Add your first proxy configuration. +

+ +
+ ) : ( + <> + {customProfiles.map((profile) => ( + setActiveProfileId(profile.id)} + onEdit={() => handleStartEdit(profile)} + onDelete={() => handleDeleteProfile(profile.id)} + /> + ))} + + {formState.mode && ( + + )} + + {!formState.mode && ( +
+ +
+ )} + + )} +
+ ) +} diff --git a/src/renderer/features/agents/atoms/index.ts b/src/renderer/features/agents/atoms/index.ts index 00f1793a0..d80b600e9 100644 --- a/src/renderer/features/agents/atoms/index.ts +++ b/src/renderer/features/agents/atoms/index.ts @@ -235,7 +235,13 @@ export const subChatModeAtomFamily = atomFamily((subChatId: string) => ) // Model ID to full Claude model string mapping +// When a custom profile is active, these map to the profile's model settings: +// - default: uses profile.model (ANTHROPIC_MODEL) +// - opus: uses profile.defaultOpusModel (ANTHROPIC_DEFAULT_OPUS_MODEL) +// - sonnet: uses profile.defaultSonnetModel (ANTHROPIC_DEFAULT_SONNET_MODEL) +// - haiku: uses profile.defaultHaikuModel (ANTHROPIC_DEFAULT_HAIKU_MODEL) export const MODEL_ID_MAP: Record = { + default: "default", opus: "opus", sonnet: "sonnet", haiku: "haiku", diff --git a/src/renderer/features/agents/lib/ipc-chat-transport.ts b/src/renderer/features/agents/lib/ipc-chat-transport.ts index 9a36a7544..bb57a5d08 100644 --- a/src/renderer/features/agents/lib/ipc-chat-transport.ts +++ b/src/renderer/features/agents/lib/ipc-chat-transport.ts @@ -2,14 +2,12 @@ import * as Sentry from "@sentry/electron/renderer" import type { ChatTransport, UIMessage } from "ai" import { toast } from "sonner" import { + activeConfigAtom, agentsLoginModalOpenAtom, autoOfflineModeAtom, - type CustomClaudeConfig, - customClaudeConfigAtom, enableTasksAtom, extendedThinkingEnabledAtom, historyEnabledAtom, - normalizeCustomClaudeConfig, selectedOllamaModelAtom, sessionInfoAtom, showOfflineModeFeaturesAtom, @@ -173,10 +171,8 @@ export class IPCChatTransport implements ChatTransport { const selectedModelId = appStore.get(lastSelectedModelIdAtom) const modelString = MODEL_ID_MAP[selectedModelId] || MODEL_ID_MAP["opus"] - const storedCustomConfig = appStore.get( - customClaudeConfigAtom, - ) as CustomClaudeConfig - const customConfig = normalizeCustomClaudeConfig(storedCustomConfig) + // Read active profile config (from profile system, supports multiple profiles) + const customConfig = appStore.get(activeConfigAtom) // Get selected Ollama model for offline mode const selectedOllamaModel = appStore.get(selectedOllamaModelAtom) diff --git a/src/renderer/features/agents/lib/models.ts b/src/renderer/features/agents/lib/models.ts index fb43b940d..a9b8448a2 100644 --- a/src/renderer/features/agents/lib/models.ts +++ b/src/renderer/features/agents/lib/models.ts @@ -1,5 +1,12 @@ -export const CLAUDE_MODELS = [ - { id: "opus", name: "Opus" }, - { id: "sonnet", name: "Sonnet" }, - { id: "haiku", name: "Haiku" }, +export interface ClaudeModel { + id: string + name: string + supportsThinking: boolean +} + +export const CLAUDE_MODELS: ClaudeModel[] = [ + { id: "default", name: "Default", supportsThinking: true }, + { id: "opus", name: "Opus", supportsThinking: true }, + { id: "sonnet", name: "Sonnet", supportsThinking: true }, + { id: "haiku", name: "Haiku", supportsThinking: false }, ] diff --git a/src/renderer/features/agents/main/active-chat.tsx b/src/renderer/features/agents/main/active-chat.tsx index 80c949bb6..dcc8ebb46 100644 --- a/src/renderer/features/agents/main/active-chat.tsx +++ b/src/renderer/features/agents/main/active-chat.tsx @@ -4,6 +4,13 @@ import { stripEmojis } from "../../../components/chat-markdown-renderer" import { Button } from "../../../components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "../../../components/ui/dropdown-menu" +import { Badge } from "../../../components/ui/badge" import { AgentIcon, AttachIcon, @@ -63,10 +70,12 @@ import { getQueryClient } from "../../../contexts/TRPCProvider" import { trackMessageSent } from "../../../lib/analytics" import { apiFetch } from "../../../lib/api-fetch" import { + activeProfileIdAtom, chatSourceModeAtom, customClaudeConfigAtom, defaultAgentModeAtom, isDesktopAtom, isFullscreenAtom, + modelProfilesAtom, normalizeCustomClaudeConfig, selectedOllamaModelAtom, soundNotificationsEnabledAtom @@ -368,6 +377,44 @@ const getAgentIcon = (agentId: string, className?: string) => { } } +// Model Profile Selector component for quick profile switching +function ModelProfileSelector() { + const [profiles] = useAtom(modelProfilesAtom) + const [activeProfileId, setActiveProfileId] = useAtom(activeProfileIdAtom) + + const activeProfile = profiles.find((p) => p.id === activeProfileId) + const customProfiles = profiles.filter((p) => !p.isOffline) + + if (customProfiles.length === 0) return null + + return ( + + + + + + {/* System (No Profile) option removed as per request - forcing use of named profiles */} + {customProfiles.map((profile) => ( + setActiveProfileId(profile.id)} + > + {profile.name} + {activeProfileId === profile.id && ( + Active + )} + + ))} + + + ) +} + // Copy button component with tooltip feedback (matches project style) function CopyButton({ onCopy, @@ -6469,6 +6516,7 @@ Make sure to preserve all functionality from both branches when resolving confli isTerminalOpen={isTerminalSidebarOpen} chatId={chatId} /> + {/* Open Locally button - desktop only, sandbox mode */} {showOpenLocally && ( diff --git a/src/renderer/features/agents/main/chat-input-area.tsx b/src/renderer/features/agents/main/chat-input-area.tsx index 8e98ede18..37a6fe1e6 100644 --- a/src/renderer/features/agents/main/chat-input-area.tsx +++ b/src/renderer/features/agents/main/chat-input-area.tsx @@ -1,9 +1,8 @@ "use client" import { useAtom, useAtomValue, useSetAtom } from "jotai" -import { ChevronDown, Loader2, RefreshCw, Zap } from "lucide-react" +import { BrainIcon, ChevronDown, Loader2, NotebookIcon, RefreshCw, RouteIcon, Zap } from "lucide-react" import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react" -import { createPortal } from "react-dom" import { Button } from "../../../components/ui/button" import { @@ -395,15 +394,7 @@ export const ChatInputArea = memo(function ChatInputArea({ const [slashSearchText, setSlashSearchText] = useState("") const [slashPosition, setSlashPosition] = useState({ top: 0, left: 0 }) - // Mode dropdown state - const [modeDropdownOpen, setModeDropdownOpen] = useState(false) - const [modeTooltip, setModeTooltip] = useState<{ - visible: boolean - position: { top: number; left: number } - mode: "agent" | "plan" - } | null>(null) - const tooltipTimeoutRef = useRef | null>(null) - const hasShownTooltipRef = useRef(false) + // Model dropdown state const [isModelDropdownOpen, setIsModelDropdownOpen] = useState(false) @@ -442,6 +433,9 @@ export const ChatInputArea = memo(function ChatInputArea({ // Extended thinking (reasoning) toggle const [thinkingEnabled, setThinkingEnabled] = useAtom(extendedThinkingEnabledAtom) + // Check if selected model supports thinking + const selectedModelSupportsThinking = selectedModel?.supportsThinking ?? true + // MCP status - from getAllMcpConfig query (provides global/local grouping) const setSettingsOpen = useSetAtom(agentsSettingsDialogOpenAtom) const setSettingsTab = useSetAtom(agentsSettingsDialogActiveTabAtom) @@ -941,7 +935,6 @@ export const ChatInputArea = memo(function ChatInputArea({ // Process other files - for text files, read content and add as file mention for (const file of otherFiles) { // Get file path using Electron's webUtils API (more reliable than file.path) - // @ts-expect-error - Electron's webUtils API const filePath: string | undefined = window.webUtils?.getPathForFile?.(file) || (file as File & { path?: string }).path let mentionId: string @@ -1141,173 +1134,7 @@ export const ChatInputArea = memo(function ChatInputArea({
- {/* Mode toggle (Agent/Plan) */} - { - setModeDropdownOpen(open) - if (!open) { - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - setModeTooltip(null) - hasShownTooltipRef.current = false - } - }} - > - - - - e.preventDefault()} - > - { - // Clear tooltip before closing dropdown (onMouseLeave won't fire) - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - setModeTooltip(null) - updateMode("agent") - setModeDropdownOpen(false) - }} - className="justify-between gap-2" - onMouseEnter={(e) => { - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - const rect = e.currentTarget.getBoundingClientRect() - const showTooltip = () => { - setModeTooltip({ - visible: true, - position: { - top: rect.top, - left: rect.right + 8, - }, - mode: "agent", - }) - hasShownTooltipRef.current = true - tooltipTimeoutRef.current = null - } - if (hasShownTooltipRef.current) { - showTooltip() - } else { - tooltipTimeoutRef.current = setTimeout( - showTooltip, - 1000, - ) - } - }} - onMouseLeave={() => { - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - setModeTooltip(null) - }} - > -
- - Agent -
- {subChatMode !== "plan" && ( - - )} -
- { - // Clear tooltip before closing dropdown (onMouseLeave won't fire) - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - setModeTooltip(null) - updateMode("plan") - setModeDropdownOpen(false) - }} - className="justify-between gap-2" - onMouseEnter={(e) => { - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - const rect = e.currentTarget.getBoundingClientRect() - const showTooltip = () => { - setModeTooltip({ - visible: true, - position: { - top: rect.top, - left: rect.right + 8, - }, - mode: "plan", - }) - hasShownTooltipRef.current = true - tooltipTimeoutRef.current = null - } - if (hasShownTooltipRef.current) { - showTooltip() - } else { - tooltipTimeoutRef.current = setTimeout( - showTooltip, - 1000, - ) - } - }} - onMouseLeave={() => { - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - setModeTooltip(null) - }} - > -
- - Plan -
- {subChatMode === "plan" && ( - - )} -
-
- {modeTooltip?.visible && - createPortal( -
-
- - {modeTooltip.mode === "agent" - ? "Apply changes directly without a plan" - : "Create a plan before making changes"} - -
-
, - document.body, - )} -
+ {/* Model selector - shows Ollama models when offline, Claude models when online */} {availableModels.isOffline && availableModels.hasOllama ? ( @@ -1357,33 +1184,20 @@ export const ChatInputArea = memo(function ChatInputArea({ ) : ( // Online mode: show Claude model selector + // Model selector works with custom profiles - selections map to profile's model settings { - if (!hasCustomClaudeConfig) { - setIsModelDropdownOpen(open) - } - }} + open={isModelDropdownOpen} + onOpenChange={setIsModelDropdownOpen} > + + {/* Mode toggle (Agent/Plan) - simple toggle button */} + +
diff --git a/src/renderer/features/agents/main/new-chat-form.tsx b/src/renderer/features/agents/main/new-chat-form.tsx index ac42ef2aa..e38a3cd5f 100644 --- a/src/renderer/features/agents/main/new-chat-form.tsx +++ b/src/renderer/features/agents/main/new-chat-form.tsx @@ -2,9 +2,8 @@ import { useVirtualizer } from "@tanstack/react-virtual" import { useAtom, useAtomValue, useSetAtom } from "jotai" -import { AlignJustify, Plus, Zap } from "lucide-react" +import { AlignJustify, BrainIcon, Plus, RouteIcon, Zap } from "lucide-react" import { useCallback, useEffect, useMemo, useRef, useState } from "react" -import { createPortal } from "react-dom" import { Button } from "../../../components/ui/button" import { DropdownMenu, @@ -13,14 +12,12 @@ import { DropdownMenuTrigger, } from "../../../components/ui/dropdown-menu" import { - AgentIcon, AttachIcon, BranchIcon, CheckIcon, ClaudeCodeIcon, CursorIcon, IconChevronDown, - PlanIcon, SearchIcon, } from "../../../components/ui/icons" import { @@ -59,6 +56,7 @@ import { selectedOllamaModelAtom, customHotkeysAtom, chatSourceModeAtom, + extendedThinkingEnabledAtom, } from "../../../lib/atoms" // Desktop uses real tRPC import { toast } from "sonner" @@ -229,6 +227,8 @@ export function NewChatForm({ const toggleMode = useCallback(() => { setAgentMode(getNextMode) }, []) + // Extended thinking toggle + const [thinkingEnabled, setThinkingEnabled] = useAtom(extendedThinkingEnabledAtom) const [workMode, setWorkMode] = useAtom(lastSelectedWorkModeAtom) const debugMode = useAtomValue(agentsDebugModeAtom) const customClaudeConfig = useAtomValue(customClaudeConfigAtom) @@ -389,15 +389,6 @@ export function NewChatForm({ const [slashSearchText, setSlashSearchText] = useState("") const [slashPosition, setSlashPosition] = useState({ top: 0, left: 0 }) - // Mode tooltip state (floating tooltip like canvas) - const [modeTooltip, setModeTooltip] = useState<{ - visible: boolean - position: { top: number; left: number } - mode: "agent" | "plan" - } | null>(null) - const tooltipTimeoutRef = useRef | null>(null) - const hasShownTooltipRef = useRef(false) - const [modeDropdownOpen, setModeDropdownOpen] = useState(false) const [isModelDropdownOpen, setIsModelDropdownOpen] = useState(false) // Voice input state @@ -1410,6 +1401,8 @@ export function NewChatForm({ [validatedProject?.path, handleAddAttachments, trpcUtils], ) + const selectedModelSupportsThinking = selectedModel?.supportsThinking ?? false + // Context items for images, files, and pasted text files const contextItems = images.length > 0 || files.length > 0 || pastedTexts.length > 0 ? ( @@ -1566,173 +1559,6 @@ export function NewChatForm({
- {/* Mode toggle (Agent/Plan) */} - { - setModeDropdownOpen(open) - if (!open) { - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - setModeTooltip(null) - hasShownTooltipRef.current = false - } - }} - > - - {agentMode === "plan" ? ( - - ) : ( - - )} - {agentMode === "plan" ? "Plan" : "Agent"} - - - e.preventDefault()} - > - { - // Clear tooltip before closing dropdown (onMouseLeave won't fire) - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - setModeTooltip(null) - setAgentMode("agent") - setModeDropdownOpen(false) - }} - className="justify-between gap-2" - onMouseEnter={(e) => { - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - const rect = - e.currentTarget.getBoundingClientRect() - const showTooltip = () => { - setModeTooltip({ - visible: true, - position: { - top: rect.top, - left: rect.right + 8, - }, - mode: "agent", - }) - hasShownTooltipRef.current = true - tooltipTimeoutRef.current = null - } - if (hasShownTooltipRef.current) { - showTooltip() - } else { - tooltipTimeoutRef.current = setTimeout( - showTooltip, - 1000, - ) - } - }} - onMouseLeave={() => { - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - setModeTooltip(null) - }} - > -
- - Agent -
- {agentMode !== "plan" && ( - - )} -
- { - // Clear tooltip before closing dropdown (onMouseLeave won't fire) - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - setModeTooltip(null) - setAgentMode("plan") - setModeDropdownOpen(false) - }} - className="justify-between gap-2" - onMouseEnter={(e) => { - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - const rect = e.currentTarget.getBoundingClientRect() - const showTooltip = () => { - setModeTooltip({ - visible: true, - position: { - top: rect.top, - left: rect.right + 8, - }, - mode: "plan", - }) - hasShownTooltipRef.current = true - tooltipTimeoutRef.current = null - } - if (hasShownTooltipRef.current) { - showTooltip() - } else { - tooltipTimeoutRef.current = setTimeout( - showTooltip, - 1000, - ) - } - }} - onMouseLeave={() => { - if (tooltipTimeoutRef.current) { - clearTimeout(tooltipTimeoutRef.current) - tooltipTimeoutRef.current = null - } - setModeTooltip(null) - }} - > -
- - Plan -
- {agentMode === "plan" && ( - - )} -
-
- {modeTooltip?.visible && - createPortal( -
-
- - {modeTooltip.mode === "agent" - ? "Apply changes directly without a plan" - : "Create a plan before making changes"} - -
-
, - document.body, - )} -
- {/* Model selector - shows Ollama models when offline, Claude models when online */} {availableModels.isOffline && availableModels.hasOllama ? ( // Offline mode: show Ollama model selector @@ -1742,7 +1568,7 @@ export function NewChatForm({ >
{isSelected && ( @@ -1838,6 +1663,45 @@ export function NewChatForm({ )} + + {/* Thinking toggle - simple toggle button */} + + + {/* Mode toggle (Agent/Plan) - simple toggle button */} +
diff --git a/src/renderer/features/onboarding/api-key-onboarding-page.tsx b/src/renderer/features/onboarding/api-key-onboarding-page.tsx index 8bb0f18ac..9fc7fe3d0 100644 --- a/src/renderer/features/onboarding/api-key-onboarding-page.tsx +++ b/src/renderer/features/onboarding/api-key-onboarding-page.tsx @@ -12,8 +12,11 @@ import { apiKeyOnboardingCompletedAtom, billingMethodAtom, customClaudeConfigAtom, + modelProfilesAtom, + activeProfileIdAtom, type CustomClaudeConfig, } from "../../lib/atoms" +import { trpc } from "../../lib/trpc" import { cn } from "../../lib/utils" // Check if the key looks like a valid Anthropic API key @@ -24,6 +27,8 @@ const isValidApiKey = (key: string) => { export function ApiKeyOnboardingPage() { const [storedConfig, setStoredConfig] = useAtom(customClaudeConfigAtom) + const [profiles, setProfiles] = useAtom(modelProfilesAtom) + const setActiveProfileId = useSetAtom(activeProfileIdAtom) const billingMethod = useAtomValue(billingMethodAtom) const setBillingMethod = useSetAtom(billingMethodAtom) const setApiKeyOnboardingCompleted = useSetAtom(apiKeyOnboardingCompletedAtom) @@ -34,11 +39,20 @@ export function ApiKeyOnboardingPage() { const defaultModel = "claude-sonnet-4-20250514" const defaultBaseUrl = "https://api.anthropic.com" + // Query for existing CLI config (to pre-fill form with detected proxy) + const { data: cliConfig } = trpc.claudeCode.hasExistingCliConfig.useQuery() + const [apiKey, setApiKey] = useState(storedConfig.token) const [model, setModel] = useState(storedConfig.model || "") const [token, setToken] = useState(storedConfig.token) const [baseUrl, setBaseUrl] = useState(storedConfig.baseUrl || "") + // Optional model configuration fields + const [defaultOpusModel, setDefaultOpusModel] = useState(storedConfig.defaultOpusModel || "") + const [defaultSonnetModel, setDefaultSonnetModel] = useState(storedConfig.defaultSonnetModel || "") + const [defaultHaikuModel, setDefaultHaikuModel] = useState(storedConfig.defaultHaikuModel || "") + const [subagentModel, setSubagentModel] = useState(storedConfig.subagentModel || "") const [isSubmitting, setIsSubmitting] = useState(false) + const [hasAppliedCliConfig, setHasAppliedCliConfig] = useState(false) // Sync from stored config on mount useEffect(() => { @@ -48,8 +62,32 @@ export function ApiKeyOnboardingPage() { } if (storedConfig.model) setModel(storedConfig.model) if (storedConfig.baseUrl) setBaseUrl(storedConfig.baseUrl) + if (storedConfig.defaultOpusModel) setDefaultOpusModel(storedConfig.defaultOpusModel) + if (storedConfig.defaultSonnetModel) setDefaultSonnetModel(storedConfig.defaultSonnetModel) + if (storedConfig.defaultHaikuModel) setDefaultHaikuModel(storedConfig.defaultHaikuModel) + if (storedConfig.subagentModel) setSubagentModel(storedConfig.subagentModel) }, []) + // Pre-fill form with detected CLI config (proxy settings) if available + useEffect(() => { + // Only proceed if we have config and haven't applied it yet + if (cliConfig && !hasAppliedCliConfig && isCustomModel) { + setHasAppliedCliConfig(true) + + // Pre-fill with detected values if the current state is empty + if (cliConfig.apiKey && token === "") { + setToken(cliConfig.apiKey) + setApiKey(cliConfig.apiKey) + } + if (cliConfig.baseUrl && baseUrl === "") setBaseUrl(cliConfig.baseUrl) + if (cliConfig.model && model === "") setModel(cliConfig.model) + if (cliConfig.defaultOpusModel && defaultOpusModel === "") setDefaultOpusModel(cliConfig.defaultOpusModel) + if (cliConfig.defaultSonnetModel && defaultSonnetModel === "") setDefaultSonnetModel(cliConfig.defaultSonnetModel) + if (cliConfig.defaultHaikuModel && defaultHaikuModel === "") setDefaultHaikuModel(cliConfig.defaultHaikuModel) + if (cliConfig.subagentModel && subagentModel === "") setSubagentModel(cliConfig.subagentModel) + } + }, [cliConfig, hasAppliedCliConfig, isCustomModel, token, baseUrl, model, defaultOpusModel, defaultSonnetModel, defaultHaikuModel, subagentModel]) + const handleBack = () => { setBillingMethod(null) } @@ -71,7 +109,7 @@ export function ApiKeyOnboardingPage() { setIsSubmitting(false) } - // Submit for custom model mode (all three fields) + // Submit for custom model mode (all fields) const submitCustomModel = () => { const trimmedModel = model.trim() const trimmedToken = token.trim() @@ -85,8 +123,29 @@ export function ApiKeyOnboardingPage() { model: trimmedModel, token: trimmedToken, baseUrl: trimmedBaseUrl, + defaultOpusModel: defaultOpusModel.trim() || undefined, + defaultSonnetModel: defaultSonnetModel.trim() || undefined, + defaultHaikuModel: defaultHaikuModel.trim() || undefined, + subagentModel: subagentModel.trim() || undefined, } + + // Save to legacy config atom setStoredConfig(config) + + // Also create a profile in modelProfilesAtom so it shows in settings + const newProfileId = crypto.randomUUID() + const newProfile = { + id: newProfileId, + name: "Default", // Saved as Default as requested + config, + } + + // Add new profile (keep existing profiles like offline profile) + setProfiles([...profiles, newProfile]) + + // Set as active profile + setActiveProfileId(newProfileId) + setApiKeyOnboardingCompleted(true) setIsSubmitting(false) @@ -188,22 +247,22 @@ export function ApiKeyOnboardingPage() { // Custom model mode with all fields return ( -
+
{/* Draggable title bar area */}
{/* Back button - fixed in top left corner below traffic lights */} -
+
{/* Header with dual icons */}
@@ -226,23 +285,21 @@ export function ApiKeyOnboardingPage() { {/* Form Fields */}
- {/* Model Name */} + {/* Base URL */}
- + setModel(e.target.value)} - placeholder="claude-sonnet-4-20250514" + value={baseUrl} + onChange={(e) => setBaseUrl(e.target.value)} + placeholder="https://api.anthropic.com" className="w-full" /> -

- Model identifier for API requests -

+

API endpoint URL

{/* API Token */}
- +
- {/* Base URL */} + {/* Default Model */}
- + setBaseUrl(e.target.value)} - placeholder="https://api.anthropic.com" + value={model} + onChange={(e) => setModel(e.target.value)} + placeholder="claude-sonnet-4-5-thinking" className="w-full" /> -

API endpoint URL

+

+ Model identifier for API requests +

+
+ + {/* Optional Model Configuration Section */} +
+

+ Additional Model Configuration (Optional) +

+
+ + {/* 2-column grid for optional model fields */} +
+ {/* Default Opus Model */} +
+ + setDefaultOpusModel(e.target.value)} + placeholder="claude-opus-4-5-thinking" + className="w-full h-8" + /> +
+ + {/* Default Sonnet Model */} +
+ + setDefaultSonnetModel(e.target.value)} + placeholder="claude-sonnet-4-5-thinking" + className="w-full h-8" + /> +
+ + {/* Default Haiku Model */} +
+ + setDefaultHaikuModel(e.target.value)} + placeholder="claude-sonnet-4-5" + className="w-full h-8" + /> +
+ + {/* Subagent Model */} +
+ + setSubagentModel(e.target.value)} + placeholder="claude-sonnet-4-5-thinking" + className="w-full h-8" + /> +
diff --git a/src/renderer/features/onboarding/cli-config-detected-page.tsx b/src/renderer/features/onboarding/cli-config-detected-page.tsx index 9229f8895..686f6f59a 100644 --- a/src/renderer/features/onboarding/cli-config-detected-page.tsx +++ b/src/renderer/features/onboarding/cli-config-detected-page.tsx @@ -1,14 +1,17 @@ "use client"; -import { useSetAtom } from "jotai"; +import { useAtom, useSetAtom } from "jotai"; import { useState } from "react"; import { ChevronLeft, Check, ExternalLink } from "lucide-react"; import { ClaudeCodeIcon, KeyFilledIcon } from "../../components/ui/icons"; import { + activeProfileIdAtom, apiKeyOnboardingCompletedAtom, billingMethodAtom, cliConfigDetectedShownAtom, + modelProfilesAtom, + type ModelProfile, } from "../../lib/atoms"; import { trpc } from "../../lib/trpc"; import { cn } from "../../lib/utils"; @@ -19,6 +22,8 @@ export function CliConfigDetectedPage() { apiKeyOnboardingCompletedAtom, ); const setCliConfigDetectedShown = useSetAtom(cliConfigDetectedShownAtom); + const [profiles, setProfiles] = useAtom(modelProfilesAtom); + const [activeProfileId, setActiveProfileId] = useAtom(activeProfileIdAtom); const [isContinuing, setIsContinuing] = useState(false); const [showOAuthFlow, setShowOAuthFlow] = useState(false); @@ -27,6 +32,34 @@ export function CliConfigDetectedPage() { trpc.claudeCode.hasExistingCliConfig.useQuery(); const handleUseExistingConfig = () => { + const cliConfigData = cliConfig; + if (!cliConfigData) return; + + // Prompt user to save as profile + const shouldSave = window.confirm( + "Would you like to save this configuration as a profile for easy switching later?", + ); + + if (shouldSave) { + const newProfile: ModelProfile = { + id: crypto.randomUUID(), + name: "Detected CLI Config", + config: { + model: cliConfigData.model || "claude-3-7-sonnet-20250219", + token: cliConfigData.apiKey || "", + baseUrl: cliConfigData.baseUrl || "https://api.anthropic.com", + // Additional model config (if available in CLI config) + defaultOpusModel: cliConfigData.defaultOpusModel || undefined, + defaultSonnetModel: cliConfigData.defaultSonnetModel || undefined, + defaultHaikuModel: cliConfigData.defaultHaikuModel || undefined, + subagentModel: cliConfigData.subagentModel || undefined, + }, + }; + + setProfiles([...profiles, newProfile]); + setActiveProfileId(newProfile.id); + } + setIsContinuing(true); // Mark CLI config detected as shown so we don't show this page again setCliConfigDetectedShown(true); diff --git a/src/renderer/lib/atoms/index.ts b/src/renderer/lib/atoms/index.ts index d0005ae9f..b6ca6d258 100644 --- a/src/renderer/lib/atoms/index.ts +++ b/src/renderer/lib/atoms/index.ts @@ -207,6 +207,11 @@ export type CustomClaudeConfig = { model: string token: string baseUrl: string + // Additional model configuration (optional) + defaultOpusModel?: string // ANTHROPIC_DEFAULT_OPUS_MODEL + defaultSonnetModel?: string // ANTHROPIC_DEFAULT_SONNET_MODEL + defaultHaikuModel?: string // ANTHROPIC_DEFAULT_HAIKU_MODEL + subagentModel?: string // CLAUDE_CODE_SUBAGENT_MODEL } // Model profile system - support multiple configs @@ -261,6 +266,8 @@ export const customClaudeConfigAtom = atomWithStorage( { getOnInit: true }, ) +// Migration block removed as per user request (legacy config no longer auto-migrates to "Migrated Config") + // OpenAI API key for voice transcription (for users without paid subscription) export const openaiApiKeyAtom = atomWithStorage( "agents:openai-api-key", @@ -360,7 +367,6 @@ export const activeConfigAtom = atom((get) => { // Preferences - Extended Thinking // When enabled, Claude will use extended thinking for deeper reasoning (128K tokens) -// Note: Extended thinking disables response streaming export const extendedThinkingEnabledAtom = atomWithStorage( "preferences:extended-thinking-enabled", false, From 3f32198f0fcaaad575095e967025c48ac065e8dd Mon Sep 17 00:00:00 2001 From: Yogi Date: Sat, 31 Jan 2026 18:55:23 +0700 Subject: [PATCH 03/16] docs: add system tray implementation plan --- docs/plans/2026-01-31-system-tray.md | 1220 ++++++++++++++++++++++++++ 1 file changed, 1220 insertions(+) create mode 100644 docs/plans/2026-01-31-system-tray.md diff --git a/docs/plans/2026-01-31-system-tray.md b/docs/plans/2026-01-31-system-tray.md new file mode 100644 index 000000000..b2db6a10f --- /dev/null +++ b/docs/plans/2026-01-31-system-tray.md @@ -0,0 +1,1220 @@ +# System Tray Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Add persistent system tray support with quick access to profiles and workspaces, allowing the app to continue running when all windows are closed. + +**Architecture:** Use Electron's `Tray` API to create a system tray icon with dynamic context menu. Modify app lifecycle to prevent quit on window close. Add IPC handlers for profile switching and workspace navigation from tray menu. + +**Tech Stack:** Electron Tray API, IPC (main ↔ renderer), existing tRPC for data fetching + +--- + +## Task 1: Setup System Tray Infrastructure + +**Files:** +- Create: `src/main/lib/tray.ts` +- Modify: `src/main/index.ts` + +**Step 1: Create tray module with basic initialization** + +Create `src/main/lib/tray.ts`: + +```typescript +import { app, Menu, Tray, nativeImage } from "electron" +import { join } from "path" +import { IS_DEV } from "../constants" + +let tray: Tray | null = null + +/** + * Get path to tray icon based on platform and theme + * Returns template image for macOS (auto-adapts to light/dark mode) + */ +function getTrayIconPath(): string { + const iconName = process.platform === "darwin" + ? "tray-iconTemplate.png" // macOS template (auto dark mode) + : "tray-icon.png" // Windows/Linux + + if (IS_DEV) { + return join(app.getAppPath(), "resources", "icons", iconName) + } + return join(process.resourcesPath, "icons", iconName) +} + +/** + * Initialize system tray + * Should be called after app is ready + */ +export function initTray(): void { + if (tray) { + console.log("[Tray] Already initialized") + return + } + + const iconPath = getTrayIconPath() + console.log("[Tray] Creating tray with icon:", iconPath) + + tray = new Tray(iconPath) + tray.setToolTip("1Code") + + // Build initial menu + buildTrayMenu() + + console.log("[Tray] Initialized successfully") +} + +/** + * Build and set the tray context menu + * Called on init and whenever menu needs to update + */ +export function buildTrayMenu(): void { + if (!tray) { + console.warn("[Tray] Cannot build menu - tray not initialized") + return + } + + const template: Electron.MenuItemConstructorOptions[] = [ + { + label: "Quit", + click: () => { + app.quit() + }, + }, + ] + + const contextMenu = Menu.buildFromTemplate(template) + tray.setContextMenu(contextMenu) +} + +/** + * Destroy the tray icon + * Should be called on app quit + */ +export function destroyTray(): void { + if (tray) { + tray.destroy() + tray = null + console.log("[Tray] Destroyed") + } +} + +/** + * Get the tray instance (for testing or advanced use) + */ +export function getTray(): Tray | null { + return tray +} +``` + +**Step 2: Add tray initialization to main process** + +Modify `src/main/index.ts` - add import at top: + +```typescript +import { initTray, destroyTray } from "./lib/tray" +``` + +**Step 3: Initialize tray after app ready** + +In `src/main/index.ts`, find the `app.whenReady()` section (around line 548) and add after menu initialization (after line 804): + +```typescript + // Initialize system tray + initTray() +``` + +**Step 4: Cleanup tray on quit** + +In `src/main/index.ts`, find the `app.on("before-quit")` handler or add one if it doesn't exist: + +```typescript +// Clean up before quit +app.on("before-quit", () => { + console.log("[App] before-quit event") + destroyTray() +}) +``` + +**Step 5: Test basic tray initialization** + +Run: `pnpm dev` +Expected: App starts with tray icon visible in menu bar (macOS) or system tray (Windows/Linux). Right-click shows "Quit" menu item. + +**Step 6: Commit** + +```bash +git add src/main/lib/tray.ts src/main/index.ts +git commit -m "feat: add basic system tray infrastructure" +``` + +--- + +## Task 2: Add Tray Icon Assets + +**Files:** +- Create: `resources/icons/tray-icon.png` +- Create: `resources/icons/tray-iconTemplate.png` +- Create: `resources/icons/[email protected]` +- Create: `resources/icons/tray-iconTemplate@2x.png` + +**Step 1: Create placeholder tray icons** + +Since you have an existing icon system, we'll use a simple approach: + +For macOS template icon (auto dark mode support): +- `tray-iconTemplate.png` - 16x16px, monochrome, black on transparent +- `tray-iconTemplate@2x.png` - 32x32px, monochrome, black on transparent + +For Windows/Linux: +- `tray-icon.png` - 16x16px, colored +- `[email protected]` - 32x32px, colored + +**Note:** You can use your existing app icon scaled down and converted to monochrome for the template icons. The "Template" suffix is required for macOS dark mode support. + +**Step 2: Test icons appear correctly** + +Run: `pnpm dev` +Expected: Tray icon visible and sized correctly on current platform + +**Step 3: Commit** + +```bash +git add resources/icons/tray-*.png +git commit -m "feat: add tray icon assets for all platforms" +``` + +--- + +## Task 3: Prevent App Quit on Window Close + +**Files:** +- Modify: `src/main/windows/main.ts` + +**Step 1: View current window close handling** + +View `src/main/windows/main.ts` to understand current window lifecycle. + +**Step 2: Modify window close behavior to hide instead of quit** + +In `src/main/windows/main.ts`, find where the window is created (in `createMainWindow` or similar). Add a `close` event handler to prevent app quit: + +```typescript +// Prevent app quit when window closes - hide to tray instead +mainWindow.on("close", (event) => { + if (!app.isQuitting) { + event.preventDefault() + mainWindow.hide() + console.log("[Window] Hidden to tray (prevented quit)") + } +}) +``` + +**Step 3: Add app quit flag** + +In `src/main/index.ts`, add flag to track intentional quits. Add near the top after imports: + +```typescript +// Track when app is intentionally quitting (vs just closing windows) +let isQuitting = false + +// Make it accessible globally +;(global as any).__isAppQuitting = () => isQuitting +``` + +**Step 4: Set quit flag in before-quit handler** + +Modify the `before-quit` handler in `src/main/index.ts`: + +```typescript +app.on("before-quit", () => { + console.log("[App] before-quit event") + isQuitting = true + ;(global as any).__isAppQuitting = () => true + destroyTray() + // ... existing cleanup +}) +``` + +**Step 5: Update window close handler to use global flag** + +In `src/main/windows/main.ts`, modify the close handler: + +```typescript +mainWindow.on("close", (event) => { + const isQuitting = (global as any).__isAppQuitting?.() || false + if (!isQuitting) { + event.preventDefault() + mainWindow.hide() + console.log("[Window] Hidden to tray (prevented quit)") + } +}) +``` + +**Step 6: Test window close behavior** + +Run: `pnpm dev` +Actions: +1. Close main window (Cmd+W or close button) +2. Verify app still running (tray icon visible) +3. Right-click tray → Quit +4. Verify app quits completely + +**Step 7: Commit** + +```bash +git add src/main/windows/main.ts src/main/index.ts +git commit -m "feat: prevent app quit on window close, hide to tray" +``` + +--- + +## Task 4: Add Tray Click to Restore Window + +**Files:** +- Modify: `src/main/lib/tray.ts` + +**Step 1: Add window restore logic on tray click** + +In `src/main/lib/tray.ts`, add import for window management: + +```typescript +import { getWindow, getAllWindows, createMainWindow } from "../windows/main" +``` + +**Step 2: Add click handler in initTray** + +In the `initTray()` function, after creating the tray, add: + +```typescript + // Left-click: restore or create window + tray.on("click", () => { + console.log("[Tray] Click event - restoring window") + + const windows = getAllWindows() + if (windows.length > 0) { + // Show and focus first window + const window = windows[0]! + if (window.isMinimized()) { + window.restore() + } + window.show() + window.focus() + } else { + // No windows exist, create new one + createMainWindow() + } + }) +``` + +**Step 3: Handle double-click on Windows** + +Add after the click handler: + +```typescript + // Double-click on Windows also shows window + tray.on("double-click", () => { + console.log("[Tray] Double-click event - restoring window") + + const windows = getAllWindows() + if (windows.length > 0) { + const window = windows[0]! + if (window.isMinimized()) { + window.restore() + } + window.show() + window.focus() + } else { + createMainWindow() + } + }) +``` + +**Step 4: Test tray click behavior** + +Run: `pnpm dev` +Actions: +1. Close window (hides to tray) +2. Click tray icon +3. Verify window reappears and is focused + +**Step 5: Commit** + +```bash +git add src/main/lib/tray.ts +git commit -m "feat: restore window on tray icon click" +``` + +--- + +## Task 5: Add IPC Handler for Profile Data + +**Files:** +- Create: `src/main/lib/ipc/tray-handlers.ts` +- Modify: `src/main/index.ts` + +**Step 1: Create IPC handlers for tray menu data** + +Create `src/main/lib/ipc/tray-handlers.ts`: + +```typescript +import { ipcMain } from "electron" +import { getDatabase } from "../db" +import { modelProfiles } from "../db/schema" +import { projects } from "../db/schema" + +/** + * Register IPC handlers for tray menu data fetching + */ +export function registerTrayHandlers(): void { + // Get all profiles for tray menu + ipcMain.handle("tray:get-profiles", async () => { + try { + const db = getDatabase() + const allProfiles = db.select().from(modelProfiles).all() + + // Also get active profile ID from localStorage + // Note: We'll need to get this from renderer via another call + return allProfiles + } catch (error) { + console.error("[Tray] Failed to get profiles:", error) + return [] + } + }) + + // Get all projects/workspaces for tray menu + ipcMain.handle("tray:get-projects", async () => { + try { + const db = getDatabase() + const allProjects = db.select().from(projects).all() + return allProjects + } catch (error) { + console.error("[Tray] Failed to get projects:", error) + return [] + } + }) + + // Get active profile ID (from renderer localStorage) + ipcMain.handle("tray:get-active-profile-id", async (event) => { + try { + // Execute JS in renderer to get localStorage value + const result = await event.sender.executeJavaScript( + `localStorage.getItem('agents:active-profile-id')` + ) + return result ? JSON.parse(result) : null + } catch (error) { + console.error("[Tray] Failed to get active profile:", error) + return null + } + }) + + console.log("[Tray] IPC handlers registered") +} +``` + +**Step 2: Register handlers in main process** + +In `src/main/index.ts`, add import: + +```typescript +import { registerTrayHandlers } from "./lib/ipc/tray-handlers" +``` + +**Step 3: Call registration in app ready** + +In the `app.whenReady()` section, after database initialization: + +```typescript + // Register tray IPC handlers + registerTrayHandlers() +``` + +**Step 4: Test IPC handlers (manual console test)** + +In renderer devtools console: +```javascript +await window.electronAPI.invoke('tray:get-profiles') +await window.electronAPI.invoke('tray:get-projects') +``` + +Expected: Returns arrays of profiles and projects + +**Step 5: Commit** + +```bash +git add src/main/lib/ipc/tray-handlers.ts src/main/index.ts +git commit -m "feat: add IPC handlers for tray menu data" +``` + +--- + +## Task 6: Build Dynamic Tray Menu with Profiles + +**Files:** +- Modify: `src/main/lib/tray.ts` +- Modify: `src/main/lib/ipc/tray-handlers.ts` + +**Step 1: Add async menu building with profile data** + +In `src/main/lib/tray.ts`, modify `buildTrayMenu` to be async and fetch data: + +```typescript +/** + * Build and set the tray context menu + * Fetches current profiles and projects from database + */ +export async function buildTrayMenu(): Promise { + if (!tray) { + console.warn("[Tray] Cannot build menu - tray not initialized") + return + } + + try { + // Fetch profiles and active profile ID + const db = getDatabase() + const allProfiles = db.select().from(modelProfiles).all() + + // Get active profile from first window's localStorage + let activeProfileId: string | null = null + const windows = getAllWindows() + if (windows.length > 0) { + try { + const result = await windows[0]!.webContents.executeJavaScript( + `localStorage.getItem('agents:active-profile-id')` + ) + activeProfileId = result ? JSON.parse(result) : null + } catch (e) { + console.warn("[Tray] Could not get active profile:", e) + } + } + + // Build profile menu items + const profileMenuItems: Electron.MenuItemConstructorOptions[] = allProfiles.map(profile => ({ + label: profile.name, + type: "checkbox", + checked: profile.id === activeProfileId, + click: () => { + handleProfileSwitch(profile.id) + }, + })) + + const template: Electron.MenuItemConstructorOptions[] = [ + { + label: "Profiles", + submenu: profileMenuItems.length > 0 + ? profileMenuItems + : [{ label: "No profiles", enabled: false }], + }, + { type: "separator" }, + { + label: "Quit", + click: () => { + app.quit() + }, + }, + ] + + const contextMenu = Menu.buildFromTemplate(template) + tray.setContextMenu(contextMenu) + } catch (error) { + console.error("[Tray] Failed to build menu:", error) + } +} +``` + +**Step 2: Add imports for database access** + +At the top of `src/main/lib/tray.ts`: + +```typescript +import { getDatabase } from "../db" +import { modelProfiles, projects } from "../db/schema" +import { getAllWindows } from "../windows/main" +``` + +**Step 3: Add profile switch handler** + +In `src/main/lib/tray.ts`, add the handler function: + +```typescript +/** + * Handle profile switch from tray menu + * Updates localStorage in all windows and reloads them + */ +async function handleProfileSwitch(profileId: string): Promise { + console.log("[Tray] Switching to profile:", profileId) + + const windows = getAllWindows() + for (const window of windows) { + try { + if (window.isDestroyed()) continue + + // Update localStorage + await window.webContents.executeJavaScript( + `localStorage.setItem('agents:active-profile-id', '${JSON.stringify(profileId)}')` + ) + + // Reload window to apply new profile + window.reload() + } catch (error) { + console.error("[Tray] Failed to switch profile for window:", error) + } + } + + // Rebuild menu to update checkmarks + await buildTrayMenu() +} +``` + +**Step 4: Update initTray to use async buildTrayMenu** + +Change the call in `initTray()`: + +```typescript + // Build initial menu (async, don't await) + buildTrayMenu().catch(err => { + console.error("[Tray] Failed to build initial menu:", err) + }) +``` + +**Step 5: Test profile switching from tray** + +Run: `pnpm dev` +Actions: +1. Right-click tray icon +2. Click on Profiles submenu +3. Select different profile +4. Verify window reloads with new profile active + +**Step 6: Commit** + +```bash +git add src/main/lib/tray.ts +git commit -m "feat: add dynamic profile menu to tray" +``` + +--- + +## Task 7: Add Workspaces Menu to Tray + +**Files:** +- Modify: `src/main/lib/tray.ts` + +**Step 1: Add workspace menu items to buildTrayMenu** + +In `buildTrayMenu()`, after fetching profiles, add project fetching: + +```typescript + // Fetch projects/workspaces + const allProjects = db.select().from(projects).all() + + // Build workspace menu items + const workspaceMenuItems: Electron.MenuItemConstructorOptions[] = allProjects.map(project => ({ + label: project.displayName || project.path, + click: () => { + handleWorkspaceClick(project.id) + }, + })) +``` + +**Step 2: Add workspaces to menu template** + +Update the template array: + +```typescript + const template: Electron.MenuItemConstructorOptions[] = [ + { + label: "Profiles", + submenu: profileMenuItems.length > 0 + ? profileMenuItems + : [{ label: "No profiles", enabled: false }], + }, + { type: "separator" }, + { + label: "Workspaces", + submenu: workspaceMenuItems.length > 0 + ? workspaceMenuItems + : [{ label: "No workspaces", enabled: false }], + }, + { type: "separator" }, + { + label: "Quit", + click: () => { + app.quit() + }, + }, + ] +``` + +**Step 3: Add workspace click handler** + +```typescript +/** + * Handle workspace click from tray menu + * Sets active workspace and opens new chat form + */ +async function handleWorkspaceClick(projectId: string): Promise { + console.log("[Tray] Opening workspace:", projectId) + + // Get or create main window + let window = getWindow() + if (!window) { + window = await createMainWindow() + } + + // Show and focus window + if (window.isMinimized()) { + window.restore() + } + window.show() + window.focus() + + // Set selected project in localStorage and trigger new chat + try { + await window.webContents.executeJavaScript(` + (async () => { + const project = ${JSON.stringify(projectId)}; + // Get project data + const projectData = await window.trpcClient.projects.list.query(); + const selectedProject = projectData.find(p => p.id === project); + if (selectedProject) { + localStorage.setItem('agents:selected-project', JSON.stringify(selectedProject)); + // Trigger new chat form opening (send IPC to renderer) + window.dispatchEvent(new CustomEvent('tray:open-workspace', { + detail: { projectId: project } + })); + } + })() + `) + } catch (error) { + console.error("[Tray] Failed to open workspace:", error) + } +} +``` + +**Step 4: Add renderer listener for workspace opening** + +This will need renderer-side handling which we'll add in next task. + +**Step 5: Test workspace menu** + +Run: `pnpm dev` +Expected: Workspaces submenu shows all projects from database + +**Step 6: Commit** + +```bash +git add src/main/lib/tray.ts +git commit -m "feat: add workspaces menu to tray" +``` + +--- + +## Task 8: Add "New Workspace" and "Settings" Menu Items + +**Files:** +- Modify: `src/main/lib/tray.ts` + +**Step 1: Add menu items to template** + +In `buildTrayMenu()`, update the template to include new items: + +```typescript + const template: Electron.MenuItemConstructorOptions[] = [ + { + label: "Profiles", + submenu: profileMenuItems.length > 0 + ? profileMenuItems + : [{ label: "No profiles", enabled: false }], + }, + { type: "separator" }, + { + label: "Workspaces", + submenu: workspaceMenuItems.length > 0 + ? workspaceMenuItems + : [{ label: "No workspaces", enabled: false }], + }, + { type: "separator" }, + { + label: "New Workspace...", + click: () => { + handleNewWorkspace() + }, + }, + { + label: "Settings...", + click: () => { + handleOpenSettings() + }, + }, + { type: "separator" }, + { + label: "Quit", + click: () => { + app.quit() + }, + }, + ] +``` + +**Step 2: Add New Workspace handler** + +```typescript +/** + * Handle "New Workspace" click + * Opens select-repo-page + */ +async function handleNewWorkspace(): Promise { + console.log("[Tray] Opening new workspace dialog") + + // Get or create main window + let window = getWindow() + if (!window) { + window = await createMainWindow() + } + + // Show and focus window + if (window.isMinimized()) { + window.restore() + } + window.show() + window.focus() + + // Navigate to select-repo page by clearing selected project + try { + await window.webContents.executeJavaScript(` + localStorage.removeItem('agents:selected-project'); + window.location.reload(); + `) + } catch (error) { + console.error("[Tray] Failed to open new workspace:", error) + } +} +``` + +**Step 3: Add Settings handler** + +```typescript +/** + * Handle "Settings" click + * Opens settings page + */ +async function handleOpenSettings(): Promise { + console.log("[Tray] Opening settings") + + // Get or create main window + let window = getWindow() + if (!window) { + window = await createMainWindow() + } + + // Show and focus window + if (window.isMinimized()) { + window.restore() + } + window.show() + window.focus() + + // Send custom event to open settings + try { + await window.webContents.executeJavaScript(` + window.dispatchEvent(new CustomEvent('tray:open-settings')); + `) + } catch (error) { + console.error("[Tray] Failed to open settings:", error) + } +} +``` + +**Step 4: Test new menu items** + +Run: `pnpm dev` +Actions: +1. Right-click tray → "New Workspace..." → should clear project and reload +2. Right-click tray → "Settings..." → should dispatch settings event + +**Step 5: Commit** + +```bash +git add src/main/lib/tray.ts +git commit -m "feat: add New Workspace and Settings to tray menu" +``` + +--- + +## Task 9: Add Renderer Event Listeners + +**Files:** +- Create: `src/renderer/lib/tray-events.ts` +- Modify: `src/renderer/App.tsx` + +**Step 1: Create tray event handler** + +Create `src/renderer/lib/tray-events.ts`: + +```typescript +import { useEffect } from "react" +import { useSetAtom } from "jotai" +import { selectedProjectAtom } from "../features/agents/atoms" + +/** + * Hook to listen for tray events and handle them + */ +export function useTrayEvents() { + const setSelectedProject = useSetAtom(selectedProjectAtom) + + useEffect(() => { + // Handle workspace selection from tray + const handleWorkspaceOpen = (event: CustomEvent) => { + const { projectId } = event.detail + console.log("[Tray Events] Workspace opened:", projectId) + + // The project is already set in localStorage by the tray handler + // Just trigger a reload to show new chat form + window.location.reload() + } + + // Handle settings open from tray + const handleSettingsOpen = () => { + console.log("[Tray Events] Settings opened") + // Navigate to settings page + // TODO: Implement settings navigation + // For now, just log + } + + window.addEventListener("tray:open-workspace", handleWorkspaceOpen as EventListener) + window.addEventListener("tray:open-settings", handleSettingsOpen) + + return () => { + window.removeEventListener("tray:open-workspace", handleWorkspaceOpen as EventListener) + window.removeEventListener("tray:open-settings", handleSettingsOpen) + } + }, [setSelectedProject]) +} +``` + +**Step 2: Use the hook in App component** + +In `src/renderer/App.tsx`, import and use the hook: + +```typescript +import { useTrayEvents } from "./lib/tray-events" +``` + +In the `App` component function: + +```typescript +export function App() { + // Listen for tray events + useTrayEvents() + + // ... rest of App component +} +``` + +**Step 3: Test tray workspace opening** + +Run: `pnpm dev` +Actions: +1. Close window +2. Right-click tray → Workspaces → select a workspace +3. Verify window opens with workspace selected + +**Step 4: Commit** + +```bash +git add src/renderer/lib/tray-events.ts src/renderer/App.tsx +git commit -m "feat: add renderer event listeners for tray actions" +``` + +--- + +## Task 10: Add Dynamic Menu Refresh + +**Files:** +- Modify: `src/main/lib/tray.ts` +- Modify: `src/main/lib/ipc/tray-handlers.ts` + +**Step 1: Export menu rebuild function for IPC** + +In `src/main/lib/tray.ts`, ensure `buildTrayMenu` is exported (it already is). + +**Step 2: Add IPC handler to trigger menu rebuild** + +In `src/main/lib/ipc/tray-handlers.ts`, add handler: + +```typescript +import { buildTrayMenu } from "../tray" + + // Rebuild tray menu (called when profiles/projects change) + ipcMain.handle("tray:rebuild-menu", async () => { + try { + await buildTrayMenu() + return { success: true } + } catch (error) { + console.error("[Tray] Failed to rebuild menu:", error) + return { success: false, error: String(error) } + } + }) +``` + +**Step 3: Add menu refresh on data changes** + +In renderer, wherever profiles or projects are added/removed, call: + +```typescript +// After profile/project changes: +await window.electronAPI?.invoke("tray:rebuild-menu") +``` + +This can be added to profile management and project management components in a follow-up. + +**Step 4: Test menu updates** + +Manual test: +1. Open app +2. Add a new profile via UI +3. Check tray menu updates (will need manual refresh for now) + +**Step 5: Commit** + +```bash +git add src/main/lib/ipc/tray-handlers.ts +git commit -m "feat: add IPC handler for tray menu refresh" +``` + +--- + +## Task 11: Add Platform-Specific Polish + +**Files:** +- Modify: `src/main/lib/tray.ts` + +**Step 1: Add macOS-specific menu improvements** + +In `buildTrayMenu()`, add macOS-specific polish: + +```typescript + // macOS: Add "Show 1Code" as first item + if (process.platform === "darwin") { + template.unshift({ + label: "Show 1Code", + click: async () => { + const windows = getAllWindows() + if (windows.length > 0) { + const window = windows[0]! + if (window.isMinimized()) window.restore() + window.show() + window.focus() + } else { + await createMainWindow() + } + }, + }) + template.splice(1, 0, { type: "separator" }) + } +``` + +**Step 2: Improve tray icon template for macOS** + +The icon with "Template" suffix should be monochrome. Ensure assets are properly formatted. + +**Step 3: Test on macOS** + +Run on macOS: +- Verify template icon adapts to light/dark menu bar +- Verify "Show 1Code" appears at top of menu +- Verify menu follows macOS design guidelines + +**Step 4: Test on other platforms (if available)** + +Windows: Verify icon and menu work correctly +Linux: Verify tray positioning and menu + +**Step 5: Commit** + +```bash +git add src/main/lib/tray.ts +git commit -m "feat: add platform-specific tray menu polish" +``` + +--- + +## Task 12: Documentation and Testing + +**Files:** +- Create: `docs/features/system-tray.md` + +**Step 1: Write feature documentation** + +Create `docs/features/system-tray.md`: + +```markdown +# System Tray + +The app includes a persistent system tray icon that allows quick access to profiles and workspaces even when all windows are closed. + +## Platform Support + +- **macOS**: Menu bar icon (template image, adapts to light/dark mode) +- **Windows**: System tray icon (bottom-right) +- **Linux**: System tray icon (desktop environment dependent) + +## Features + +### Left Click +- Restores or creates the main window +- If windows are minimized, brings them to front +- If no windows exist, creates a new one + +### Right Click Menu + +``` +├── Show 1Code (macOS only) +├── ───────── +├── Profiles ⊳ +│ ├── ✓ Active Profile +│ └── Other Profiles +├── ───────── +├── Workspaces ⊳ +│ └── Project List +├── ───────── +├── New Workspace... +├── Settings... +├── ───────── +└── Quit +``` + +### Profile Switching +- Click any profile to switch immediately +- All windows reload with new profile +- Active profile shown with checkmark + +### Workspace Navigation +- Click workspace to open new chat with that workspace selected +- Creates window if needed +- Sets workspace and shows new chat form + +### Background Mode +- App continues running when all windows are closed +- Only way to quit is via tray menu or Cmd+Q +- Windows can be restored by clicking tray icon + +## Implementation Details + +### Files +- `src/main/lib/tray.ts` - Tray initialization and menu building +- `src/main/lib/ipc/tray-handlers.ts` - IPC handlers for tray data +- `src/renderer/lib/tray-events.ts` - Renderer event listeners +- `resources/icons/tray-*.png` - Tray icon assets + +### Menu Updates +Menu rebuilds automatically when: +- Profiles are added/removed/switched +- Projects are added/removed +- App starts + +To manually trigger rebuild: +```typescript +await window.electronAPI?.invoke("tray:rebuild-menu") +``` + +## Testing + +1. **Basic functionality**: + - Close all windows → verify tray remains + - Click tray → verify window restores + - Right-click → verify menu appears + +2. **Profile switching**: + - Right-click → Profiles → select profile + - Verify window reloads with new profile + +3. **Workspace navigation**: + - Right-click → Workspaces → select workspace + - Verify new chat form opens with workspace + +4. **Platform-specific**: + - macOS: Verify template icon in light/dark modes + - Windows: Verify tray icon positioning + - Linux: Test on various desktop environments +``` + +**Step 2: Add troubleshooting section** + +Append to the doc: + +```markdown +## Troubleshooting + +### Tray icon not appearing (macOS) +- Check System Settings → Control Center → Menu Bar Only +- Ensure "Show in Menu Bar" is enabled for the app + +### Tray icon not appearing (Linux) +- Ensure desktop environment supports system tray +- Install system tray extensions if using GNOME +- Check if `libappindicator` is installed + +### Menu not updating +- Call `tray:rebuild-menu` IPC after data changes +- Check console for errors + +### App won't quit +- Use tray menu → Quit +- Or press Cmd+Q from menu bar (macOS) +- Force quit as last resort +``` + +**Step 3: Commit documentation** + +```bash +git add docs/features/system-tray.md +git commit -m "docs: add system tray feature documentation" +``` + +--- + +## Verification Plan + +### Manual Testing +1. **Tray Initialization** + - Start app → verify tray icon appears + - Right-click tray → verify menu matches design + +2. **Window Lifecycle** + - Close all windows → verify app stays running + - Click tray → verify window restores + - Quit from tray → verify clean shutdown + +3. **Profile Switching** + - Create multiple profiles + - Switch via tray menu → verify reload and active checkmark + +4. **Workspace Navigation** + - Add multiple projects + - Click workspace in tray → verify new chat with workspace + +5. **New Workspace / Settings** + - Click "New Workspace" → verify select-repo-page + - Click "Settings" → verify settings navigation + +### Platform Testing +- Test on macOS (menu bar, template icons) +- Test on Windows (system tray, standard icons) +- Test on Linux if possible (varies by DE) + +### Edge Cases +- No profiles: verify "No profiles" placeholder +- No workspaces: verify "No workspaces" placeholder +- All windows destroyed: verify tray click creates new window +- Rapid clicking: verify no duplicate windows + +--- + +## Notes + +- Tray icons should be simple, monochrome for macOS template +- Menu rebuilding is async - errors are logged but don't crash +- Window restoration prefers existing windows over creating new ones +- Profile switching causes window reload (slight UX disruption, but ensures clean state) From 1a0bd81c6dbff9cc116197853607786a41282294 Mon Sep 17 00:00:00 2001 From: Yogi Date: Sun, 1 Feb 2026 02:41:08 +0700 Subject: [PATCH 04/16] feat: add system tray with profile/workspace switching - Add system tray icon with context menu for macOS - Support quick switching between model profiles from tray - Add workspace selection and "New Workspace" from tray - Add Settings menu item that opens settings dialog - Bundle trayTemplate.png in production build via extraResources - Add tray-events hook for renderer communication - Fix timing issues with Settings navigation from tray - Ensure profiles are read even when no window exists --- docs/features/system-tray.md | 77 + ...-31-per-workspace-model-profiles-design.md | 650 ++ drizzle.config.ts | 8 + drizzle/0008_add_model_profile_id.sql | 2 + drizzle/0009_add_selected_model_id.sql | 2 + drizzle/meta/_journal.json | 16 +- package.json | 4 + src/main/index.ts | 11 + src/main/lib/claude/env.ts | 13 + src/main/lib/db/schema/index.ts | 3 + src/main/lib/tray.ts | 459 ++ src/main/lib/trpc/routers/chats.ts | 42 + src/main/windows/main.ts | 21 +- src/renderer/App.tsx | 4 + .../features/agents/lib/ipc-chat-transport.ts | 54 +- .../features/agents/main/active-chat.tsx | 6622 +++++++++-------- .../features/agents/main/chat-input-area.tsx | 172 +- .../features/agents/main/new-chat-form.tsx | 204 +- .../features/agents/ui/profile-selector.tsx | 165 + .../onboarding/api-key-onboarding-page.tsx | 8 +- .../onboarding/cli-config-detected-page.tsx | 52 +- .../features/sidebar/agents-sidebar.tsx | 71 + src/renderer/lib/atoms/index.ts | 89 +- src/renderer/lib/tray-events.ts | 37 + 24 files changed, 5637 insertions(+), 3149 deletions(-) create mode 100644 docs/features/system-tray.md create mode 100644 docs/plans/2025-01-31-per-workspace-model-profiles-design.md create mode 100644 drizzle/0008_add_model_profile_id.sql create mode 100644 drizzle/0009_add_selected_model_id.sql create mode 100644 src/main/lib/tray.ts create mode 100644 src/renderer/features/agents/ui/profile-selector.tsx create mode 100644 src/renderer/lib/tray-events.ts diff --git a/docs/features/system-tray.md b/docs/features/system-tray.md new file mode 100644 index 000000000..bcb1e9f8c --- /dev/null +++ b/docs/features/system-tray.md @@ -0,0 +1,77 @@ +# System Tray + +The app includes a persistent system tray icon that allows quick access to profiles and workspaces even when all windows are closed. + +## Platform Support + +- **macOS**: Menu bar icon (template image, adapts to light/dark mode) +- **Windows**: System tray icon (bottom-right) +- **Linux**: System tray icon (desktop environment dependent) + +## Features + +### Left Click +- Restores or creates the main window +- If windows are minimized, brings them to front +- If no windows exist, creates a new one + +### Right Click Menu + +``` +├── Show 1Code (macOS only) +├── ───────── +├── Profiles ⊳ +│ ├── ✓ Active Profile +│ └── Other Profiles +├── ───────── +├── Workspaces ⊳ +│ └── Project List +├── ───────── +├── New Workspace... +├── Settings... +├── ───────── +└── Quit +``` + +### Profile Switching +- Click any profile to switch immediately +- All windows reload with new profile +- Active profile shown with checkmark + +### Workspace Navigation +- Click workspace to open new chat with that workspace selected +- Creates window if needed +- Sets workspace and reloads + +### Background Mode +- App continues running when all windows are closed +- Only way to quit is via tray menu or Cmd+Q +- Windows can be restored by clicking tray icon + +## Implementation + +### Files +- `src/main/lib/tray.ts` - Tray initialization and menu building +- `src/renderer/lib/tray-events.ts` - Renderer event listeners +- `resources/icons/tray-*.png` - Tray icon assets + +### Menu Updates +Menu rebuilds when: +- Profiles are added/removed/switched +- Projects are added/removed +- App starts + +## Troubleshooting + +### Tray icon not appearing (macOS) +- Check System Settings → Control Center → Menu Bar Only +- Ensure "Show in Menu Bar" is enabled for the app + +### Tray icon not appearing (Linux) +- Ensure desktop environment supports system tray +- Install system tray extensions if using GNOME +- Check if `libappindicator` is installed + +### App won't quit +- Use tray menu → Quit +- Or press Cmd+Q from menu bar (macOS) diff --git a/docs/plans/2025-01-31-per-workspace-model-profiles-design.md b/docs/plans/2025-01-31-per-workspace-model-profiles-design.md new file mode 100644 index 000000000..ed80c85ed --- /dev/null +++ b/docs/plans/2025-01-31-per-workspace-model-profiles-design.md @@ -0,0 +1,650 @@ +# Per-Workspace Model Profiles + +**Date:** 2025-01-31 +**Status:** Approved +**Author:** AI Assistant + +## Overview + +Currently, model profiles are stored globally in localStorage. All chats share the same active profile. This design changes model profiles to be **per-chat (workspace)**, allowing each workspace to use a different model profile. + +## Goals + +1. Each chat/workspace can independently select a model profile +2. Each profile contains a list of available models with display names +3. Model selector shows `DisplayName (modelId)` format (e.g., "Opus (glm-4.7)") +4. New chats inherit the last-used profile +5. Profile can be changed from chat input area and right-click context menu + +## Non-Goals + +- Per-project profiles (may add later as enhancement) +- Syncing profiles across devices +- Profile import/export + +--- + +## Data Model Changes + +### 1. Extend `ModelProfile` Type + +**File:** `src/renderer/lib/atoms/index.ts` + +```typescript +// NEW: Model mapping within a profile +export type ModelMapping = { + id: string // Internal reference ID (e.g., "opus", "sonnet", "haiku") + displayName: string // User-facing name (e.g., "Opus", "Sonnet 3.5") + modelId: string // Actual model ID sent to API (e.g., "glm-4.7", "claude-3-opus-20240229") + supportsThinking?: boolean // Whether this model supports extended thinking +} + +// UPDATED: Add models array to profile +export type ModelProfile = { + id: string + name: string // Profile name (e.g., "My OpenRouter Proxy") + config: CustomClaudeConfig // Base URL, token, default model + models: ModelMapping[] // NEW: Available models for this profile + isOffline?: boolean // Mark as offline/Ollama profile +} +``` + +### 2. Database Schema Changes + +**File:** `src/main/lib/db/schema/index.ts` + +Add two new columns to `chats` table: + +```typescript +export const chats = sqliteTable("chats", { + // ... existing fields ... + + // NEW: Model profile reference + // References profile ID from localStorage modelProfilesAtom + // null = use lastUsedProfileId (global default) + modelProfileId: text("model_profile_id"), + + // NEW: Selected model within the profile + // References ModelMapping.id from the profile's models array + // null = use first model in profile + selectedModelId: text("selected_model_id"), +}) +``` + +### 3. Migration + +**File:** `drizzle/XXXX_add_model_profile_to_chats.sql` + +```sql +ALTER TABLE chats ADD COLUMN model_profile_id TEXT; +ALTER TABLE chats ADD COLUMN selected_model_id TEXT; +``` + +### 4. Rename Global Atom + +**File:** `src/renderer/lib/atoms/index.ts` + +```typescript +// RENAME: activeProfileIdAtom → lastUsedProfileIdAtom +// This now serves as: +// 1. Default for new chats +// 2. Fallback for chats with null modelProfileId +// 3. Updated whenever user changes profile on any chat + +export const lastUsedProfileIdAtom = atomWithStorage( + "agents:last-used-profile-id", // New storage key + null, + undefined, + { getOnInit: true }, +) + +// Migration: read old key, write to new key, delete old +if (typeof window !== "undefined") { + const oldKey = "agents:active-profile-id" + const newKey = "agents:last-used-profile-id" + const oldValue = localStorage.getItem(oldKey) + if (oldValue !== null && localStorage.getItem(newKey) === null) { + localStorage.setItem(newKey, oldValue) + localStorage.removeItem(oldKey) + } +} +``` + +### 5. Type Exports + +**File:** `src/main/lib/db/schema/index.ts` + +```typescript +// Update Chat type to include new fields +export type Chat = typeof chats.$inferSelect +// Chat now includes: modelProfileId: string | null, selectedModelId: string | null +``` + +--- + +## UI Components + +### 1. Profile Selector (New Component) + +**File:** `src/renderer/features/agents/ui/profile-selector.tsx` + +A dropdown component for selecting model profile. + +```typescript +interface ProfileSelectorProps { + chatId: string + currentProfileId: string | null // null = using default + onProfileChange: (profileId: string) => void +} +``` + +**Behavior:** +- Displays current profile name (or "Default" if null) +- Dropdown shows all profiles from `modelProfilesAtom` +- Checkmark on currently selected profile +- On select: calls `onProfileChange` which updates DB and atoms + +**Visual Design:** +``` +┌──────────────────┐ +│ My Proxy ▼ │ ← Compact button with dropdown arrow +└──────────────────┘ + +Dropdown open: +┌──────────────────────┐ +│ ✓ My Proxy │ ← Current selection +│ OpenRouter │ +│ Local Ollama │ +│ ─────────────────── │ +│ Manage Profiles... │ ← Opens settings +└──────────────────────┘ +``` + +### 2. Updated Model Selector + +**File:** `src/renderer/features/agents/main/chat-input-area.tsx` + +Update existing model dropdown to: +1. Source models from current profile's `models` array (not global `CLAUDE_MODELS`) +2. Display format: `DisplayName (modelId)` (e.g., "Opus (glm-4.7)") +3. Store selection in `chat.selectedModelId` + +**Visual Design:** +``` +┌─────────────────────────┐ +│ ❈ Opus (glm-4.7) ▼ │ +└─────────────────────────┘ + +Dropdown open: +┌─────────────────────────────┐ +│ ✓ Opus (glm-4.7) │ +│ Sonnet (claude-3-sonnet) │ +│ Haiku (claude-3-haiku) │ +└─────────────────────────────┘ +``` + +### 3. Chat Input Area Layout + +**File:** `src/renderer/features/agents/main/chat-input-area.tsx` + +Update `PromptInputActions` layout: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Plan, @ for context, / for commands │ +├─────────────────────────────────────────────────────────────────┤ +│ [My Proxy ▼] ❈ Opus (glm-4.7) ▼ 🧠 Thinking 📍 │ ○ 📎 ⬆ │ +│ └─ NEW ──┘ └──── UPDATED ────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +Component order (left to right): +1. **Profile Selector** (NEW) - dropdown to select profile +2. **Model Selector** (UPDATED) - shows models from selected profile +3. **Thinking Toggle** (existing) +4. **Mode Toggle** (existing) +5. **Context Indicator** (existing) +6. **Attach Button** (existing) +7. **Send Button** (existing) + +### 4. Chat Right-Click Context Menu + +**File:** `src/renderer/features/sidebar/components/chat-item.tsx` (or equivalent) + +Add "Model Profile" submenu to existing context menu: + +``` +┌─────────────────────────┐ +│ Rename │ +│ Archive │ +│ ─────────────────────── │ +│ Model Profile ▸ │ ← NEW submenu +│ ─────────────────────── │ +│ Delete │ +└─────────────────────────┘ + +Submenu: +┌─────────────────────────┐ +│ ✓ My Proxy │ +│ OpenRouter │ +│ Local Ollama │ +│ ─────────────────────── │ +│ Manage Profiles... │ +└─────────────────────────┘ +``` + +### 5. Model Profile Form Update + +**File:** `src/renderer/components/dialogs/settings-tabs/model-profile-form.tsx` + +Add models configuration to profile form: + +``` +┌─────────────────────────────────────────────────────────┐ +│ Profile Name: [My OpenRouter Proxy ] │ +│ Base URL: [https://api.openrouter.ai ] │ +│ API Token: [sk-or-... ] │ +│ │ +│ ─────────────── Models ─────────────────────────────── │ +│ │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ Display Name Model ID Thinking [×] │ │ +│ │ [Opus ] [glm-4.7 ] [✓] │ │ +│ │ [Sonnet ] [claude-3-sonnet ] [✓] │ │ +│ │ [Haiku ] [claude-3-haiku ] [ ] │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ [+ Add Model] │ +│ │ +│ [Cancel] [Save Profile] │ +└─────────────────────────────────────────────────────────┘ +``` + +--- + +## State Management + +### 1. New Atoms/Hooks + +**File:** `src/renderer/features/agents/atoms/index.ts` + +```typescript +// Get effective profile ID for a chat (with fallback) +export function useEffectiveProfileId(chat: Chat | null): string | null { + const lastUsedProfileId = useAtomValue(lastUsedProfileIdAtom) + return chat?.modelProfileId ?? lastUsedProfileId +} + +// Get effective model ID for a chat (with fallback) +export function useEffectiveModelId( + chat: Chat | null, + profile: ModelProfile | null +): string | null { + if (!profile) return null + const chatModelId = chat?.selectedModelId + // If chat has a model selected and it exists in profile, use it + if (chatModelId && profile.models.some(m => m.id === chatModelId)) { + return chatModelId + } + // Otherwise use first model in profile + return profile.models[0]?.id ?? null +} + +// Get full model info for display +export function useCurrentModel( + chat: Chat | null, + profiles: ModelProfile[] +): { profile: ModelProfile | null; model: ModelMapping | null } { + const effectiveProfileId = useEffectiveProfileId(chat) + const profile = profiles.find(p => p.id === effectiveProfileId) ?? null + const effectiveModelId = useEffectiveModelId(chat, profile) + const model = profile?.models.find(m => m.id === effectiveModelId) ?? null + return { profile, model } +} +``` + +### 2. tRPC Router Updates + +**File:** `src/main/lib/trpc/routers/chats.ts` + +```typescript +// Update chat's model profile +updateModelProfile: publicProcedure + .input(z.object({ + chatId: z.string(), + modelProfileId: z.string().nullable(), + })) + .mutation(async ({ input }) => { + const db = getDatabase() + db.update(chats) + .set({ + modelProfileId: input.modelProfileId, + updatedAt: new Date(), + }) + .where(eq(chats.id, input.chatId)) + .run() + return { success: true } + }), + +// Update chat's selected model +updateSelectedModel: publicProcedure + .input(z.object({ + chatId: z.string(), + selectedModelId: z.string().nullable(), + })) + .mutation(async ({ input }) => { + const db = getDatabase() + db.update(chats) + .set({ + selectedModelId: input.selectedModelId, + updatedAt: new Date(), + }) + .where(eq(chats.id, input.chatId)) + .run() + return { success: true } + }), +``` + +### 3. Chat Creation Update + +**File:** `src/main/lib/trpc/routers/chats.ts` + +Update `create` procedure to accept optional profile: + +```typescript +create: publicProcedure + .input(z.object({ + projectId: z.string(), + name: z.string().optional(), + modelProfileId: z.string().nullable().optional(), // NEW + selectedModelId: z.string().nullable().optional(), // NEW + })) + .mutation(async ({ input }) => { + const db = getDatabase() + const [chat] = db.insert(chats).values({ + projectId: input.projectId, + name: input.name, + modelProfileId: input.modelProfileId ?? null, // NEW + selectedModelId: input.selectedModelId ?? null, // NEW + }).returning().all() + return chat + }), +``` + +--- + +## Behavior Specifications + +### 1. Profile Change Flow + +When user changes profile (from input area or context menu): + +``` +1. User selects new profile from dropdown +2. Call trpc.chats.updateModelProfile({ chatId, modelProfileId: newProfileId }) +3. Update lastUsedProfileIdAtom (so new chats inherit this) +4. Model dropdown reloads with new profile's models array +5. Auto-select first model from new profile (or try to match by displayName) +6. If model changed, call trpc.chats.updateSelectedModel({ chatId, selectedModelId }) +7. UI updates immediately (optimistic) +``` + +### 2. Model Change Flow + +When user changes model within current profile: + +``` +1. User selects new model from dropdown +2. Call trpc.chats.updateSelectedModel({ chatId, selectedModelId: newModelId }) +3. UI updates immediately (optimistic) +4. Next message uses new model +``` + +### 3. New Chat Creation Flow + +When user creates a new chat: + +``` +1. Get lastUsedProfileId from atom +2. Get lastUsedModelId from profile (could store in atom too) +3. Create chat with: + - modelProfileId: lastUsedProfileId + - selectedModelId: lastUsedModelId (or first model in profile) +4. Chat immediately shows correct profile and model +``` + +### 4. Sending Message Flow + +When sending a message to Claude: + +```typescript +// In claude.ts router +const chat = await db.select().from(chats).where(eq(chats.id, chatId)).get() +const profiles = /* get from IPC or passed in */ + +// Get effective profile +const effectiveProfileId = chat.modelProfileId ?? lastUsedProfileId +const profile = profiles.find(p => p.id === effectiveProfileId) + +if (!profile) { + // No profile configured - use Claude Code default (OAuth) + return await sendWithDefaultAuth(message) +} + +// Get effective model +const effectiveModelId = chat.selectedModelId ?? profile.models[0]?.id +const model = profile.models.find(m => m.id === effectiveModelId) + +// Build config for Claude SDK +const config = { + baseUrl: profile.config.baseUrl, + apiKey: profile.config.token, + model: model?.modelId ?? profile.config.model, + // Extended thinking support + supportsThinking: model?.supportsThinking ?? true, +} + +return await sendWithCustomConfig(message, config) +``` + +### 5. Fallback Chain + +When resolving which profile/model to use: + +``` +Profile Resolution: +1. chat.modelProfileId (if set and profile exists) +2. lastUsedProfileIdAtom (if set and profile exists) +3. First non-offline profile in modelProfilesAtom +4. null (use Claude Code OAuth default) + +Model Resolution (within profile): +1. chat.selectedModelId (if set and model exists in profile) +2. First model in profile.models array +3. profile.config.model (legacy fallback) +``` + +--- + +## Migration Strategy + +### 1. Existing Profiles + +Existing profiles in localStorage don't have `models` array. Add migration: + +```typescript +// In atoms/index.ts or a migration hook +const migrateProfiles = (profiles: ModelProfile[]): ModelProfile[] => { + return profiles.map(profile => { + if (profile.models && profile.models.length > 0) { + return profile // Already migrated + } + + // Generate default models from legacy config + const defaultModel: ModelMapping = { + id: 'default', + displayName: 'Default', + modelId: profile.config.model || 'claude-3-opus', + supportsThinking: true, + } + + return { + ...profile, + models: [defaultModel], + } + }) +} +``` + +### 2. Existing Chats + +Existing chats have `modelProfileId: null` and `selectedModelId: null`. This is fine - they'll use the fallback chain (lastUsedProfileId → first profile → default). + +### 3. LocalStorage Key Rename + +Migrate `activeProfileIdAtom` to `lastUsedProfileIdAtom`: + +```typescript +// Run once on app start +if (typeof window !== "undefined") { + const oldKey = "agents:active-profile-id" + const newKey = "agents:last-used-profile-id" + const oldValue = localStorage.getItem(oldKey) + if (oldValue !== null && localStorage.getItem(newKey) === null) { + localStorage.setItem(newKey, oldValue) + localStorage.removeItem(oldKey) + } +} +``` + +--- + +## Implementation Tasks + +### Phase 1: Data Model (Backend) + +- [ ] **1.1** Add `ModelMapping` type to atoms +- [ ] **1.2** Update `ModelProfile` type to include `models: ModelMapping[]` +- [ ] **1.3** Add migration for existing profiles (add default models array) +- [ ] **1.4** Add `modelProfileId` and `selectedModelId` columns to chats schema +- [ ] **1.5** Create Drizzle migration file +- [ ] **1.6** Rename `activeProfileIdAtom` → `lastUsedProfileIdAtom` with migration +- [ ] **1.7** Update Chat type exports + +### Phase 2: tRPC API + +- [ ] **2.1** Add `chats.updateModelProfile` mutation +- [ ] **2.2** Add `chats.updateSelectedModel` mutation +- [ ] **2.3** Update `chats.create` to accept modelProfileId and selectedModelId +- [ ] **2.4** Update `chats.getById` to return new fields + +### Phase 3: UI Components + +- [ ] **3.1** Create `ProfileSelector` component +- [ ] **3.2** Update model selector to show `DisplayName (modelId)` format +- [ ] **3.3** Update model selector to source from profile's models array +- [ ] **3.4** Integrate ProfileSelector into ChatInputArea (left of model selector) +- [ ] **3.5** Add "Model Profile" submenu to chat context menu +- [ ] **3.6** Update ModelProfileForm to include models configuration + +### Phase 4: State & Logic + +- [ ] **4.1** Create `useEffectiveProfileId` hook +- [ ] **4.2** Create `useEffectiveModelId` hook +- [ ] **4.3** Create `useCurrentModel` hook +- [ ] **4.4** Update ChatInputArea to use new hooks +- [ ] **4.5** Update profile change handler (update DB + lastUsedProfileIdAtom) +- [ ] **4.6** Update model change handler (update DB) +- [ ] **4.7** Update new chat creation to inherit from lastUsedProfileIdAtom + +### Phase 5: Claude SDK Integration + +- [ ] **5.1** Update `claude.ts` router to resolve effective profile +- [ ] **5.2** Update message sending to use profile's model config +- [ ] **5.3** Pass `supportsThinking` from model to SDK + +### Phase 6: Testing + +- [ ] **6.1** Test profile switching in chat input area +- [ ] **6.2** Test profile switching via context menu +- [ ] **6.3** Test model switching within profile +- [ ] **6.4** Test new chat inherits correct profile +- [ ] **6.5** Test fallback when profile is deleted +- [ ] **6.6** Test migration of existing profiles +- [ ] **6.7** Test migration of existing chats + +--- + +## File Changes Summary + +| File | Change Type | Description | +|------|-------------|-------------| +| `src/renderer/lib/atoms/index.ts` | Modify | Add ModelMapping type, update ModelProfile, rename atom | +| `src/main/lib/db/schema/index.ts` | Modify | Add modelProfileId, selectedModelId to chats | +| `drizzle/XXXX_add_model_profile.sql` | Create | Migration for new columns | +| `src/main/lib/trpc/routers/chats.ts` | Modify | Add mutations, update create | +| `src/renderer/features/agents/ui/profile-selector.tsx` | Create | New profile selector component | +| `src/renderer/features/agents/main/chat-input-area.tsx` | Modify | Add ProfileSelector, update model display | +| `src/renderer/features/agents/main/new-chat-form.tsx` | Modify | Pass profile to chat creation | +| `src/renderer/features/sidebar/components/chat-item.tsx` | Modify | Add profile submenu to context menu | +| `src/renderer/components/dialogs/settings-tabs/model-profile-form.tsx` | Modify | Add models configuration UI | +| `src/renderer/components/dialogs/settings-tabs/model-profiles-section.tsx` | Modify | Update to use new profile structure | +| `src/main/lib/trpc/routers/claude.ts` | Modify | Resolve effective profile for messages | +| `src/renderer/features/agents/atoms/index.ts` | Modify | Add hooks for profile/model resolution | + +--- + +## Risks & Mitigations + +| Risk | Impact | Mitigation | +|------|--------|------------| +| Existing profiles break | High | Add migration to add default models array | +| Performance with many profiles | Low | Profiles stored in localStorage, fast lookup | +| Profile deleted while in use | Medium | Fallback chain handles gracefully | +| Model ID mismatch after profile edit | Medium | Re-select first model if current not found | + +--- + +## Future Enhancements + +1. **Per-project default profile** - Projects could have a default profile that new chats inherit +2. **Profile sync** - Sync profiles across devices via cloud storage +3. **Profile import/export** - Share profile configs as JSON files +4. **Model usage stats** - Track which models are used most per profile + +--- + +## Appendix: Example Profile Data + +```json +{ + "id": "abc123", + "name": "My OpenRouter Proxy", + "config": { + "model": "glm-4.7", + "token": "sk-or-v1-xxxx", + "baseUrl": "https://openrouter.ai/api/v1" + }, + "models": [ + { + "id": "opus", + "displayName": "Opus", + "modelId": "glm-4.7", + "supportsThinking": true + }, + { + "id": "sonnet", + "displayName": "Sonnet", + "modelId": "claude-3-sonnet-20240229", + "supportsThinking": true + }, + { + "id": "haiku", + "displayName": "Haiku", + "modelId": "claude-3-haiku-20240307", + "supportsThinking": false + } + ], + "isOffline": false +} +``` diff --git a/drizzle.config.ts b/drizzle.config.ts index 08fab6e69..efa1a903b 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,7 +1,15 @@ import { defineConfig } from "drizzle-kit" +import { homedir } from "os" +import { join } from "path" + +// Dev database path (matches Electron's userData for dev mode) +const devDbPath = join(homedir(), "Library/Application Support/Agents Dev/data/agents.db") export default defineConfig({ schema: "./src/main/lib/db/schema/index.ts", out: "./drizzle", dialect: "sqlite", + dbCredentials: { + url: devDbPath, + }, }) diff --git a/drizzle/0008_add_model_profile_id.sql b/drizzle/0008_add_model_profile_id.sql new file mode 100644 index 000000000..4dba051fd --- /dev/null +++ b/drizzle/0008_add_model_profile_id.sql @@ -0,0 +1,2 @@ +-- Add model_profile_id column to chats table +ALTER TABLE `chats` ADD `model_profile_id` text; diff --git a/drizzle/0009_add_selected_model_id.sql b/drizzle/0009_add_selected_model_id.sql new file mode 100644 index 000000000..491cd1fbe --- /dev/null +++ b/drizzle/0009_add_selected_model_id.sql @@ -0,0 +1,2 @@ +-- Add selected_model_id column to chats table +ALTER TABLE `chats` ADD `selected_model_id` text; diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 88a3e0a60..75aa3f60b 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -57,6 +57,20 @@ "when": 1769810815497, "tag": "0007_clammy_grim_reaper", "breakpoints": true + }, + { + "idx": 8, + "version": "6", + "when": 1769999999999, + "tag": "0008_add_model_profile_id", + "breakpoints": true + }, + { + "idx": 9, + "version": "6", + "when": 1769999999999, + "tag": "0009_add_selected_model_id", + "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/package.json b/package.json index d5b94213c..c6205e59e 100644 --- a/package.json +++ b/package.json @@ -166,6 +166,10 @@ { "from": "resources/bin/VERSION", "to": "bin/VERSION" + }, + { + "from": "build/trayTemplate.png", + "to": "trayTemplate.png" } ], "asar": true, diff --git a/src/main/index.ts b/src/main/index.ts index 32bf51eb7..a7f67d21b 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -28,6 +28,7 @@ import { } from "./lib/cli" import { cleanupGitWatchers } from "./lib/git/watcher" import { cancelAllPendingOAuth, handleMcpOAuthCallback } from "./lib/mcp-auth" +import { initTray, destroyTray } from "./lib/tray" import { createMainWindow, createWindow, @@ -852,6 +853,9 @@ if (gotTheLock) { console.error("[App] Failed to initialize database:", error) } + // Initialize system tray + initTray() + // Create main window createMainWindow() @@ -903,9 +907,16 @@ if (gotTheLock) { } }) + // Track when app is intentionally quitting (vs just closing windows) + let isQuitting = false + ;(global as any).__isAppQuitting = () => isQuitting + // Cleanup before quit app.on("before-quit", async () => { console.log("[App] Shutting down...") + isQuitting = true + ;(global as any).__isAppQuitting = () => true + destroyTray() cancelAllPendingOAuth() await cleanupGitWatchers() await shutdownAnalytics() diff --git a/src/main/lib/claude/env.ts b/src/main/lib/claude/env.ts index 53097fea2..5b0e5a187 100644 --- a/src/main/lib/claude/env.ts +++ b/src/main/lib/claude/env.ts @@ -147,7 +147,10 @@ function loadClaudeCliSettings(): Record { const homeDir = os.homedir() const settingsPath = path.join(homeDir, ".claude", "settings.json") + console.log(`[claude-env] Looking for settings at: ${settingsPath}`) + if (!fs.existsSync(settingsPath)) { + console.log(`[claude-env] No settings.json found at ${settingsPath}`) return {} } @@ -157,7 +160,10 @@ function loadClaudeCliSettings(): Record { // Config values are stored in the 'env' object within settings.json if (settings && typeof settings === 'object' && settings.env && typeof settings.env === 'object') { console.log(`[claude-env] Loaded ${Object.keys(settings.env).length} vars from ${settingsPath}`) + console.log(`[claude-env] Detected keys: ${Object.keys(settings.env).join(', ')}`) return settings.env as Record + } else { + console.log(`[claude-env] settings.json found but no 'env' object inside`) } } catch (error) { console.warn("[claude-env] Failed to load settings.json:", error) @@ -240,6 +246,13 @@ export function getClaudeShellEnvironment(): Record { } } + // Log detected config for debugging + console.log(`[claude-env] Final config detection:`) + console.log(` - ANTHROPIC_API_KEY: ${env.ANTHROPIC_API_KEY ? '***' : 'not set'}`) + console.log(` - ANTHROPIC_AUTH_TOKEN: ${env.ANTHROPIC_AUTH_TOKEN ? '***' : 'not set'}`) + console.log(` - ANTHROPIC_BASE_URL: ${env.ANTHROPIC_BASE_URL || 'not set'}`) + console.log(` - ANTHROPIC_MODEL: ${env.ANTHROPIC_MODEL || 'not set'}`) + // 3. Cache and return cachedShellEnv = env return { ...env } diff --git a/src/main/lib/db/schema/index.ts b/src/main/lib/db/schema/index.ts index fe6aa3490..2f5410766 100644 --- a/src/main/lib/db/schema/index.ts +++ b/src/main/lib/db/schema/index.ts @@ -51,6 +51,9 @@ export const chats = sqliteTable("chats", { // PR tracking fields prUrl: text("pr_url"), prNumber: integer("pr_number"), + // Model profile fields (per-workspace model configuration) + modelProfileId: text("model_profile_id"), // References profile ID from localStorage + selectedModelId: text("selected_model_id"), // Which model within the profile }, (table) => [ index("chats_worktree_path_idx").on(table.worktreePath), ]) diff --git a/src/main/lib/tray.ts b/src/main/lib/tray.ts new file mode 100644 index 000000000..874c9d1b8 --- /dev/null +++ b/src/main/lib/tray.ts @@ -0,0 +1,459 @@ +import { app, Menu, Tray, nativeImage } from "electron" +import { join } from "path" +import { existsSync } from "fs" +import { eq } from "drizzle-orm" +import { IS_DEV } from "../constants" +import { getWindow, getAllWindows, createMainWindow } from "../windows/main" +import { getDatabase } from "./db" +import { projects } from "./db/schema" + +let tray: Tray | null = null + +/** + * Get path to tray icon based on platform and theme + * Returns template image for macOS (auto-adapts to light/dark mode) + */ +function getTrayIconPath(): string { + // macOS uses trayTemplate.png which auto-adapts to light/dark mode + const iconName = "trayTemplate.png" + + // In dev mode, __dirname is in out/main, so go up to project root + // In production, icon is in app resources + if (IS_DEV) { + // __dirname = /path/to/project/out/main + // We need: /path/to/project/build/trayTemplate.png + const devPath = join(__dirname, "../../build", iconName) + console.log("[Tray] Dev icon path:", devPath) + if (existsSync(devPath)) { + return devPath + } + // Fallback: try app.getAppPath() + const fallbackPath = join(app.getAppPath(), "build", iconName) + console.log("[Tray] Fallback icon path:", fallbackPath) + return fallbackPath + } + return join(process.resourcesPath, iconName) +} + +/** + * Initialize system tray + * Should be called after app is ready + */ +export function initTray(): void { + if (tray) { + console.log("[Tray] Already initialized") + return + } + + try { + const iconPath = getTrayIconPath() + console.log("[Tray] Creating tray with icon:", iconPath) + console.log("[Tray] Icon exists:", existsSync(iconPath)) + + if (!existsSync(iconPath)) { + console.error("[Tray] Icon file not found at:", iconPath) + return + } + + // Create native image and set as template for macOS (auto dark/light mode) + const icon = nativeImage.createFromPath(iconPath) + if (process.platform === "darwin") { + icon.setTemplateImage(true) + } + + // Resize for proper display (16x16 for macOS menu bar) + const resizedIcon = icon.resize({ width: 16, height: 16 }) + + tray = new Tray(resizedIcon) + tray.setToolTip("1Code") + + // Left-click: restore or create window + tray.on("click", () => { + console.log("[Tray] Click event - restoring window") + restoreOrCreateWindow() + }) + + // Double-click on Windows also shows window + tray.on("double-click", () => { + console.log("[Tray] Double-click event - restoring window") + restoreOrCreateWindow() + }) + + // Right-click: rebuild menu before showing (ensures dynamic data is current) + tray.on("right-click", () => { + console.log("[Tray] Right-click - rebuilding menu for dynamic data") + buildTrayMenu().catch(err => { + console.error("[Tray] Failed to rebuild menu on right-click:", err) + }) + }) + + // Build initial menu (async, don't await) + buildTrayMenu().catch(err => { + console.error("[Tray] Failed to build initial menu:", err) + }) + + console.log("[Tray] Initialized successfully") + } catch (error) { + console.error("[Tray] Failed to initialize:", error) + } +} + +/** + * Restore an existing window or create a new one + */ +function restoreOrCreateWindow(): void { + // On macOS, show dock icon when restoring window + if (process.platform === "darwin" && app.dock) { + app.dock.show() + } + + const windows = getAllWindows() + if (windows.length > 0) { + // Show and focus first window + const window = windows[0]! + if (window.isMinimized()) { + window.restore() + } + window.show() + window.focus() + } else { + // No windows exist, create new one + createMainWindow() + } +} + +/** + * Build and set the tray context menu + * Fetches current profiles and projects from database/localStorage + */ +export async function buildTrayMenu(): Promise { + if (!tray) { + console.warn("[Tray] Cannot build menu - tray not initialized") + return + } + + let allProjects: { id: string; name: string; path: string }[] = [] + + // Try to fetch projects from database (may fail if DB not ready) + try { + const db = getDatabase() + allProjects = db.select().from(projects).all() + } catch (dbError) { + console.warn("[Tray] Could not fetch projects from database:", dbError) + // Continue with empty projects - show basic menu + } + + try { + + // Get active profile from first window's localStorage + let activeProfileId: string | null = null + let allProfiles: { id: string; name: string }[] = [] + + // Get or create a window to read localStorage + let windows = getAllWindows() + let window = windows.find(w => !w.isDestroyed()) + + // If no window, create a hidden one to read localStorage + let createdHiddenWindow = false + if (!window) { + console.log("[Tray] No window available, creating hidden window to read profiles") + window = createMainWindow() + createdHiddenWindow = true + // Wait for window to load + await new Promise((resolve) => { + window!.webContents.once('did-finish-load', () => resolve()) + }) + } + + if (window && !window.isDestroyed()) { + try { + // Get profiles from localStorage + const profilesJson = await window.webContents.executeJavaScript( + `localStorage.getItem('agents:model-profiles')` + ) + console.log("[Tray] Raw profiles from localStorage:", profilesJson) + if (profilesJson) { + const parsed = JSON.parse(profilesJson) + console.log("[Tray] Parsed profiles:", parsed) + allProfiles = Array.isArray(parsed) ? parsed : [] + } + + // Get active profile ID + const activeIdJson = await window.webContents.executeJavaScript( + `localStorage.getItem('agents:active-profile-id')` + ) + console.log("[Tray] Raw active profile ID:", activeIdJson) + activeProfileId = activeIdJson ? JSON.parse(activeIdJson) : null + console.log("[Tray] Parsed active profile ID:", activeProfileId) + } catch (e) { + console.warn("[Tray] Could not get profile data from renderer:", e) + } + + // If we created a hidden window just to read, close it + if (createdHiddenWindow && window && !window.isDestroyed()) { + window.close() + } + } + + // Filter out offline profiles (matching UI behavior) + const customProfiles = allProfiles.filter((p: any) => !p.isOffline) + console.log("[Tray] Custom profiles (non-offline):", customProfiles.map((p: any) => p.name)) + + // Build profile menu items + // First item: "Use Claude Default" option (when no custom profile is active) + const profileMenuItems: Electron.MenuItemConstructorOptions[] = [ + { + label: "Use Claude Default", + type: "checkbox" as const, + checked: activeProfileId === null, + click: () => { + handleProfileSwitch(null) + }, + }, + ...(customProfiles.length > 0 ? [{ type: "separator" as const }] : []), + ...customProfiles.map((profile: any) => ({ + label: profile.name, + type: "checkbox" as const, + checked: profile.id === activeProfileId, + click: () => { + handleProfileSwitch(profile.id) + }, + })) + ] + + // Build workspace menu items + const workspaceMenuItems: Electron.MenuItemConstructorOptions[] = allProjects.map(project => ({ + label: project.name || project.path.split("/").pop() || project.path, + click: () => { + handleWorkspaceClick(project.id) + }, + })) + + const template: Electron.MenuItemConstructorOptions[] = [] + + // macOS: Add "Show 1Code" as first item + if (process.platform === "darwin") { + template.push({ + label: "Show 1Code", + click: () => { + restoreOrCreateWindow() + }, + }) + template.push({ type: "separator" }) + } + + template.push( + { + label: "Profiles", + submenu: profileMenuItems, + }, + { type: "separator" }, + { + label: "Workspaces", + submenu: workspaceMenuItems.length > 0 + ? workspaceMenuItems + : [{ label: "No workspaces", enabled: false }], + }, + { type: "separator" }, + { + label: "New Workspace...", + click: () => { + handleNewWorkspace() + }, + }, + { + label: "Settings...", + click: () => { + handleOpenSettings() + }, + }, + { type: "separator" }, + { + label: "Quit", + click: () => { + app.quit() + }, + }, + ) + + const contextMenu = Menu.buildFromTemplate(template) + tray.setContextMenu(contextMenu) + } catch (error) { + console.error("[Tray] Failed to build menu:", error) + } +} + +/** + * Handle profile switch from tray menu + * Updates localStorage in all windows and reloads them + */ +async function handleProfileSwitch(profileId: string | null): Promise { + console.log("[Tray] Switching to profile:", profileId) + + const windows = getAllWindows() + for (const window of windows) { + try { + if (window.isDestroyed()) continue + + // Update localStorage + if (profileId === null) { + // Clear profile to use default Claude + await window.webContents.executeJavaScript( + `localStorage.removeItem('agents:active-profile-id')` + ) + } else { + await window.webContents.executeJavaScript( + `localStorage.setItem('agents:active-profile-id', ${JSON.stringify(JSON.stringify(profileId))})` + ) + } + + // Reload window to apply new profile + window.reload() + } catch (error) { + console.error("[Tray] Failed to switch profile for window:", error) + } + } + + // Rebuild menu to update checkmarks + await buildTrayMenu() +} + +/** + * Handle workspace click from tray menu + * Sets active workspace and opens window + */ +async function handleWorkspaceClick(projectId: string): Promise { + console.log("[Tray] Opening workspace:", projectId) + + // Get or create main window + let window = getWindow() + if (!window) { + window = createMainWindow() + } + + // Show and focus window + if (window.isMinimized()) { + window.restore() + } + window.show() + window.focus() + + // Set selected project in localStorage and trigger reload + try { + const db = getDatabase() + const projectData = db.select().from(projects).where( + eq(projects.id, projectId) + ).get() + + if (projectData) { + await window.webContents.executeJavaScript(` + localStorage.setItem('agents:selected-project', ${JSON.stringify(JSON.stringify(projectData))}); + window.location.reload(); + `) + } + } catch (error) { + console.error("[Tray] Failed to open workspace:", error) + } +} + +/** + * Handle "New Workspace" click + * Opens select-repo-page by clearing selected project + */ +async function handleNewWorkspace(): Promise { + console.log("[Tray] Opening new workspace dialog") + + // Get or create main window + let window = getWindow() + if (!window) { + window = createMainWindow() + } + + // Show and focus window + if (window.isMinimized()) { + window.restore() + } + window.show() + window.focus() + + // Wait a bit for window to be fully visible and ready + await new Promise(resolve => setTimeout(resolve, 100)) + + // Navigate to select-repo page by clearing selected project + try { + await window.webContents.executeJavaScript(` + localStorage.removeItem('agents:selected-project'); + window.location.reload(); + `) + } catch (error) { + console.error("[Tray] Failed to open new workspace:", error) + } +} + +/** + * Handle "Settings" click + * Opens settings by dispatching custom event + */ +async function handleOpenSettings(): Promise { + console.log("[Tray] Opening settings") + + // On macOS, show dock icon when showing window + if (process.platform === "darwin" && app.dock) { + app.dock.show() + } + + // Get or create main window + let window = getWindow() + const isNewWindow = !window + if (!window) { + window = createMainWindow() + } + + // Show and focus window + if (window.isMinimized()) { + window.restore() + } + window.show() + window.focus() + + // Wait for window to be fully loaded before dispatching event + const dispatchSettingsEvent = async () => { + try { + await window!.webContents.executeJavaScript(` + window.dispatchEvent(new CustomEvent('tray:open-settings')); + `) + } catch (error) { + console.error("[Tray] Failed to open settings:", error) + } + } + + if (isNewWindow) { + // New window: wait for did-finish-load to ensure React is mounted + window.webContents.once('did-finish-load', () => { + // Additional delay to ensure React hooks are registered + setTimeout(dispatchSettingsEvent, 300) + }) + } else { + // Existing window: dispatch immediately + await dispatchSettingsEvent() + } +} + +/** + * Destroy the tray icon + * Should be called on app quit + */ +export function destroyTray(): void { + if (tray) { + tray.destroy() + tray = null + console.log("[Tray] Destroyed") + } +} + +/** + * Get the tray instance (for testing or advanced use) + */ +export function getTray(): Tray | null { + return tray +} diff --git a/src/main/lib/trpc/routers/chats.ts b/src/main/lib/trpc/routers/chats.ts index c21c793e8..9297c65be 100644 --- a/src/main/lib/trpc/routers/chats.ts +++ b/src/main/lib/trpc/routers/chats.ts @@ -425,6 +425,48 @@ export const chatsRouter = router({ .get() }), + /** + * Update chat's model profile (per-workspace model configuration) + */ + updateModelProfile: publicProcedure + .input(z.object({ + id: z.string(), + modelProfileId: z.string().nullable(), + })) + .mutation(({ input }) => { + const db = getDatabase() + return db + .update(chats) + .set({ + modelProfileId: input.modelProfileId, + updatedAt: new Date(), + }) + .where(eq(chats.id, input.id)) + .returning() + .get() + }), + + /** + * Update chat's selected model within the current profile + */ + updateSelectedModel: publicProcedure + .input(z.object({ + id: z.string(), + selectedModelId: z.string().nullable(), + })) + .mutation(({ input }) => { + const db = getDatabase() + return db + .update(chats) + .set({ + selectedModelId: input.selectedModelId, + updatedAt: new Date(), + }) + .where(eq(chats.id, input.id)) + .returning() + .get() + }), + /** * Archive a chat (also kills any terminal processes in the workspace) * Optionally deletes the worktree to free disk space diff --git a/src/main/windows/main.ts b/src/main/windows/main.ts index e0c031540..969b486f7 100644 --- a/src/main/windows/main.ts +++ b/src/main/windows/main.ts @@ -631,7 +631,26 @@ export function createWindow(options?: { chatId?: string; subChatId?: string }): return { action: "deny" } }) - // Handle window close + // Handle window close - hide to tray instead of closing + window.on("close", (event) => { + const isQuitting = (global as any).__isAppQuitting?.() || false + if (!isQuitting) { + event.preventDefault() + window.hide() + console.log(`[Main] Window ${window.id} hidden to tray`) + + // On macOS, hide dock icon when all windows are hidden + if (process.platform === "darwin" && app.dock) { + const allWindows = BrowserWindow.getAllWindows() + const visibleWindows = allWindows.filter(w => w.isVisible() && !w.isDestroyed()) + if (visibleWindows.length === 0) { + app.dock.hide() + console.log("[Main] Dock icon hidden") + } + } + } + }) + window.on("closed", () => { console.log(`[Main] Window ${window.id} closed`) // windowManager handles cleanup via 'closed' event listener diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index ac2072fa9..641c43f4d 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -30,6 +30,7 @@ import { } from "./lib/atoms"; import { appStore } from "./lib/jotai-store"; import { VSCodeThemeProvider } from "./lib/themes/theme-provider"; +import { useTrayEvents } from "./lib/tray-events"; import { trpc } from "./lib/trpc"; /** @@ -178,6 +179,9 @@ function AppContent() { } export function App() { + // Listen for tray events (settings, workspace selection) + useTrayEvents(); + // Initialize analytics on mount useEffect(() => { initAnalytics(); diff --git a/src/renderer/features/agents/lib/ipc-chat-transport.ts b/src/renderer/features/agents/lib/ipc-chat-transport.ts index bb57a5d08..55240664b 100644 --- a/src/renderer/features/agents/lib/ipc-chat-transport.ts +++ b/src/renderer/features/agents/lib/ipc-chat-transport.ts @@ -8,6 +8,9 @@ import { enableTasksAtom, extendedThinkingEnabledAtom, historyEnabledAtom, + modelProfilesAtom, + lastUsedProfileIdAtom, + selectedProfileModelIdAtom, selectedOllamaModelAtom, sessionInfoAtom, showOfflineModeFeaturesAtom, @@ -168,11 +171,56 @@ export class IPCChatTransport implements ChatTransport { const enableTasks = appStore.get(enableTasksAtom) // Read model selection dynamically (so model changes apply to existing chats) - const selectedModelId = appStore.get(lastSelectedModelIdAtom) - const modelString = MODEL_ID_MAP[selectedModelId] || MODEL_ID_MAP["opus"] + // First try to get model from profile system, then fall back to legacy MODEL_ID_MAP + const profiles = appStore.get(modelProfilesAtom) + const lastUsedProfileId = appStore.get(lastUsedProfileIdAtom) + const selectedProfileModelId = appStore.get(selectedProfileModelIdAtom) + + // Find the effective profile + const effectiveProfile = lastUsedProfileId + ? profiles.find(p => p.id === lastUsedProfileId) + : profiles.filter(p => !p.isOffline)[0] + + // Resolve model from profile config based on selected model ID + let modelString: string | undefined + if (effectiveProfile?.config) { + const config = effectiveProfile.config + switch (selectedProfileModelId) { + case "main": + modelString = config.model + break + case "opus": + modelString = config.defaultOpusModel + break + case "sonnet": + modelString = config.defaultSonnetModel + break + case "haiku": + modelString = config.defaultHaikuModel + break + case "subagent": + modelString = config.subagentModel + break + default: + // Default to main model if no specific selection + modelString = config.model + } + } + + // Fall back to legacy model selection if no profile model found + if (!modelString) { + const selectedModelId = appStore.get(lastSelectedModelIdAtom) + modelString = MODEL_ID_MAP[selectedModelId] || MODEL_ID_MAP["opus"] + } + + console.log(`[IPC Transport] Using model: ${modelString} (profile: ${effectiveProfile?.name || 'none'}, selectedProfileModelId: ${selectedProfileModelId})`) // Read active profile config (from profile system, supports multiple profiles) - const customConfig = appStore.get(activeConfigAtom) + // Override the model in customConfig with the selected model from profile + const baseCustomConfig = appStore.get(activeConfigAtom) + const customConfig = baseCustomConfig && modelString + ? { ...baseCustomConfig, model: modelString } + : baseCustomConfig // Get selected Ollama model for offline mode const selectedOllamaModel = appStore.get(selectedOllamaModelAtom) diff --git a/src/renderer/features/agents/main/active-chat.tsx b/src/renderer/features/agents/main/active-chat.tsx index dcc8ebb46..587aa2ee0 100644 --- a/src/renderer/features/agents/main/active-chat.tsx +++ b/src/renderer/features/agents/main/active-chat.tsx @@ -1,16 +1,14 @@ -"use client" +"use client"; -import { - stripEmojis -} from "../../../components/chat-markdown-renderer" -import { Button } from "../../../components/ui/button" +import { stripEmojis } from "../../../components/chat-markdown-renderer"; +import { Button } from "../../../components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, -} from "../../../components/ui/dropdown-menu" -import { Badge } from "../../../components/ui/badge" +} from "../../../components/ui/dropdown-menu"; +import { Badge } from "../../../components/ui/badge"; import { AgentIcon, AttachIcon, @@ -25,32 +23,32 @@ import { IconSpinner, IconTextUndo, PauseIcon, - VolumeIcon -} from "../../../components/ui/icons" -import { Kbd } from "../../../components/ui/kbd" + VolumeIcon, +} from "../../../components/ui/icons"; +import { Kbd } from "../../../components/ui/kbd"; import { PromptInput, - PromptInputActions -} from "../../../components/ui/prompt-input" -import { ResizableSidebar } from "../../../components/ui/resizable-sidebar" + PromptInputActions, +} from "../../../components/ui/prompt-input"; +import { ResizableSidebar } from "../../../components/ui/resizable-sidebar"; import { Tooltip, TooltipContent, TooltipTrigger, -} from "../../../components/ui/tooltip" +} from "../../../components/ui/tooltip"; // e2b API routes are used instead of useSandboxManager for agents // import { clearSubChatSelectionAtom, isSubChatMultiSelectModeAtom, selectedSubChatIdsAtom } from "@/lib/atoms/agent-subchat-selection" -import { Chat, useChat } from "@ai-sdk/react" -import { DiffModeEnum } from "@git-diff-view/react" -import { atom, useAtom, useAtomValue, useSetAtom } from "jotai" +import { Chat, useChat } from "@ai-sdk/react"; +import { DiffModeEnum } from "@git-diff-view/react"; +import { atom, useAtom, useAtomValue, useSetAtom } from "jotai"; import { ArrowDown, ChevronDown, GitFork, ListTree, - TerminalSquare -} from "lucide-react" -import { AnimatePresence, motion } from "motion/react" + TerminalSquare, +} from "lucide-react"; +import { AnimatePresence, motion } from "motion/react"; import { createContext, memo, @@ -60,49 +58,60 @@ import { useLayoutEffect, useMemo, useRef, - useState -} from "react" -import { flushSync } from "react-dom" -import { toast } from "sonner" -import { useShallow } from "zustand/react/shallow" -import type { FileStatus } from "../../../../shared/changes-types" -import { getQueryClient } from "../../../contexts/TRPCProvider" -import { trackMessageSent } from "../../../lib/analytics" -import { apiFetch } from "../../../lib/api-fetch" + useState, +} from "react"; +import { flushSync } from "react-dom"; +import { toast } from "sonner"; +import { useShallow } from "zustand/react/shallow"; +import type { FileStatus } from "../../../../shared/changes-types"; +import { getQueryClient } from "../../../contexts/TRPCProvider"; +import { trackMessageSent } from "../../../lib/analytics"; +import { apiFetch } from "../../../lib/api-fetch"; import { activeProfileIdAtom, chatSourceModeAtom, customClaudeConfigAtom, defaultAgentModeAtom, - isDesktopAtom, isFullscreenAtom, + isDesktopAtom, + isFullscreenAtom, modelProfilesAtom, normalizeCustomClaudeConfig, selectedOllamaModelAtom, - soundNotificationsEnabledAtom -} from "../../../lib/atoms" -import { useFileChangeListener, useGitWatcher } from "../../../lib/hooks/use-file-change-listener" -import { useRemoteChat } from "../../../lib/hooks/use-remote-chats" -import { useResolvedHotkeyDisplay } from "../../../lib/hotkeys" -import { appStore } from "../../../lib/jotai-store" -import { api } from "../../../lib/mock-api" -import { trpc, trpcClient } from "../../../lib/trpc" -import { cn } from "../../../lib/utils" -import { isDesktopApp } from "../../../lib/utils/platform" -import { ChangesPanel } from "../../changes" -import { DiffCenterPeekDialog } from "../../changes/components/diff-center-peek-dialog" -import { DiffFullPageView } from "../../changes/components/diff-full-page-view" -import { DiffSidebarHeader } from "../../changes/components/diff-sidebar-header" -import { getStatusIndicator } from "../../changes/utils/status" + soundNotificationsEnabledAtom, +} from "../../../lib/atoms"; +import { + useFileChangeListener, + useGitWatcher, +} from "../../../lib/hooks/use-file-change-listener"; +import { useRemoteChat } from "../../../lib/hooks/use-remote-chats"; +import { useResolvedHotkeyDisplay } from "../../../lib/hotkeys"; +import { appStore } from "../../../lib/jotai-store"; +import { api } from "../../../lib/mock-api"; +import { trpc, trpcClient } from "../../../lib/trpc"; +import { cn } from "../../../lib/utils"; +import { isDesktopApp } from "../../../lib/utils/platform"; +import { ChangesPanel } from "../../changes"; +import { DiffCenterPeekDialog } from "../../changes/components/diff-center-peek-dialog"; +import { DiffFullPageView } from "../../changes/components/diff-full-page-view"; +import { DiffSidebarHeader } from "../../changes/components/diff-sidebar-header"; +import { getStatusIndicator } from "../../changes/utils/status"; import { detailsSidebarOpenAtom, unifiedSidebarEnabledAtom, -} from "../../details-sidebar/atoms" -import { DetailsSidebar } from "../../details-sidebar/details-sidebar" -import { FileViewerSidebar } from "../../file-viewer" -import { FileSearchDialog } from "../../file-viewer/components/file-search-dialog" -import { terminalSidebarOpenAtomFamily, terminalDisplayModeAtom, terminalBottomHeightAtom } from "../../terminal/atoms" -import { TerminalSidebar, TerminalBottomPanelContent } from "../../terminal/terminal-sidebar" -import { ResizableBottomPanel } from "@/components/ui/resizable-bottom-panel" +} from "../../details-sidebar/atoms"; +import { DetailsSidebar } from "../../details-sidebar/details-sidebar"; +import { FileViewerSidebar } from "../../file-viewer"; +import { FileSearchDialog } from "../../file-viewer/components/file-search-dialog"; +import { + terminalSidebarOpenAtomFamily, + terminalDisplayModeAtom, + terminalBottomHeightAtom, +} from "../../terminal/atoms"; +import { + TerminalSidebar, + TerminalBottomPanelContent, +} from "../../terminal/terminal-sidebar"; +import { ResizableBottomPanel } from "@/components/ui/resizable-bottom-panel"; import { agentsChangesPanelCollapsedAtom, agentsChangesPanelWidthAtom, @@ -150,126 +159,140 @@ import { workspaceDiffCacheAtomFamily, pendingMentionAtom, type AgentMode, - type SelectedCommit -} from "../atoms" -import { BUILTIN_SLASH_COMMANDS } from "../commands" -import { AgentSendButton } from "../components/agent-send-button" -import { OpenLocallyDialog } from "../components/open-locally-dialog" -import { PreviewSetupHoverCard } from "../components/preview-setup-hover-card" -import type { TextSelectionSource } from "../context/text-selection-context" -import { TextSelectionProvider } from "../context/text-selection-context" -import { useAgentsFileUpload } from "../hooks/use-agents-file-upload" -import { useAutoImport } from "../hooks/use-auto-import" -import { useChangedFilesTracking } from "../hooks/use-changed-files-tracking" -import { useDesktopNotifications } from "../hooks/use-desktop-notifications" -import { useFocusInputOnEnter } from "../hooks/use-focus-input-on-enter" -import { useHaptic } from "../hooks/use-haptic" -import { usePastedTextFiles } from "../hooks/use-pasted-text-files" -import { useTextContextSelection } from "../hooks/use-text-context-selection" -import { useToggleFocusOnCmdEsc } from "../hooks/use-toggle-focus-on-cmd-esc" -import { - clearSubChatDraft, - getSubChatDraftFull -} from "../lib/drafts" -import { IPCChatTransport } from "../lib/ipc-chat-transport" + type SelectedCommit, +} from "../atoms"; +import { BUILTIN_SLASH_COMMANDS } from "../commands"; +import { AgentSendButton } from "../components/agent-send-button"; +import { OpenLocallyDialog } from "../components/open-locally-dialog"; +import { PreviewSetupHoverCard } from "../components/preview-setup-hover-card"; +import type { TextSelectionSource } from "../context/text-selection-context"; +import { TextSelectionProvider } from "../context/text-selection-context"; +import { useAgentsFileUpload } from "../hooks/use-agents-file-upload"; +import { useAutoImport } from "../hooks/use-auto-import"; +import { useChangedFilesTracking } from "../hooks/use-changed-files-tracking"; +import { useDesktopNotifications } from "../hooks/use-desktop-notifications"; +import { useFocusInputOnEnter } from "../hooks/use-focus-input-on-enter"; +import { useHaptic } from "../hooks/use-haptic"; +import { usePastedTextFiles } from "../hooks/use-pasted-text-files"; +import { useTextContextSelection } from "../hooks/use-text-context-selection"; +import { useToggleFocusOnCmdEsc } from "../hooks/use-toggle-focus-on-cmd-esc"; +import { clearSubChatDraft, getSubChatDraftFull } from "../lib/drafts"; +import { IPCChatTransport } from "../lib/ipc-chat-transport"; import { createQueueItem, generateQueueId, toQueuedFile, toQueuedImage, toQueuedTextContext, -} from "../lib/queue-utils" -import { RemoteChatTransport } from "../lib/remote-chat-transport" +} from "../lib/queue-utils"; +import { RemoteChatTransport } from "../lib/remote-chat-transport"; import { FileOpenProvider, MENTION_PREFIXES, type AgentsMentionsEditorHandle, -} from "../mentions" +} from "../mentions"; import { ChatSearchBar, chatSearchCurrentMatchAtom, - SearchHighlightProvider -} from "../search" -import { agentChatStore } from "../stores/agent-chat-store" -import { EMPTY_QUEUE, useMessageQueueStore } from "../stores/message-queue-store" -import { clearSubChatCaches, isRollingBackAtom, rollbackHandlerAtom, syncMessagesWithStatusAtom } from "../stores/message-store" -import { useStreamingStatusStore } from "../stores/streaming-status-store" + SearchHighlightProvider, +} from "../search"; +import { agentChatStore } from "../stores/agent-chat-store"; +import { + EMPTY_QUEUE, + useMessageQueueStore, +} from "../stores/message-queue-store"; +import { + clearSubChatCaches, + isRollingBackAtom, + rollbackHandlerAtom, + syncMessagesWithStatusAtom, +} from "../stores/message-store"; +import { useStreamingStatusStore } from "../stores/streaming-status-store"; import { useAgentSubChatStore, type SubChatMeta, -} from "../stores/sub-chat-store" +} from "../stores/sub-chat-store"; import { AgentDiffView, diffViewModeAtom, splitUnifiedDiffByFile, type AgentDiffViewRef, type ParsedDiffFile, -} from "../ui/agent-diff-view" -import { AgentPlanSidebar } from "../ui/agent-plan-sidebar" -import { AgentPreview } from "../ui/agent-preview" -import { AgentQueueIndicator } from "../ui/agent-queue-indicator" -import { AgentToolCall } from "../ui/agent-tool-call" -import { AgentToolRegistry } from "../ui/agent-tool-registry" -import { isPlanFile } from "../ui/agent-tool-utils" -import { AgentUserMessageBubble } from "../ui/agent-user-message-bubble" -import { AgentUserQuestion, type AgentUserQuestionHandle } from "../ui/agent-user-question" -import { AgentsHeaderControls } from "../ui/agents-header-controls" -import { ChatTitleEditor } from "../ui/chat-title-editor" -import { MobileChatHeader } from "../ui/mobile-chat-header" -import { QuickCommentInput } from "../ui/quick-comment-input" -import { SubChatSelector } from "../ui/sub-chat-selector" -import { SubChatStatusCard } from "../ui/sub-chat-status-card" -import { TextSelectionPopover } from "../ui/text-selection-popover" -import { autoRenameAgentChat } from "../utils/auto-rename" -import { generateCommitToPrMessage, generatePrMessage, generateReviewMessage } from "../utils/pr-message" -import { ChatInputArea } from "./chat-input-area" -import { IsolatedMessagesSection } from "./isolated-messages-section" -const clearSubChatSelectionAtom = atom(null, () => {}) -const isSubChatMultiSelectModeAtom = atom(false) -const selectedSubChatIdsAtom = atom(new Set()) +} from "../ui/agent-diff-view"; +import { AgentPlanSidebar } from "../ui/agent-plan-sidebar"; +import { AgentPreview } from "../ui/agent-preview"; +import { AgentQueueIndicator } from "../ui/agent-queue-indicator"; +import { AgentToolCall } from "../ui/agent-tool-call"; +import { AgentToolRegistry } from "../ui/agent-tool-registry"; +import { isPlanFile } from "../ui/agent-tool-utils"; +import { AgentUserMessageBubble } from "../ui/agent-user-message-bubble"; +import { + AgentUserQuestion, + type AgentUserQuestionHandle, +} from "../ui/agent-user-question"; +import { AgentsHeaderControls } from "../ui/agents-header-controls"; +import { ChatTitleEditor } from "../ui/chat-title-editor"; +import { MobileChatHeader } from "../ui/mobile-chat-header"; +import { QuickCommentInput } from "../ui/quick-comment-input"; +import { SubChatSelector } from "../ui/sub-chat-selector"; +import { SubChatStatusCard } from "../ui/sub-chat-status-card"; +import { TextSelectionPopover } from "../ui/text-selection-popover"; +import { autoRenameAgentChat } from "../utils/auto-rename"; +import { + generateCommitToPrMessage, + generatePrMessage, + generateReviewMessage, +} from "../utils/pr-message"; +import { ChatInputArea } from "./chat-input-area"; +import { IsolatedMessagesSection } from "./isolated-messages-section"; +const clearSubChatSelectionAtom = atom(null, () => {}); +const isSubChatMultiSelectModeAtom = atom(false); +const selectedSubChatIdsAtom = atom(new Set()); // import { selectedTeamIdAtom } from "@/lib/atoms/team" -const selectedTeamIdAtom = atom(null) +const selectedTeamIdAtom = atom(null); // import type { PlanType } from "@/lib/config/subscription-plans" -type PlanType = string +type PlanType = string; // UTF-8 safe base64 encoding (btoa doesn't support Unicode) function utf8ToBase64(str: string): string { - const bytes = new TextEncoder().encode(str) - const binString = Array.from(bytes, (byte) => String.fromCodePoint(byte)).join("") - return btoa(binString) + const bytes = new TextEncoder().encode(str); + const binString = Array.from(bytes, (byte) => + String.fromCodePoint(byte), + ).join(""); + return btoa(binString); } /** Wait for streaming to finish by subscribing to the status store. * Includes a 30s safety timeout — if the store never transitions to "ready", * the promise resolves anyway to prevent hanging the UI indefinitely. */ -const STREAMING_READY_TIMEOUT_MS = 30_000 +const STREAMING_READY_TIMEOUT_MS = 30_000; function waitForStreamingReady(subChatId: string): Promise { return new Promise((resolve) => { if (!useStreamingStatusStore.getState().isStreaming(subChatId)) { - resolve() - return + resolve(); + return; } const timeout = setTimeout(() => { console.warn( - `[waitForStreamingReady] Timed out after ${STREAMING_READY_TIMEOUT_MS}ms for subChat ${subChatId.slice(-8)}, proceeding anyway` - ) - unsub() - resolve() - }, STREAMING_READY_TIMEOUT_MS) + `[waitForStreamingReady] Timed out after ${STREAMING_READY_TIMEOUT_MS}ms for subChat ${subChatId.slice(-8)}, proceeding anyway`, + ); + unsub(); + resolve(); + }, STREAMING_READY_TIMEOUT_MS); const unsub = useStreamingStatusStore.subscribe( (state) => state.statuses[subChatId], (status) => { if (status === "ready" || status === undefined) { - clearTimeout(timeout) - unsub() - resolve() + clearTimeout(timeout); + unsub(); + resolve(); } - } - ) - }) + }, + ); + }); } // Exploring tools - these get grouped when 2+ consecutive @@ -279,37 +302,37 @@ const EXPLORING_TOOLS = new Set([ "tool-Glob", "tool-WebSearch", "tool-WebFetch", -]) +]); // Group consecutive exploring tools into exploring-group function groupExploringTools(parts: any[], nestedToolIds: Set): any[] { - const result: any[] = [] - let currentGroup: any[] = [] + const result: any[] = []; + let currentGroup: any[] = []; for (const part of parts) { // Skip nested tools - they shouldn't be grouped, they render inside parent - const isNested = part.toolCallId && nestedToolIds.has(part.toolCallId) + const isNested = part.toolCallId && nestedToolIds.has(part.toolCallId); if (EXPLORING_TOOLS.has(part.type) && !isNested) { - currentGroup.push(part) + currentGroup.push(part); } else { // Flush group if 3+ if (currentGroup.length >= 3) { - result.push({ type: "exploring-group", parts: currentGroup }) + result.push({ type: "exploring-group", parts: currentGroup }); } else { - result.push(...currentGroup) + result.push(...currentGroup); } - currentGroup = [] - result.push(part) + currentGroup = []; + result.push(part); } } // Flush remaining if (currentGroup.length >= 3) { - result.push({ type: "exploring-group", parts: currentGroup }) + result.push({ type: "exploring-group", parts: currentGroup }); } else { - result.push(...currentGroup) + result.push(...currentGroup); } - return result + return result; } // Get the ID of the first sub-chat by creation date @@ -318,13 +341,13 @@ function getFirstSubChatId( | Array<{ id: string; created_at?: Date | string | null }> | undefined, ): string | null { - if (!subChats?.length) return null + if (!subChats?.length) return null; const sorted = [...subChats].sort( (a, b) => (a.created_at ? new Date(a.created_at).getTime() : 0) - (b.created_at ? new Date(b.created_at).getTime() : 0), - ) - return sorted[0]?.id ?? null + ); + return sorted[0]?.id ?? null; } // Layout constants for chat header and sticky messages @@ -340,52 +363,52 @@ const CHAT_LAYOUT = { // Header padding when absolute headerPaddingSidebarOpen: "pt-1.5 pb-12 px-3 pl-2", headerPaddingSidebarClosed: "p-2 pt-1.5", -} as const +} as const; // Codex icon (OpenAI style) const CodexIcon = (props: React.SVGProps) => ( -) +); // Model options for Claude Code const claudeModels = [ { id: "opus", name: "Opus" }, { id: "sonnet", name: "Sonnet" }, { id: "haiku", name: "Haiku" }, -] +]; // Agent providers const agents = [ { id: "claude-code", name: "Claude Code", hasModels: true }, { id: "cursor", name: "Cursor CLI", disabled: true }, { id: "codex", name: "OpenAI Codex", disabled: true }, -] +]; // Helper function to get agent icon const getAgentIcon = (agentId: string, className?: string) => { switch (agentId) { case "claude-code": - return + return ; case "cursor": - return + return ; case "codex": - return + return ; default: - return null + return null; } -} +}; // Model Profile Selector component for quick profile switching function ModelProfileSelector() { - const [profiles] = useAtom(modelProfilesAtom) - const [activeProfileId, setActiveProfileId] = useAtom(activeProfileIdAtom) + const [profiles] = useAtom(modelProfilesAtom); + const [activeProfileId, setActiveProfileId] = useAtom(activeProfileIdAtom); - const activeProfile = profiles.find((p) => p.id === activeProfileId) - const customProfiles = profiles.filter((p) => !p.isOffline) + const activeProfile = profiles.find((p) => p.id === activeProfileId); + const customProfiles = profiles.filter((p) => !p.isOffline); - if (customProfiles.length === 0) return null + if (customProfiles.length === 0) return null; return ( @@ -412,7 +435,7 @@ function ModelProfileSelector() { ))} - ) + ); } // Copy button component with tooltip feedback (matches project style) @@ -420,18 +443,18 @@ function CopyButton({ onCopy, isMobile = false, }: { - onCopy: () => void - isMobile?: boolean + onCopy: () => void; + isMobile?: boolean; }) { - const [copied, setCopied] = useState(false) - const { trigger: triggerHaptic } = useHaptic() + const [copied, setCopied] = useState(false); + const { trigger: triggerHaptic } = useHaptic(); const handleCopy = () => { - onCopy() - triggerHaptic("medium") - setCopied(true) - setTimeout(() => setCopied(false), 2000) - } + onCopy(); + triggerHaptic("medium"); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; return (
- ) + ); } // Play button component for TTS (text-to-speech) with streaming support -type PlayButtonState = "idle" | "loading" | "playing" +type PlayButtonState = "idle" | "loading" | "playing"; -const PLAYBACK_SPEEDS = [1, 2, 3] as const -type PlaybackSpeed = (typeof PLAYBACK_SPEEDS)[number] +const PLAYBACK_SPEEDS = [1, 2, 3] as const; +type PlaybackSpeed = (typeof PLAYBACK_SPEEDS)[number]; function PlayButton({ text, @@ -469,34 +492,34 @@ function PlayButton({ playbackRate = 1, onPlaybackRateChange, }: { - text: string - isMobile?: boolean - playbackRate?: PlaybackSpeed - onPlaybackRateChange?: (rate: PlaybackSpeed) => void + text: string; + isMobile?: boolean; + playbackRate?: PlaybackSpeed; + onPlaybackRateChange?: (rate: PlaybackSpeed) => void; }) { - const [state, setState] = useState("idle") - const audioRef = useRef(null) - const mediaSourceRef = useRef(null) - const sourceBufferRef = useRef(null) - const abortControllerRef = useRef(null) - const chunkCountRef = useRef(0) + const [state, setState] = useState("idle"); + const audioRef = useRef(null); + const mediaSourceRef = useRef(null); + const sourceBufferRef = useRef(null); + const abortControllerRef = useRef(null); + const chunkCountRef = useRef(0); // Update playback rate when it changes useEffect(() => { if (audioRef.current) { - audioRef.current.playbackRate = playbackRate + audioRef.current.playbackRate = playbackRate; } - }, [playbackRate]) + }, [playbackRate]); const cleanup = useCallback(() => { if (abortControllerRef.current) { - abortControllerRef.current.abort() - abortControllerRef.current = null + abortControllerRef.current.abort(); + abortControllerRef.current = null; } if (audioRef.current) { - audioRef.current.pause() + audioRef.current.pause(); if (audioRef.current.src) { - URL.revokeObjectURL(audioRef.current.src) + URL.revokeObjectURL(audioRef.current.src); } } if ( @@ -504,133 +527,133 @@ function PlayButton({ mediaSourceRef.current.readyState === "open" ) { try { - mediaSourceRef.current.endOfStream() + mediaSourceRef.current.endOfStream(); } catch { // Ignore errors during cleanup } } - audioRef.current = null - mediaSourceRef.current = null - sourceBufferRef.current = null - chunkCountRef.current = 0 - }, []) + audioRef.current = null; + mediaSourceRef.current = null; + sourceBufferRef.current = null; + chunkCountRef.current = 0; + }, []); const handlePlay = async () => { // If playing, stop the audio if (state === "playing") { - cleanup() - setState("idle") - return + cleanup(); + setState("idle"); + return; } // If loading, cancel and reset if (state === "loading") { - cleanup() - setState("idle") - return + cleanup(); + setState("idle"); + return; } // Start loading - setState("loading") - chunkCountRef.current = 0 + setState("loading"); + chunkCountRef.current = 0; try { // Check if MediaSource is supported for streaming const supportsMediaSource = typeof MediaSource !== "undefined" && - MediaSource.isTypeSupported("audio/mpeg") + MediaSource.isTypeSupported("audio/mpeg"); if (supportsMediaSource) { // Use streaming approach with MediaSource API - await playWithStreaming() + await playWithStreaming(); } else { // Fallback: wait for full response (Safari, older browsers) - await playWithFallback() + await playWithFallback(); } } catch (error) { if ((error as Error).name !== "AbortError") { - console.error("[PlayButton] TTS error:", error) + console.error("[PlayButton] TTS error:", error); } - cleanup() - setState("idle") + cleanup(); + setState("idle"); } - } + }; const playWithStreaming = async () => { - const mediaSource = new MediaSource() - mediaSourceRef.current = mediaSource + const mediaSource = new MediaSource(); + mediaSourceRef.current = mediaSource; - const audio = new Audio() - audioRef.current = audio + const audio = new Audio(); + audioRef.current = audio; - audio.src = URL.createObjectURL(mediaSource) + audio.src = URL.createObjectURL(mediaSource); audio.onended = () => { - cleanup() - setState("idle") - } + cleanup(); + setState("idle"); + }; audio.onerror = () => { - cleanup() - setState("idle") - } + cleanup(); + setState("idle"); + }; // Track if we've already started playing - let hasStartedPlaying = false + let hasStartedPlaying = false; // Start playback when browser has enough data (canplay event) audio.oncanplay = async () => { - if (hasStartedPlaying) return - hasStartedPlaying = true + if (hasStartedPlaying) return; + hasStartedPlaying = true; try { - await audio.play() - audio.playbackRate = playbackRate - setState("playing") + await audio.play(); + audio.playbackRate = playbackRate; + setState("playing"); } catch { - cleanup() - setState("idle") + cleanup(); + setState("idle"); } - } + }; // Wait for MediaSource to open await new Promise((resolve, reject) => { mediaSource.addEventListener("sourceopen", () => resolve(), { once: true, - }) + }); mediaSource.addEventListener( "error", () => reject(new Error("MediaSource error")), { once: true, }, - ) - }) + ); + }); - const sourceBuffer = mediaSource.addSourceBuffer("audio/mpeg") - sourceBufferRef.current = sourceBuffer + const sourceBuffer = mediaSource.addSourceBuffer("audio/mpeg"); + sourceBufferRef.current = sourceBuffer; // Create abort controller for this request - abortControllerRef.current = new AbortController() + abortControllerRef.current = new AbortController(); - const fetchStartTime = Date.now() + const fetchStartTime = Date.now(); const response = await apiFetch("/api/tts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text }), signal: abortControllerRef.current.signal, - }) + }); if (!response.ok) { - throw new Error("TTS request failed") + throw new Error("TTS request failed"); } if (!response.body) { - throw new Error("No response body") + throw new Error("No response body"); } - const reader = response.body.getReader() - const pendingChunks: Uint8Array[] = [] - let isAppending = false + const reader = response.body.getReader(); + const pendingChunks: Uint8Array[] = []; + let isAppending = false; const appendNextChunk = () => { if ( @@ -639,101 +662,101 @@ function PlayButton({ !sourceBufferRef.current || sourceBufferRef.current.updating ) { - return + return; } - isAppending = true - const chunk = pendingChunks.shift()! + isAppending = true; + const chunk = pendingChunks.shift()!; try { // Use ArrayBuffer.isView to ensure TypeScript knows this is a valid BufferSource - const buffer = new Uint8Array(chunk.buffer.slice(0)) as BufferSource - sourceBufferRef.current.appendBuffer(buffer) + const buffer = new Uint8Array(chunk.buffer.slice(0)) as BufferSource; + sourceBufferRef.current.appendBuffer(buffer); } catch { // Buffer might be full or source closed - isAppending = false + isAppending = false; } - } + }; sourceBuffer.addEventListener("updateend", () => { - isAppending = false - appendNextChunk() - }) + isAppending = false; + appendNextChunk(); + }); // Read stream chunks const processStream = async () => { while (true) { - const { done, value } = await reader.read() + const { done, value } = await reader.read(); if (done) { // Wait for all pending chunks to be appended while (pendingChunks.length > 0 || sourceBuffer.updating) { - await new Promise((r) => setTimeout(r, 50)) + await new Promise((r) => setTimeout(r, 50)); } if (mediaSource.readyState === "open") { try { - mediaSource.endOfStream() + mediaSource.endOfStream(); } catch { // Ignore } } - break + break; } if (value) { - chunkCountRef.current++ - pendingChunks.push(value) - appendNextChunk() + chunkCountRef.current++; + pendingChunks.push(value); + appendNextChunk(); // Just accumulate data, don't try to play yet // Playback will start via canplay event listener } } - } + }; // Start processing stream - playback will start via canplay event - processStream() - } + processStream(); + }; const playWithFallback = async () => { - abortControllerRef.current = new AbortController() + abortControllerRef.current = new AbortController(); const response = await apiFetch("/api/tts", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text }), signal: abortControllerRef.current.signal, - }) + }); if (!response.ok) { - throw new Error("TTS request failed") + throw new Error("TTS request failed"); } - const audioBlob = await response.blob() - const audioUrl = URL.createObjectURL(audioBlob) + const audioBlob = await response.blob(); + const audioUrl = URL.createObjectURL(audioBlob); - const audio = new Audio(audioUrl) - audioRef.current = audio + const audio = new Audio(audioUrl); + audioRef.current = audio; audio.onended = () => { - cleanup() - setState("idle") - } + cleanup(); + setState("idle"); + }; audio.onerror = () => { - cleanup() - setState("idle") - } + cleanup(); + setState("idle"); + }; - await audio.play() + await audio.play(); // Set playback rate AFTER play() - browser resets it when setting src - audio.playbackRate = playbackRate - setState("playing") - } + audio.playbackRate = playbackRate; + setState("playing"); + }; // Cleanup on unmount useEffect(() => { - return cleanup - }, [cleanup]) + return cleanup; + }, [cleanup]); return (
@@ -760,9 +783,9 @@ function PlayButton({ {state === "playing" && ( )}
- ) + ); } // Rollback button component for reverting to a previous message state @@ -799,9 +822,9 @@ function RollbackButton({ onRollback, isRollingBack = false, }: { - disabled?: boolean - onRollback: () => void - isRollingBack?: boolean + disabled?: boolean; + onRollback: () => void; + isRollingBack?: boolean; }) { return ( @@ -822,7 +845,7 @@ function RollbackButton({ {isRollingBack ? "Rolling back..." : "Rollback to here"} - ) + ); } // Isolated scroll-to-bottom button - uses own scroll listener to avoid re-renders of parent @@ -833,73 +856,75 @@ const ScrollToBottomButton = memo(function ScrollToBottomButton({ subChatId, isActive = true, }: { - containerRef: React.RefObject - onScrollToBottom: () => void - hasStackedCards?: boolean - subChatId?: string - isActive?: boolean + containerRef: React.RefObject; + onScrollToBottom: () => void; + hasStackedCards?: boolean; + subChatId?: string; + isActive?: boolean; }) { - const [isVisible, setIsVisible] = useState(false) + const [isVisible, setIsVisible] = useState(false); // Keep isActive in ref for scroll event handler - const isActiveRef = useRef(isActive) - isActiveRef.current = isActive + const isActiveRef = useRef(isActive); + isActiveRef.current = isActive; useEffect(() => { // Skip scroll monitoring for inactive tabs (keep-alive) - if (!isActive) return + if (!isActive) return; - const container = containerRef.current - if (!container) return + const container = containerRef.current; + if (!container) return; // RAF throttle to avoid setState on every scroll event - let rafId: number | null = null - let lastAtBottom: boolean | null = null + let rafId: number | null = null; + let lastAtBottom: boolean | null = null; const checkVisibility = () => { // Skip if not active or RAF already pending - if (!isActiveRef.current || rafId !== null) return + if (!isActiveRef.current || rafId !== null) return; rafId = requestAnimationFrame(() => { - rafId = null + rafId = null; // Double-check active state in RAF callback - if (!isActiveRef.current) return + if (!isActiveRef.current) return; - const threshold = 50 + const threshold = 50; const atBottom = - container.scrollHeight - container.scrollTop - container.clientHeight <= - threshold + container.scrollHeight - + container.scrollTop - + container.clientHeight <= + threshold; // Only update state if value actually changed if (lastAtBottom !== atBottom) { - lastAtBottom = atBottom - setIsVisible(!atBottom) + lastAtBottom = atBottom; + setIsVisible(!atBottom); } - }) - } + }); + }; // Check initial state after a short delay to allow scroll position to be set // This handles the case when entering a sub-chat that's scrolled to a specific position const timeoutId = setTimeout(() => { // Skip if not active - if (!isActiveRef.current) return + if (!isActiveRef.current) return; // Direct check for initial state (no RAF needed) - const threshold = 50 + const threshold = 50; const atBottom = container.scrollHeight - container.scrollTop - container.clientHeight <= - threshold - lastAtBottom = atBottom - setIsVisible(!atBottom) - }, 50) + threshold; + lastAtBottom = atBottom; + setIsVisible(!atBottom); + }, 50); - container.addEventListener("scroll", checkVisibility, { passive: true }) + container.addEventListener("scroll", checkVisibility, { passive: true }); return () => { - clearTimeout(timeoutId) - if (rafId !== null) cancelAnimationFrame(rafId) - container.removeEventListener("scroll", checkVisibility) - } - }, [containerRef, subChatId, isActive]) + clearTimeout(timeoutId); + if (rafId !== null) cancelAnimationFrame(rafId); + container.removeEventListener("scroll", checkVisibility); + }; + }, [containerRef, subChatId, isActive]); return ( @@ -914,7 +939,9 @@ const ScrollToBottomButton = memo(function ScrollToBottomButton({ onClick={onScrollToBottom} className={cn( "absolute right-4 p-2 rounded-full bg-background border border-border shadow-md hover:bg-accent active:scale-[0.97] transition-colors z-20", - hasStackedCards ? "bottom-44 sm:bottom-36" : "bottom-32 sm:bottom-24" + hasStackedCards + ? "bottom-44 sm:bottom-36" + : "bottom-32 sm:bottom-24", )} aria-label="Scroll to bottom" > @@ -933,42 +960,44 @@ const ScrollToBottomButton = memo(function ScrollToBottomButton({ )} - ) -}) + ); +}); // Message group wrapper - measures user message height for sticky todo positioning interface MessageGroupProps { - children: React.ReactNode - isLastGroup?: boolean + children: React.ReactNode; + isLastGroup?: boolean; } function MessageGroup({ children, isLastGroup }: MessageGroupProps) { - const groupRef = useRef(null) - const userMessageRef = useRef(null) + const groupRef = useRef(null); + const userMessageRef = useRef(null); useEffect(() => { - const groupEl = groupRef.current - if (!groupEl) return + const groupEl = groupRef.current; + if (!groupEl) return; // Find the actual bubble element (not the wrapper which includes gradient) - const bubbleEl = groupEl.querySelector('[data-user-bubble]') as HTMLDivElement | null - if (!bubbleEl) return + const bubbleEl = groupEl.querySelector( + "[data-user-bubble]", + ) as HTMLDivElement | null; + if (!bubbleEl) return; - userMessageRef.current = bubbleEl + userMessageRef.current = bubbleEl; const updateHeight = () => { - const height = bubbleEl.offsetHeight + const height = bubbleEl.offsetHeight; // Set CSS variable directly on DOM - no React state, no re-renders - groupEl.style.setProperty('--user-message-height', `${height}px`) - } + groupEl.style.setProperty("--user-message-height", `${height}px`); + }; - updateHeight() + updateHeight(); - const observer = new ResizeObserver(updateHeight) - observer.observe(bubbleEl) + const observer = new ResizeObserver(updateHeight); + observer.observe(bubbleEl); - return () => observer.disconnect() - }, []) + return () => observer.disconnect(); + }, []); return (
{children}
- ) + ); } // Collapsible steps component for intermediate content before final response interface CollapsibleStepsProps { - stepsCount: number - children: React.ReactNode - defaultExpanded?: boolean + stepsCount: number; + children: React.ReactNode; + defaultExpanded?: boolean; } function CollapsibleSteps({ @@ -1002,9 +1033,9 @@ function CollapsibleSteps({ children, defaultExpanded = false, }: CollapsibleStepsProps) { - const [isExpanded, setIsExpanded] = useState(defaultExpanded) + const [isExpanded, setIsExpanded] = useState(defaultExpanded); - if (stepsCount === 0) return null + if (stepsCount === 0) return null; return (
@@ -1022,8 +1053,8 @@ function CollapsibleSteps({
- ) + ); } // ============================================================================ @@ -1052,60 +1083,84 @@ function CollapsibleSteps({ // ============================================================================ interface DiffStateContextValue { - selectedFilePath: string | null - filteredSubChatId: string | null - viewedCount: number - handleDiffFileSelect: (file: { path: string }, category: string) => void - handleSelectNextFile: (filePath: string) => void - handleCommitSuccess: () => void - handleCloseDiff: () => void - handleViewedCountChange: (count: number) => void + selectedFilePath: string | null; + filteredSubChatId: string | null; + viewedCount: number; + handleDiffFileSelect: (file: { path: string }, category: string) => void; + handleSelectNextFile: (filePath: string) => void; + handleCommitSuccess: () => void; + handleCloseDiff: () => void; + handleViewedCountChange: (count: number) => void; /** Ref to register a function that resets activeTab to "changes" before closing */ - resetActiveTabRef: React.MutableRefObject<(() => void) | null> + resetActiveTabRef: React.MutableRefObject<(() => void) | null>; } -const DiffStateContext = createContext(null) +const DiffStateContext = createContext(null); function useDiffState() { - const ctx = useContext(DiffStateContext) - if (!ctx) throw new Error('useDiffState must be used within DiffStateProvider') - return ctx + const ctx = useContext(DiffStateContext); + if (!ctx) + throw new Error("useDiffState must be used within DiffStateProvider"); + return ctx; } // Diff sidebar content component with responsive layout interface DiffSidebarContentProps { - worktreePath: string | null - selectedFilePath: string | null - onFileSelect: (file: { path: string }, category: string) => void - chatId: string - sandboxId: string | null - repository: { owner: string; name: string } | null - diffStats: { isLoading: boolean; hasChanges: boolean; fileCount: number; additions: number; deletions: number } - setDiffStats: (stats: { isLoading: boolean; hasChanges: boolean; fileCount: number; additions: number; deletions: number }) => void - diffContent: string | null - parsedFileDiffs: unknown - prefetchedFileContents: Record | undefined - setDiffCollapseState: (state: Map) => void - diffViewRef: React.RefObject<{ expandAll: () => void; collapseAll: () => void; getViewedCount: () => number; markAllViewed: () => void; markAllUnviewed: () => void } | null> - agentChat: { prUrl?: string; prNumber?: number } | null | undefined + worktreePath: string | null; + selectedFilePath: string | null; + onFileSelect: (file: { path: string }, category: string) => void; + chatId: string; + sandboxId: string | null; + repository: { owner: string; name: string } | null; + diffStats: { + isLoading: boolean; + hasChanges: boolean; + fileCount: number; + additions: number; + deletions: number; + }; + setDiffStats: (stats: { + isLoading: boolean; + hasChanges: boolean; + fileCount: number; + additions: number; + deletions: number; + }) => void; + diffContent: string | null; + parsedFileDiffs: unknown; + prefetchedFileContents: Record | undefined; + setDiffCollapseState: (state: Map) => void; + diffViewRef: React.RefObject<{ + expandAll: () => void; + collapseAll: () => void; + getViewedCount: () => number; + markAllViewed: () => void; + markAllUnviewed: () => void; + } | null>; + agentChat: { prUrl?: string; prNumber?: number } | null | undefined; // Real-time sidebar width for responsive layout during resize - sidebarWidth: number + sidebarWidth: number; // Commit with AI - onCommitWithAI?: () => void - isCommittingWithAI?: boolean + onCommitWithAI?: () => void; + isCommittingWithAI?: boolean; // Diff view mode - diffMode: DiffModeEnum - setDiffMode: (mode: DiffModeEnum) => void + diffMode: DiffModeEnum; + setDiffMode: (mode: DiffModeEnum) => void; // Create PR callback - onCreatePr?: () => void + onCreatePr?: () => void; // Called after successful commit to reset diff view state - onCommitSuccess?: () => void + onCommitSuccess?: () => void; // Subchats with changed files for filtering - subChats?: Array<{ id: string; name: string; filePaths: string[]; fileCount: number }> + subChats?: Array<{ + id: string; + name: string; + filePaths: string[]; + fileCount: number; + }>; // Initial subchat filter (e.g., from Review button) - initialSubChatFilter?: string | null + initialSubChatFilter?: string | null; // Callback when marking file as viewed to select next file - onSelectNextFile?: (filePath: string) => void + onSelectNextFile?: (filePath: string) => void; } // Memoized commit file item for History tab @@ -1113,17 +1168,19 @@ const CommitFileItem = memo(function CommitFileItem({ file, onClick, }: { - file: { path: string; status: FileStatus } - onClick: () => void + file: { path: string; status: FileStatus }; + onClick: () => void; }) { - const fileName = file.path.split('/').pop() || file.path - const dirPath = file.path.includes('/') ? file.path.substring(0, file.path.lastIndexOf('/')) : '' + const fileName = file.path.split("/").pop() || file.path; + const dirPath = file.path.includes("/") + ? file.path.substring(0, file.path.lastIndexOf("/")) + : ""; return (
@@ -1137,12 +1194,10 @@ const CommitFileItem = memo(function CommitFileItem({ {fileName}
-
- {getStatusIndicator(file.status)} -
+
{getStatusIndicator(file.status)}
- ) -}) + ); +}); const DiffSidebarContent = memo(function DiffSidebarContent({ worktreePath, @@ -1164,7 +1219,14 @@ const DiffSidebarContent = memo(function DiffSidebarContent({ setDiffMode, onCreatePr, subChats = [], -}: Omit) { +}: Omit< + DiffSidebarContentProps, + | "selectedFilePath" + | "onFileSelect" + | "onCommitSuccess" + | "initialSubChatFilter" + | "onSelectNextFile" +>) { // Get values from context instead of props const { selectedFilePath, @@ -1174,104 +1236,117 @@ const DiffSidebarContent = memo(function DiffSidebarContent({ handleCommitSuccess, handleViewedCountChange, resetActiveTabRef, - } = useDiffState() + } = useDiffState(); // Compute initial selected file synchronously for first render // This prevents AgentDiffView from rendering all files before filter kicks in const initialSelectedFile = useMemo(() => { - if (selectedFilePath) return selectedFilePath + if (selectedFilePath) return selectedFilePath; if (parsedFileDiffs && parsedFileDiffs.length > 0) { - const firstFile = parsedFileDiffs[0] - const filePath = firstFile.newPath !== '/dev/null' ? firstFile.newPath : firstFile.oldPath - if (filePath && filePath !== '/dev/null') { - return filePath + const firstFile = parsedFileDiffs[0]; + const filePath = + firstFile.newPath !== "/dev/null" + ? firstFile.newPath + : firstFile.oldPath; + if (filePath && filePath !== "/dev/null") { + return filePath; } } - return null - }, [selectedFilePath, parsedFileDiffs]) - const [changesPanelWidth, setChangesPanelWidth] = useAtom(agentsChangesPanelWidthAtom) - const [isChangesPanelCollapsed, setIsChangesPanelCollapsed] = useAtom(agentsChangesPanelCollapsedAtom) - const [isResizing, setIsResizing] = useState(false) + return null; + }, [selectedFilePath, parsedFileDiffs]); + const [changesPanelWidth, setChangesPanelWidth] = useAtom( + agentsChangesPanelWidthAtom, + ); + const [isChangesPanelCollapsed, setIsChangesPanelCollapsed] = useAtom( + agentsChangesPanelCollapsedAtom, + ); + const [isResizing, setIsResizing] = useState(false); // Active tab state (Changes/History) - const [activeTab, setActiveTab] = useState<"changes" | "history">("changes") + const [activeTab, setActiveTab] = useState<"changes" | "history">("changes"); // Register the reset function so handleCloseDiff can reset to "changes" tab before closing // This prevents React 19 ref cleanup issues with HistoryView's ContextMenu components useEffect(() => { - resetActiveTabRef.current = () => setActiveTab("changes") + resetActiveTabRef.current = () => setActiveTab("changes"); return () => { - resetActiveTabRef.current = null - } - }, [resetActiveTabRef]) + resetActiveTabRef.current = null; + }; + }, [resetActiveTabRef]); // Selected commit for History tab - const [selectedCommit, setSelectedCommit] = useAtom(selectedCommitAtom) + const [selectedCommit, setSelectedCommit] = useAtom(selectedCommitAtom); // When sidebar is narrow (< 500px), use vertical layout - const isNarrow = sidebarWidth < 500 + const isNarrow = sidebarWidth < 500; // Get diff stats for collapsed header display const { data: diffStatus } = trpc.changes.getStatus.useQuery( { worktreePath: worktreePath || "" }, - { enabled: !!worktreePath && isNarrow } - ) + { enabled: !!worktreePath && isNarrow }, + ); // Handle resize drag const handleResizePointerDown = useCallback( (event: React.PointerEvent) => { - if (event.button !== 0) return + if (event.button !== 0) return; - event.preventDefault() - event.stopPropagation() + event.preventDefault(); + event.stopPropagation(); - const startX = event.clientX - const startWidth = changesPanelWidth - const pointerId = event.pointerId - const handleElement = event.currentTarget as HTMLElement + const startX = event.clientX; + const startWidth = changesPanelWidth; + const pointerId = event.pointerId; + const handleElement = event.currentTarget as HTMLElement; - const minWidth = 200 - const maxWidth = 450 + const minWidth = 200; + const maxWidth = 450; const clampWidth = (width: number) => - Math.max(minWidth, Math.min(maxWidth, width)) + Math.max(minWidth, Math.min(maxWidth, width)); - handleElement.setPointerCapture?.(pointerId) - setIsResizing(true) + handleElement.setPointerCapture?.(pointerId); + setIsResizing(true); const handlePointerMove = (e: PointerEvent) => { - const delta = e.clientX - startX - const newWidth = clampWidth(startWidth + delta) - setChangesPanelWidth(newWidth) - } + const delta = e.clientX - startX; + const newWidth = clampWidth(startWidth + delta); + setChangesPanelWidth(newWidth); + }; const handlePointerUp = () => { if (handleElement.hasPointerCapture?.(pointerId)) { - handleElement.releasePointerCapture(pointerId) + handleElement.releasePointerCapture(pointerId); } - document.removeEventListener("pointermove", handlePointerMove) - document.removeEventListener("pointerup", handlePointerUp) - setIsResizing(false) - } + document.removeEventListener("pointermove", handlePointerMove); + document.removeEventListener("pointerup", handlePointerUp); + setIsResizing(false); + }; - document.addEventListener("pointermove", handlePointerMove) - document.addEventListener("pointerup", handlePointerUp, { once: true }) + document.addEventListener("pointermove", handlePointerMove); + document.addEventListener("pointerup", handlePointerUp, { once: true }); }, - [changesPanelWidth, setChangesPanelWidth] - ) + [changesPanelWidth, setChangesPanelWidth], + ); // Handle commit selection in History tab - const handleCommitSelect = useCallback((commit: SelectedCommit) => { - setSelectedCommit(commit) - // Reset file selection when changing commits - // The HistoryView will auto-select first file - }, [setSelectedCommit]) + const handleCommitSelect = useCallback( + (commit: SelectedCommit) => { + setSelectedCommit(commit); + // Reset file selection when changing commits + // The HistoryView will auto-select first file + }, + [setSelectedCommit], + ); // Handle file selection in commit (History tab) - const handleCommitFileSelect = useCallback((file: { path: string }, commitHash: string) => { - // Set selected file path for highlighting - handleDiffFileSelect(file, "") - }, [handleDiffFileSelect]) + const handleCommitFileSelect = useCallback( + (file: { path: string }, commitHash: string) => { + // Set selected file path for highlighting + handleDiffFileSelect(file, ""); + }, + [handleDiffFileSelect], + ); // Fetch commit files when a commit is selected const { data: commitFiles } = trpc.changes.getCommitFiles.useQuery( @@ -1282,8 +1357,8 @@ const DiffSidebarContent = memo(function DiffSidebarContent({ { enabled: !!worktreePath && !!selectedCommit, staleTime: 60000, // Cache for 1 minute - } - ) + }, + ); // Fetch commit file diff when a commit is selected const { data: commitFileDiff } = trpc.changes.getCommitFileDiff.useQuery( @@ -1295,32 +1370,39 @@ const DiffSidebarContent = memo(function DiffSidebarContent({ { enabled: !!worktreePath && !!selectedCommit && !!selectedFilePath, staleTime: 60000, // Cache for 1 minute - } - ) + }, + ); // Use commit diff or regular diff based on selection // Only use commit data when in History tab, otherwise always use regular diff - const shouldUseCommitDiff = activeTab === "history" && selectedCommit - const effectiveDiff = shouldUseCommitDiff && commitFileDiff ? commitFileDiff : diffContent - const effectiveParsedFiles = shouldUseCommitDiff ? null : parsedFileDiffs - const effectivePrefetchedContents = shouldUseCommitDiff ? {} : prefetchedFileContents + const shouldUseCommitDiff = activeTab === "history" && selectedCommit; + const effectiveDiff = + shouldUseCommitDiff && commitFileDiff ? commitFileDiff : diffContent; + const effectiveParsedFiles = shouldUseCommitDiff ? null : parsedFileDiffs; + const effectivePrefetchedContents = shouldUseCommitDiff + ? {} + : prefetchedFileContents; if (isNarrow) { // Count changed files for collapsed header const changedFilesCount = diffStatus - ? (diffStatus.staged?.length || 0) + (diffStatus.unstaged?.length || 0) + (diffStatus.untracked?.length || 0) - : 0 - const stagedCount = diffStatus?.staged?.length || 0 + ? (diffStatus.staged?.length || 0) + + (diffStatus.unstaged?.length || 0) + + (diffStatus.untracked?.length || 0) + : 0; + const stagedCount = diffStatus?.staged?.length || 0; // Vertical layout: ChangesPanel on top, diff/file list below return (
{/* Top: ChangesPanel (file list + commit) */} {worktreePath && ( -
+
@@ -1395,14 +1484,17 @@ const DiffSidebarContent = memo(function DiffSidebarContent({ /> ))} - ) - )} + ))}
{/* Diff view - always mounted to prevent expensive re-initialization */} -
+
- ) + ); } // Horizontal layout: files on left, diff on right @@ -1464,17 +1556,23 @@ const DiffSidebarContent = memo(function DiffSidebarContent({ )} {/* Right: File list (when History tab) or AgentDiffView (when Changes tab) */} {/* Both views are always mounted but hidden via CSS to prevent expensive re-mounts */} -
+
{/* History view - files in commit */} -
- {selectedCommit && ( - !commitFiles ? ( +
+ {selectedCommit && + (!commitFiles ? (
Loading files...
@@ -1492,8 +1590,8 @@ const DiffSidebarContent = memo(function DiffSidebarContent({
)}
- {selectedCommit.author} • {selectedCommit.date ? new Date(selectedCommit.date).toLocaleString() : 'Unknown date'} + {selectedCommit.author} •{" "} + {selectedCommit.date + ? new Date(selectedCommit.date).toLocaleString() + : "Unknown date"}
@@ -1521,14 +1622,17 @@ const DiffSidebarContent = memo(function DiffSidebarContent({ /> ))} - ) - )} + ))}
{/* Diff view - always mounted to prevent expensive re-initialization */} -
+
- ) -}) + ); +}); // ============================================================================ // DiffStateProvider - manages diff state in isolation from ChatView @@ -1557,16 +1661,22 @@ const DiffSidebarContent = memo(function DiffSidebarContent({ // ============================================================================ interface DiffStateProviderProps { - isDiffSidebarOpen: boolean - parsedFileDiffs: ParsedDiffFile[] | null - isDiffSidebarNarrow: boolean - setIsDiffSidebarOpen: (open: boolean) => void - setDiffStats: (stats: { isLoading: boolean; hasChanges: boolean; fileCount: number; additions: number; deletions: number }) => void - setDiffContent: (content: string | null) => void - setParsedFileDiffs: (files: ParsedDiffFile[] | null) => void - setPrefetchedFileContents: (contents: Record) => void - fetchDiffStats: () => void - children: React.ReactNode + isDiffSidebarOpen: boolean; + parsedFileDiffs: ParsedDiffFile[] | null; + isDiffSidebarNarrow: boolean; + setIsDiffSidebarOpen: (open: boolean) => void; + setDiffStats: (stats: { + isLoading: boolean; + hasChanges: boolean; + fileCount: number; + additions: number; + deletions: number; + }) => void; + setDiffContent: (content: string | null) => void; + setParsedFileDiffs: (files: ParsedDiffFile[] | null) => void; + setPrefetchedFileContents: (contents: Record) => void; + fetchDiffStats: () => void; + children: React.ReactNode; } const DiffStateProvider = memo(function DiffStateProvider({ @@ -1582,111 +1692,152 @@ const DiffStateProvider = memo(function DiffStateProvider({ children, }: DiffStateProviderProps) { // Viewed count state - kept here to avoid re-rendering ChatView - const [viewedCount, setViewedCount] = useState(0) + const [viewedCount, setViewedCount] = useState(0); // Ref for resetting activeTab to "changes" before closing // This prevents React 19 ref cleanup issues with HistoryView's ContextMenu components - const resetActiveTabRef = useRef<(() => void) | null>(null) + const resetActiveTabRef = useRef<(() => void) | null>(null); // All diff-related atoms are read HERE, not in ChatView - const [selectedFilePath, setSelectedFilePath] = useAtom(selectedDiffFilePathAtom) - const [, setFilteredDiffFiles] = useAtom(filteredDiffFilesAtom) - const [filteredSubChatId, setFilteredSubChatId] = useAtom(filteredSubChatIdAtom) - const isChangesPanelCollapsed = useAtomValue(agentsChangesPanelCollapsedAtom) + const [selectedFilePath, setSelectedFilePath] = useAtom( + selectedDiffFilePathAtom, + ); + const [, setFilteredDiffFiles] = useAtom(filteredDiffFilesAtom); + const [filteredSubChatId, setFilteredSubChatId] = useAtom( + filteredSubChatIdAtom, + ); + const isChangesPanelCollapsed = useAtomValue(agentsChangesPanelCollapsedAtom); // Auto-select first file when diff sidebar opens - use useLayoutEffect for synchronous update // This prevents the initial render from showing all 11 files before filter kicks in useLayoutEffect(() => { if (!isDiffSidebarOpen) { - setSelectedFilePath(null) - setFilteredDiffFiles(null) - return + setSelectedFilePath(null); + setFilteredDiffFiles(null); + return; } // Determine which file to select - let fileToSelect = selectedFilePath + let fileToSelect = selectedFilePath; if (!fileToSelect && parsedFileDiffs && parsedFileDiffs.length > 0) { - const firstFile = parsedFileDiffs[0] - fileToSelect = firstFile.newPath !== '/dev/null' ? firstFile.newPath : firstFile.oldPath - if (fileToSelect && fileToSelect !== '/dev/null') { - setSelectedFilePath(fileToSelect) + const firstFile = parsedFileDiffs[0]; + fileToSelect = + firstFile.newPath !== "/dev/null" + ? firstFile.newPath + : firstFile.oldPath; + if (fileToSelect && fileToSelect !== "/dev/null") { + setSelectedFilePath(fileToSelect); } } // Filter logic based on layout mode - const shouldShowAllFiles = isDiffSidebarNarrow && isChangesPanelCollapsed + const shouldShowAllFiles = isDiffSidebarNarrow && isChangesPanelCollapsed; if (shouldShowAllFiles) { - setFilteredDiffFiles(null) + setFilteredDiffFiles(null); } else if (fileToSelect) { - setFilteredDiffFiles([fileToSelect]) + setFilteredDiffFiles([fileToSelect]); } else { - setFilteredDiffFiles(null) + setFilteredDiffFiles(null); } - }, [isDiffSidebarOpen, selectedFilePath, parsedFileDiffs, isDiffSidebarNarrow, isChangesPanelCollapsed, setFilteredDiffFiles, setSelectedFilePath]) + }, [ + isDiffSidebarOpen, + selectedFilePath, + parsedFileDiffs, + isDiffSidebarNarrow, + isChangesPanelCollapsed, + setFilteredDiffFiles, + setSelectedFilePath, + ]); // Stable callbacks - const handleDiffFileSelect = useCallback((file: { path: string }, _category: string) => { - setSelectedFilePath(file.path) - setFilteredDiffFiles([file.path]) - }, [setSelectedFilePath, setFilteredDiffFiles]) + const handleDiffFileSelect = useCallback( + (file: { path: string }, _category: string) => { + setSelectedFilePath(file.path); + setFilteredDiffFiles([file.path]); + }, + [setSelectedFilePath, setFilteredDiffFiles], + ); - const handleSelectNextFile = useCallback((filePath: string) => { - setSelectedFilePath(filePath) - setFilteredDiffFiles([filePath]) - }, [setSelectedFilePath, setFilteredDiffFiles]) + const handleSelectNextFile = useCallback( + (filePath: string) => { + setSelectedFilePath(filePath); + setFilteredDiffFiles([filePath]); + }, + [setSelectedFilePath, setFilteredDiffFiles], + ); const handleCommitSuccess = useCallback(() => { - setSelectedFilePath(null) - setFilteredDiffFiles(null) - setParsedFileDiffs(null) - setDiffContent(null) - setPrefetchedFileContents({}) + setSelectedFilePath(null); + setFilteredDiffFiles(null); + setParsedFileDiffs(null); + setDiffContent(null); + setPrefetchedFileContents({}); setDiffStats({ fileCount: 0, additions: 0, deletions: 0, isLoading: true, hasChanges: false, - }) + }); setTimeout(() => { - fetchDiffStats() - }, 500) - }, [setSelectedFilePath, setFilteredDiffFiles, setParsedFileDiffs, setDiffContent, setPrefetchedFileContents, setDiffStats, fetchDiffStats]) + fetchDiffStats(); + }, 500); + }, [ + setSelectedFilePath, + setFilteredDiffFiles, + setParsedFileDiffs, + setDiffContent, + setPrefetchedFileContents, + setDiffStats, + fetchDiffStats, + ]); const handleCloseDiff = useCallback(() => { // Use flushSync to reset activeTab synchronously before closing. // This unmounts HistoryView's ContextMenu components in a single commit, // preventing React 19 ref cleanup "Maximum update depth exceeded" error. flushSync(() => { - resetActiveTabRef.current?.() - }) - setIsDiffSidebarOpen(false) - setFilteredSubChatId(null) - }, [setIsDiffSidebarOpen, setFilteredSubChatId]) + resetActiveTabRef.current?.(); + }); + setIsDiffSidebarOpen(false); + setFilteredSubChatId(null); + }, [setIsDiffSidebarOpen, setFilteredSubChatId]); const handleViewedCountChange = useCallback((count: number) => { - setViewedCount(count) - }, []) - - const contextValue = useMemo(() => ({ - selectedFilePath, - filteredSubChatId, - viewedCount, - handleDiffFileSelect, - handleSelectNextFile, - handleCommitSuccess, - handleCloseDiff, - handleViewedCountChange, - resetActiveTabRef, - }), [selectedFilePath, filteredSubChatId, viewedCount, handleDiffFileSelect, handleSelectNextFile, handleCommitSuccess, handleCloseDiff, handleViewedCountChange]) + setViewedCount(count); + }, []); + + const contextValue = useMemo( + () => ({ + selectedFilePath, + filteredSubChatId, + viewedCount, + handleDiffFileSelect, + handleSelectNextFile, + handleCommitSuccess, + handleCloseDiff, + handleViewedCountChange, + resetActiveTabRef, + }), + [ + selectedFilePath, + filteredSubChatId, + viewedCount, + handleDiffFileSelect, + handleSelectNextFile, + handleCommitSuccess, + handleCloseDiff, + handleViewedCountChange, + ], + ); return ( {children} - ) -}) + ); +}); // ============================================================================ // DiffSidebarRenderer - renders the diff sidebar using context for state @@ -1694,49 +1845,80 @@ const DiffStateProvider = memo(function DiffStateProvider({ // ============================================================================ interface DiffSidebarRendererProps { - worktreePath: string | null - chatId: string - sandboxId: string | null - repository: { owner: string; name: string } | null - diffStats: { isLoading: boolean; hasChanges: boolean; fileCount: number; additions: number; deletions: number } - diffContent: string | null - parsedFileDiffs: ParsedDiffFile[] | null - prefetchedFileContents: Record - setDiffCollapseState: (state: { allCollapsed: boolean; allExpanded: boolean }) => void - diffViewRef: React.RefObject - diffSidebarRef: React.RefObject - agentChat: { prUrl?: string; prNumber?: number } | null | undefined - branchData: { current: string } | undefined - gitStatus: { pushCount?: number; pullCount?: number; hasUpstream?: boolean; ahead?: number; behind?: number; staged?: any[]; unstaged?: any[]; untracked?: any[] } | undefined - isGitStatusLoading: boolean - isDiffSidebarOpen: boolean - diffDisplayMode: "side-peek" | "center-peek" | "full-page" - diffSidebarWidth: number - handleReview: () => void - isReviewing: boolean - handleCreatePrDirect: () => void - handleCreatePr: () => void - isCreatingPr: boolean - handleMergePr: () => void - mergePrMutation: { isPending: boolean } - handleRefreshGitStatus: () => void - hasPrNumber: boolean - isPrOpen: boolean - hasMergeConflicts: boolean - handleFixConflicts: () => void - handleExpandAll: () => void - handleCollapseAll: () => void - diffMode: DiffModeEnum - setDiffMode: (mode: DiffModeEnum) => void - handleMarkAllViewed: () => void - handleMarkAllUnviewed: () => void - isDesktop: boolean - isFullscreen: boolean - setDiffDisplayMode: (mode: "side-peek" | "center-peek" | "full-page") => void - handleCommitToPr: (selectedPaths?: string[]) => void - isCommittingToPr: boolean - subChatsWithFiles: Array<{ id: string; name: string; filePaths: string[]; fileCount: number }> - setDiffStats: (stats: { isLoading: boolean; hasChanges: boolean; fileCount: number; additions: number; deletions: number }) => void + worktreePath: string | null; + chatId: string; + sandboxId: string | null; + repository: { owner: string; name: string } | null; + diffStats: { + isLoading: boolean; + hasChanges: boolean; + fileCount: number; + additions: number; + deletions: number; + }; + diffContent: string | null; + parsedFileDiffs: ParsedDiffFile[] | null; + prefetchedFileContents: Record; + setDiffCollapseState: (state: { + allCollapsed: boolean; + allExpanded: boolean; + }) => void; + diffViewRef: React.RefObject; + diffSidebarRef: React.RefObject; + agentChat: { prUrl?: string; prNumber?: number } | null | undefined; + branchData: { current: string } | undefined; + gitStatus: + | { + pushCount?: number; + pullCount?: number; + hasUpstream?: boolean; + ahead?: number; + behind?: number; + staged?: any[]; + unstaged?: any[]; + untracked?: any[]; + } + | undefined; + isGitStatusLoading: boolean; + isDiffSidebarOpen: boolean; + diffDisplayMode: "side-peek" | "center-peek" | "full-page"; + diffSidebarWidth: number; + handleReview: () => void; + isReviewing: boolean; + handleCreatePrDirect: () => void; + handleCreatePr: () => void; + isCreatingPr: boolean; + handleMergePr: () => void; + mergePrMutation: { isPending: boolean }; + handleRefreshGitStatus: () => void; + hasPrNumber: boolean; + isPrOpen: boolean; + hasMergeConflicts: boolean; + handleFixConflicts: () => void; + handleExpandAll: () => void; + handleCollapseAll: () => void; + diffMode: DiffModeEnum; + setDiffMode: (mode: DiffModeEnum) => void; + handleMarkAllViewed: () => void; + handleMarkAllUnviewed: () => void; + isDesktop: boolean; + isFullscreen: boolean; + setDiffDisplayMode: (mode: "side-peek" | "center-peek" | "full-page") => void; + handleCommitToPr: (selectedPaths?: string[]) => void; + isCommittingToPr: boolean; + subChatsWithFiles: Array<{ + id: string; + name: string; + filePaths: string[]; + fileCount: number; + }>; + setDiffStats: (stats: { + isLoading: boolean; + hasChanges: boolean; + fileCount: number; + additions: number; + deletions: number; + }) => void; } const DiffSidebarRenderer = memo(function DiffSidebarRenderer({ @@ -1785,14 +1967,18 @@ const DiffSidebarRenderer = memo(function DiffSidebarRenderer({ setDiffStats, }: DiffSidebarRendererProps) { // Get callbacks and state from context - const { handleCloseDiff, viewedCount, handleViewedCountChange } = useDiffState() + const { handleCloseDiff, viewedCount, handleViewedCountChange } = + useDiffState(); // Width for responsive layouts - use stored width for sidebar, fixed for dialog/fullpage - const effectiveWidth = diffDisplayMode === "side-peek" - ? diffSidebarWidth - : diffDisplayMode === "center-peek" - ? 1200 - : typeof window !== 'undefined' ? window.innerWidth : 1200 + const effectiveWidth = + diffDisplayMode === "side-peek" + ? diffSidebarWidth + : diffDisplayMode === "center-peek" + ? 1200 + : typeof window !== "undefined" + ? window.innerWidth + : 1200; const diffViewContent = (
- ) + ); // Render based on display mode if (diffDisplayMode === "side-peek") { @@ -1895,7 +2081,7 @@ const DiffSidebarRenderer = memo(function DiffSidebarRenderer({ > {diffViewContent} - ) + ); } if (diffDisplayMode === "center-peek") { @@ -1906,22 +2092,19 @@ const DiffSidebarRenderer = memo(function DiffSidebarRenderer({ > {diffViewContent} - ) + ); } if (diffDisplayMode === "full-page") { return ( - + {diffViewContent} - ) + ); } - return null -}) + return null; +}); // Inner chat component - only rendered when chat object is ready // Memoized to prevent re-renders when parent state changes (e.g., selectedFilePath) @@ -1948,259 +2131,261 @@ const ChatViewInner = memo(function ChatViewInner({ existingPrUrl, isActive = true, }: { - chat: Chat - subChatId: string - parentChatId: string - isFirstSubChat: boolean - onAutoRename: (userMessage: string, subChatId: string) => void - onCreateNewSubChat?: () => void - refreshDiff?: () => void - teamId?: string - repository?: string - streamId?: string | null - isMobile?: boolean - sandboxSetupStatus?: "cloning" | "ready" | "error" - sandboxSetupError?: string - onRetrySetup?: () => void - isSubChatsSidebarOpen?: boolean - sandboxId?: string - projectPath?: string - isArchived?: boolean - onRestoreWorkspace?: () => void - existingPrUrl?: string | null - isActive?: boolean + chat: Chat; + subChatId: string; + parentChatId: string; + isFirstSubChat: boolean; + onAutoRename: (userMessage: string, subChatId: string) => void; + onCreateNewSubChat?: () => void; + refreshDiff?: () => void; + teamId?: string; + repository?: string; + streamId?: string | null; + isMobile?: boolean; + sandboxSetupStatus?: "cloning" | "ready" | "error"; + sandboxSetupError?: string; + onRetrySetup?: () => void; + isSubChatsSidebarOpen?: boolean; + sandboxId?: string; + projectPath?: string; + isArchived?: boolean; + onRestoreWorkspace?: () => void; + existingPrUrl?: string | null; + isActive?: boolean; }) { - const hasTriggeredRenameRef = useRef(false) - const hasTriggeredAutoGenerateRef = useRef(false) + const hasTriggeredRenameRef = useRef(false); + const hasTriggeredAutoGenerateRef = useRef(false); // Keep isActive in ref for use in callbacks (avoid stale closures) - const isActiveRef = useRef(isActive) - isActiveRef.current = isActive + const isActiveRef = useRef(isActive); + isActiveRef.current = isActive; // Scroll management state (like canvas chat) // Using only ref to avoid re-renders on scroll - const shouldAutoScrollRef = useRef(true) - const isAutoScrollingRef = useRef(false) // Flag to ignore scroll events caused by auto-scroll - const isInitializingScrollRef = useRef(false) // Flag to ignore scroll events during scroll initialization (content loading) - const hasUnapprovedPlanRef = useRef(false) // Track unapproved plan state for scroll initialization - const chatContainerRef = useRef(null) + const shouldAutoScrollRef = useRef(true); + const isAutoScrollingRef = useRef(false); // Flag to ignore scroll events caused by auto-scroll + const isInitializingScrollRef = useRef(false); // Flag to ignore scroll events during scroll initialization (content loading) + const hasUnapprovedPlanRef = useRef(false); // Track unapproved plan state for scroll initialization + const chatContainerRef = useRef(null); // Cleanup isAutoScrollingRef on unmount to prevent stuck state useEffect(() => { return () => { - isAutoScrollingRef.current = false - } - }, []) + isAutoScrollingRef.current = false; + }; + }, []); // Track chat container height via CSS custom property (no re-renders) - const chatContainerObserverRef = useRef(null) + const chatContainerObserverRef = useRef(null); - const editorRef = useRef(null) - const fileInputRef = useRef(null) - const questionRef = useRef(null) - const prevChatKeyRef = useRef(null) - const prevSubChatIdRef = useRef(null) + const editorRef = useRef(null); + const fileInputRef = useRef(null); + const questionRef = useRef(null); + const prevChatKeyRef = useRef(null); + const prevSubChatIdRef = useRef(null); // Consume pending mentions from external components (e.g. MCP widget in sidebar) - const [pendingMention, setPendingMention] = useAtom(pendingMentionAtom) + const [pendingMention, setPendingMention] = useAtom(pendingMentionAtom); useEffect(() => { if (pendingMention) { - editorRef.current?.insertMention(pendingMention) - editorRef.current?.focus() - setPendingMention(null) + editorRef.current?.insertMention(pendingMention); + editorRef.current?.focus(); + setPendingMention(null); } - }, [pendingMention, setPendingMention]) + }, [pendingMention, setPendingMention]); // TTS playback rate state (persists across messages and sessions via localStorage) const [ttsPlaybackRate, setTtsPlaybackRate] = useState(() => { if (typeof window !== "undefined") { - const saved = localStorage.getItem("tts-playback-rate") + const saved = localStorage.getItem("tts-playback-rate"); if (saved && PLAYBACK_SPEEDS.includes(Number(saved) as PlaybackSpeed)) { - return Number(saved) as PlaybackSpeed + return Number(saved) as PlaybackSpeed; } } - return 1 - }) + return 1; + }); // Save playback rate to localStorage when it changes const handlePlaybackRateChange = useCallback((rate: PlaybackSpeed) => { - setTtsPlaybackRate(rate) - localStorage.setItem("tts-playback-rate", String(rate)) - }, []) + setTtsPlaybackRate(rate); + localStorage.setItem("tts-playback-rate", String(rate)); + }, []); // PR creation loading state - from atom to allow resetting after message sent - const setIsCreatingPr = useSetAtom(isCreatingPrAtom) + const setIsCreatingPr = useSetAtom(isCreatingPrAtom); // Rollback state - const [isRollingBack, setIsRollingBack] = useState(false) + const [isRollingBack, setIsRollingBack] = useState(false); // Check if user is at bottom of chat (like canvas) const isAtBottom = useCallback(() => { - const container = chatContainerRef.current - if (!container) return true - const threshold = 50 // pixels from bottom + const container = chatContainerRef.current; + if (!container) return true; + const threshold = 50; // pixels from bottom return ( container.scrollHeight - container.scrollTop - container.clientHeight <= threshold - ) - }, []) - + ); + }, []); // Track previous scroll position to detect scroll direction - const prevScrollTopRef = useRef(0) + const prevScrollTopRef = useRef(0); // Handle scroll events to detect user scrolling // Updates shouldAutoScrollRef based on scroll direction // Using refs only to avoid re-renders on scroll const handleScroll = useCallback(() => { // Skip scroll handling for inactive tabs (keep-alive) - if (!isActiveRef.current) return + if (!isActiveRef.current) return; - const container = chatContainerRef.current - if (!container) return + const container = chatContainerRef.current; + if (!container) return; - const currentScrollTop = container.scrollTop - const prevScrollTop = prevScrollTopRef.current - prevScrollTopRef.current = currentScrollTop + const currentScrollTop = container.scrollTop; + const prevScrollTop = prevScrollTopRef.current; + prevScrollTopRef.current = currentScrollTop; // Ignore scroll events during initialization (content loading) - if (isInitializingScrollRef.current) return + if (isInitializingScrollRef.current) return; // If user scrolls UP - disable auto-scroll immediately // This works even during auto-scroll animation (user intent takes priority) if (currentScrollTop < prevScrollTop) { - shouldAutoScrollRef.current = false - return + shouldAutoScrollRef.current = false; + return; } // Ignore other scroll direction checks during auto-scroll animation - if (isAutoScrollingRef.current) return + if (isAutoScrollingRef.current) return; // If user scrolls DOWN and reaches bottom - enable auto-scroll - shouldAutoScrollRef.current = isAtBottom() - }, [isAtBottom]) + shouldAutoScrollRef.current = isAtBottom(); + }, [isAtBottom]); // Scroll to bottom handler with ease-in-out animation const scrollToBottom = useCallback(() => { - const container = chatContainerRef.current - if (!container) return + const container = chatContainerRef.current; + if (!container) return; - isAutoScrollingRef.current = true - shouldAutoScrollRef.current = true + isAutoScrollingRef.current = true; + shouldAutoScrollRef.current = true; - const start = container.scrollTop - const duration = 300 // ms - const startTime = performance.now() + const start = container.scrollTop; + const duration = 300; // ms + const startTime = performance.now(); // Ease-in-out cubic function const easeInOutCubic = (t: number) => - t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2 + t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; const animateScroll = (currentTime: number) => { - const elapsed = currentTime - startTime - const progress = Math.min(elapsed / duration, 1) - const easedProgress = easeInOutCubic(progress) + const elapsed = currentTime - startTime; + const progress = Math.min(elapsed / duration, 1); + const easedProgress = easeInOutCubic(progress); // Calculate end on each frame to handle dynamic content - const end = container.scrollHeight - container.clientHeight - container.scrollTop = start + (end - start) * easedProgress + const end = container.scrollHeight - container.clientHeight; + container.scrollTop = start + (end - start) * easedProgress; if (progress < 1) { - requestAnimationFrame(animateScroll) + requestAnimationFrame(animateScroll); } else { // Ensure we're at the absolute bottom - container.scrollTop = container.scrollHeight - isAutoScrollingRef.current = false + container.scrollTop = container.scrollHeight; + isAutoScrollingRef.current = false; } - } + }; - requestAnimationFrame(animateScroll) - }, []) + requestAnimationFrame(animateScroll); + }, []); // tRPC utils for cache invalidation - const utils = api.useUtils() + const utils = api.useUtils(); // Get sub-chat name from store const subChatName = useAgentSubChatStore( (state) => state.allSubChats.find((sc) => sc.id === subChatId)?.name || "", - ) + ); // Mutation for renaming sub-chat const renameSubChatMutation = api.agents.renameSubChat.useMutation({ onError: (error) => { if (error.data?.code === "NOT_FOUND") { - toast.error("Send a message first before renaming this chat") + toast.error("Send a message first before renaming this chat"); } else { - toast.error("Failed to rename chat") + toast.error("Failed to rename chat"); } }, - }) + }); // Handler for renaming sub-chat // Using ref for mutation to avoid callback recreation - const renameSubChatMutationRef = useRef(renameSubChatMutation) - renameSubChatMutationRef.current = renameSubChatMutation - const subChatNameRef = useRef(subChatName) - subChatNameRef.current = subChatName + const renameSubChatMutationRef = useRef(renameSubChatMutation); + renameSubChatMutationRef.current = renameSubChatMutation; + const subChatNameRef = useRef(subChatName); + subChatNameRef.current = subChatName; const handleRenameSubChat = useCallback( async (newName: string) => { // Optimistic update in store - useAgentSubChatStore.getState().updateSubChatName(subChatId, newName) + useAgentSubChatStore.getState().updateSubChatName(subChatId, newName); // Save to database try { await renameSubChatMutationRef.current.mutateAsync({ subChatId, name: newName, - }) + }); } catch { // Revert on error (toast shown by mutation onError) useAgentSubChatStore .getState() - .updateSubChatName(subChatId, subChatNameRef.current || "New Chat") + .updateSubChatName(subChatId, subChatNameRef.current || "New Chat"); } }, [subChatId], - ) + ); // Plan mode state (per-subChat using atomFamily) - const [subChatMode, setSubChatMode] = useAtom(subChatModeAtomFamily(subChatId)) + const [subChatMode, setSubChatMode] = useAtom( + subChatModeAtomFamily(subChatId), + ); // Mutation for updating sub-chat mode in database const updateSubChatModeMutation = api.agents.updateSubChatMode.useMutation({ onSuccess: () => { // Invalidate to refetch with new mode from DB - utils.agents.getAgentChat.invalidate({ chatId: parentChatId }) + utils.agents.getAgentChat.invalidate({ chatId: parentChatId }); }, onError: (error, variables) => { // Don't revert if sub-chat not found in DB - it may not be persisted yet // This is expected for new sub-chats that haven't been saved to DB if (error.message === "Sub-chat not found") { - console.warn("Sub-chat not found in DB, keeping local mode state") - return + console.warn("Sub-chat not found in DB, keeping local mode state"); + return; } // Revert local state on error to maintain sync with database - const revertedMode: AgentMode = variables.mode === "plan" ? "agent" : "plan" - setSubChatMode(revertedMode) + const revertedMode: AgentMode = + variables.mode === "plan" ? "agent" : "plan"; + setSubChatMode(revertedMode); // Also update store for consistency useAgentSubChatStore .getState() - .updateSubChatMode(variables.subChatId, revertedMode) - console.error("Failed to update sub-chat mode:", error.message) + .updateSubChatMode(variables.subChatId, revertedMode); + console.error("Failed to update sub-chat mode:", error.message); }, - }) + }); // Sync atomFamily mode to Zustand store on mount/subChatId change // This ensures the sidebar shows the correct mode icon useEffect(() => { if (subChatId) { // Read mode directly from atomFamily to ensure we get the correct value - const mode = appStore.get(subChatModeAtomFamily(subChatId)) - useAgentSubChatStore.getState().updateSubChatMode(subChatId, mode) + const mode = appStore.get(subChatModeAtomFamily(subChatId)); + useAgentSubChatStore.getState().updateSubChatMode(subChatId, mode); } - }, [subChatId]) + }, [subChatId]); // NOTE: We no longer clear caches on deactivation. // With proper subChatId isolation, each chat's caches are separate. @@ -2210,45 +2395,51 @@ const ChatViewInner = memo(function ChatViewInner({ // CRITICAL: Use a delayed cleanup to avoid clearing caches during temporary unmount/remount // (e.g., React StrictMode, HMR, or parent re-render causing component remount) useEffect(() => { - const currentSubChatId = subChatId + const currentSubChatId = subChatId; return () => { // Delay cache clearing to allow remount to happen first // If the component remounts with the same subChatId, the sync will repopulate the atoms // If it truly unmounts, the timeout will clear the caches const timeoutId = setTimeout(() => { - clearSubChatCaches(currentSubChatId) - }, 100) + clearSubChatCaches(currentSubChatId); + }, 100); // Store the timeout so it can be cancelled if the component remounts // We use a global map to track pending cleanups - ;(window as any).__pendingCacheCleanups = (window as any).__pendingCacheCleanups || new Map() - ;(window as any).__pendingCacheCleanups.set(currentSubChatId, timeoutId) - } - }, [subChatId]) + (window as any).__pendingCacheCleanups = + (window as any).__pendingCacheCleanups || new Map(); + (window as any).__pendingCacheCleanups.set(currentSubChatId, timeoutId); + }; + }, [subChatId]); // Cancel pending cleanup if we remount with the same subChatId useEffect(() => { - const pendingCleanups = (window as any).__pendingCacheCleanups as Map | undefined + const pendingCleanups = (window as any).__pendingCacheCleanups as + | Map + | undefined; if (pendingCleanups?.has(subChatId)) { - clearTimeout(pendingCleanups.get(subChatId)) - pendingCleanups.delete(subChatId) + clearTimeout(pendingCleanups.get(subChatId)); + pendingCleanups.delete(subChatId); } - }, [subChatId]) + }, [subChatId]); // Handle mode changes - updates atomFamily, store, and database together // No effect needed - this is called directly when user toggles mode - const handleModeChange = useCallback((newMode: AgentMode) => { - // Update atomFamily (source of truth for UI) - setSubChatMode(newMode) + const handleModeChange = useCallback( + (newMode: AgentMode) => { + // Update atomFamily (source of truth for UI) + setSubChatMode(newMode); - // Update Zustand store (for other components that read from store) - useAgentSubChatStore.getState().updateSubChatMode(subChatId, newMode) + // Update Zustand store (for other components that read from store) + useAgentSubChatStore.getState().updateSubChatMode(subChatId, newMode); - // Save to database (skip temp IDs that haven't been persisted yet) - if (!subChatId.startsWith("temp-")) { - updateSubChatModeMutation.mutate({ subChatId, mode: newMode }) - } - }, [subChatId, setSubChatMode, updateSubChatModeMutation]) + // Save to database (skip temp IDs that haven't been persisted yet) + if (!subChatId.startsWith("temp-")) { + updateSubChatModeMutation.mutate({ subChatId, mode: newMode }); + } + }, + [subChatId, setSubChatMode, updateSubChatModeMutation], + ); // File/image upload hook const { @@ -2261,7 +2452,7 @@ const ChatViewInner = memo(function ChatViewInner({ isUploading, setImagesFromDraft, setFilesFromDraft, - } = useAgentsFileUpload() + } = useAgentsFileUpload(); // Text context selection hook (for selecting text from assistant messages and diff) const { @@ -2277,7 +2468,7 @@ const ChatViewInner = memo(function ChatViewInner({ diffTextContextsRef, setTextContextsFromDraft, setDiffTextContextsFromDraft, - } = useTextContextSelection() + } = useTextContextSelection(); // Pasted text files (large pasted text saved as files) const { @@ -2286,93 +2477,93 @@ const ChatViewInner = memo(function ChatViewInner({ removePastedText, clearPastedTexts, pastedTextsRef, - } = usePastedTextFiles(subChatId) + } = usePastedTextFiles(subChatId); // File contents cache - stores content for file mentions (keyed by mentionId) // This content gets added to the prompt when sending, without showing a separate card - const fileContentsRef = useRef>(new Map()) + const fileContentsRef = useRef>(new Map()); const cacheFileContent = useCallback((mentionId: string, content: string) => { - fileContentsRef.current.set(mentionId, content) - }, []) + fileContentsRef.current.set(mentionId, content); + }, []); const clearFileContents = useCallback(() => { - fileContentsRef.current.clear() - }, []) + fileContentsRef.current.clear(); + }, []); // Clear file contents cache when switching subChats to prevent stale data useEffect(() => { - fileContentsRef.current.clear() - }, [subChatId]) + fileContentsRef.current.clear(); + }, [subChatId]); // Quick comment state const [quickCommentState, setQuickCommentState] = useState<{ - selectedText: string - source: TextSelectionSource - rect: DOMRect - } | null>(null) + selectedText: string; + source: TextSelectionSource; + rect: DOMRect; + } | null>(null); // Message queue for sending messages while streaming - const queue = useMessageQueueStore((s) => s.queues[subChatId] ?? EMPTY_QUEUE) - const addToQueue = useMessageQueueStore((s) => s.addToQueue) - const removeFromQueue = useMessageQueueStore((s) => s.removeFromQueue) - const popItemFromQueue = useMessageQueueStore((s) => s.popItem) + const queue = useMessageQueueStore((s) => s.queues[subChatId] ?? EMPTY_QUEUE); + const addToQueue = useMessageQueueStore((s) => s.addToQueue); + const removeFromQueue = useMessageQueueStore((s) => s.removeFromQueue); + const popItemFromQueue = useMessageQueueStore((s) => s.popItem); // Plan approval pending state (for tool approval loading) const [planApprovalPending, setPlanApprovalPending] = useState< Record - >({}) + >({}); // Track chat changes for rename trigger reset - const chatRef = useRef | null>(null) + const chatRef = useRef | null>(null); if (prevSubChatIdRef.current !== subChatId) { - hasTriggeredRenameRef.current = false // Reset on sub-chat change - hasTriggeredAutoGenerateRef.current = false // Reset auto-generate on sub-chat change - prevSubChatIdRef.current = subChatId + hasTriggeredRenameRef.current = false; // Reset on sub-chat change + hasTriggeredAutoGenerateRef.current = false; // Reset auto-generate on sub-chat change + prevSubChatIdRef.current = subChatId; } - chatRef.current = chat + chatRef.current = chat; // Restore draft when subChatId changes (switching between sub-chats) - const prevSubChatIdForDraftRef = useRef(null) + const prevSubChatIdForDraftRef = useRef(null); useEffect(() => { // Restore full draft (text + attachments + text contexts) for new sub-chat const savedDraft = parentChatId ? getSubChatDraftFull(parentChatId, subChatId) - : null + : null; if (savedDraft) { // Restore text if (savedDraft.text) { - editorRef.current?.setValue(savedDraft.text) + editorRef.current?.setValue(savedDraft.text); } else { - editorRef.current?.clear() + editorRef.current?.clear(); } // Restore images if (savedDraft.images.length > 0) { - setImagesFromDraft(savedDraft.images) + setImagesFromDraft(savedDraft.images); } else { - clearAll() + clearAll(); } // Restore files if (savedDraft.files.length > 0) { - setFilesFromDraft(savedDraft.files) + setFilesFromDraft(savedDraft.files); } // Restore text contexts if (savedDraft.textContexts.length > 0) { - setTextContextsFromDraft(savedDraft.textContexts) + setTextContextsFromDraft(savedDraft.textContexts); } else { - clearTextContexts() + clearTextContexts(); } } else if ( prevSubChatIdForDraftRef.current && prevSubChatIdForDraftRef.current !== subChatId ) { // Clear everything when switching to a sub-chat with no draft - editorRef.current?.clear() - clearAll() - clearTextContexts() + editorRef.current?.clear(); + clearAll(); + clearTextContexts(); } - prevSubChatIdForDraftRef.current = subChatId + prevSubChatIdForDraftRef.current = subChatId; }, [ subChatId, parentChatId, @@ -2381,279 +2572,320 @@ const ChatViewInner = memo(function ChatViewInner({ setTextContextsFromDraft, clearAll, clearTextContexts, - ]) + ]); // Use subChatId as stable key to prevent HMR-induced duplicate resume requests // resume: !!streamId to reconnect to active streams (background streaming support) - const { messages, sendMessage, status, stop, regenerate, setMessages } = useChat({ - id: subChatId, - chat, - resume: !!streamId, - experimental_throttle: 50, // Throttle updates to reduce re-renders during streaming - }) + const { messages, sendMessage, status, stop, regenerate, setMessages } = + useChat({ + id: subChatId, + chat, + resume: !!streamId, + experimental_throttle: 50, // Throttle updates to reduce re-renders during streaming + }); // Refs for useChat functions to keep callbacks stable across renders - const sendMessageRef = useRef(sendMessage) - sendMessageRef.current = sendMessage - const stopRef = useRef(stop) - stopRef.current = stop + const sendMessageRef = useRef(sendMessage); + sendMessageRef.current = sendMessage; + const stopRef = useRef(stop); + stopRef.current = stop; - const isStreaming = status === "streaming" || status === "submitted" + const isStreaming = status === "streaming" || status === "submitted"; // Ref for isStreaming to use in callbacks/effects that need fresh value - const isStreamingRef = useRef(isStreaming) - isStreamingRef.current = isStreaming + const isStreamingRef = useRef(isStreaming); + isStreamingRef.current = isStreaming; // Track compacting status from SDK - const compactingSubChats = useAtomValue(compactingSubChatsAtom) - const isCompacting = compactingSubChats.has(subChatId) + const compactingSubChats = useAtomValue(compactingSubChatsAtom); + const isCompacting = compactingSubChats.has(subChatId); // Desktop/fullscreen state for window drag region - const isDesktop = useAtomValue(isDesktopAtom) - const isFullscreen = useAtomValue(isFullscreenAtom) + const isDesktop = useAtomValue(isDesktopAtom); + const isFullscreen = useAtomValue(isFullscreenAtom); // Handler to trigger manual context compaction const handleCompact = useCallback(() => { - if (isStreamingRef.current) return // Can't compact while streaming + if (isStreamingRef.current) return; // Can't compact while streaming sendMessageRef.current({ role: "user", parts: [{ type: "text", text: "/compact" }], - }) - }, []) + }); + }, []); // Handler to stop streaming - memoized to prevent ChatInputArea re-renders const handleStop = useCallback(async () => { // Mark as manually aborted to prevent completion sound - agentChatStore.setManuallyAborted(subChatId, true) - await stopRef.current() - }, [subChatId]) + agentChatStore.setManuallyAborted(subChatId, true); + await stopRef.current(); + }, [subChatId]); // Wrapper for addTextContext that handles TextSelectionSource - const addTextContext = useCallback((text: string, source: TextSelectionSource) => { - if (source.type === "assistant-message") { - addTextContextOriginal(text, source.messageId) - } else if (source.type === "diff") { - addDiffTextContext(text, source.filePath, source.lineNumber, source.lineType) - } else if (source.type === "tool-edit") { - // Tool edit selections are treated as code selections (similar to diff) - addDiffTextContext(text, source.filePath) - } else if (source.type === "plan") { - // Plan selections are treated as code selections (similar to diff) - addDiffTextContext(text, source.planPath) - } else if (source.type === "file-viewer") { - // File viewer selections are treated as code selections - addDiffTextContext(text, source.filePath) - } - }, [addTextContextOriginal, addDiffTextContext]) + const addTextContext = useCallback( + (text: string, source: TextSelectionSource) => { + if (source.type === "assistant-message") { + addTextContextOriginal(text, source.messageId); + } else if (source.type === "diff") { + addDiffTextContext( + text, + source.filePath, + source.lineNumber, + source.lineType, + ); + } else if (source.type === "tool-edit") { + // Tool edit selections are treated as code selections (similar to diff) + addDiffTextContext(text, source.filePath); + } else if (source.type === "plan") { + // Plan selections are treated as code selections (similar to diff) + addDiffTextContext(text, source.planPath); + } else if (source.type === "file-viewer") { + // File viewer selections are treated as code selections + addDiffTextContext(text, source.filePath); + } + }, + [addTextContextOriginal, addDiffTextContext], + ); // Focus handler for text selection popover - focus chat input after adding to context const handleFocusInput = useCallback(() => { - editorRef.current?.focus() - }, []) + editorRef.current?.focus(); + }, []); // Listen for file-viewer "Add to Context" from the custom context menu useEffect(() => { const handler = (e: Event) => { const detail = (e as CustomEvent).detail as { - text: string - source: TextSelectionSource - } + text: string; + source: TextSelectionSource; + }; if (detail.text && detail.source) { - addTextContext(detail.text, detail.source) - editorRef.current?.focus() + addTextContext(detail.text, detail.source); + editorRef.current?.focus(); } - } - window.addEventListener("file-viewer-add-to-context", handler) - return () => window.removeEventListener("file-viewer-add-to-context", handler) - }, [addTextContext]) + }; + window.addEventListener("file-viewer-add-to-context", handler); + return () => + window.removeEventListener("file-viewer-add-to-context", handler); + }, [addTextContext]); // Handler for quick comment trigger from popover - const handleQuickComment = useCallback((text: string, source: TextSelectionSource, rect: DOMRect) => { - setQuickCommentState({ selectedText: text, source, rect }) - }, []) + const handleQuickComment = useCallback( + (text: string, source: TextSelectionSource, rect: DOMRect) => { + setQuickCommentState({ selectedText: text, source, rect }); + }, + [], + ); // Handler for quick comment submission - const handleQuickCommentSubmit = useCallback((comment: string, selectedText: string, source: TextSelectionSource) => { - // Format message with mention token + comment - const preview = selectedText.slice(0, 50).replace(/[:\[\]]/g, "") - const encodedText = utf8ToBase64(selectedText) - - let mentionToken: string - if (source.type === "diff") { - const lineNum = source.lineNumber || 0 - mentionToken = `@[${MENTION_PREFIXES.DIFF}${source.filePath}:${lineNum}:${preview}:${encodedText}]` - } else if (source.type === "tool-edit") { - // Tool edit is treated as code/diff context - mentionToken = `@[${MENTION_PREFIXES.DIFF}${source.filePath}:0:${preview}:${encodedText}]` - } else { - mentionToken = `@[${MENTION_PREFIXES.QUOTE}${preview}:${encodedText}]` - } + const handleQuickCommentSubmit = useCallback( + (comment: string, selectedText: string, source: TextSelectionSource) => { + // Format message with mention token + comment + const preview = selectedText.slice(0, 50).replace(/[:\[\]]/g, ""); + const encodedText = utf8ToBase64(selectedText); + + let mentionToken: string; + if (source.type === "diff") { + const lineNum = source.lineNumber || 0; + mentionToken = `@[${MENTION_PREFIXES.DIFF}${source.filePath}:${lineNum}:${preview}:${encodedText}]`; + } else if (source.type === "tool-edit") { + // Tool edit is treated as code/diff context + mentionToken = `@[${MENTION_PREFIXES.DIFF}${source.filePath}:0:${preview}:${encodedText}]`; + } else { + mentionToken = `@[${MENTION_PREFIXES.QUOTE}${preview}:${encodedText}]`; + } - const message = `${mentionToken} ${comment}` + const message = `${mentionToken} ${comment}`; - // If streaming, add to queue - if (isStreamingRef.current) { - const item = createQueueItem(generateQueueId(), message) - addToQueue(subChatId, item) - toast.success("Reply queued", { description: "Will be sent when current response completes" }) - } else { - // Send directly - sendMessageRef.current({ - role: "user", - parts: [{ type: "text", text: message }], - }) - toast.success("Reply sent") - } + // If streaming, add to queue + if (isStreamingRef.current) { + const item = createQueueItem(generateQueueId(), message); + addToQueue(subChatId, item); + toast.success("Reply queued", { + description: "Will be sent when current response completes", + }); + } else { + // Send directly + sendMessageRef.current({ + role: "user", + parts: [{ type: "text", text: message }], + }); + toast.success("Reply sent"); + } - // Clear state and selection - setQuickCommentState(null) - window.getSelection()?.removeAllRanges() - }, [addToQueue, subChatId]) + // Clear state and selection + setQuickCommentState(null); + window.getSelection()?.removeAllRanges(); + }, + [addToQueue, subChatId], + ); // Handler for quick comment cancel const handleQuickCommentCancel = useCallback(() => { - setQuickCommentState(null) - }, []) + setQuickCommentState(null); + }, []); // Sync loading status to atom for UI indicators // When streaming starts, set loading. When it stops, clear loading. // Unseen changes, sound notification, and sidebar refresh are handled in onFinish callback - const setLoadingSubChats = useSetAtom(loadingSubChatsAtom) + const setLoadingSubChats = useSetAtom(loadingSubChatsAtom); useEffect(() => { - const storedParentChatId = agentChatStore.getParentChatId(subChatId) - if (!storedParentChatId) return + const storedParentChatId = agentChatStore.getParentChatId(subChatId); + if (!storedParentChatId) return; if (isStreaming) { - setLoading(setLoadingSubChats, subChatId, storedParentChatId) + setLoading(setLoadingSubChats, subChatId, storedParentChatId); } else { - clearLoading(setLoadingSubChats, subChatId) + clearLoading(setLoadingSubChats, subChatId); } - }, [isStreaming, subChatId, setLoadingSubChats]) + }, [isStreaming, subChatId, setLoadingSubChats]); // Watch for pending PR message and send it // Only the active tab should consume pending messages to prevent // inactive ChatViewInner instances from stealing the message - const [pendingPrMessage, setPendingPrMessage] = useAtom(pendingPrMessageAtom) + const [pendingPrMessage, setPendingPrMessage] = useAtom(pendingPrMessageAtom); useEffect(() => { if (pendingPrMessage && !isStreaming && isActive) { // Clear the pending message immediately to prevent double-sending - setPendingPrMessage(null) + setPendingPrMessage(null); // Send the message to Claude sendMessage({ role: "user", parts: [{ type: "text", text: pendingPrMessage }], - }) + }); // Reset creating PR state after message is sent - setIsCreatingPr(false) + setIsCreatingPr(false); } - }, [pendingPrMessage, isStreaming, isActive, sendMessage, setPendingPrMessage]) + }, [ + pendingPrMessage, + isStreaming, + isActive, + sendMessage, + setPendingPrMessage, + ]); // Watch for pending Review message and send it const [pendingReviewMessage, setPendingReviewMessage] = useAtom( pendingReviewMessageAtom, - ) + ); useEffect(() => { if (pendingReviewMessage && !isStreaming && isActive) { // Clear the pending message immediately to prevent double-sending - setPendingReviewMessage(null) + setPendingReviewMessage(null); // Send the message to Claude sendMessage({ role: "user", parts: [{ type: "text", text: pendingReviewMessage }], - }) + }); } - }, [pendingReviewMessage, isStreaming, isActive, sendMessage, setPendingReviewMessage]) + }, [ + pendingReviewMessage, + isStreaming, + isActive, + sendMessage, + setPendingReviewMessage, + ]); // Watch for pending conflict resolution message and send it const [pendingConflictMessage, setPendingConflictMessage] = useAtom( pendingConflictResolutionMessageAtom, - ) + ); useEffect(() => { if (pendingConflictMessage && !isStreaming && isActive) { // Clear the pending message immediately to prevent double-sending - setPendingConflictMessage(null) + setPendingConflictMessage(null); // Send the message to Claude sendMessage({ role: "user", parts: [{ type: "text", text: pendingConflictMessage }], - }) + }); } - }, [pendingConflictMessage, isStreaming, isActive, sendMessage, setPendingConflictMessage]) + }, [ + pendingConflictMessage, + isStreaming, + isActive, + sendMessage, + setPendingConflictMessage, + ]); // Handle pending "Build plan" from sidebar (atom - effect is defined after handleApprovePlan) const [pendingBuildPlanSubChatId, setPendingBuildPlanSubChatId] = useAtom( pendingBuildPlanSubChatIdAtom, - ) + ); // Pending user questions from AskUserQuestion tool const [pendingQuestionsMap, setPendingQuestionsMap] = useAtom( pendingUserQuestionsAtom, - ) + ); // Get pending questions for this specific subChat - const pendingQuestions = pendingQuestionsMap.get(subChatId) ?? null + const pendingQuestions = pendingQuestionsMap.get(subChatId) ?? null; // Expired user questions (timed out but still answerable as normal messages) const [expiredQuestionsMap, setExpiredQuestionsMap] = useAtom( expiredUserQuestionsAtom, - ) - const expiredQuestions = expiredQuestionsMap.get(subChatId) ?? null + ); + const expiredQuestions = expiredQuestionsMap.get(subChatId) ?? null; // Unified display questions: prefer pending (live), fall back to expired - const displayQuestions = pendingQuestions ?? expiredQuestions - const isQuestionExpired = !pendingQuestions && !!expiredQuestions + const displayQuestions = pendingQuestions ?? expiredQuestions; + const isQuestionExpired = !pendingQuestions && !!expiredQuestions; // Track whether chat input has content (for custom text with questions) - const [inputHasContent, setInputHasContent] = useState(false) + const [inputHasContent, setInputHasContent] = useState(false); // Memoize the last assistant message to avoid unnecessary recalculations const lastAssistantMessage = useMemo( () => messages.findLast((m) => m.role === "assistant"), [messages], - ) + ); // Pre-compute token data for ChatInputArea to avoid passing unstable messages array // This prevents ChatInputArea from re-rendering on every streaming chunk const messageTokenData = useMemo(() => { - let totalInputTokens = 0 - let totalOutputTokens = 0 - let totalCostUsd = 0 + let totalInputTokens = 0; + let totalOutputTokens = 0; + let totalCostUsd = 0; for (const msg of messages) { if (msg.metadata) { - totalInputTokens += msg.metadata.inputTokens || 0 - totalOutputTokens += msg.metadata.outputTokens || 0 - totalCostUsd += msg.metadata.totalCostUsd || 0 + totalInputTokens += msg.metadata.inputTokens || 0; + totalOutputTokens += msg.metadata.outputTokens || 0; + totalCostUsd += msg.metadata.totalCostUsd || 0; } } - return { totalInputTokens, totalOutputTokens, totalCostUsd, messageCount: messages.length } - }, [messages]) + return { + totalInputTokens, + totalOutputTokens, + totalCostUsd, + messageCount: messages.length, + }; + }, [messages]); // Track previous streaming state to detect stream stop - const prevIsStreamingRef = useRef(isStreaming) + const prevIsStreamingRef = useRef(isStreaming); // Track if we recently stopped streaming (to prevent sync effect from restoring) - const recentlyStoppedStreamRef = useRef(false) + const recentlyStoppedStreamRef = useRef(false); // Clear pending questions when streaming is aborted // This effect runs when isStreaming transitions from true to false useEffect(() => { - const wasStreaming = prevIsStreamingRef.current - prevIsStreamingRef.current = isStreaming + const wasStreaming = prevIsStreamingRef.current; + prevIsStreamingRef.current = isStreaming; // Detect streaming stop transition if (wasStreaming && !isStreaming) { // Mark that we recently stopped streaming - recentlyStoppedStreamRef.current = true + recentlyStoppedStreamRef.current = true; // Clear the flag after a delay const flagTimeout = setTimeout(() => { - recentlyStoppedStreamRef.current = false - }, 500) + recentlyStoppedStreamRef.current = false; + }, 500); // Streaming just stopped - if there's a pending question for this chat, // clear it after a brief delay (backend already handled the abort) @@ -2662,21 +2894,21 @@ const ChatViewInner = memo(function ChatViewInner({ // Re-check if still showing the same question (might have been cleared by other means) setPendingQuestionsMap((current) => { if (current.has(subChatId)) { - const newMap = new Map(current) - newMap.delete(subChatId) - return newMap + const newMap = new Map(current); + newMap.delete(subChatId); + return newMap; } - return current - }) - }, 150) // Small delay to allow for race conditions with transport chunks + return current; + }); + }, 150); // Small delay to allow for race conditions with transport chunks return () => { - clearTimeout(timeout) - clearTimeout(flagTimeout) - } + clearTimeout(timeout); + clearTimeout(flagTimeout); + }; } - return () => clearTimeout(flagTimeout) + return () => clearTimeout(flagTimeout); } - }, [isStreaming, subChatId, pendingQuestions, setPendingQuestionsMap]) + }, [isStreaming, subChatId, pendingQuestions, setPendingQuestionsMap]); // Sync pending questions with messages state // This handles: 1) restoring on chat switch, 2) clearing when question is answered/timed out @@ -2689,20 +2921,19 @@ const ChatViewInner = memo(function ChatViewInner({ part.state !== "output-error" && part.state !== "result" && part.input?.questions, - ) as any | undefined - + ) as any | undefined; // Helper to clear pending question for this subChat const clearPendingQuestion = () => { setPendingQuestionsMap((current) => { if (current.has(subChatId)) { - const newMap = new Map(current) - newMap.delete(subChatId) - return newMap + const newMap = new Map(current); + newMap.delete(subChatId); + return newMap; } - return current - }) - } + return current; + }); + }; // If streaming and we already have a pending question for this chat, keep it // (transport will manage it via chunks) @@ -2717,12 +2948,12 @@ const ChatViewInner = memo(function ChatViewInner({ (part.state === "output-available" || part.state === "output-error" || part.state === "result"), - ) + ); if (answeredPart) { - clearPendingQuestion() + clearPendingQuestion(); } } - return + return; } // Not streaming - DON'T restore pending questions from messages @@ -2735,35 +2966,41 @@ const ChatViewInner = memo(function ChatViewInner({ if (pendingQuestionPart) { // Don't restore - if there's an existing pending question for this chat, clear it if (pendingQuestions) { - clearPendingQuestion() + clearPendingQuestion(); } } else { // No pending question - clear if belongs to this sub-chat if (pendingQuestions) { - clearPendingQuestion() + clearPendingQuestion(); } } - }, [subChatId, lastAssistantMessage, isStreaming, pendingQuestions, setPendingQuestionsMap]) + }, [ + subChatId, + lastAssistantMessage, + isStreaming, + pendingQuestions, + setPendingQuestionsMap, + ]); // Helper to clear pending and expired questions for this subChat (used in callbacks) const clearPendingQuestionCallback = useCallback(() => { setPendingQuestionsMap((current) => { if (current.has(subChatId)) { - const newMap = new Map(current) - newMap.delete(subChatId) - return newMap + const newMap = new Map(current); + newMap.delete(subChatId); + return newMap; } - return current - }) + return current; + }); setExpiredQuestionsMap((current) => { if (current.has(subChatId)) { - const newMap = new Map(current) - newMap.delete(subChatId) - return newMap + const newMap = new Map(current); + newMap.delete(subChatId); + return newMap; } - return current - }) - }, [subChatId, setPendingQuestionsMap, setExpiredQuestionsMap]) + return current; + }); + }, [subChatId, setPendingQuestionsMap, setExpiredQuestionsMap]); // Shared helpers for question answer handlers const formatAnswersAsText = useCallback( @@ -2772,60 +3009,66 @@ const ChatViewInner = memo(function ChatViewInner({ .map(([question, answer]) => `${question}: ${answer}`) .join("\n"), [], - ) + ); const clearInputAndDraft = useCallback(() => { - editorRef.current?.clear() + editorRef.current?.clear(); if (parentChatId) { - clearSubChatDraft(parentChatId, subChatId) + clearSubChatDraft(parentChatId, subChatId); } - }, [parentChatId, subChatId]) + }, [parentChatId, subChatId]); const sendUserMessage = useCallback(async (text: string) => { - shouldAutoScrollRef.current = true + shouldAutoScrollRef.current = true; await sendMessageRef.current({ role: "user", parts: [{ type: "text", text }], - }) - }, []) + }); + }, []); // Handle answering questions const handleQuestionsAnswer = useCallback( async (answers: Record) => { - if (!displayQuestions) return + if (!displayQuestions) return; if (isQuestionExpired) { // Question timed out - send answers as a normal user message - clearPendingQuestionCallback() - await sendUserMessage(formatAnswersAsText(answers)) + clearPendingQuestionCallback(); + await sendUserMessage(formatAnswersAsText(answers)); } else { // Question is still live - use tool approval path await trpcClient.claude.respondToolApproval.mutate({ toolUseId: displayQuestions.toolUseId, approved: true, updatedInput: { questions: displayQuestions.questions, answers }, - }) - clearPendingQuestionCallback() + }); + clearPendingQuestionCallback(); } }, - [displayQuestions, isQuestionExpired, clearPendingQuestionCallback, sendUserMessage, formatAnswersAsText], - ) + [ + displayQuestions, + isQuestionExpired, + clearPendingQuestionCallback, + sendUserMessage, + formatAnswersAsText, + ], + ); // Handle skipping questions const handleQuestionsSkip = useCallback(async () => { - if (!displayQuestions) return + if (!displayQuestions) return; if (isQuestionExpired) { // Expired question - just clear the UI, no backend call needed - clearPendingQuestionCallback() - return + clearPendingQuestionCallback(); + return; } - const toolUseId = displayQuestions.toolUseId + const toolUseId = displayQuestions.toolUseId; // Clear UI immediately - don't wait for backend // This ensures dialog closes even if stream was already aborted - clearPendingQuestionCallback() + clearPendingQuestionCallback(); // Try to notify backend (may fail if already aborted - that's ok) try { @@ -2833,96 +3076,99 @@ const ChatViewInner = memo(function ChatViewInner({ toolUseId, approved: false, message: QUESTIONS_SKIPPED_MESSAGE, - }) + }); } catch { // Stream likely already aborted - ignore } - }, [displayQuestions, isQuestionExpired, clearPendingQuestionCallback]) + }, [displayQuestions, isQuestionExpired, clearPendingQuestionCallback]); // Ref to prevent double submit of question answer - const isSubmittingQuestionAnswerRef = useRef(false) + const isSubmittingQuestionAnswerRef = useRef(false); // Handle answering questions with custom text from input (called on Enter in input) - const handleSubmitWithQuestionAnswer = useCallback( - async () => { - if (!displayQuestions) return - if (isSubmittingQuestionAnswerRef.current) return - isSubmittingQuestionAnswerRef.current = true - - try { - // 1. Get custom text from input - const customText = editorRef.current?.getValue()?.trim() || "" - if (!customText) { - isSubmittingQuestionAnswerRef.current = false - return - } + const handleSubmitWithQuestionAnswer = useCallback(async () => { + if (!displayQuestions) return; + if (isSubmittingQuestionAnswerRef.current) return; + isSubmittingQuestionAnswerRef.current = true; - // 2. Get already selected answers from question component - const selectedAnswers = questionRef.current?.getAnswers() || {} - const formattedAnswers: Record = { ...selectedAnswers } - - // 3. Add custom text to the last question as "Other" - const lastQuestion = - displayQuestions.questions[displayQuestions.questions.length - 1] - if (lastQuestion) { - const existingAnswer = formattedAnswers[lastQuestion.question] - if (existingAnswer) { - // Append to existing answer - formattedAnswers[lastQuestion.question] = `${existingAnswer}, Other: ${customText}` - } else { - formattedAnswers[lastQuestion.question] = `Other: ${customText}` - } - } + try { + // 1. Get custom text from input + const customText = editorRef.current?.getValue()?.trim() || ""; + if (!customText) { + isSubmittingQuestionAnswerRef.current = false; + return; + } - if (isQuestionExpired) { - // Expired: send user's custom text as-is (don't format) - clearPendingQuestionCallback() - clearInputAndDraft() - // await sendUserMessage(formatAnswersAsText(formattedAnswers)) - await sendUserMessage(customText) + // 2. Get already selected answers from question component + const selectedAnswers = questionRef.current?.getAnswers() || {}; + const formattedAnswers: Record = { ...selectedAnswers }; + + // 3. Add custom text to the last question as "Other" + const lastQuestion = + displayQuestions.questions[displayQuestions.questions.length - 1]; + if (lastQuestion) { + const existingAnswer = formattedAnswers[lastQuestion.question]; + if (existingAnswer) { + // Append to existing answer + formattedAnswers[lastQuestion.question] = + `${existingAnswer}, Other: ${customText}`; } else { - // Live: use existing tool approval flow - await trpcClient.claude.respondToolApproval.mutate({ - toolUseId: displayQuestions.toolUseId, - approved: true, - updatedInput: { - questions: displayQuestions.questions, - answers: formattedAnswers, - }, - }) - clearPendingQuestionCallback() - - // Stop stream if currently streaming - if (isStreamingRef.current) { - agentChatStore.setManuallyAborted(subChatId, true) - await stopRef.current() - await new Promise((resolve) => setTimeout(resolve, 100)) - } + formattedAnswers[lastQuestion.question] = `Other: ${customText}`; + } + } - clearInputAndDraft() - await sendUserMessage(customText) + if (isQuestionExpired) { + // Expired: send user's custom text as-is (don't format) + clearPendingQuestionCallback(); + clearInputAndDraft(); + // await sendUserMessage(formatAnswersAsText(formattedAnswers)) + await sendUserMessage(customText); + } else { + // Live: use existing tool approval flow + await trpcClient.claude.respondToolApproval.mutate({ + toolUseId: displayQuestions.toolUseId, + approved: true, + updatedInput: { + questions: displayQuestions.questions, + answers: formattedAnswers, + }, + }); + clearPendingQuestionCallback(); + + // Stop stream if currently streaming + if (isStreamingRef.current) { + agentChatStore.setManuallyAborted(subChatId, true); + await stopRef.current(); + await new Promise((resolve) => setTimeout(resolve, 100)); } - } finally { - isSubmittingQuestionAnswerRef.current = false + + clearInputAndDraft(); + await sendUserMessage(customText); } - }, - [displayQuestions, isQuestionExpired, clearPendingQuestionCallback, clearInputAndDraft, sendUserMessage, formatAnswersAsText, subChatId], - ) + } finally { + isSubmittingQuestionAnswerRef.current = false; + } + }, [ + displayQuestions, + isQuestionExpired, + clearPendingQuestionCallback, + clearInputAndDraft, + sendUserMessage, + formatAnswersAsText, + subChatId, + ]); // Memoize the callback to prevent ChatInputArea re-renders // Only provide callback when there's a pending or expired question for this subChat const submitWithQuestionAnswerCallback = useMemo( - () => - displayQuestions - ? handleSubmitWithQuestionAnswer - : undefined, + () => (displayQuestions ? handleSubmitWithQuestionAnswer : undefined), [displayQuestions, handleSubmitWithQuestionAnswer], - ) + ); // Watch for pending auth retry message (after successful OAuth flow) const [pendingAuthRetry, setPendingAuthRetry] = useAtom( pendingAuthRetryMessageAtom, - ) + ); useEffect(() => { // Only retry when: @@ -2937,12 +3183,12 @@ const ChatViewInner = memo(function ChatViewInner({ !isStreaming ) { // Clear the pending message immediately to prevent double-sending - setPendingAuthRetry(null) + setPendingAuthRetry(null); // Build message parts const parts: Array< { type: "text"; text: string } | { type: "data-image"; data: any } - > = [{ type: "text", text: pendingAuthRetry.prompt }] + > = [{ type: "text", text: pendingAuthRetry.prompt }]; // Add images if present if (pendingAuthRetry.images && pendingAuthRetry.images.length > 0) { @@ -2954,7 +3200,7 @@ const ChatViewInner = memo(function ChatViewInner({ mediaType: img.mediaType, filename: img.filename, }, - }) + }); } } @@ -2962,7 +3208,7 @@ const ChatViewInner = memo(function ChatViewInner({ sendMessage({ role: "user", parts, - }) + }); } }, [ pendingAuthRetry, @@ -2970,119 +3216,125 @@ const ChatViewInner = memo(function ChatViewInner({ sendMessage, setPendingAuthRetry, subChatId, - ]) + ]); const handlePlanApproval = useCallback( async (toolUseId: string, approved: boolean) => { - if (!toolUseId) return - setPlanApprovalPending((prev) => ({ ...prev, [toolUseId]: true })) + if (!toolUseId) return; + setPlanApprovalPending((prev) => ({ ...prev, [toolUseId]: true })); try { await trpcClient.claude.respondToolApproval.mutate({ toolUseId, approved, - }) + }); } catch (error) { - console.error("[plan-approval] Failed to respond:", error) - toast.error("Failed to send plan approval. Please try again.") + console.error("[plan-approval] Failed to respond:", error); + toast.error("Failed to send plan approval. Please try again."); } finally { setPlanApprovalPending((prev) => { - const next = { ...prev } - delete next[toolUseId] - return next - }) + const next = { ...prev }; + delete next[toolUseId]; + return next; + }); } }, [], - ) + ); // Handle plan approval - sends "Build plan" message and switches to agent mode const handleApprovePlan = useCallback(() => { // Update store mode synchronously BEFORE sending (transport reads from store) - useAgentSubChatStore.getState().updateSubChatMode(subChatId, "agent") + useAgentSubChatStore.getState().updateSubChatMode(subChatId, "agent"); // Sync mode to database for sidebar indicator (getPendingPlanApprovals) if (!subChatId.startsWith("temp-")) { - updateSubChatModeMutation.mutate({ subChatId, mode: "agent" }) + updateSubChatModeMutation.mutate({ subChatId, mode: "agent" }); } // Update atomFamily state (for UI) - this also syncs to store via effect - setSubChatMode("agent") + setSubChatMode("agent"); // Enable auto-scroll and immediately scroll to bottom - shouldAutoScrollRef.current = true - scrollToBottom() + shouldAutoScrollRef.current = true; + scrollToBottom(); // Send "Build plan" message (now in agent mode) sendMessageRef.current({ role: "user", parts: [{ type: "text", text: "Implement plan" }], - }) - }, [subChatId, setSubChatMode, scrollToBottom, updateSubChatModeMutation]) + }); + }, [subChatId, setSubChatMode, scrollToBottom, updateSubChatModeMutation]); // Handle pending "Build plan" from sidebar useEffect(() => { // Only trigger if this is the target sub-chat and we're active if (pendingBuildPlanSubChatId === subChatId && isActive) { - setPendingBuildPlanSubChatId(null) // Clear immediately to prevent double-trigger - handleApprovePlan() + setPendingBuildPlanSubChatId(null); // Clear immediately to prevent double-trigger + handleApprovePlan(); } - }, [pendingBuildPlanSubChatId, subChatId, isActive, setPendingBuildPlanSubChatId, handleApprovePlan]) + }, [ + pendingBuildPlanSubChatId, + subChatId, + isActive, + setPendingBuildPlanSubChatId, + handleApprovePlan, + ]); // Detect PR URLs in assistant messages and store them // Initialize with existing PR URL to prevent duplicate toast on re-mount - const detectedPrUrlRef = useRef(existingPrUrl ?? null) + const detectedPrUrlRef = useRef(existingPrUrl ?? null); useEffect(() => { // Only check after streaming ends - if (isStreaming) return + if (isStreaming) return; // Look through messages for PR URLs for (const msg of messages) { - if (msg.role !== "assistant") continue + if (msg.role !== "assistant") continue; // Extract text content from message const textContent = msg.parts ?.filter((p: any) => p.type === "text") .map((p: any) => p.text) - .join(" ") || "" + .join(" ") || ""; // Match GitHub PR URL pattern const prUrlMatch = textContent.match( /https:\/\/github\.com\/[^/\s]+\/[^/\s]+\/pull\/(\d+)/, - ) + ); if (prUrlMatch && prUrlMatch[0] !== detectedPrUrlRef.current) { - const prUrl = prUrlMatch[0] - const prNumber = parseInt(prUrlMatch[1], 10) + const prUrl = prUrlMatch[0]; + const prNumber = parseInt(prUrlMatch[1], 10); // Store to prevent duplicate calls - detectedPrUrlRef.current = prUrl + detectedPrUrlRef.current = prUrl; // Update database trpcClient.chats.updatePrInfo .mutate({ chatId: parentChatId, prUrl, prNumber }) .then(() => { // Invalidate the agentChat query to refetch with new PR info - utils.agents.getAgentChat.invalidate({ chatId: parentChatId }) - }) + utils.agents.getAgentChat.invalidate({ chatId: parentChatId }); + }); - break // Only process first PR URL found + break; // Only process first PR URL found } } - }, [messages, isStreaming, parentChatId]) + }, [messages, isStreaming, parentChatId]); // Track plan Edit completions to trigger sidebar refetch const triggerPlanEditRefetch = useSetAtom( - useMemo(() => planEditRefetchTriggerAtomFamily(subChatId), [subChatId]) - ) - const lastPlanEditCountRef = useRef(0) + useMemo(() => planEditRefetchTriggerAtomFamily(subChatId), [subChatId]), + ); + const lastPlanEditCountRef = useRef(0); useEffect(() => { // Count completed plan Edits - let completedPlanEdits = 0 + let completedPlanEdits = 0; for (const msg of messages) { - if (msg.role !== "assistant" || !(msg as any).parts) continue + if (msg.role !== "assistant" || !(msg as any).parts) continue; for (const part of (msg as any).parts as any[]) { if ( part.type === "tool-Edit" && @@ -3090,68 +3342,64 @@ const ChatViewInner = memo(function ChatViewInner({ part.state !== "pending" && isPlanFile(part.input?.file_path || "") ) { - completedPlanEdits++ + completedPlanEdits++; } } } // Trigger refetch if count increased (new Edit completed) if (completedPlanEdits > lastPlanEditCountRef.current) { - lastPlanEditCountRef.current = completedPlanEdits - triggerPlanEditRefetch() + lastPlanEditCountRef.current = completedPlanEdits; + triggerPlanEditRefetch(); } - }, [messages, triggerPlanEditRefetch]) + }, [messages, triggerPlanEditRefetch]); - const { changedFiles: changedFilesForSubChat, recomputeChangedFiles } = useChangedFilesTracking( - messages, - subChatId, - isStreaming, - parentChatId, - ) + const { changedFiles: changedFilesForSubChat, recomputeChangedFiles } = + useChangedFilesTracking(messages, subChatId, isStreaming, parentChatId); // Rollback handler - truncates messages to the clicked assistant message and restores git state // The SDK UUID from the last assistant message will be used for resumeSessionAt on next send const handleRollback = useCallback( async (assistantMsg: (typeof messages)[0]) => { if (isRollingBack) { - toast.error("Rollback already in progress") - return + toast.error("Rollback already in progress"); + return; } if (isStreaming) { - toast.error("Cannot rollback while streaming") - return + toast.error("Cannot rollback while streaming"); + return; } - const sdkUuid = (assistantMsg.metadata as any)?.sdkMessageUuid + const sdkUuid = (assistantMsg.metadata as any)?.sdkMessageUuid; if (!sdkUuid) { - toast.error("Cannot rollback: message has no SDK UUID") - return + toast.error("Cannot rollback: message has no SDK UUID"); + return; } - setIsRollingBack(true) + setIsRollingBack(true); try { // Single call handles both message truncation and git rollback const result = await trpcClient.chats.rollbackToMessage.mutate({ subChatId, sdkMessageUuid: sdkUuid, - }) + }); if (!result.success) { - toast.error(`Failed to rollback: ${result.error}`) - setIsRollingBack(false) - return + toast.error(`Failed to rollback: ${result.error}`); + setIsRollingBack(false); + return; } // Update local state with truncated messages from server - setMessages(result.messages) - recomputeChangedFiles(result.messages) - refreshDiff?.() + setMessages(result.messages); + recomputeChangedFiles(result.messages); + refreshDiff?.(); } catch (error) { - console.error("[handleRollback] Error:", error) - toast.error("Failed to rollback") + console.error("[handleRollback] Error:", error); + toast.error("Failed to rollback"); } finally { - setIsRollingBack(false) + setIsRollingBack(false); } }, [ @@ -3162,28 +3410,28 @@ const ChatViewInner = memo(function ChatViewInner({ recomputeChangedFiles, refreshDiff, ], - ) + ); // Expose rollback handler/state via atoms for message action bar - const setRollbackHandler = useSetAtom(rollbackHandlerAtom) + const setRollbackHandler = useSetAtom(rollbackHandlerAtom); useEffect(() => { - setRollbackHandler(() => handleRollback) - return () => setRollbackHandler(null) - }, [handleRollback, setRollbackHandler]) + setRollbackHandler(() => handleRollback); + return () => setRollbackHandler(null); + }, [handleRollback, setRollbackHandler]); - const setIsRollingBackAtom = useSetAtom(isRollingBackAtom) + const setIsRollingBackAtom = useSetAtom(isRollingBackAtom); useEffect(() => { - setIsRollingBackAtom(isRollingBack) - }, [isRollingBack, setIsRollingBackAtom]) + setIsRollingBackAtom(isRollingBack); + }, [isRollingBack, setIsRollingBackAtom]); // ESC, Ctrl+C and Cmd+Shift+Backspace handler for stopping stream useEffect(() => { // Skip keyboard handlers for inactive tabs (keep-alive) - if (!isActive) return + if (!isActive) return; const handleKeyDown = async (e: KeyboardEvent) => { - let shouldStop = false - let shouldSkipQuestions = false + let shouldStop = false; + let shouldSkipQuestions = false; // Check for Escape key without modifiers (works even from input fields, like terminal Ctrl+C) // Ignore if Cmd/Ctrl is pressed (reserved for Cmd+Esc to focus input) @@ -3195,40 +3443,40 @@ const ChatViewInner = memo(function ChatViewInner({ !e.altKey && isStreaming ) { - const target = e.target as HTMLElement + const target = e.target as HTMLElement; // Allow ESC to propagate if it originated from a modal/dialog/dropdown const isInsideOverlay = target.closest( '[role="dialog"], [role="alertdialog"], [role="menu"], [role="listbox"], [data-radix-popper-content-wrapper], [data-state="open"]', - ) + ); // Also check if any dialog/modal is open anywhere in the document (not just at event target) // This prevents stopping stream when settings dialog is open but not focused const hasOpenDialog = document.querySelector( '[role="dialog"][aria-modal="true"], [data-modal="agents-settings"]', - ) + ); if (!isInsideOverlay && !hasOpenDialog) { // If there are pending/expired questions for this chat, skip/dismiss them instead of stopping stream if (displayQuestions) { - shouldSkipQuestions = true + shouldSkipQuestions = true; } else { - shouldStop = true + shouldStop = true; } } } // Check for Ctrl+C (only Ctrl, not Cmd on Mac) if (e.ctrlKey && !e.metaKey && e.code === "KeyC") { - if (!isStreaming) return + if (!isStreaming) return; - const selection = window.getSelection() - const hasSelection = selection && selection.toString().length > 0 + const selection = window.getSelection(); + const hasSelection = selection && selection.toString().length > 0; // If there's a text selection, let browser handle copy - if (hasSelection) return + if (hasSelection) return; - shouldStop = true + shouldStop = true; } // Check for Cmd+Shift+Backspace (Mac) or Ctrl+Shift+Backspace (Windows/Linux) @@ -3238,29 +3486,36 @@ const ChatViewInner = memo(function ChatViewInner({ e.key === "Backspace" && isStreaming ) { - shouldStop = true + shouldStop = true; } if (shouldSkipQuestions) { - e.preventDefault() - await handleQuestionsSkip() + e.preventDefault(); + await handleQuestionsSkip(); } else if (shouldStop) { - e.preventDefault() + e.preventDefault(); // Mark as manually aborted to prevent completion sound - agentChatStore.setManuallyAborted(subChatId, true) - await stop() + agentChatStore.setManuallyAborted(subChatId, true); + await stop(); } - } + }; - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) - }, [isActive, isStreaming, stop, subChatId, displayQuestions, handleQuestionsSkip]) + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [ + isActive, + isStreaming, + stop, + subChatId, + displayQuestions, + handleQuestionsSkip, + ]); // Keyboard shortcut: Enter to focus input when not already focused - useFocusInputOnEnter(editorRef) + useFocusInputOnEnter(editorRef); // Keyboard shortcut: Cmd+Esc to toggle focus/blur (without stopping generation) - useToggleFocusOnCmdEsc(editorRef) + useToggleFocusOnCmdEsc(editorRef); // Auto-trigger AI response when we have initial message but no response yet // Also trigger auto-rename for initial sub-chat with pre-populated message @@ -3272,19 +3527,19 @@ const ChatViewInner = memo(function ChatViewInner({ !streamId && !hasTriggeredAutoGenerateRef.current ) { - hasTriggeredAutoGenerateRef.current = true + hasTriggeredAutoGenerateRef.current = true; // Trigger rename for pre-populated initial message (from createAgentChat) if (!hasTriggeredRenameRef.current && isFirstSubChat) { - const firstMsg = messages[0] + const firstMsg = messages[0]; if (firstMsg?.role === "user") { - const textPart = firstMsg.parts?.find((p: any) => p.type === "text") + const textPart = firstMsg.parts?.find((p: any) => p.type === "text"); if (textPart && "text" in textPart) { - hasTriggeredRenameRef.current = true - onAutoRename(textPart.text, subChatId) + hasTriggeredRenameRef.current = true; + onAutoRename(textPart.text, subChatId); } } } - regenerate() + regenerate(); } }, [ status, @@ -3294,228 +3549,228 @@ const ChatViewInner = memo(function ChatViewInner({ onAutoRename, streamId, subChatId, - ]) + ]); // Ref to track if initial scroll has been set for this sub-chat - const scrollInitializedRef = useRef(false) + const scrollInitializedRef = useRef(false); // Track if this tab has been initialized (for keep-alive) - const hasInitializedRef = useRef(false) + const hasInitializedRef = useRef(false); // Initialize scroll position on mount (only once per tab with keep-alive) // Strategy: wait for content to stabilize, then scroll to bottom ONCE // No jumping around - just wait and scroll when ready useLayoutEffect(() => { // Skip if not active (keep-alive: hidden tabs don't need scroll init) - if (!isActive) return + if (!isActive) return; - const container = chatContainerRef.current - if (!container) return + const container = chatContainerRef.current; + if (!container) return; // With keep-alive, only initialize once per tab mount - if (hasInitializedRef.current) return - hasInitializedRef.current = true + if (hasInitializedRef.current) return; + hasInitializedRef.current = true; // Reset on sub-chat change - scrollInitializedRef.current = false - isInitializingScrollRef.current = true + scrollInitializedRef.current = false; + isInitializingScrollRef.current = true; // IMMEDIATE scroll to bottom - no waiting - container.scrollTop = container.scrollHeight - shouldAutoScrollRef.current = true + container.scrollTop = container.scrollHeight; + shouldAutoScrollRef.current = true; // Mark as initialized IMMEDIATELY - scrollInitializedRef.current = true - isInitializingScrollRef.current = false + scrollInitializedRef.current = true; + isInitializingScrollRef.current = false; // MutationObserver for async content (images, code blocks loading after initial render) const observer = new MutationObserver((mutations) => { // Skip if not active (keep-alive: don't scroll hidden tabs) - if (!isActive) return - if (!shouldAutoScrollRef.current) return + if (!isActive) return; + if (!shouldAutoScrollRef.current) return; // Check if content was added const hasAddedContent = mutations.some( - (m) => m.type === "childList" && m.addedNodes.length > 0 - ) + (m) => m.type === "childList" && m.addedNodes.length > 0, + ); if (hasAddedContent) { requestAnimationFrame(() => { - isAutoScrollingRef.current = true - container.scrollTop = container.scrollHeight + isAutoScrollingRef.current = true; + container.scrollTop = container.scrollHeight; requestAnimationFrame(() => { - isAutoScrollingRef.current = false - }) - }) + isAutoScrollingRef.current = false; + }); + }); } - }) + }); - observer.observe(container, { childList: true, subtree: true }) + observer.observe(container, { childList: true, subtree: true }); return () => { - observer.disconnect() - } + observer.disconnect(); + }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [subChatId, isActive]) + }, [subChatId, isActive]); // Attach scroll listener (separate effect) useEffect(() => { - const container = chatContainerRef.current - if (!container) return + const container = chatContainerRef.current; + if (!container) return; - container.addEventListener("scroll", handleScroll, { passive: true }) + container.addEventListener("scroll", handleScroll, { passive: true }); return () => { - container.removeEventListener("scroll", handleScroll) - } - }, [handleScroll]) + container.removeEventListener("scroll", handleScroll); + }; + }, [handleScroll]); // Auto scroll to bottom when messages change during streaming // Only kicks in after content fills the viewport (overflow behavior) useEffect(() => { // Skip if not active (keep-alive: don't scroll hidden tabs) - if (!isActive) return + if (!isActive) return; // Skip if scroll not yet initialized - if (!scrollInitializedRef.current) return + if (!scrollInitializedRef.current) return; // Auto-scroll during streaming if user hasn't scrolled up if (shouldAutoScrollRef.current && status === "streaming") { - const container = chatContainerRef.current + const container = chatContainerRef.current; if (container) { // Always scroll during streaming if auto-scroll is enabled // (user can disable by scrolling up) requestAnimationFrame(() => { - isAutoScrollingRef.current = true - container.scrollTop = container.scrollHeight + isAutoScrollingRef.current = true; + container.scrollTop = container.scrollHeight; requestAnimationFrame(() => { - isAutoScrollingRef.current = false - }) - }) + isAutoScrollingRef.current = false; + }); + }); } } - }, [isActive, messages, status, subChatId]) + }, [isActive, messages, status, subChatId]); // Auto-focus input when switching to this chat (any sub-chat change) // Skip on mobile to prevent keyboard from opening automatically useEffect(() => { // Skip if not active (keep-alive: don't focus hidden tabs) - if (!isActive) return - if (isMobile) return // Don't autofocus on mobile + if (!isActive) return; + if (isMobile) return; // Don't autofocus on mobile // Use requestAnimationFrame to ensure DOM is ready after render requestAnimationFrame(() => { - editorRef.current?.focus() - }) - }, [isActive, subChatId, isMobile]) + editorRef.current?.focus(); + }); + }, [isActive, subChatId, isMobile]); // Refs for handleSend to avoid recreating callback on every messages change - const messagesLengthRef = useRef(messages.length) - messagesLengthRef.current = messages.length - const subChatModeRef = useRef(subChatMode) - subChatModeRef.current = subChatMode - const imagesRef = useRef(images) - imagesRef.current = images - const filesRef = useRef(files) - filesRef.current = files + const messagesLengthRef = useRef(messages.length); + messagesLengthRef.current = messages.length; + const subChatModeRef = useRef(subChatMode); + subChatModeRef.current = subChatMode; + const imagesRef = useRef(images); + imagesRef.current = images; + const filesRef = useRef(files); + filesRef.current = files; const handleSend = useCallback(async () => { // Block sending while sandbox is still being set up if (sandboxSetupStatus !== "ready") { - return + return; } // Clear any expired questions when user sends a new message setExpiredQuestionsMap((current) => { if (current.has(subChatId)) { - const newMap = new Map(current) - newMap.delete(subChatId) - return newMap + const newMap = new Map(current); + newMap.delete(subChatId); + return newMap; } - return current - }) + return current; + }); // Get value from uncontrolled editor - const inputValue = editorRef.current?.getValue() || "" - const hasText = inputValue.trim().length > 0 - const currentImages = imagesRef.current - const currentFiles = filesRef.current - const currentTextContexts = textContextsRef.current - const currentPastedTexts = pastedTextsRef.current + const inputValue = editorRef.current?.getValue() || ""; + const hasText = inputValue.trim().length > 0; + const currentImages = imagesRef.current; + const currentFiles = filesRef.current; + const currentTextContexts = textContextsRef.current; + const currentPastedTexts = pastedTextsRef.current; const hasImages = - currentImages.filter((img) => !img.isLoading && img.url).length > 0 - const hasTextContexts = currentTextContexts.length > 0 - const hasPastedTexts = currentPastedTexts.length > 0 + currentImages.filter((img) => !img.isLoading && img.url).length > 0; + const hasTextContexts = currentTextContexts.length > 0; + const hasPastedTexts = currentPastedTexts.length > 0; - if (!hasText && !hasImages && !hasTextContexts && !hasPastedTexts) return + if (!hasText && !hasImages && !hasTextContexts && !hasPastedTexts) return; // If streaming, add to queue instead of sending directly if (isStreamingRef.current) { const queuedImages = currentImages .filter((img) => !img.isLoading && img.url) - .map(toQueuedImage) + .map(toQueuedImage); const queuedFiles = currentFiles .filter((f) => !f.isLoading && f.url) - .map(toQueuedFile) - const queuedTextContexts = currentTextContexts.map(toQueuedTextContext) + .map(toQueuedFile); + const queuedTextContexts = currentTextContexts.map(toQueuedTextContext); const item = createQueueItem( generateQueueId(), inputValue.trim(), queuedImages.length > 0 ? queuedImages : undefined, queuedFiles.length > 0 ? queuedFiles : undefined, - queuedTextContexts.length > 0 ? queuedTextContexts : undefined - ) - addToQueue(subChatId, item) + queuedTextContexts.length > 0 ? queuedTextContexts : undefined, + ); + addToQueue(subChatId, item); // Clear input and attachments - editorRef.current?.clear() + editorRef.current?.clear(); if (parentChatId) { - clearSubChatDraft(parentChatId, subChatId) + clearSubChatDraft(parentChatId, subChatId); } - clearAll() - clearTextContexts() - return + clearAll(); + clearTextContexts(); + return; } // Auto-restore archived workspace when sending a message if (isArchived && onRestoreWorkspace) { - onRestoreWorkspace() + onRestoreWorkspace(); } - const text = inputValue.trim() + const text = inputValue.trim(); // Expand custom slash commands with arguments (e.g. "/Apex my argument") // This mirrors the logic in new-chat-form.tsx - let finalText = text - const slashMatch = text.match(/^\/(\S+)\s*(.*)$/s) + let finalText = text; + const slashMatch = text.match(/^\/(\S+)\s*(.*)$/s); if (slashMatch) { - const [, commandName, args] = slashMatch + const [, commandName, args] = slashMatch; const builtinNames = new Set( BUILTIN_SLASH_COMMANDS.map((cmd) => cmd.name), - ) + ); if (!builtinNames.has(commandName)) { try { const commands = await trpcClient.commands.list.query({ projectPath, - }) + }); const cmd = commands.find( (c) => c.name.toLowerCase() === commandName.toLowerCase(), - ) + ); if (cmd) { const { content } = await trpcClient.commands.getContent.query({ path: cmd.path, - }) - finalText = content.replace(/\$ARGUMENTS/g, args.trim()) + }); + finalText = content.replace(/\$ARGUMENTS/g, args.trim()); } } catch (error) { - console.error("Failed to expand custom slash command:", error) + console.error("Failed to expand custom slash command:", error); } } } // Clear editor and draft from localStorage - editorRef.current?.clear() + editorRef.current?.clear(); if (parentChatId) { - clearSubChatDraft(parentChatId, subChatId) + clearSubChatDraft(parentChatId, subChatId); } // Track message sent @@ -3523,12 +3778,12 @@ const ChatViewInner = memo(function ChatViewInner({ workspaceId: subChatId, messageLength: finalText.length, mode: subChatModeRef.current, - }) + }); // Trigger auto-rename on first message in a new sub-chat if (messagesLengthRef.current === 0 && !hasTriggeredRenameRef.current) { - hasTriggeredRenameRef.current = true - onAutoRename(finalText || "Image message", subChatId) + hasTriggeredRenameRef.current = true; + onAutoRename(finalText || "Image message", subChatId); } // Build message parts: images first, then files, then text @@ -3556,39 +3811,45 @@ const ChatViewInner = memo(function ChatViewInner({ size: f.size, }, })), - ] + ]; // Add text contexts as mention tokens - const currentDiffTextContexts = diffTextContextsRef.current - let mentionPrefix = "" + const currentDiffTextContexts = diffTextContextsRef.current; + let mentionPrefix = ""; - if (currentTextContexts.length > 0 || currentDiffTextContexts.length > 0 || currentPastedTexts.length > 0) { + if ( + currentTextContexts.length > 0 || + currentDiffTextContexts.length > 0 || + currentPastedTexts.length > 0 + ) { const quoteMentions = currentTextContexts.map((tc) => { - const preview = tc.preview.replace(/[:\[\]]/g, "") // Sanitize preview - const encodedText = utf8ToBase64(tc.text) // Base64 encode full text - return `@[${MENTION_PREFIXES.QUOTE}${preview}:${encodedText}]` - }) + const preview = tc.preview.replace(/[:\[\]]/g, ""); // Sanitize preview + const encodedText = utf8ToBase64(tc.text); // Base64 encode full text + return `@[${MENTION_PREFIXES.QUOTE}${preview}:${encodedText}]`; + }); const diffMentions = currentDiffTextContexts.map((dtc) => { - const preview = dtc.preview.replace(/[:\[\]]/g, "") // Sanitize preview - const encodedText = utf8ToBase64(dtc.text) // Base64 encode full text - const lineNum = dtc.lineNumber || 0 - return `@[${MENTION_PREFIXES.DIFF}${dtc.filePath}:${lineNum}:${preview}:${encodedText}]` - }) + const preview = dtc.preview.replace(/[:\[\]]/g, ""); // Sanitize preview + const encodedText = utf8ToBase64(dtc.text); // Base64 encode full text + const lineNum = dtc.lineNumber || 0; + return `@[${MENTION_PREFIXES.DIFF}${dtc.filePath}:${lineNum}:${preview}:${encodedText}]`; + }); // Add pasted text as pasted mentions (format: pasted:size:preview|filepath) // Using | as separator since filepath can contain colons const pastedTextMentions = currentPastedTexts.map((pt) => { // Sanitize preview to remove special characters that break mention parsing - const sanitizedPreview = pt.preview.replace(/[:\[\]|]/g, "") - return `@[${MENTION_PREFIXES.PASTED}${pt.size}:${sanitizedPreview}|${pt.filePath}]` - }) + const sanitizedPreview = pt.preview.replace(/[:\[\]|]/g, ""); + return `@[${MENTION_PREFIXES.PASTED}${pt.size}:${sanitizedPreview}|${pt.filePath}]`; + }); - mentionPrefix = [...quoteMentions, ...diffMentions, ...pastedTextMentions].join(" ") + " " + mentionPrefix = + [...quoteMentions, ...diffMentions, ...pastedTextMentions].join(" ") + + " "; } if (finalText || mentionPrefix) { - parts.push({ type: "text", text: mentionPrefix + (finalText || "") }) + parts.push({ type: "text", text: mentionPrefix + (finalText || "") }); } // Add cached file contents as hidden parts (sent to agent but not displayed in UI) @@ -3596,71 +3857,76 @@ const ChatViewInner = memo(function ChatViewInner({ if (fileContentsRef.current.size > 0) { for (const [mentionId, content] of fileContentsRef.current.entries()) { // Extract file path from mentionId (file:local:path or file:external:path) - const filePath = mentionId.replace(/^file:(local|external):/, "") + const filePath = mentionId.replace(/^file:(local|external):/, ""); parts.push({ type: "file-content", filePath, content, - }) + }); } } - clearAll() - clearTextContexts() - clearDiffTextContexts() - clearPastedTexts() - clearFileContents() + clearAll(); + clearTextContexts(); + clearDiffTextContexts(); + clearPastedTexts(); + clearFileContents(); // Optimistic update: immediately update chat's updated_at and resort array for instant sidebar resorting if (teamId) { - const now = new Date() + const now = new Date(); utils.agents.getAgentChats.setData({ teamId }, (old: any) => { - if (!old) return old + if (!old) return old; // Update the timestamp and sort by updated_at descending const updated = old.map((c: any) => c.id === parentChatId ? { ...c, updated_at: now } : c, - ) + ); return updated.sort( (a: any, b: any) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime(), - ) - }) + ); + }); } // Desktop app: Optimistic update for chats.list to update sidebar immediately - const queryClient = getQueryClient() + const queryClient = getQueryClient(); if (queryClient) { - const now = new Date() - const queries = queryClient.getQueryCache().getAll() - const chatsListQuery = queries.find(q => - Array.isArray(q.queryKey) && - Array.isArray(q.queryKey[0]) && - q.queryKey[0][0] === 'chats' && - q.queryKey[0][1] === 'list' - ) + const now = new Date(); + const queries = queryClient.getQueryCache().getAll(); + const chatsListQuery = queries.find( + (q) => + Array.isArray(q.queryKey) && + Array.isArray(q.queryKey[0]) && + q.queryKey[0][0] === "chats" && + q.queryKey[0][1] === "list", + ); if (chatsListQuery) { - queryClient.setQueryData(chatsListQuery.queryKey, (old: any[] | undefined) => { - if (!old) return old - // Update the timestamp and sort by updatedAt descending - const updated = old.map((c: any) => - c.id === parentChatId ? { ...c, updatedAt: now } : c, - ) - return updated.sort( - (a: any, b: any) => - new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(), - ) - }) + queryClient.setQueryData( + chatsListQuery.queryKey, + (old: any[] | undefined) => { + if (!old) return old; + // Update the timestamp and sort by updatedAt descending + const updated = old.map((c: any) => + c.id === parentChatId ? { ...c, updatedAt: now } : c, + ); + return updated.sort( + (a: any, b: any) => + new Date(b.updatedAt).getTime() - + new Date(a.updatedAt).getTime(), + ); + }, + ); } } // Optimistically update sub-chat timestamp to move it to top - useAgentSubChatStore.getState().updateSubChatTimestamp(subChatId) + useAgentSubChatStore.getState().updateSubChatTimestamp(subChatId); // Enable auto-scroll and immediately scroll to bottom - shouldAutoScrollRef.current = true - scrollToBottom() + shouldAutoScrollRef.current = true; + scrollToBottom(); - await sendMessageRef.current({ role: "user", parts }) + await sendMessageRef.current({ role: "user", parts }); }, [ sandboxSetupStatus, isArchived, @@ -3674,160 +3940,172 @@ const ChatViewInner = memo(function ChatViewInner({ teamId, addToQueue, setExpiredQuestionsMap, - ]) + ]); // Queue handlers for sending queued messages - const handleSendFromQueue = useCallback(async (itemId: string) => { - const item = popItemFromQueue(subChatId, itemId) - if (!item) return + const handleSendFromQueue = useCallback( + async (itemId: string) => { + const item = popItemFromQueue(subChatId, itemId); + if (!item) return; - try { - // Stop current stream if streaming and wait for status to become ready. - // The server-side save block preserves sessionId on abort, so the next - // message can resume the session with full conversation context. - if (isStreamingRef.current) { - await handleStop() - await waitForStreamingReady(subChatId) - } + try { + // Stop current stream if streaming and wait for status to become ready. + // The server-side save block preserves sessionId on abort, so the next + // message can resume the session with full conversation context. + if (isStreamingRef.current) { + await handleStop(); + await waitForStreamingReady(subChatId); + } - // Build message parts from queued item - const parts: any[] = [ - ...(item.images || []).map((img) => ({ - type: "data-image" as const, - data: { - url: img.url, - mediaType: img.mediaType, - filename: img.filename, - base64Data: img.base64Data, - }, - })), - ...(item.files || []).map((f) => ({ - type: "data-file" as const, - data: { - url: f.url, - mediaType: f.mediaType, - filename: f.filename, - size: f.size, - }, - })), - ] - - // Add text contexts as mention tokens - let mentionPrefix = "" - if (item.textContexts && item.textContexts.length > 0) { - const quoteMentions = item.textContexts.map((tc) => { - const preview = tc.text.slice(0, 50).replace(/[:\[\]]/g, "") // Create and sanitize preview - const encodedText = utf8ToBase64(tc.text) // Base64 encode full text - return `@[${MENTION_PREFIXES.QUOTE}${preview}:${encodedText}]` - }) - mentionPrefix = quoteMentions.join(" ") + " " - } + // Build message parts from queued item + const parts: any[] = [ + ...(item.images || []).map((img) => ({ + type: "data-image" as const, + data: { + url: img.url, + mediaType: img.mediaType, + filename: img.filename, + base64Data: img.base64Data, + }, + })), + ...(item.files || []).map((f) => ({ + type: "data-file" as const, + data: { + url: f.url, + mediaType: f.mediaType, + filename: f.filename, + size: f.size, + }, + })), + ]; + + // Add text contexts as mention tokens + let mentionPrefix = ""; + if (item.textContexts && item.textContexts.length > 0) { + const quoteMentions = item.textContexts.map((tc) => { + const preview = tc.text.slice(0, 50).replace(/[:\[\]]/g, ""); // Create and sanitize preview + const encodedText = utf8ToBase64(tc.text); // Base64 encode full text + return `@[${MENTION_PREFIXES.QUOTE}${preview}:${encodedText}]`; + }); + mentionPrefix = quoteMentions.join(" ") + " "; + } - // Add diff text contexts as mention tokens - if (item.diffTextContexts && item.diffTextContexts.length > 0) { - const diffMentions = item.diffTextContexts.map((dtc) => { - const preview = dtc.text.slice(0, 50).replace(/[:\[\]]/g, "") // Create and sanitize preview - const encodedText = utf8ToBase64(dtc.text) // Base64 encode full text - const lineNum = dtc.lineNumber || 0 - return `@[${MENTION_PREFIXES.DIFF}${dtc.filePath}:${lineNum}:${preview}:${encodedText}]` - }) - mentionPrefix += diffMentions.join(" ") + " " - } + // Add diff text contexts as mention tokens + if (item.diffTextContexts && item.diffTextContexts.length > 0) { + const diffMentions = item.diffTextContexts.map((dtc) => { + const preview = dtc.text.slice(0, 50).replace(/[:\[\]]/g, ""); // Create and sanitize preview + const encodedText = utf8ToBase64(dtc.text); // Base64 encode full text + const lineNum = dtc.lineNumber || 0; + return `@[${MENTION_PREFIXES.DIFF}${dtc.filePath}:${lineNum}:${preview}:${encodedText}]`; + }); + mentionPrefix += diffMentions.join(" ") + " "; + } - if (item.message || mentionPrefix) { - parts.push({ type: "text", text: mentionPrefix + (item.message || "") }) - } + if (item.message || mentionPrefix) { + parts.push({ + type: "text", + text: mentionPrefix + (item.message || ""), + }); + } - // Track message sent - trackMessageSent({ - workspaceId: subChatId, - messageLength: item.message.length, - mode: subChatModeRef.current, - }) + // Track message sent + trackMessageSent({ + workspaceId: subChatId, + messageLength: item.message.length, + mode: subChatModeRef.current, + }); - // Update timestamps - useAgentSubChatStore.getState().updateSubChatTimestamp(subChatId) + // Update timestamps + useAgentSubChatStore.getState().updateSubChatTimestamp(subChatId); - // Enable auto-scroll and immediately scroll to bottom - shouldAutoScrollRef.current = true - scrollToBottom() + // Enable auto-scroll and immediately scroll to bottom + shouldAutoScrollRef.current = true; + scrollToBottom(); - await sendMessageRef.current({ role: "user", parts }) - } catch (error) { - console.error("[handleSendFromQueue] Error sending queued message:", error) - // Requeue the item at the front so it isn't lost - useMessageQueueStore.getState().prependItem(subChatId, item) - } - }, [subChatId, popItemFromQueue, handleStop]) + await sendMessageRef.current({ role: "user", parts }); + } catch (error) { + console.error( + "[handleSendFromQueue] Error sending queued message:", + error, + ); + // Requeue the item at the front so it isn't lost + useMessageQueueStore.getState().prependItem(subChatId, item); + } + }, + [subChatId, popItemFromQueue, handleStop], + ); - const handleRemoveFromQueue = useCallback((itemId: string) => { - removeFromQueue(subChatId, itemId) - }, [subChatId, removeFromQueue]) + const handleRemoveFromQueue = useCallback( + (itemId: string) => { + removeFromQueue(subChatId, itemId); + }, + [subChatId, removeFromQueue], + ); // Force send - stop stream and send immediately, bypassing queue (Opt+Enter) const handleForceSend = useCallback(async () => { // Block sending while sandbox is still being set up if (sandboxSetupStatus !== "ready") { - return + return; } // Get value from uncontrolled editor - const inputValue = editorRef.current?.getValue() || "" - const hasText = inputValue.trim().length > 0 - const currentImages = imagesRef.current - const currentFiles = filesRef.current + const inputValue = editorRef.current?.getValue() || ""; + const hasText = inputValue.trim().length > 0; + const currentImages = imagesRef.current; + const currentFiles = filesRef.current; const hasImages = - currentImages.filter((img) => !img.isLoading && img.url).length > 0 + currentImages.filter((img) => !img.isLoading && img.url).length > 0; - if (!hasText && !hasImages) return + if (!hasText && !hasImages) return; // Stop current stream if streaming and wait for status to become ready. // The server-side save block sets sessionId=null on abort, so the next // message starts fresh without needing an explicit cancel mutation. if (isStreamingRef.current) { - await handleStop() - await waitForStreamingReady(subChatId) + await handleStop(); + await waitForStreamingReady(subChatId); } // Auto-restore archived workspace when sending a message if (isArchived && onRestoreWorkspace) { - onRestoreWorkspace() + onRestoreWorkspace(); } - const text = inputValue.trim() + const text = inputValue.trim(); // Expand custom slash commands with arguments (e.g. "/Apex my argument") - let finalText = text - const slashMatch = text.match(/^\/(\S+)\s*(.*)$/s) + let finalText = text; + const slashMatch = text.match(/^\/(\S+)\s*(.*)$/s); if (slashMatch) { - const [, commandName, args] = slashMatch + const [, commandName, args] = slashMatch; const builtinNames = new Set( BUILTIN_SLASH_COMMANDS.map((cmd) => cmd.name), - ) + ); if (!builtinNames.has(commandName)) { try { const commands = await trpcClient.commands.list.query({ projectPath, - }) + }); const cmd = commands.find( (c) => c.name.toLowerCase() === commandName.toLowerCase(), - ) + ); if (cmd) { const { content } = await trpcClient.commands.getContent.query({ path: cmd.path, - }) - finalText = content.replace(/\$ARGUMENTS/g, args.trim()) + }); + finalText = content.replace(/\$ARGUMENTS/g, args.trim()); } } catch (error) { - console.error("Failed to expand custom slash command:", error) + console.error("Failed to expand custom slash command:", error); } } } // Clear editor and draft from localStorage - editorRef.current?.clear() + editorRef.current?.clear(); if (parentChatId) { - clearSubChatDraft(parentChatId, subChatId) + clearSubChatDraft(parentChatId, subChatId); } // Track message sent @@ -3835,7 +4113,7 @@ const ChatViewInner = memo(function ChatViewInner({ workspaceId: subChatId, messageLength: finalText.length, mode: subChatModeRef.current, - }) + }); // Build message parts const parts: any[] = [ @@ -3861,28 +4139,28 @@ const ChatViewInner = memo(function ChatViewInner({ size: f.size, }, })), - ] + ]; if (finalText) { - parts.push({ type: "text", text: finalText }) + parts.push({ type: "text", text: finalText }); } // Clear attachments - clearAll() + clearAll(); // Update timestamps - useAgentSubChatStore.getState().updateSubChatTimestamp(subChatId) + useAgentSubChatStore.getState().updateSubChatTimestamp(subChatId); // Force scroll to bottom - shouldAutoScrollRef.current = true - scrollToBottom() + shouldAutoScrollRef.current = true; + scrollToBottom(); try { - await sendMessageRef.current({ role: "user", parts }) + await sendMessageRef.current({ role: "user", parts }); } catch (error) { - console.error("[handleForceSend] Error sending message:", error) + console.error("[handleForceSend] Error sending message:", error); // Restore editor content so the user can retry - editorRef.current?.setValue(finalText) + editorRef.current?.setValue(finalText); } }, [ sandboxSetupStatus, @@ -3892,7 +4170,7 @@ const ChatViewInner = memo(function ChatViewInner({ subChatId, handleStop, clearAll, - ]) + ]); // NOTE: Auto-processing of queue is now handled globally by QueueProcessor // component in agents-layout.tsx. This ensures queues continue processing @@ -3905,64 +4183,67 @@ const ChatViewInner = memo(function ChatViewInner({ ?.filter((p: any) => p.type === "text") .map((p: any) => p.text) .join("\n") || "" - ) - } + ); + }; // Helper to copy message content const copyMessageContent = (msg: any) => { - const textContent = getMessageTextContent(msg) + const textContent = getMessageTextContent(msg); if (textContent) { - navigator.clipboard.writeText(stripEmojis(textContent)) + navigator.clipboard.writeText(stripEmojis(textContent)); } - } + }; // Check if there's an unapproved plan (in plan mode with completed ExitPlanMode) const hasUnapprovedPlan = useMemo(() => { // If already in agent mode, plan is approved (mode is the source of truth) - if (subChatMode !== "plan") return false + if (subChatMode !== "plan") return false; // Look for completed ExitPlanMode in messages for (let i = messages.length - 1; i >= 0; i--) { - const msg = messages[i] + const msg = messages[i]; // If assistant message with completed ExitPlanMode, we found an unapproved plan if (msg.role === "assistant" && msg.parts) { const exitPlanPart = msg.parts.find( - (p: any) => p.type === "tool-ExitPlanMode" - ) + (p: any) => p.type === "tool-ExitPlanMode", + ); // Check if ExitPlanMode is completed (has output, even if empty) if (exitPlanPart && exitPlanPart.output !== undefined) { - return true + return true; } } } - return false - }, [messages, subChatMode]) + return false; + }, [messages, subChatMode]); // Keep ref in sync for use in initializeScroll (which runs in useLayoutEffect) - hasUnapprovedPlanRef.current = hasUnapprovedPlan + hasUnapprovedPlanRef.current = hasUnapprovedPlan; // Update pending plan approvals atom for sidebar indicators - const setPendingPlanApprovals = useSetAtom(pendingPlanApprovalsAtom) + const setPendingPlanApprovals = useSetAtom(pendingPlanApprovalsAtom); useEffect(() => { setPendingPlanApprovals((prev: Map) => { - const newMap = new Map(prev) + const newMap = new Map(prev); if (hasUnapprovedPlan) { - newMap.set(subChatId, parentChatId) + newMap.set(subChatId, parentChatId); } else { - newMap.delete(subChatId) + newMap.delete(subChatId); } // Only return new map if it changed - if (newMap.size !== prev.size || ![...newMap.keys()].every((id) => prev.has(id))) { - return newMap + if ( + newMap.size !== prev.size || + ![...newMap.keys()].every((id) => prev.has(id)) + ) { + return newMap; } - return prev - }) - }, [hasUnapprovedPlan, subChatId, parentChatId, setPendingPlanApprovals]) + return prev; + }); + }, [hasUnapprovedPlan, subChatId, parentChatId, setPendingPlanApprovals]); // Keyboard shortcut: Cmd+Enter to approve plan useEffect(() => { - if (!isActive) return + if (!isActive) return; const handleKeyDown = (e: KeyboardEvent) => { if ( @@ -3972,14 +4253,14 @@ const ChatViewInner = memo(function ChatViewInner({ hasUnapprovedPlan && !isStreaming ) { - e.preventDefault() - handleApprovePlan() + e.preventDefault(); + handleApprovePlan(); } - } + }; - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) - }, [isActive, hasUnapprovedPlan, isStreaming, handleApprovePlan]) + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [isActive, hasUnapprovedPlan, isStreaming, handleApprovePlan]); // Cmd/Ctrl + Arrow Down to scroll to bottom (works even when focused in input) // But don't intercept if input has content - let native cursor navigation work @@ -3992,113 +4273,120 @@ const ChatViewInner = memo(function ChatViewInner({ !e.shiftKey ) { // Don't intercept if input has content - let native cursor navigation work - const inputValue = editorRef.current?.getValue() || "" + const inputValue = editorRef.current?.getValue() || ""; if (inputValue.trim().length > 0) { - return + return; } - e.preventDefault() - scrollToBottom() + e.preventDefault(); + scrollToBottom(); } - } + }; - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) - }, [scrollToBottom]) + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [scrollToBottom]); // Clean up pending plan approval when unmounting useEffect(() => { return () => { setPendingPlanApprovals((prev: Map) => { if (prev.has(subChatId)) { - const newMap = new Map(prev) - newMap.delete(subChatId) - return newMap + const newMap = new Map(prev); + newMap.delete(subChatId); + return newMap; } - return prev - }) - } - }, [subChatId, setPendingPlanApprovals]) + return prev; + }); + }; + }, [subChatId, setPendingPlanApprovals]); // Compute sticky top class for user messages const stickyTopClass = isMobile ? CHAT_LAYOUT.stickyTopMobile : isSubChatsSidebarOpen ? CHAT_LAYOUT.stickyTopSidebarOpen - : CHAT_LAYOUT.stickyTopSidebarClosed + : CHAT_LAYOUT.stickyTopSidebarClosed; // Sync messages to Jotai store for isolated rendering // CRITICAL: Only sync from the ACTIVE tab to prevent overwriting global atoms // Each tab has its own useChat() instance, but global atoms (messageIdsAtom, etc.) are shared. // Only the active tab should update these global atoms. - const syncMessages = useSetAtom(syncMessagesWithStatusAtom) + const syncMessages = useSetAtom(syncMessagesWithStatusAtom); useLayoutEffect(() => { // Skip syncing for inactive tabs - they shouldn't update global atoms - if (!isActive) return - syncMessages({ messages, status, subChatId }) - }, [messages, status, subChatId, syncMessages, isActive]) + if (!isActive) return; + syncMessages({ messages, status, subChatId }); + }, [messages, status, subChatId, syncMessages, isActive]); // Sync status to global streaming status store for queue processing - const setStreamingStatus = useStreamingStatusStore((s) => s.setStatus) + const setStreamingStatus = useStreamingStatusStore((s) => s.setStatus); useEffect(() => { - setStreamingStatus(subChatId, status as "ready" | "streaming" | "submitted" | "error") - }, [subChatId, status, setStreamingStatus]) + setStreamingStatus( + subChatId, + status as "ready" | "streaming" | "submitted" | "error", + ); + }, [subChatId, status, setStreamingStatus]); // Chat search - scroll to current match // Use ref to track scroll lock and prevent race conditions - const searchScrollLockRef = useRef(0) - const currentSearchMatch = useAtomValue(chatSearchCurrentMatchAtom) + const searchScrollLockRef = useRef(0); + const currentSearchMatch = useAtomValue(chatSearchCurrentMatchAtom); useEffect(() => { - if (!currentSearchMatch) return + if (!currentSearchMatch) return; - const container = chatContainerRef.current - if (!container) return + const container = chatContainerRef.current; + if (!container) return; // Increment lock to cancel any pending scroll operations - const currentLock = ++searchScrollLockRef.current + const currentLock = ++searchScrollLockRef.current; // Use double requestAnimationFrame + small delay to ensure DOM has updated with new highlights requestAnimationFrame(() => { requestAnimationFrame(() => { setTimeout(() => { // Check if this scroll operation is still valid (not superseded by newer one) - if (searchScrollLockRef.current !== currentLock) return + if (searchScrollLockRef.current !== currentLock) return; // First try to find the highlight mark - let targetElement: Element | null = container.querySelector(".search-highlight-current") + let targetElement: Element | null = container.querySelector( + ".search-highlight-current", + ); // If no highlight mark, find the message element with matching data attributes if (!targetElement) { - const selector = `[data-message-id="${currentSearchMatch.messageId}"][data-part-index="${currentSearchMatch.partIndex}"]` - targetElement = container.querySelector(selector) + const selector = `[data-message-id="${currentSearchMatch.messageId}"][data-part-index="${currentSearchMatch.partIndex}"]`; + targetElement = container.querySelector(selector); } if (targetElement) { // Check if this is inside a sticky user message container - const stickyParent = targetElement.closest("[data-user-message-id]") + const stickyParent = targetElement.closest( + "[data-user-message-id]", + ); if (stickyParent) { - const messageGroupWrapper = stickyParent.parentElement + const messageGroupWrapper = stickyParent.parentElement; if (messageGroupWrapper) { messageGroupWrapper.scrollIntoView({ behavior: "smooth", block: "start", - }) - return + }); + return; } } targetElement.scrollIntoView({ behavior: "smooth", block: "center", - }) + }); } - }, 50) - }) - }) - }, [currentSearchMatch]) + }, 50); + }); + }); + }, [currentSearchMatch]); // Calculate top offset for search bar based on sub-chat selector - const searchBarTopOffset = isSubChatsSidebarOpen ? "52px" : undefined + const searchBarTopOffset = isSubChatsSidebarOpen ? "52px" : undefined; return ( @@ -4126,176 +4414,179 @@ const ChatViewInner = memo(function ChatViewInner({ {/* Chat title - flex above scroll area (desktop only) */} {!isMobile && ( -
- 0} - /> -
- )} +
+ 0} + /> +
+ )} - {/* Messages */} -
{ - // Cleanup previous observer - if (chatContainerObserverRef.current) { - chatContainerObserverRef.current.disconnect() - chatContainerObserverRef.current = null - } + {/* Messages */} +
{ + // Cleanup previous observer + if (chatContainerObserverRef.current) { + chatContainerObserverRef.current.disconnect(); + chatContainerObserverRef.current = null; + } - chatContainerRef.current = el + chatContainerRef.current = el; - // Setup ResizeObserver for --chat-container-height CSS variable - if (el) { - const observer = new ResizeObserver((entries) => { - const height = entries[0]?.contentRect.height ?? 0 - el.style.setProperty("--chat-container-height", `${height}px`) - }) - observer.observe(el) - chatContainerObserverRef.current = observer - } - }} - className="flex-1 overflow-y-auto w-full relative allow-text-selection outline-none" - tabIndex={-1} - data-chat-container - > -
{ + const height = entries[0]?.contentRect.height ?? 0; + el.style.setProperty("--chat-container-height", `${height}px`); + }); + observer.observe(el); + chatContainerObserverRef.current = observer; + } }} + className="flex-1 overflow-y-auto w-full relative allow-text-selection outline-none" + tabIndex={-1} + data-chat-container > -
- {/* ISOLATED: Messages rendered via Jotai atom subscription +
+
+ {/* ISOLATED: Messages rendered via Jotai atom subscription Each component subscribes to specific atoms and only re-renders when those change KEY: Force remount on subChatId change to ensure fresh atom reads after syncMessages */} - + +
-
- {/* User questions panel - shows for both live (pending) and expired (timed out) questions */} - {displayQuestions && ( -
-
- -
-
- )} - - {/* Stacked cards container - queue + status */} - {!displayQuestions && - (queue.length > 0 || changedFilesForSubChat.length > 0) && ( -
-
- {/* Queue indicator card - top card */} - {queue.length > 0 && ( - 0} - /> - )} - {/* Status card - bottom card, only when there are changed files */} - {changedFilesForSubChat.length > 0 && ( - 0} - /> - )} + {/* User questions panel - shows for both live (pending) and expired (timed out) questions */} + {displayQuestions && ( +
+
+
)} - {/* Input - isolated component to prevent re-renders */} - + {/* Stacked cards container - queue + status */} + {!displayQuestions && + (queue.length > 0 || changedFilesForSubChat.length > 0) && ( +
+
+ {/* Queue indicator card - top card */} + {queue.length > 0 && ( + 0} + /> + )} + {/* Status card - bottom card, only when there are changed files */} + {changedFilesForSubChat.length > 0 && ( + 0} + /> + )} +
+
+ )} + + {/* Input - isolated component to prevent re-renders */} + {/* Scroll to bottom button - isolated component to avoid re-renders during streaming */} 0 || changedFilesForSubChat.length > 0)} + hasStackedCards={ + !displayQuestions && + (queue.length > 0 || changedFilesForSubChat.length > 0) + } subChatId={subChatId} isActive={isActive} />
- ) -}) + ); +}); // Chat View wrapper - handles loading and creates chat object export function ChatView({ @@ -4310,133 +4601,144 @@ export function ChatView({ onOpenDiff, onOpenTerminal, }: { - chatId: string - isSidebarOpen: boolean - onToggleSidebar: () => void - selectedTeamName?: string - selectedTeamImageUrl?: string - isMobileFullscreen?: boolean - onBackToChats?: () => void - onOpenPreview?: () => void - onOpenDiff?: () => void - onOpenTerminal?: () => void + chatId: string; + isSidebarOpen: boolean; + onToggleSidebar: () => void; + selectedTeamName?: string; + selectedTeamImageUrl?: string; + isMobileFullscreen?: boolean; + onBackToChats?: () => void; + onOpenPreview?: () => void; + onOpenDiff?: () => void; + onOpenTerminal?: () => void; }) { - const [selectedTeamId] = useAtom(selectedTeamIdAtom) - const [selectedModelId] = useAtom(lastSelectedModelIdAtom) + const [selectedTeamId] = useAtom(selectedTeamIdAtom); + const [selectedModelId] = useAtom(lastSelectedModelIdAtom); // Get active sub-chat ID from store for mode tracking (reactive) - const activeSubChatIdForMode = useAgentSubChatStore((state) => state.activeSubChatId) + const activeSubChatIdForMode = useAgentSubChatStore( + (state) => state.activeSubChatId, + ); // Use per-subChat mode atom - falls back to "agent" if no active sub-chat const subChatModeAtom = useMemo( () => subChatModeAtomFamily(activeSubChatIdForMode || ""), [activeSubChatIdForMode], - ) - const [subChatMode] = useAtom(subChatModeAtom) + ); + const [subChatMode] = useAtom(subChatModeAtom); // Default mode for new sub-chats (used as fallback when no active sub-chat) - const defaultAgentMode = useAtomValue(defaultAgentModeAtom) + const defaultAgentMode = useAtomValue(defaultAgentModeAtom); // Current mode - use subChatMode when there's an active sub-chat, otherwise use user's default preference - const currentMode: AgentMode = activeSubChatIdForMode ? subChatMode : defaultAgentMode - - const isDesktop = useAtomValue(isDesktopAtom) - const isFullscreen = useAtomValue(isFullscreenAtom) - const customClaudeConfig = useAtomValue(customClaudeConfigAtom) - const selectedOllamaModel = useAtomValue(selectedOllamaModelAtom) + const currentMode: AgentMode = activeSubChatIdForMode + ? subChatMode + : defaultAgentMode; + + const isDesktop = useAtomValue(isDesktopAtom); + const isFullscreen = useAtomValue(isFullscreenAtom); + const customClaudeConfig = useAtomValue(customClaudeConfigAtom); + const selectedOllamaModel = useAtomValue(selectedOllamaModelAtom); const normalizedCustomClaudeConfig = - normalizeCustomClaudeConfig(customClaudeConfig) - const hasCustomClaudeConfig = Boolean(normalizedCustomClaudeConfig) - const setLoadingSubChats = useSetAtom(loadingSubChatsAtom) - const unseenChanges = useAtomValue(agentsUnseenChangesAtom) - const setUnseenChanges = useSetAtom(agentsUnseenChangesAtom) - const setSubChatUnseenChanges = useSetAtom(agentsSubChatUnseenChangesAtom) - const setJustCreatedIds = useSetAtom(justCreatedIdsAtom) - const selectedChatId = useAtomValue(selectedAgentChatIdAtom) - const setUndoStack = useSetAtom(undoStackAtom) - const setSelectedFilePath = useSetAtom(selectedDiffFilePathAtom) - const setFilteredDiffFiles = useSetAtom(filteredDiffFilesAtom) - const { notifyAgentComplete } = useDesktopNotifications() + normalizeCustomClaudeConfig(customClaudeConfig); + const hasCustomClaudeConfig = Boolean(normalizedCustomClaudeConfig); + const setLoadingSubChats = useSetAtom(loadingSubChatsAtom); + const unseenChanges = useAtomValue(agentsUnseenChangesAtom); + const setUnseenChanges = useSetAtom(agentsUnseenChangesAtom); + const setSubChatUnseenChanges = useSetAtom(agentsSubChatUnseenChangesAtom); + const setJustCreatedIds = useSetAtom(justCreatedIdsAtom); + const selectedChatId = useAtomValue(selectedAgentChatIdAtom); + const setUndoStack = useSetAtom(undoStackAtom); + const setSelectedFilePath = useSetAtom(selectedDiffFilePathAtom); + const setFilteredDiffFiles = useSetAtom(filteredDiffFilesAtom); + const { notifyAgentComplete } = useDesktopNotifications(); // Check if any chat has unseen changes - const hasAnyUnseenChanges = unseenChanges.size > 0 - const [, forceUpdate] = useState({}) + const hasAnyUnseenChanges = unseenChanges.size > 0; + const [, forceUpdate] = useState({}); const [isPreviewSidebarOpen, setIsPreviewSidebarOpen] = useAtom( agentsPreviewSidebarOpenAtom, - ) + ); // Per-chat diff sidebar state - each chat remembers its own open/close state const diffSidebarAtom = useMemo( () => diffSidebarOpenAtomFamily(chatId), [chatId], - ) - const [isDiffSidebarOpen, setIsDiffSidebarOpen] = useAtom(diffSidebarAtom) + ); + const [isDiffSidebarOpen, setIsDiffSidebarOpen] = useAtom(diffSidebarAtom); // Subscribe to activeSubChatId for plan sidebar (needs to update when switching sub-chats) - const activeSubChatIdForPlan = useAgentSubChatStore((state) => state.activeSubChatId) + const activeSubChatIdForPlan = useAgentSubChatStore( + (state) => state.activeSubChatId, + ); // Per-subChat plan sidebar state - each sub-chat remembers its own open/close state const planSidebarAtom = useMemo( () => planSidebarOpenAtomFamily(activeSubChatIdForPlan || ""), [activeSubChatIdForPlan], - ) - const [isPlanSidebarOpen, setIsPlanSidebarOpen] = useAtom(planSidebarAtom) + ); + const [isPlanSidebarOpen, setIsPlanSidebarOpen] = useAtom(planSidebarAtom); const currentPlanPathAtom = useMemo( () => currentPlanPathAtomFamily(activeSubChatIdForPlan || ""), [activeSubChatIdForPlan], - ) - const [currentPlanPath, setCurrentPlanPath] = useAtom(currentPlanPathAtom) + ); + const [currentPlanPath, setCurrentPlanPath] = useAtom(currentPlanPathAtom); // File viewer sidebar state - per-chat open file path const fileViewerAtom = useMemo( () => fileViewerOpenAtomFamily(chatId), [chatId], - ) - const [fileViewerPath, setFileViewerPath] = useAtom(fileViewerAtom) - const [fileViewerDisplayMode] = useAtom(fileViewerDisplayModeAtom) + ); + const [fileViewerPath, setFileViewerPath] = useAtom(fileViewerAtom); + const [fileViewerDisplayMode] = useAtom(fileViewerDisplayModeAtom); // File search dialog (Cmd+P) - const [fileSearchOpen, setFileSearchOpen] = useAtom(fileSearchDialogOpenAtom) + const [fileSearchOpen, setFileSearchOpen] = useAtom(fileSearchDialogOpenAtom); // Details sidebar state (unified sidebar that combines all right sidebars) - const isUnifiedSidebarEnabled = useAtomValue(unifiedSidebarEnabledAtom) - const [isDetailsSidebarOpen, setIsDetailsSidebarOpen] = useAtom(detailsSidebarOpenAtom) + const isUnifiedSidebarEnabled = useAtomValue(unifiedSidebarEnabledAtom); + const [isDetailsSidebarOpen, setIsDetailsSidebarOpen] = useAtom( + detailsSidebarOpenAtom, + ); // Resolved hotkeys for tooltips - const toggleDetailsHotkey = useResolvedHotkeyDisplay("toggle-details") - const toggleTerminalHotkey = useResolvedHotkeyDisplay("toggle-terminal") + const toggleDetailsHotkey = useResolvedHotkeyDisplay("toggle-details"); + const toggleTerminalHotkey = useResolvedHotkeyDisplay("toggle-terminal"); // Close plan sidebar when switching to a sub-chat that has no plan - const prevSubChatIdRef = useRef(activeSubChatIdForPlan) + const prevSubChatIdRef = useRef(activeSubChatIdForPlan); useEffect(() => { if (prevSubChatIdRef.current !== activeSubChatIdForPlan) { // Sub-chat changed - if new one has no plan path, close sidebar if (!currentPlanPath) { - setIsPlanSidebarOpen(false) + setIsPlanSidebarOpen(false); } - prevSubChatIdRef.current = activeSubChatIdForPlan + prevSubChatIdRef.current = activeSubChatIdForPlan; } - }, [activeSubChatIdForPlan, currentPlanPath, setIsPlanSidebarOpen]) - const setPendingBuildPlanSubChatId = useSetAtom(pendingBuildPlanSubChatIdAtom) + }, [activeSubChatIdForPlan, currentPlanPath, setIsPlanSidebarOpen]); + const setPendingBuildPlanSubChatId = useSetAtom( + pendingBuildPlanSubChatIdAtom, + ); // Read plan edit refetch trigger from atom (set by ChatViewInner when Edit completes) const planEditRefetchTriggerAtom = useMemo( () => planEditRefetchTriggerAtomFamily(activeSubChatIdForPlan || ""), [activeSubChatIdForPlan], - ) - const planEditRefetchTrigger = useAtomValue(planEditRefetchTriggerAtom) + ); + const planEditRefetchTrigger = useAtomValue(planEditRefetchTriggerAtom); // Handler for plan sidebar "Build plan" button // Uses getState() to get fresh activeSubChatId (avoids stale closure) const handleApprovePlanFromSidebar = useCallback(() => { - const activeSubChatId = useAgentSubChatStore.getState().activeSubChatId + const activeSubChatId = useAgentSubChatStore.getState().activeSubChatId; if (activeSubChatId) { - setPendingBuildPlanSubChatId(activeSubChatId) + setPendingBuildPlanSubChatId(activeSubChatId); } - }, [setPendingBuildPlanSubChatId]) + }, [setPendingBuildPlanSubChatId]); // Per-chat terminal sidebar state - each chat remembers its own open/close state const terminalSidebarAtom = useMemo( () => terminalSidebarOpenAtomFamily(chatId), [chatId], - ) - const [isTerminalSidebarOpen, setIsTerminalSidebarOpen] = useAtom(terminalSidebarAtom) - const terminalDisplayMode = useAtomValue(terminalDisplayModeAtom) + ); + const [isTerminalSidebarOpen, setIsTerminalSidebarOpen] = + useAtom(terminalSidebarAtom); + const terminalDisplayMode = useAtomValue(terminalDisplayModeAtom); // Keyboard shortcut: Cmd+J to toggle terminal useEffect(() => { @@ -4448,15 +4750,15 @@ export function ChatView({ !e.ctrlKey && e.code === "KeyJ" ) { - e.preventDefault() - e.stopPropagation() - setIsTerminalSidebarOpen(!isTerminalSidebarOpen) + e.preventDefault(); + e.stopPropagation(); + setIsTerminalSidebarOpen(!isTerminalSidebarOpen); } - } + }; - window.addEventListener("keydown", handleKeyDown, true) - return () => window.removeEventListener("keydown", handleKeyDown, true) - }, [isTerminalSidebarOpen, setIsTerminalSidebarOpen]) + window.addEventListener("keydown", handleKeyDown, true); + return () => window.removeEventListener("keydown", handleKeyDown, true); + }, [isTerminalSidebarOpen, setIsTerminalSidebarOpen]); // Mutual exclusion: Details sidebar vs Plan/Terminal/Diff(side-peek) sidebars // When one opens, close the conflicting ones and remember for restoration @@ -4464,89 +4766,93 @@ export function ChatView({ // Track what was auto-closed and by whom for restoration const autoClosedStateRef = useRef<{ // What closed Details - detailsClosedBy: "plan" | "terminal" | "diff" | null + detailsClosedBy: "plan" | "terminal" | "diff" | null; // What Details closed - planClosedByDetails: boolean - terminalClosedByDetails: boolean - diffClosedByDetails: boolean + planClosedByDetails: boolean; + terminalClosedByDetails: boolean; + diffClosedByDetails: boolean; }>({ detailsClosedBy: null, planClosedByDetails: false, terminalClosedByDetails: false, diffClosedByDetails: false, - }) + }); // Track previous states to detect opens/closes const prevSidebarStatesRef = useRef({ details: isDetailsSidebarOpen, plan: isPlanSidebarOpen && !!currentPlanPath, terminal: isTerminalSidebarOpen, - }) + }); useEffect(() => { - const prev = prevSidebarStatesRef.current - const auto = autoClosedStateRef.current - const isPlanOpen = isPlanSidebarOpen && !!currentPlanPath + const prev = prevSidebarStatesRef.current; + const auto = autoClosedStateRef.current; + const isPlanOpen = isPlanSidebarOpen && !!currentPlanPath; // Detect state changes - const detailsJustOpened = isDetailsSidebarOpen && !prev.details - const detailsJustClosed = !isDetailsSidebarOpen && prev.details - const planJustOpened = isPlanOpen && !prev.plan - const planJustClosed = !isPlanOpen && prev.plan - const terminalJustOpened = isTerminalSidebarOpen && !prev.terminal - const terminalJustClosed = !isTerminalSidebarOpen && prev.terminal + const detailsJustOpened = isDetailsSidebarOpen && !prev.details; + const detailsJustClosed = !isDetailsSidebarOpen && prev.details; + const planJustOpened = isPlanOpen && !prev.plan; + const planJustClosed = !isPlanOpen && prev.plan; + const terminalJustOpened = isTerminalSidebarOpen && !prev.terminal; + const terminalJustClosed = !isTerminalSidebarOpen && prev.terminal; // Terminal in "bottom" mode doesn't conflict with Details sidebar - const terminalConflictsWithDetails = terminalDisplayMode === "side-peek" + const terminalConflictsWithDetails = terminalDisplayMode === "side-peek"; // Details opened → close conflicting sidebars and remember if (detailsJustOpened) { if (isPlanOpen) { - auto.planClosedByDetails = true - setIsPlanSidebarOpen(false) + auto.planClosedByDetails = true; + setIsPlanSidebarOpen(false); } if (isTerminalSidebarOpen && terminalConflictsWithDetails) { - auto.terminalClosedByDetails = true - setIsTerminalSidebarOpen(false) + auto.terminalClosedByDetails = true; + setIsTerminalSidebarOpen(false); } } // Details closed → restore what it closed else if (detailsJustClosed) { if (auto.planClosedByDetails) { - auto.planClosedByDetails = false - setIsPlanSidebarOpen(true) + auto.planClosedByDetails = false; + setIsPlanSidebarOpen(true); } if (auto.terminalClosedByDetails) { - auto.terminalClosedByDetails = false - setIsTerminalSidebarOpen(true) + auto.terminalClosedByDetails = false; + setIsTerminalSidebarOpen(true); } } // Plan opened → close Details and remember else if (planJustOpened && isDetailsSidebarOpen) { - auto.detailsClosedBy = "plan" - setIsDetailsSidebarOpen(false) + auto.detailsClosedBy = "plan"; + setIsDetailsSidebarOpen(false); } // Plan closed → restore Details if we closed it else if (planJustClosed && auto.detailsClosedBy === "plan") { - auto.detailsClosedBy = null - setIsDetailsSidebarOpen(true) + auto.detailsClosedBy = null; + setIsDetailsSidebarOpen(true); } // Terminal opened → close Details and remember (only in side-peek mode) - else if (terminalJustOpened && isDetailsSidebarOpen && terminalConflictsWithDetails) { - auto.detailsClosedBy = "terminal" - setIsDetailsSidebarOpen(false) + else if ( + terminalJustOpened && + isDetailsSidebarOpen && + terminalConflictsWithDetails + ) { + auto.detailsClosedBy = "terminal"; + setIsDetailsSidebarOpen(false); } // Terminal closed → restore Details if we closed it else if (terminalJustClosed && auto.detailsClosedBy === "terminal") { - auto.detailsClosedBy = null - setIsDetailsSidebarOpen(true) + auto.detailsClosedBy = null; + setIsDetailsSidebarOpen(true); } prevSidebarStatesRef.current = { details: isDetailsSidebarOpen, plan: isPlanOpen, terminal: isTerminalSidebarOpen, - } + }; }, [ isDetailsSidebarOpen, isPlanSidebarOpen, @@ -4556,260 +4862,289 @@ export function ChatView({ setIsDetailsSidebarOpen, setIsPlanSidebarOpen, setIsTerminalSidebarOpen, - ]) + ]); // Diff data cache - stored in atoms to persist across workspace switches const diffCacheAtom = useMemo( () => workspaceDiffCacheAtomFamily(chatId), [chatId], - ) - const [diffCache, setDiffCache] = useAtom(diffCacheAtom) + ); + const [diffCache, setDiffCache] = useAtom(diffCacheAtom); // Extract diff data from cache - const diffStats = diffCache.diffStats - const parsedFileDiffs = diffCache.parsedFileDiffs as ParsedDiffFile[] | null - const prefetchedFileContents = diffCache.prefetchedFileContents - const diffContent = diffCache.diffContent + const diffStats = diffCache.diffStats; + const parsedFileDiffs = diffCache.parsedFileDiffs as ParsedDiffFile[] | null; + const prefetchedFileContents = diffCache.prefetchedFileContents; + const diffContent = diffCache.diffContent; // Smart setters that update the cache - const setDiffStats = useCallback((val: any) => { - setDiffCache((prev) => { - const newVal = typeof val === 'function' ? val(prev.diffStats) : val - // Only update if something changed - if ( - prev.diffStats.fileCount === newVal.fileCount && - prev.diffStats.additions === newVal.additions && - prev.diffStats.deletions === newVal.deletions && - prev.diffStats.isLoading === newVal.isLoading && - prev.diffStats.hasChanges === newVal.hasChanges - ) { - return prev // Return same reference to prevent re-render - } - return { ...prev, diffStats: newVal } - }) - }, [setDiffCache]) - - const setParsedFileDiffs = useCallback((files: ParsedDiffFile[] | null) => { - setDiffCache((prev) => ({ ...prev, parsedFileDiffs: files as any })) - }, [setDiffCache]) + const setDiffStats = useCallback( + (val: any) => { + setDiffCache((prev) => { + const newVal = typeof val === "function" ? val(prev.diffStats) : val; + // Only update if something changed + if ( + prev.diffStats.fileCount === newVal.fileCount && + prev.diffStats.additions === newVal.additions && + prev.diffStats.deletions === newVal.deletions && + prev.diffStats.isLoading === newVal.isLoading && + prev.diffStats.hasChanges === newVal.hasChanges + ) { + return prev; // Return same reference to prevent re-render + } + return { ...prev, diffStats: newVal }; + }); + }, + [setDiffCache], + ); - const setPrefetchedFileContents = useCallback((contents: Record) => { - setDiffCache((prev) => ({ ...prev, prefetchedFileContents: contents })) - }, [setDiffCache]) + const setParsedFileDiffs = useCallback( + (files: ParsedDiffFile[] | null) => { + setDiffCache((prev) => ({ ...prev, parsedFileDiffs: files as any })); + }, + [setDiffCache], + ); - const setDiffContent = useCallback((content: string | null) => { - setDiffCache((prev) => ({ ...prev, diffContent: content })) - }, [setDiffCache]) - const [diffMode, setDiffMode] = useAtom(diffViewModeAtom) - const [diffDisplayMode, setDiffDisplayMode] = useAtom(diffViewDisplayModeAtom) - const subChatsSidebarMode = useAtomValue(agentsSubChatsSidebarModeAtom) + const setPrefetchedFileContents = useCallback( + (contents: Record) => { + setDiffCache((prev) => ({ ...prev, prefetchedFileContents: contents })); + }, + [setDiffCache], + ); + const setDiffContent = useCallback( + (content: string | null) => { + setDiffCache((prev) => ({ ...prev, diffContent: content })); + }, + [setDiffCache], + ); + const [diffMode, setDiffMode] = useAtom(diffViewModeAtom); + const [diffDisplayMode, setDiffDisplayMode] = useAtom( + diffViewDisplayModeAtom, + ); + const subChatsSidebarMode = useAtomValue(agentsSubChatsSidebarModeAtom); // Force narrow width when switching to side-peek mode (from dialog/fullscreen) useEffect(() => { if (diffDisplayMode === "side-peek") { // Set to narrow width (400px) to ensure correct layout - appStore.set(agentsDiffSidebarWidthAtom, 400) + appStore.set(agentsDiffSidebarWidthAtom, 400); } - }, [diffDisplayMode]) + }, [diffDisplayMode]); // Handle Diff + Details sidebar conflict (side-peek mode only) // - If Diff opens in side-peek while Details is open: switch Diff to center-peek (dialog) mode // - If user manually switches Diff to side-peek while Details is open: close Details and remember // - If Details opens while Diff is in side-peek mode: close Diff and remember - const prevDiffStateRef = useRef<{ isOpen: boolean; mode: string; detailsOpen: boolean }>({ + const prevDiffStateRef = useRef<{ + isOpen: boolean; + mode: string; + detailsOpen: boolean; + }>({ isOpen: isDiffSidebarOpen, mode: diffDisplayMode, detailsOpen: isDetailsSidebarOpen, - }) + }); // Flag to skip center-peek switch when restoring Diff after Details closes - const isRestoringDiffRef = useRef(false) + const isRestoringDiffRef = useRef(false); useEffect(() => { - const prev = prevDiffStateRef.current - const auto = autoClosedStateRef.current - const isNowSidePeek = isDiffSidebarOpen && diffDisplayMode === "side-peek" - const wasSidePeek = prev.isOpen && prev.mode === "side-peek" - const detailsJustOpened = isDetailsSidebarOpen && !prev.detailsOpen - const detailsJustClosed = !isDetailsSidebarOpen && prev.detailsOpen - const diffSidePeekJustClosed = wasSidePeek && !isNowSidePeek + const prev = prevDiffStateRef.current; + const auto = autoClosedStateRef.current; + const isNowSidePeek = isDiffSidebarOpen && diffDisplayMode === "side-peek"; + const wasSidePeek = prev.isOpen && prev.mode === "side-peek"; + const detailsJustOpened = isDetailsSidebarOpen && !prev.detailsOpen; + const detailsJustClosed = !isDetailsSidebarOpen && prev.detailsOpen; + const diffSidePeekJustClosed = wasSidePeek && !isNowSidePeek; if (isNowSidePeek && isDetailsSidebarOpen) { // Details just opened while Diff is in side-peek → close Diff and remember if (detailsJustOpened) { - auto.diffClosedByDetails = true - setIsDiffSidebarOpen(false) + auto.diffClosedByDetails = true; + setIsDiffSidebarOpen(false); } // Diff just opened in side-peek mode → switch to dialog (don't close Details) // Skip if we're restoring Diff after Details closed else if (!prev.isOpen && !isRestoringDiffRef.current) { - setDiffDisplayMode("center-peek") + setDiffDisplayMode("center-peek"); } // User manually switched to side-peek while Diff was already open → close Details and remember else if (prev.isOpen && prev.mode !== "side-peek") { - auto.detailsClosedBy = "diff" - setIsDetailsSidebarOpen(false) + auto.detailsClosedBy = "diff"; + setIsDetailsSidebarOpen(false); } } // Diff side-peek closed → restore Details if we closed it else if (diffSidePeekJustClosed && auto.detailsClosedBy === "diff") { - auto.detailsClosedBy = null - setIsDetailsSidebarOpen(true) + auto.detailsClosedBy = null; + setIsDetailsSidebarOpen(true); } // Details closed → restore Diff if we closed it (in side-peek mode, not switching to dialog) else if (detailsJustClosed && auto.diffClosedByDetails) { - auto.diffClosedByDetails = false - isRestoringDiffRef.current = true - setIsDiffSidebarOpen(true) + auto.diffClosedByDetails = false; + isRestoringDiffRef.current = true; + setIsDiffSidebarOpen(true); // Reset flag after state update requestAnimationFrame(() => { - isRestoringDiffRef.current = false - }) + isRestoringDiffRef.current = false; + }); } - prevDiffStateRef.current = { isOpen: isDiffSidebarOpen, mode: diffDisplayMode, detailsOpen: isDetailsSidebarOpen } - }, [isDiffSidebarOpen, diffDisplayMode, isDetailsSidebarOpen, setDiffDisplayMode, setIsDetailsSidebarOpen, setIsDiffSidebarOpen]) + prevDiffStateRef.current = { + isOpen: isDiffSidebarOpen, + mode: diffDisplayMode, + detailsOpen: isDetailsSidebarOpen, + }; + }, [ + isDiffSidebarOpen, + diffDisplayMode, + isDetailsSidebarOpen, + setDiffDisplayMode, + setIsDetailsSidebarOpen, + setIsDiffSidebarOpen, + ]); // Hide traffic lights when full-page diff is open (they would overlap with content) useEffect(() => { - if (!isDesktop || isFullscreen) return - if (typeof window === "undefined" || !window.desktopApi?.setTrafficLightVisibility) return + if (!isDesktop || isFullscreen) return; + if ( + typeof window === "undefined" || + !window.desktopApi?.setTrafficLightVisibility + ) + return; if (isDiffSidebarOpen && diffDisplayMode === "full-page") { - window.desktopApi.setTrafficLightVisibility(false) + window.desktopApi.setTrafficLightVisibility(false); } - }, [isDiffSidebarOpen, diffDisplayMode, isDesktop, isFullscreen]) + }, [isDiffSidebarOpen, diffDisplayMode, isDesktop, isFullscreen]); // Track diff sidebar width for responsive header - const storedDiffSidebarWidth = useAtomValue(agentsDiffSidebarWidthAtom) - const diffSidebarRef = useRef(null) - const diffViewRef = useRef(null) + const storedDiffSidebarWidth = useAtomValue(agentsDiffSidebarWidthAtom); + const diffSidebarRef = useRef(null); + const diffViewRef = useRef(null); const [diffSidebarWidth, setDiffSidebarWidth] = useState( storedDiffSidebarWidth, - ) + ); // Track if all diff files are collapsed/expanded for button disabled states const [diffCollapseState, setDiffCollapseState] = useState({ allCollapsed: false, allExpanded: true, - }) + }); // Compute isNarrow for filtering logic (same threshold as DiffSidebarContent) - const isDiffSidebarNarrow = diffSidebarWidth < 500 + const isDiffSidebarNarrow = diffSidebarWidth < 500; // ResizeObserver to track diff sidebar width in real-time (atom only updates after resize ends) useEffect(() => { if (!isDiffSidebarOpen) { - return + return; } - let observer: ResizeObserver | null = null - let rafId: number | null = null + let observer: ResizeObserver | null = null; + let rafId: number | null = null; const checkRef = () => { - const element = diffSidebarRef.current + const element = diffSidebarRef.current; if (!element) { // Retry if ref not ready yet - rafId = requestAnimationFrame(checkRef) - return + rafId = requestAnimationFrame(checkRef); + return; } // Set initial width - setDiffSidebarWidth(element.offsetWidth || storedDiffSidebarWidth) + setDiffSidebarWidth(element.offsetWidth || storedDiffSidebarWidth); observer = new ResizeObserver((entries) => { for (const entry of entries) { - const width = entry.contentRect.width + const width = entry.contentRect.width; if (width > 0) { - setDiffSidebarWidth(width) + setDiffSidebarWidth(width); } } - }) + }); - observer.observe(element) - } + observer.observe(element); + }; - checkRef() + checkRef(); return () => { - if (rafId !== null) cancelAnimationFrame(rafId) - if (observer) observer.disconnect() - } - }, [isDiffSidebarOpen, storedDiffSidebarWidth]) + if (rafId !== null) cancelAnimationFrame(rafId); + if (observer) observer.disconnect(); + }; + }, [isDiffSidebarOpen, storedDiffSidebarWidth]); // Track changed files across all sub-chats for throttled diff refresh - const subChatFiles = useAtomValue(subChatFilesAtom) + const subChatFiles = useAtomValue(subChatFilesAtom); // Initialize to Date.now() to prevent double-fetch on mount // (the "mount" effect already fetches, throttle should wait) - const lastDiffFetchTimeRef = useRef(Date.now()) - const DIFF_THROTTLE_MS = 2000 // Max 1 fetch per 2 seconds + const lastDiffFetchTimeRef = useRef(Date.now()); + const DIFF_THROTTLE_MS = 2000; // Max 1 fetch per 2 seconds // Clear "unseen changes" when chat is opened useEffect(() => { setUnseenChanges((prev: Set) => { if (prev.has(chatId)) { - const next = new Set(prev) - next.delete(chatId) - return next + const next = new Set(prev); + next.delete(chatId); + return next; } - return prev - }) - }, [chatId, setUnseenChanges]) + return prev; + }); + }, [chatId, setUnseenChanges]); // Get sub-chat state from store (reactive subscription for tabsToRender) - const { - activeSubChatId, - openSubChatIds, - pinnedSubChatIds, - allSubChats, - } = useAgentSubChatStore( - useShallow((state) => ({ - activeSubChatId: state.activeSubChatId, - openSubChatIds: state.openSubChatIds, - pinnedSubChatIds: state.pinnedSubChatIds, - allSubChats: state.allSubChats, - })) - ) + const { activeSubChatId, openSubChatIds, pinnedSubChatIds, allSubChats } = + useAgentSubChatStore( + useShallow((state) => ({ + activeSubChatId: state.activeSubChatId, + openSubChatIds: state.openSubChatIds, + pinnedSubChatIds: state.pinnedSubChatIds, + allSubChats: state.allSubChats, + })), + ); // Clear sub-chat "unseen changes" indicator when sub-chat becomes active useEffect(() => { - if (!activeSubChatId) return + if (!activeSubChatId) return; setSubChatUnseenChanges((prev: Set) => { if (prev.has(activeSubChatId)) { - const next = new Set(prev) - next.delete(activeSubChatId) - return next + const next = new Set(prev); + next.delete(activeSubChatId); + return next; } - return prev - }) - }, [activeSubChatId, setSubChatUnseenChanges]) + return prev; + }); + }, [activeSubChatId, setSubChatUnseenChanges]); // tRPC utils for optimistic cache updates - const utils = api.useUtils() + const utils = api.useUtils(); // tRPC mutations for renaming - const renameSubChatMutation = api.agents.renameSubChat.useMutation() - const renameChatMutation = api.agents.renameChat.useMutation() + const renameSubChatMutation = api.agents.renameSubChat.useMutation(); + const renameChatMutation = api.agents.renameChat.useMutation(); const generateSubChatNameMutation = - api.agents.generateSubChatName.useMutation() + api.agents.generateSubChatName.useMutation(); // PR creation loading state - using atom to allow ChatViewInner to reset it - const [isCreatingPr, setIsCreatingPr] = useAtom(isCreatingPrAtom) + const [isCreatingPr, setIsCreatingPr] = useAtom(isCreatingPrAtom); // Review loading state - const [isReviewing, setIsReviewing] = useState(false) + const [isReviewing, setIsReviewing] = useState(false); // Subchat filter setter - used by handleReview to filter by active subchat - const setFilteredSubChatId = useSetAtom(filteredSubChatIdAtom) + const setFilteredSubChatId = useSetAtom(filteredSubChatIdAtom); // Determine if we're in sandbox mode - const chatSourceMode = useAtomValue(chatSourceModeAtom) + const chatSourceMode = useAtomValue(chatSourceModeAtom); // Fetch chat data from local or remote based on mode - const { data: localAgentChat, isLoading: isLocalLoading } = api.agents.getAgentChat.useQuery( - { chatId }, - { enabled: !!chatId && chatSourceMode === "local" }, - ) + const { data: localAgentChat, isLoading: isLocalLoading } = + api.agents.getAgentChat.useQuery( + { chatId }, + { enabled: !!chatId && chatSourceMode === "local" }, + ); const { data: remoteAgentChat, isLoading: isRemoteLoading } = useRemoteChat( chatSourceMode === "sandbox" ? chatId : null, - ) + ); // Use the appropriate data source // IMPORTANT: Must memoize to prevent infinite re-render loop @@ -4817,7 +5152,7 @@ export function ChatView({ // which triggers the useEffect that calls setAllSubChats(), causing re-renders const agentChat = useMemo(() => { if (chatSourceMode === "sandbox") { - if (!remoteAgentChat) return null + if (!remoteAgentChat) return null; return { ...remoteAgentChat, // Transform remote chat to match local structure @@ -4835,70 +5170,75 @@ export function ChatView({ isRemote: true, // Preserve stats from remote chat for diff display remoteStats: remoteAgentChat.stats, - subChats: remoteAgentChat.subChats?.map(sc => ({ - ...sc, - created_at: new Date(sc.created_at), - updated_at: new Date(sc.updated_at), - })) ?? [], - } + subChats: + remoteAgentChat.subChats?.map((sc) => ({ + ...sc, + created_at: new Date(sc.created_at), + updated_at: new Date(sc.updated_at), + })) ?? [], + }; } - return localAgentChat - }, [chatSourceMode, remoteAgentChat, localAgentChat]) + return localAgentChat; + }, [chatSourceMode, remoteAgentChat, localAgentChat]); - const isLoading = chatSourceMode === "sandbox" ? isRemoteLoading : isLocalLoading + const isLoading = + chatSourceMode === "sandbox" ? isRemoteLoading : isLocalLoading; // Compute if we're waiting for local chat data (used as loading gate) - const isLocalChatLoading = chatSourceMode === "local" && isLocalLoading + const isLocalChatLoading = chatSourceMode === "local" && isLocalLoading; // Projects query for "Open Locally" functionality - const { data: projects } = trpc.projects.list.useQuery() + const { data: projects } = trpc.projects.list.useQuery(); // Open Locally dialog state - const [openLocallyDialogOpen, setOpenLocallyDialogOpen] = useState(false) + const [openLocallyDialogOpen, setOpenLocallyDialogOpen] = useState(false); // Auto-import hook for "Open Locally" - const { getMatchingProjects, autoImport, isImporting } = useAutoImport() + const { getMatchingProjects, autoImport, isImporting } = useAutoImport(); // Handler for "Open Locally" button in header const handleOpenLocally = useCallback(() => { - if (!remoteAgentChat) return + if (!remoteAgentChat) return; - const matchingProjects = getMatchingProjects(projects ?? [], remoteAgentChat) + const matchingProjects = getMatchingProjects( + projects ?? [], + remoteAgentChat, + ); if (matchingProjects.length === 1) { // Auto-import: single match found - autoImport(remoteAgentChat, matchingProjects[0]!) + autoImport(remoteAgentChat, matchingProjects[0]!); } else { // Show dialog: 0 or 2+ matches - setOpenLocallyDialogOpen(true) + setOpenLocallyDialogOpen(true); } - }, [remoteAgentChat, projects, getMatchingProjects, autoImport]) + }, [remoteAgentChat, projects, getMatchingProjects, autoImport]); // Determine if "Open Locally" button should show - const showOpenLocally = chatSourceMode === "sandbox" && !!remoteAgentChat + const showOpenLocally = chatSourceMode === "sandbox" && !!remoteAgentChat; // Get matching projects for dialog (only computed when needed) const openLocallyMatchingProjects = useMemo(() => { - if (!remoteAgentChat) return [] - return getMatchingProjects(projects ?? [], remoteAgentChat) - }, [remoteAgentChat, projects, getMatchingProjects]) + if (!remoteAgentChat) return []; + return getMatchingProjects(projects ?? [], remoteAgentChat); + }, [remoteAgentChat, projects, getMatchingProjects]); const agentSubChats = (agentChat?.subChats ?? []) as Array<{ - id: string - name?: string | null - mode?: "plan" | "agent" | null - created_at?: Date | string | null - updated_at?: Date | string | null - messages?: any - stream_id?: string | null - }> + id: string; + name?: string | null; + mode?: "plan" | "agent" | null; + created_at?: Date | string | null; + updated_at?: Date | string | null; + messages?: any; + stream_id?: string | null; + }>; // Workspace isolation: limit mounted tabs to prevent memory growth // CRITICAL: Filter by workspace to prevent rendering sub-chats from other workspaces // Always render: active + pinned, then fill with recent up to limit - const MAX_MOUNTED_TABS = 5 + const MAX_MOUNTED_TABS = 5; const tabsToRender = useMemo(() => { - if (!activeSubChatId) return [] + if (!activeSubChatId) return []; // Combine server data (agentSubChats) with local store (allSubChats) for validation. // This handles: @@ -4907,125 +5247,151 @@ export function ChatView({ // // By combining both sources, we validate against all known sub-chats from both server and local state. const validSubChatIds = new Set([ - ...agentSubChats.map(sc => sc.id), - ...allSubChats.map(sc => sc.id), - ]) + ...agentSubChats.map((sc) => sc.id), + ...allSubChats.map((sc) => sc.id), + ]); // If active sub-chat doesn't belong to this workspace → return [] // This prevents rendering sub-chats from another workspace during race condition if (!validSubChatIds.has(activeSubChatId)) { - return [] + return []; } // Filter openSubChatIds and pinnedSubChatIds to only valid IDs for this workspace - const validOpenIds = openSubChatIds.filter(id => validSubChatIds.has(id)) - const validPinnedIds = pinnedSubChatIds.filter(id => validSubChatIds.has(id)) + const validOpenIds = openSubChatIds.filter((id) => validSubChatIds.has(id)); + const validPinnedIds = pinnedSubChatIds.filter((id) => + validSubChatIds.has(id), + ); // Start with active (must always be mounted) - const mustRender = new Set([activeSubChatId]) + const mustRender = new Set([activeSubChatId]); // Add pinned tabs (only valid ones) for (const id of validPinnedIds) { - mustRender.add(id) + mustRender.add(id); } // If we have room, add recent tabs from openSubChatIds (only valid ones) if (mustRender.size < MAX_MOUNTED_TABS) { - const remaining = MAX_MOUNTED_TABS - mustRender.size + const remaining = MAX_MOUNTED_TABS - mustRender.size; const recentTabs = validOpenIds - .filter(id => !mustRender.has(id)) - .slice(-remaining) // Take the most recent (end of array) + .filter((id) => !mustRender.has(id)) + .slice(-remaining); // Take the most recent (end of array) for (const id of recentTabs) { - mustRender.add(id) + mustRender.add(id); } } // Return tabs to render // Always include activeSubChatId even if not in validOpenIds (handles race condition // where openSubChatIds from localStorage doesn't include the active tab yet) - const result = validOpenIds.filter(id => mustRender.has(id)) + const result = validOpenIds.filter((id) => mustRender.has(id)); if (!result.includes(activeSubChatId)) { - result.unshift(activeSubChatId) + result.unshift(activeSubChatId); } - return result - }, [activeSubChatId, pinnedSubChatIds, openSubChatIds, allSubChats, agentSubChats]) + return result; + }, [ + activeSubChatId, + pinnedSubChatIds, + openSubChatIds, + allSubChats, + agentSubChats, + ]); // Get PR status when PR exists (for checking if it's open/merged/closed) - const hasPrNumber = !!agentChat?.prNumber - const { data: prStatusData, isLoading: isPrStatusLoading } = trpc.chats.getPrStatus.useQuery( - { chatId }, - { - enabled: hasPrNumber, - refetchInterval: 30000, // Poll every 30 seconds - } - ) - const prState = prStatusData?.pr?.state as "open" | "draft" | "merged" | "closed" | undefined - const prMergeable = prStatusData?.pr?.mergeable - const hasMergeConflicts = prMergeable === "CONFLICTING" + const hasPrNumber = !!agentChat?.prNumber; + const { data: prStatusData, isLoading: isPrStatusLoading } = + trpc.chats.getPrStatus.useQuery( + { chatId }, + { + enabled: hasPrNumber, + refetchInterval: 30000, // Poll every 30 seconds + }, + ); + const prState = prStatusData?.pr?.state as + | "open" + | "draft" + | "merged" + | "closed" + | undefined; + const prMergeable = prStatusData?.pr?.mergeable; + const hasMergeConflicts = prMergeable === "CONFLICTING"; // PR is open if state is explicitly "open" or "draft" // When PR status is still loading, assume open to avoid showing wrong button - const isPrOpen = hasPrNumber && (isPrStatusLoading || prState === "open" || prState === "draft") + const isPrOpen = + hasPrNumber && + (isPrStatusLoading || prState === "open" || prState === "draft"); // Merge PR mutation - const trpcUtils = trpc.useUtils() + const trpcUtils = trpc.useUtils(); // Direct PR creation mutation (push branch and open GitHub) const createPrMutation = trpc.changes.createPR.useMutation({ onSuccess: () => { - toast.success("Opening GitHub to create PR...", { position: "top-center" }) - refetchGitStatus() + toast.success("Opening GitHub to create PR...", { + position: "top-center", + }); + refetchGitStatus(); }, onError: (error) => { - toast.error(error.message || "Failed to create PR", { position: "top-center" }) + toast.error(error.message || "Failed to create PR", { + position: "top-center", + }); }, - }) + }); // Sync from main mutation (for resolving merge conflicts) const mergeFromDefaultMutation = trpc.changes.mergeFromDefault.useMutation({ onSuccess: () => { - toast.success("Branch synced with main. You can now merge the PR.", { position: "top-center" }) + toast.success("Branch synced with main. You can now merge the PR.", { + position: "top-center", + }); // Invalidate PR status to refresh mergeability - trpcUtils.chats.getPrStatus.invalidate({ chatId }) + trpcUtils.chats.getPrStatus.invalidate({ chatId }); }, onError: (error) => { - toast.error(error.message || "Failed to sync with main", { position: "top-center" }) + toast.error(error.message || "Failed to sync with main", { + position: "top-center", + }); }, - }) + }); const mergePrMutation = trpc.chats.mergePr.useMutation({ onSuccess: () => { - toast.success("PR merged successfully!", { position: "top-center" }) + toast.success("PR merged successfully!", { position: "top-center" }); // Invalidate PR status to update button state - trpcUtils.chats.getPrStatus.invalidate({ chatId }) + trpcUtils.chats.getPrStatus.invalidate({ chatId }); }, onError: (error) => { - const errorMsg = error.message || "Failed to merge PR" + const errorMsg = error.message || "Failed to merge PR"; // Check if it's a merge conflict error if (errorMsg.includes("MERGE_CONFLICT")) { - toast.error( - "PR has merge conflicts. Sync with main to resolve.", - { - position: "top-center", - duration: 8000, - action: worktreePath ? { - label: "Sync with Main", - onClick: () => { - mergeFromDefaultMutation.mutate({ worktreePath, useRebase: false }) - }, - } : undefined, - } - ) + toast.error("PR has merge conflicts. Sync with main to resolve.", { + position: "top-center", + duration: 8000, + action: worktreePath + ? { + label: "Sync with Main", + onClick: () => { + mergeFromDefaultMutation.mutate({ + worktreePath, + useRebase: false, + }); + }, + } + : undefined, + }); } else { - toast.error(errorMsg, { position: "top-center" }) + toast.error(errorMsg, { position: "top-center" }); } }, - }) + }); const handleMergePr = useCallback(() => { - mergePrMutation.mutate({ chatId, method: "squash" }) - }, [chatId, mergePrMutation]) + mergePrMutation.mutate({ chatId, method: "squash" }); + }, [chatId, mergePrMutation]); // Restore archived workspace mutation (silent - no toast) const restoreWorkspaceMutation = trpc.chats.restore.useMutation({ @@ -5033,98 +5399,100 @@ export function ChatView({ if (restoredChat) { // Update the main chat list cache trpcUtils.chats.list.setData({}, (oldData) => { - if (!oldData) return [restoredChat] - if (oldData.some((c) => c.id === restoredChat.id)) return oldData - return [restoredChat, ...oldData] - }) + if (!oldData) return [restoredChat]; + if (oldData.some((c) => c.id === restoredChat.id)) return oldData; + return [restoredChat, ...oldData]; + }); } // Invalidate both lists to refresh - trpcUtils.chats.list.invalidate() - trpcUtils.chats.listArchived.invalidate() + trpcUtils.chats.list.invalidate(); + trpcUtils.chats.listArchived.invalidate(); // Invalidate this chat's data to update isArchived state - utils.agents.getAgentChat.invalidate({ chatId }) + utils.agents.getAgentChat.invalidate({ chatId }); }, - }) + }); const handleRestoreWorkspace = useCallback(() => { - restoreWorkspaceMutation.mutate({ id: chatId }) - }, [chatId, restoreWorkspaceMutation]) + restoreWorkspaceMutation.mutate({ id: chatId }); + }, [chatId, restoreWorkspaceMutation]); // Check if this workspace is archived - const isArchived = !!agentChat?.archivedAt + const isArchived = !!agentChat?.archivedAt; // Get user usage data for credit checks - const { data: usageData } = api.usage.getUserUsage.useQuery() + const { data: usageData } = api.usage.getUserUsage.useQuery(); // Desktop: use worktreePath instead of sandbox - const worktreePath = agentChat?.worktreePath as string | null + const worktreePath = agentChat?.worktreePath as string | null; // Desktop: original project path for MCP config lookup - const originalProjectPath = (agentChat as any)?.project?.path as string | undefined + const originalProjectPath = (agentChat as any)?.project?.path as + | string + | undefined; // Fallback for web: use sandbox_id - const sandboxId = agentChat?.sandbox_id - const sandboxUrl = sandboxId ? `https://3003-${sandboxId}.e2b.app` : null + const sandboxId = agentChat?.sandbox_id; + const sandboxUrl = sandboxId ? `https://3003-${sandboxId}.e2b.app` : null; // Desktop uses worktreePath, web uses sandboxUrl - const chatWorkingDir = worktreePath || sandboxUrl + const chatWorkingDir = worktreePath || sandboxUrl; // Listen for file changes from Claude Write/Edit tools and invalidate git status - useFileChangeListener(worktreePath) + useFileChangeListener(worktreePath); // Subscribe to GitWatcher for real-time file system monitoring (chokidar on main process) - useGitWatcher(worktreePath) + useGitWatcher(worktreePath); // Extract port, repository, and quick setup flag from meta const meta = agentChat?.meta as { - sandboxConfig?: { port?: number } - repository?: string - branch?: string | null - isQuickSetup?: boolean - } | null - const repository = meta?.repository + sandboxConfig?: { port?: number }; + repository?: string; + branch?: string | null; + isQuickSetup?: boolean; + } | null; + const repository = meta?.repository; // Remote info for Details sidebar (when worktreePath is null but sandboxId exists) const remoteInfo = useMemo(() => { - if (worktreePath || !sandboxId) return null + if (worktreePath || !sandboxId) return null; return { repository: meta?.repository, branch: meta?.branch, sandboxId, - } - }, [worktreePath, sandboxId, meta?.repository, meta?.branch]) + }; + }, [worktreePath, sandboxId, meta?.repository, meta?.branch]); // Track if we've already triggered sandbox setup for this chat // Check if this is a quick setup (no preview available) - const isQuickSetup = meta?.isQuickSetup || !meta?.sandboxConfig?.port - const previewPort = meta?.sandboxConfig?.port ?? 3000 + const isQuickSetup = meta?.isQuickSetup || !meta?.sandboxConfig?.port; + const previewPort = meta?.sandboxConfig?.port ?? 3000; // Check if preview can be opened (sandbox with port exists and not quick setup) const canOpenPreview = !!( sandboxId && !isQuickSetup && meta?.sandboxConfig?.port - ) + ); // Check if diff button can be shown (stats available) // This shows the Changes button with stats in header - const canShowDiffButton = !!worktreePath || !!sandboxId + const canShowDiffButton = !!worktreePath || !!sandboxId; // Check if diff sidebar can be opened (actual diff content available) // Desktop remote chats (sandboxId without worktree) cannot open diff sidebar - only stats in header - const canOpenDiff = !!worktreePath || (!!sandboxId && !isDesktopApp()) + const canOpenDiff = !!worktreePath || (!!sandboxId && !isDesktopApp()); // Create list of subchats with changed files for filtering // Only include subchats that have uncommitted changes, sorted by most recent first const subChatsWithFiles = useMemo(() => { const result: Array<{ - id: string - name: string - filePaths: string[] - fileCount: number - updatedAt: string - }> = [] + id: string; + name: string; + filePaths: string[]; + fileCount: number; + updatedAt: string; + }> = []; // Only include subchats that have files (uncommitted changes) for (const subChat of allSubChats) { - const files = subChatFiles.get(subChat.id) || [] + const files = subChatFiles.get(subChat.id) || []; if (files.length > 0) { result.push({ id: subChat.id, @@ -5132,70 +5500,75 @@ export function ChatView({ filePaths: files.map((f) => f.filePath), fileCount: files.length, updatedAt: subChat.updated_at || subChat.created_at || "", - }) + }); } } // Sort by most recent first result.sort((a, b) => { - if (!a.updatedAt && !b.updatedAt) return 0 - if (!a.updatedAt) return 1 - if (!b.updatedAt) return -1 - return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() - }) + if (!a.updatedAt && !b.updatedAt) return 0; + if (!a.updatedAt) return 1; + if (!b.updatedAt) return -1; + return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(); + }); - return result - }, [allSubChats, subChatFiles]) + return result; + }, [allSubChats, subChatFiles]); // Close preview sidebar if preview becomes unavailable useEffect(() => { if (!canOpenPreview && isPreviewSidebarOpen) { - setIsPreviewSidebarOpen(false) + setIsPreviewSidebarOpen(false); } - }, [canOpenPreview, isPreviewSidebarOpen, setIsPreviewSidebarOpen]) + }, [canOpenPreview, isPreviewSidebarOpen, setIsPreviewSidebarOpen]); // Note: We no longer forcibly close diff sidebar when canOpenDiff is false. // The sidebar render is guarded by canOpenDiff, so it naturally hides. // Per-chat state (diffSidebarOpenAtomFamily) preserves each chat's preference. // Fetch diff stats - extracted as callback for reuse in onFinish - const fetchDiffStatsDebounceRef = useRef(null) - const isFetchingDiffRef = useRef(false) + const fetchDiffStatsDebounceRef = useRef(null); + const isFetchingDiffRef = useRef(false); const fetchDiffStats = useCallback(async () => { - console.log("[fetchDiffStats] Called with:", { worktreePath, sandboxId, chatId, isDesktop: isDesktopApp() }) + console.log("[fetchDiffStats] Called with:", { + worktreePath, + sandboxId, + chatId, + isDesktop: isDesktopApp(), + }); // Desktop uses worktreePath, web uses sandboxId // Don't reset stats if worktreePath is temporarily undefined - just skip the fetch // This prevents the button from becoming disabled when component re-renders if (!worktreePath && !sandboxId) { - console.log("[fetchDiffStats] Skipping - no worktreePath or sandboxId") - return + console.log("[fetchDiffStats] Skipping - no worktreePath or sandboxId"); + return; } // Prevent duplicate parallel fetches if (isFetchingDiffRef.current) { - console.log("[fetchDiffStats] Skipping - already fetching") - return + console.log("[fetchDiffStats] Skipping - already fetching"); + return; } - isFetchingDiffRef.current = true - console.log("[fetchDiffStats] Starting fetch...") + isFetchingDiffRef.current = true; + console.log("[fetchDiffStats] Starting fetch..."); try { // Desktop: use new getParsedDiff endpoint (all-in-one: parsing + file contents) if (worktreePath && chatId) { - const result = await trpcClient.chats.getParsedDiff.query({ chatId }) + const result = await trpcClient.chats.getParsedDiff.query({ chatId }); if (result.files.length > 0) { // Store parsed files directly (already parsed on server) - setParsedFileDiffs(result.files) + setParsedFileDiffs(result.files); // Store prefetched file contents - setPrefetchedFileContents(result.fileContents) + setPrefetchedFileContents(result.fileContents); // Set diff content to null since we have parsed files // (AgentDiffView will use parsedFileDiffs when available) - setDiffContent(null) + setDiffContent(null); setDiffStats({ fileCount: result.files.length, @@ -5203,7 +5576,7 @@ export function ChatView({ deletions: result.totalDeletions, isLoading: false, hasChanges: result.files.length > 0, - }) + }); } else { setDiffStats({ fileCount: 0, @@ -5211,30 +5584,33 @@ export function ChatView({ deletions: 0, isLoading: false, hasChanges: false, - }) + }); // Use empty array instead of null to signal "no changes" vs "still loading" - setParsedFileDiffs([]) - setPrefetchedFileContents({}) - setDiffContent(null) + setParsedFileDiffs([]); + setPrefetchedFileContents({}); + setDiffContent(null); } - return + return; } // Desktop without chat (viewing main repo directly) if (worktreePath && !chatId) { // TODO: Need to add endpoint that accepts worktreePath directly - return + return; } // Remote sandbox: use stats from chat data (desktop) or fetch diff (web) if (sandboxId) { - console.log("[fetchDiffStats] Sandbox mode - sandboxId:", sandboxId) + console.log("[fetchDiffStats] Sandbox mode - sandboxId:", sandboxId); // Desktop app: use stats already provided in chat data // The diff sidebar won't work for remote chats (no worktree), but stats will show if (isDesktopApp()) { - const remoteStats = (agentChat as any)?.remoteStats - console.log("[fetchDiffStats] Desktop remote chat - using remoteStats:", remoteStats) + const remoteStats = (agentChat as any)?.remoteStats; + console.log( + "[fetchDiffStats] Desktop remote chat - using remoteStats:", + remoteStats, + ); if (remoteStats) { setDiffStats({ @@ -5243,7 +5619,7 @@ export function ChatView({ deletions: remoteStats.deletions, isLoading: false, hasChanges: remoteStats.fileCount > 0, - }) + }); } else { setDiffStats({ fileCount: 0, @@ -5251,251 +5627,269 @@ export function ChatView({ deletions: 0, isLoading: false, hasChanges: false, - }) + }); } // No parsed files for remote chats - diff view not available - setParsedFileDiffs([]) - setPrefetchedFileContents({}) - setDiffContent(null) - return + setParsedFileDiffs([]); + setPrefetchedFileContents({}); + setDiffContent(null); + return; } // Web: use relative fetch to get actual diff - let rawDiff: string | null = null - const response = await fetch(`/api/agents/sandbox/${sandboxId}/diff`) + let rawDiff: string | null = null; + const response = await fetch(`/api/agents/sandbox/${sandboxId}/diff`); if (!response.ok) { - setDiffStats((prev) => ({ ...prev, isLoading: false })) - return + setDiffStats((prev) => ({ ...prev, isLoading: false })); + return; } - const data = await response.json() - rawDiff = data.diff || null + const data = await response.json(); + rawDiff = data.diff || null; // Store raw diff for AgentDiffView - console.log("[fetchDiffStats] Setting diff content, length:", rawDiff?.length ?? 0) - setDiffContent(rawDiff) + console.log( + "[fetchDiffStats] Setting diff content, length:", + rawDiff?.length ?? 0, + ); + setDiffContent(rawDiff); if (rawDiff && rawDiff.trim()) { // Parse diff to get file list and stats (client-side for web) - console.log("[fetchDiffStats] Parsing diff...") - const parsedFiles = splitUnifiedDiffByFile(rawDiff) - console.log("[fetchDiffStats] Parsed files:", parsedFiles.length, "files") - setParsedFileDiffs(parsedFiles) - - let additions = 0 - let deletions = 0 + console.log("[fetchDiffStats] Parsing diff..."); + const parsedFiles = splitUnifiedDiffByFile(rawDiff); + console.log( + "[fetchDiffStats] Parsed files:", + parsedFiles.length, + "files", + ); + setParsedFileDiffs(parsedFiles); + + let additions = 0; + let deletions = 0; for (const file of parsedFiles) { - additions += file.additions - deletions += file.deletions + additions += file.additions; + deletions += file.deletions; } - console.log("[fetchDiffStats] Setting stats:", { fileCount: parsedFiles.length, additions, deletions }) + console.log("[fetchDiffStats] Setting stats:", { + fileCount: parsedFiles.length, + additions, + deletions, + }); setDiffStats({ fileCount: parsedFiles.length, additions, deletions, isLoading: false, hasChanges: parsedFiles.length > 0, - }) + }); } else { - console.log("[fetchDiffStats] No diff content, setting empty stats") + console.log("[fetchDiffStats] No diff content, setting empty stats"); setDiffStats({ fileCount: 0, additions: 0, deletions: 0, isLoading: false, hasChanges: false, - }) + }); // Use empty array instead of null to signal "no changes" vs "still loading" - setParsedFileDiffs([]) - setPrefetchedFileContents({}) + setParsedFileDiffs([]); + setPrefetchedFileContents({}); } } } catch (error) { - console.error("[fetchDiffStats] Error:", error) - setDiffStats((prev) => ({ ...prev, isLoading: false })) + console.error("[fetchDiffStats] Error:", error); + setDiffStats((prev) => ({ ...prev, isLoading: false })); } finally { - console.log("[fetchDiffStats] Done") - isFetchingDiffRef.current = false + console.log("[fetchDiffStats] Done"); + isFetchingDiffRef.current = false; } - }, [worktreePath, sandboxId, chatId, agentChat]) // Note: activeSubChatId removed - diff is same for whole chat + }, [worktreePath, sandboxId, chatId, agentChat]); // Note: activeSubChatId removed - diff is same for whole chat // Debounced version for calling after stream ends const fetchDiffStatsDebounced = useCallback(() => { if (fetchDiffStatsDebounceRef.current) { - clearTimeout(fetchDiffStatsDebounceRef.current) + clearTimeout(fetchDiffStatsDebounceRef.current); } fetchDiffStatsDebounceRef.current = setTimeout(() => { - fetchDiffStats() - }, 500) // 500ms debounce to avoid spamming if multiple streams end - }, [fetchDiffStats]) + fetchDiffStats(); + }, 500); // 500ms debounce to avoid spamming if multiple streams end + }, [fetchDiffStats]); // Ref to hold the latest fetchDiffStatsDebounced for use in onFinish callbacks - const fetchDiffStatsRef = useRef(fetchDiffStatsDebounced) + const fetchDiffStatsRef = useRef(fetchDiffStatsDebounced); useEffect(() => { - fetchDiffStatsRef.current = fetchDiffStatsDebounced - }, [fetchDiffStatsDebounced]) + fetchDiffStatsRef.current = fetchDiffStatsDebounced; + }, [fetchDiffStatsDebounced]); // Fetch diff stats on mount and when worktreePath/sandboxId changes useEffect(() => { - fetchDiffStats() - }, [fetchDiffStats]) + fetchDiffStats(); + }, [fetchDiffStats]); // Refresh diff stats when diff sidebar opens (background refresh - don't block UI) // Keep existing data visible while fetching, only update if data changed useEffect(() => { if (isDiffSidebarOpen) { // Fetch in background - existing parsedFileDiffs will be shown immediately - fetchDiffStats() + fetchDiffStats(); } - }, [isDiffSidebarOpen, fetchDiffStats]) + }, [isDiffSidebarOpen, fetchDiffStats]); // Calculate total file count across all sub-chats for change detection const totalSubChatFileCount = useMemo(() => { - let count = 0 + let count = 0; subChatFiles.forEach((files) => { - count += files.length - }) - return count - }, [subChatFiles]) + count += files.length; + }); + return count; + }, [subChatFiles]); // Throttled refetch when sub-chat files change (agent edits/writes files) // This keeps the top-right diff sidebar in sync with the bottom "Generated X files" bar useEffect(() => { // Skip if no files tracked yet (initial state) - if (totalSubChatFileCount === 0) return + if (totalSubChatFileCount === 0) return; - const now = Date.now() - const timeSinceLastFetch = now - lastDiffFetchTimeRef.current + const now = Date.now(); + const timeSinceLastFetch = now - lastDiffFetchTimeRef.current; if (timeSinceLastFetch >= DIFF_THROTTLE_MS) { // Enough time passed, fetch immediately - lastDiffFetchTimeRef.current = now - fetchDiffStats() + lastDiffFetchTimeRef.current = now; + fetchDiffStats(); } else { // Schedule fetch for when throttle window ends - const delay = DIFF_THROTTLE_MS - timeSinceLastFetch + const delay = DIFF_THROTTLE_MS - timeSinceLastFetch; const timer = setTimeout(() => { - lastDiffFetchTimeRef.current = Date.now() - fetchDiffStats() - }, delay) - return () => clearTimeout(timer) + lastDiffFetchTimeRef.current = Date.now(); + fetchDiffStats(); + }, delay); + return () => clearTimeout(timer); } - }, [totalSubChatFileCount, fetchDiffStats]) + }, [totalSubChatFileCount, fetchDiffStats]); // Handle Create PR (Direct) - pushes branch and opens GitHub compare URL const handleCreatePrDirect = useCallback(async () => { if (!worktreePath) { - toast.error("No workspace path available", { position: "top-center" }) - return + toast.error("No workspace path available", { position: "top-center" }); + return; } - setIsCreatingPr(true) + setIsCreatingPr(true); try { - await createPrMutation.mutateAsync({ worktreePath }) + await createPrMutation.mutateAsync({ worktreePath }); } finally { - setIsCreatingPr(false) + setIsCreatingPr(false); } - }, [worktreePath, createPrMutation]) + }, [worktreePath, createPrMutation]); // Handle Create PR with AI - sends a message to Claude to create the PR - const setPendingPrMessage = useSetAtom(pendingPrMessageAtom) + const setPendingPrMessage = useSetAtom(pendingPrMessageAtom); const handleCreatePr = useCallback(async () => { if (!chatId) { - toast.error("Chat ID is required", { position: "top-center" }) - return + toast.error("Chat ID is required", { position: "top-center" }); + return; } - setIsCreatingPr(true) + setIsCreatingPr(true); try { // Get PR context from backend - const context = await trpcClient.chats.getPrContext.query({ chatId }) + const context = await trpcClient.chats.getPrContext.query({ chatId }); if (!context) { - toast.error("Could not get git context", { position: "top-center" }) - setIsCreatingPr(false) - return + toast.error("Could not get git context", { position: "top-center" }); + setIsCreatingPr(false); + return; } // Generate message and set it for ChatViewInner to send - const message = generatePrMessage(context) - setPendingPrMessage(message) + const message = generatePrMessage(context); + setPendingPrMessage(message); // Don't reset isCreatingPr here - it will be reset after message is sent } catch (error) { toast.error( error instanceof Error ? error.message : "Failed to prepare PR request", { position: "top-center" }, - ) - setIsCreatingPr(false) + ); + setIsCreatingPr(false); } - }, [chatId, setPendingPrMessage]) + }, [chatId, setPendingPrMessage]); // Handle Commit to existing PR - sends a message to Claude to commit and push // selectedPaths parameter is optional - if provided, only those files will be mentioned - const [isCommittingToPr, setIsCommittingToPr] = useState(false) - const handleCommitToPr = useCallback(async (_selectedPaths?: string[]) => { - if (!chatId) { - toast.error("Chat ID is required", { position: "top-center" }) - return - } - - try { - setIsCommittingToPr(true) - const context = await trpcClient.chats.getPrContext.query({ chatId }) - if (!context) { - toast.error("Could not get git context", { position: "top-center" }) - return + const [isCommittingToPr, setIsCommittingToPr] = useState(false); + const handleCommitToPr = useCallback( + async (_selectedPaths?: string[]) => { + if (!chatId) { + toast.error("Chat ID is required", { position: "top-center" }); + return; } - const message = generateCommitToPrMessage(context) - setPendingPrMessage(message) - } catch (error) { - toast.error( - error instanceof Error ? error.message : "Failed to prepare commit request", - { position: "top-center" }, - ) - } finally { - setIsCommittingToPr(false) - } - }, [chatId, setPendingPrMessage]) + try { + setIsCommittingToPr(true); + const context = await trpcClient.chats.getPrContext.query({ chatId }); + if (!context) { + toast.error("Could not get git context", { position: "top-center" }); + return; + } + + const message = generateCommitToPrMessage(context); + setPendingPrMessage(message); + } catch (error) { + toast.error( + error instanceof Error + ? error.message + : "Failed to prepare commit request", + { position: "top-center" }, + ); + } finally { + setIsCommittingToPr(false); + } + }, + [chatId, setPendingPrMessage], + ); // Handle Review - sends a message to Claude to review the diff - const setPendingReviewMessage = useSetAtom(pendingReviewMessageAtom) + const setPendingReviewMessage = useSetAtom(pendingReviewMessageAtom); const handleReview = useCallback(async () => { if (!chatId) { - toast.error("Chat ID is required", { position: "top-center" }) - return + toast.error("Chat ID is required", { position: "top-center" }); + return; } - setIsReviewing(true) + setIsReviewing(true); try { // Get PR context from backend - const context = await trpcClient.chats.getPrContext.query({ chatId }) + const context = await trpcClient.chats.getPrContext.query({ chatId }); if (!context) { - toast.error("Could not get git context", { position: "top-center" }) - return + toast.error("Could not get git context", { position: "top-center" }); + return; } // Set filter to show only files from the active subchat if (activeSubChatId) { - setFilteredSubChatId(activeSubChatId) + setFilteredSubChatId(activeSubChatId); } // Generate review message and set it for ChatViewInner to send - const message = generateReviewMessage(context) - setPendingReviewMessage(message) + const message = generateReviewMessage(context); + setPendingReviewMessage(message); } catch (error) { toast.error( error instanceof Error ? error.message : "Failed to start review", { position: "top-center" }, - ) + ); } finally { - setIsReviewing(false) + setIsReviewing(false); } - }, [chatId, activeSubChatId, setPendingReviewMessage, setFilteredSubChatId]) + }, [chatId, activeSubChatId, setPendingReviewMessage, setFilteredSubChatId]); // Handle Fix Conflicts - sends a message to Claude to sync with main and fix merge conflicts - const setPendingConflictResolutionMessage = useSetAtom(pendingConflictResolutionMessageAtom) + const setPendingConflictResolutionMessage = useSetAtom( + pendingConflictResolutionMessageAtom, + ); const handleFixConflicts = useCallback(() => { const message = `This PR has merge conflicts with the main branch. Please: @@ -5505,116 +5899,126 @@ export function ChatView({ 3. After resolving conflicts, commit the merge 4. Push the changes to update the PR -Make sure to preserve all functionality from both branches when resolving conflicts.` +Make sure to preserve all functionality from both branches when resolving conflicts.`; - setPendingConflictResolutionMessage(message) - }, [setPendingConflictResolutionMessage]) + setPendingConflictResolutionMessage(message); + }, [setPendingConflictResolutionMessage]); // Fetch branch data for diff sidebar header const { data: branchData } = trpc.changes.getBranches.useQuery( { worktreePath: worktreePath || "" }, - { enabled: !!worktreePath } - ) + { enabled: !!worktreePath }, + ); // Fetch git status for sync counts (pushCount, pullCount, hasUpstream) - const { data: gitStatus, refetch: refetchGitStatus, isLoading: isGitStatusLoading } = trpc.changes.getStatus.useQuery( + const { + data: gitStatus, + refetch: refetchGitStatus, + isLoading: isGitStatusLoading, + } = trpc.changes.getStatus.useQuery( { worktreePath: worktreePath || "" }, - { enabled: !!worktreePath && isDiffSidebarOpen, staleTime: 30000 } - ) + { enabled: !!worktreePath && isDiffSidebarOpen, staleTime: 30000 }, + ); // Refetch git status and diff stats when window gains focus useEffect(() => { - if (!worktreePath || !isDiffSidebarOpen) return + if (!worktreePath || !isDiffSidebarOpen) return; const handleWindowFocus = () => { // Refetch git status - refetchGitStatus() + refetchGitStatus(); // Refetch diff stats to get latest changes - fetchDiffStats() - } + fetchDiffStats(); + }; - window.addEventListener('focus', handleWindowFocus) - return () => window.removeEventListener('focus', handleWindowFocus) - }, [worktreePath, isDiffSidebarOpen, refetchGitStatus, fetchDiffStats]) + window.addEventListener("focus", handleWindowFocus); + return () => window.removeEventListener("focus", handleWindowFocus); + }, [worktreePath, isDiffSidebarOpen, refetchGitStatus, fetchDiffStats]); // Sync parsedFileDiffs with git status - clear diff data when all files are committed // This fixes the issue where diff sidebar shows stale files after external git commit useEffect(() => { - if (!gitStatus || isGitStatusLoading) return + if (!gitStatus || isGitStatusLoading) return; // Check if git status shows no uncommitted changes const hasUncommittedChanges = (gitStatus.staged?.length ?? 0) > 0 || (gitStatus.unstaged?.length ?? 0) > 0 || - (gitStatus.untracked?.length ?? 0) > 0 + (gitStatus.untracked?.length ?? 0) > 0; // If git shows no changes but we still have parsedFileDiffs, clear them - if (!hasUncommittedChanges && parsedFileDiffs && parsedFileDiffs.length > 0) { - console.log('[active-chat] Git status empty but parsedFileDiffs has files, refreshing diff data') - setParsedFileDiffs([]) - setPrefetchedFileContents({}) - setDiffContent(null) + if ( + !hasUncommittedChanges && + parsedFileDiffs && + parsedFileDiffs.length > 0 + ) { + console.log( + "[active-chat] Git status empty but parsedFileDiffs has files, refreshing diff data", + ); + setParsedFileDiffs([]); + setPrefetchedFileContents({}); + setDiffContent(null); setDiffStats({ fileCount: 0, additions: 0, deletions: 0, isLoading: false, hasChanges: false, - }) + }); } - }, [gitStatus, isGitStatusLoading, parsedFileDiffs]) + }, [gitStatus, isGitStatusLoading, parsedFileDiffs]); // Stable callbacks for DiffSidebarHeader to prevent re-renders const handleRefreshGitStatus = useCallback(() => { - refetchGitStatus() - }, [refetchGitStatus]) + refetchGitStatus(); + }, [refetchGitStatus]); const handleExpandAll = useCallback(() => { - diffViewRef.current?.expandAll() - }, []) + diffViewRef.current?.expandAll(); + }, []); const handleCollapseAll = useCallback(() => { - diffViewRef.current?.collapseAll() - }, []) + diffViewRef.current?.collapseAll(); + }, []); const handleMarkAllViewed = useCallback(() => { - diffViewRef.current?.markAllViewed() - }, []) + diffViewRef.current?.markAllViewed(); + }, []); const handleMarkAllUnviewed = useCallback(() => { - diffViewRef.current?.markAllUnviewed() - }, []) + diffViewRef.current?.markAllUnviewed(); + }, []); // Initialize store when chat data loads useEffect(() => { - if (!agentChat) return + if (!agentChat) return; - const store = useAgentSubChatStore.getState() + const store = useAgentSubChatStore.getState(); // Only initialize if chatId changed if (store.chatId !== chatId) { - store.setChatId(chatId) + store.setChatId(chatId); } // Re-get fresh state after setChatId may have loaded from localStorage - const freshState = useAgentSubChatStore.getState() + const freshState = useAgentSubChatStore.getState(); // Get sub-chats from DB (like Canvas - no isPersistedInDb flag) // Build a map of existing local sub-chats to preserve their created_at if DB doesn't have it const existingSubChatsMap = new Map( freshState.allSubChats.map((sc) => [sc.id, sc]), - ) + ); const dbSubChats: SubChatMeta[] = agentSubChats.map((sc) => { - const existingLocal = existingSubChatsMap.get(sc.id) + const existingLocal = existingSubChatsMap.get(sc.id); const createdAt = typeof sc.created_at === "string" ? sc.created_at - : sc.created_at?.toISOString() + : sc.created_at?.toISOString(); const updatedAt = typeof sc.updated_at === "string" ? sc.updated_at - : sc.updated_at?.toISOString() + : sc.updated_at?.toISOString(); return { id: sc.id, name: sc.name || "New Chat", @@ -5626,117 +6030,128 @@ Make sure to preserve all functionality from both branches when resolving confli (sc.mode as "plan" | "agent" | undefined) || existingLocal?.mode || "agent", - } - }) - const dbSubChatIds = new Set(dbSubChats.map((sc) => sc.id)) + }; + }); + const dbSubChatIds = new Set(dbSubChats.map((sc) => sc.id)); // Start with DB sub-chats - const allSubChats: SubChatMeta[] = [...dbSubChats] + const allSubChats: SubChatMeta[] = [...dbSubChats]; // For each open tab ID that's NOT in DB, add placeholder (like Canvas) // This prevents losing tabs during race conditions - const currentOpenIds = freshState.openSubChatIds + const currentOpenIds = freshState.openSubChatIds; currentOpenIds.forEach((id) => { if (!dbSubChatIds.has(id)) { allSubChats.push({ id, name: "New Chat", created_at: new Date().toISOString(), - }) + }); } - }) + }); - freshState.setAllSubChats(allSubChats) + freshState.setAllSubChats(allSubChats); // Initialize atomFamily mode for each sub-chat from database // This ensures new chats with mode="plan" use the correct mode for (const sc of dbSubChats) { if (sc.mode) { - appStore.set(subChatModeAtomFamily(sc.id), sc.mode) + appStore.set(subChatModeAtomFamily(sc.id), sc.mode); } } // All open tabs are now valid (we created placeholders for non-DB ones) - const validOpenIds = currentOpenIds + const validOpenIds = currentOpenIds; if (validOpenIds.length === 0 && allSubChats.length > 0) { // No valid open tabs, open the first sub-chat - freshState.addToOpenSubChats(allSubChats[0].id) - freshState.setActiveSubChat(allSubChats[0].id) + freshState.addToOpenSubChats(allSubChats[0].id); + freshState.setActiveSubChat(allSubChats[0].id); } else if (validOpenIds.length > 0) { // Validate active tab is in open tabs - const currentActive = freshState.activeSubChatId + const currentActive = freshState.activeSubChatId; if (!currentActive || !validOpenIds.includes(currentActive)) { - freshState.setActiveSubChat(validOpenIds[0]) + freshState.setActiveSubChat(validOpenIds[0]); } } - }, [agentChat, chatId]) + }, [agentChat, chatId]); // Auto-detect plan path from ACTIVE sub-chat messages when sub-chat changes // This ensures the plan sidebar shows the correct plan for the active sub-chat only useEffect(() => { - if (!agentSubChats || agentSubChats.length === 0 || !activeSubChatIdForPlan) { - setCurrentPlanPath(null) - return + if ( + !agentSubChats || + agentSubChats.length === 0 || + !activeSubChatIdForPlan + ) { + setCurrentPlanPath(null); + return; } // Find the active sub-chat - const activeSubChat = agentSubChats.find(sc => sc.id === activeSubChatIdForPlan) + const activeSubChat = agentSubChats.find( + (sc) => sc.id === activeSubChatIdForPlan, + ); if (!activeSubChat) { - setCurrentPlanPath(null) - return + setCurrentPlanPath(null); + return; } // Find last plan file path from active sub-chat only - let lastPlanPath: string | null = null - const messages = (activeSubChat.messages as any[]) || [] + let lastPlanPath: string | null = null; + const messages = (activeSubChat.messages as any[]) || []; for (const msg of messages) { - if (msg.role !== "assistant") continue - const parts = msg.parts || [] + if (msg.role !== "assistant") continue; + const parts = msg.parts || []; for (const part of parts) { if ( part.type === "tool-Write" && isPlanFile(part.input?.file_path || "") ) { - lastPlanPath = part.input.file_path + lastPlanPath = part.input.file_path; } } } - setCurrentPlanPath(lastPlanPath) - }, [agentSubChats, activeSubChatIdForPlan, setCurrentPlanPath]) + setCurrentPlanPath(lastPlanPath); + }, [agentSubChats, activeSubChatIdForPlan, setCurrentPlanPath]); // Create or get Chat instance for a sub-chat const getOrCreateChat = useCallback( (subChatId: string): Chat | null => { // Desktop uses worktreePath, web uses sandboxUrl if (!chatWorkingDir || !agentChat) { - return null + return null; } // Return existing chat if we have it - const existing = agentChatStore.get(subChatId) + const existing = agentChatStore.get(subChatId); if (existing) { - return existing + return existing; } // Find sub-chat data - const subChat = agentSubChats.find((sc) => sc.id === subChatId) - const messages = (subChat?.messages as any[]) || [] + const subChat = agentSubChats.find((sc) => sc.id === subChatId); + const messages = (subChat?.messages as any[]) || []; // Get mode from store metadata (falls back to currentMode) const subChatMeta = useAgentSubChatStore .getState() - .allSubChats.find((sc) => sc.id === subChatId) - const subChatMode = subChatMeta?.mode || currentMode + .allSubChats.find((sc) => sc.id === subChatId); + const subChatMode = subChatMeta?.mode || currentMode; // Create transport based on chat type (local worktree vs remote sandbox) // Note: Extended thinking setting is read dynamically inside the transport // projectPath: original project path for MCP config lookup (worktreePath is the cwd) - const projectPath = (agentChat as any)?.project?.path as string | undefined - const chatSandboxId = (agentChat as any)?.sandboxId || (agentChat as any)?.sandbox_id - const chatSandboxUrl = chatSandboxId ? `https://3003-${chatSandboxId}.e2b.app` : null - const isRemoteChat = !!(agentChat as any)?.isRemote || !!chatSandboxId + const projectPath = (agentChat as any)?.project?.path as + | string + | undefined; + const chatSandboxId = + (agentChat as any)?.sandboxId || (agentChat as any)?.sandbox_id; + const chatSandboxUrl = chatSandboxId + ? `https://3003-${chatSandboxId}.e2b.app` + : null; + const isRemoteChat = !!(agentChat as any)?.isRemote || !!chatSandboxId; console.log("[getOrCreateChat] Transport selection", { subChatId: subChatId.slice(-8), @@ -5744,15 +6159,19 @@ Make sure to preserve all functionality from both branches when resolving confli chatSandboxId, chatSandboxUrl, worktreePath: worktreePath ? "exists" : "none", - }) + }); - let transport: IPCChatTransport | RemoteChatTransport | null = null + let transport: IPCChatTransport | RemoteChatTransport | null = null; if (isRemoteChat && chatSandboxUrl) { // Remote sandbox chat: use HTTP SSE transport - const subChatName = subChat?.name || "Chat" - const modelString = MODEL_ID_MAP[selectedModelId] || MODEL_ID_MAP["opus"] - console.log("[getOrCreateChat] Using RemoteChatTransport", { sandboxUrl: chatSandboxUrl, model: modelString }) + const subChatName = subChat?.name || "Chat"; + const modelString = + MODEL_ID_MAP[selectedModelId] || MODEL_ID_MAP["opus"]; + console.log("[getOrCreateChat] Using RemoteChatTransport", { + sandboxUrl: chatSandboxUrl, + model: modelString, + }); transport = new RemoteChatTransport({ chatId, subChatId, @@ -5760,7 +6179,7 @@ Make sure to preserve all functionality from both branches when resolving confli sandboxUrl: chatSandboxUrl, mode: subChatMode, model: modelString, - }) + }); } else if (worktreePath) { // Local worktree chat: use IPC transport transport = new IPCChatTransport({ @@ -5769,12 +6188,12 @@ Make sure to preserve all functionality from both branches when resolving confli cwd: worktreePath, projectPath, mode: subChatMode, - }) + }); } if (!transport) { - console.error("[getOrCreateChat] No transport available") - return null + console.error("[getOrCreateChat] No transport available"); + return null; } const newChat = new Chat({ @@ -5783,76 +6202,78 @@ Make sure to preserve all functionality from both branches when resolving confli transport, onError: () => { // Sync status to global store on error (allows queue to continue) - useStreamingStatusStore.getState().setStatus(subChatId, "ready") + useStreamingStatusStore.getState().setStatus(subChatId, "ready"); }, // Clear loading when streaming completes (works even if component unmounted) onFinish: () => { - clearLoading(setLoadingSubChats, subChatId) + clearLoading(setLoadingSubChats, subChatId); // Sync status to global store for queue processing (even when component unmounted) - useStreamingStatusStore.getState().setStatus(subChatId, "ready") + useStreamingStatusStore.getState().setStatus(subChatId, "ready"); // Check if this was a manual abort (ESC/Ctrl+C) - skip sound if so const wasManuallyAborted = - agentChatStore.wasManuallyAborted(subChatId) - agentChatStore.clearManuallyAborted(subChatId) + agentChatStore.wasManuallyAborted(subChatId); + agentChatStore.clearManuallyAborted(subChatId); // Get CURRENT values at runtime (not stale closure values) const currentActiveSubChatId = - useAgentSubChatStore.getState().activeSubChatId - const currentSelectedChatId = appStore.get(selectedAgentChatIdAtom) + useAgentSubChatStore.getState().activeSubChatId; + const currentSelectedChatId = appStore.get(selectedAgentChatIdAtom); - const isViewingThisSubChat = currentActiveSubChatId === subChatId - const isViewingThisChat = currentSelectedChatId === chatId + const isViewingThisSubChat = currentActiveSubChatId === subChatId; + const isViewingThisChat = currentSelectedChatId === chatId; if (!isViewingThisSubChat) { setSubChatUnseenChanges((prev: Set) => { - const next = new Set(prev) - next.add(subChatId) - return next - }) + const next = new Set(prev); + next.add(subChatId); + return next; + }); } // Also mark parent chat as unseen if user is not viewing it if (!isViewingThisChat) { setUnseenChanges((prev: Set) => { - const next = new Set(prev) - next.add(chatId) - return next - }) + const next = new Set(prev); + next.add(chatId); + return next; + }); // Play completion sound only if NOT manually aborted and sound is enabled if (!wasManuallyAborted) { - const isSoundEnabled = appStore.get(soundNotificationsEnabledAtom) + const isSoundEnabled = appStore.get( + soundNotificationsEnabledAtom, + ); if (isSoundEnabled) { try { - const audio = new Audio("./sound.mp3") - audio.volume = 1.0 - audio.play().catch(() => {}) + const audio = new Audio("./sound.mp3"); + audio.volume = 1.0; + audio.play().catch(() => {}); } catch { // Ignore audio errors } } // Show native notification (desktop app, when window not focused) - notifyAgentComplete(agentChat?.name || "Agent") + notifyAgentComplete(agentChat?.name || "Agent"); } } // Refresh diff stats after agent finishes making changes - fetchDiffStatsRef.current() + fetchDiffStatsRef.current(); // Note: sidebar timestamp update is handled via optimistic update in handleSend // No need to refetch here as it would overwrite the optimistic update with stale data }, - }) + }); - agentChatStore.set(subChatId, newChat, chatId) + agentChatStore.set(subChatId, newChat, chatId); // Store streamId at creation time to prevent resume during active streaming // tRPC refetch would update stream_id in DB, but store stays stable - agentChatStore.setStreamId(subChatId, subChat?.stream_id || null) - forceUpdate({}) // Trigger re-render to use new chat - return newChat + agentChatStore.setStreamId(subChatId, subChat?.stream_id || null); + forceUpdate({}); // Trigger re-render to use new chat + return newChat; }, [ agentChat, @@ -5865,38 +6286,38 @@ Make sure to preserve all functionality from both branches when resolving confli setUnseenChanges, notifyAgentComplete, ], - ) + ); // Handle creating a new sub-chat const handleCreateNewSubChat = useCallback(async () => { - const store = useAgentSubChatStore.getState() + const store = useAgentSubChatStore.getState(); // New sub-chats use the user's default mode preference - const newSubChatMode = defaultAgentMode + const newSubChatMode = defaultAgentMode; // Check if this is a remote sandbox chat - const isRemoteChat = !!(agentChat as any)?.isRemote + const isRemoteChat = !!(agentChat as any)?.isRemote; - let newId: string + let newId: string; if (isRemoteChat) { // Sandbox mode: lazy creation (web app pattern) // Sub-chat will be persisted on first message via RemoteChatTransport UPSERT - newId = crypto.randomUUID() + newId = crypto.randomUUID(); } else { // Local mode: create sub-chat in DB first to get the real ID const newSubChat = await trpcClient.chats.createSubChat.mutate({ chatId, name: "New Chat", mode: newSubChatMode, - }) - newId = newSubChat.id - utils.agents.getAgentChat.invalidate({ chatId }) + }); + newId = newSubChat.id; + utils.agents.getAgentChat.invalidate({ chatId }); // Optimistic update: add new sub-chat to React Query cache immediately // This is CRITICAL for workspace isolation - without this, the new sub-chat // won't be in validSubChatIds and will be filtered out by tabsToRender utils.agents.getAgentChat.setData({ chatId }, (old) => { - if (!old) return old + if (!old) return old; return { ...old, subChats: [ @@ -5911,12 +6332,12 @@ Make sure to preserve all functionality from both branches when resolving confli stream_id: null, }, ], - } - }) + }; + }); } // Track this subchat as just created for typewriter effect - setJustCreatedIds((prev) => new Set([...prev, newId])) + setJustCreatedIds((prev) => new Set([...prev, newId])); // Add to allSubChats with placeholder name store.addToAllSubChats({ @@ -5924,34 +6345,41 @@ Make sure to preserve all functionality from both branches when resolving confli name: "New Chat", created_at: new Date().toISOString(), mode: newSubChatMode, - }) + }); // Set the mode atomFamily for the new sub-chat (so currentMode reads correct value) - appStore.set(subChatModeAtomFamily(newId), newSubChatMode) + appStore.set(subChatModeAtomFamily(newId), newSubChatMode); // Add to open tabs and set as active - store.addToOpenSubChats(newId) - store.setActiveSubChat(newId) + store.addToOpenSubChats(newId); + store.setActiveSubChat(newId); // Create empty Chat instance for the new sub-chat - const projectPath = (agentChat as any)?.project?.path as string | undefined - const newSubChatSandboxId = (agentChat as any)?.sandboxId || (agentChat as any)?.sandbox_id - const newSubChatSandboxUrl = newSubChatSandboxId ? `https://3003-${newSubChatSandboxId}.e2b.app` : null - const isNewSubChatRemote = !!(agentChat as any)?.isRemote || !!newSubChatSandboxId + const projectPath = (agentChat as any)?.project?.path as string | undefined; + const newSubChatSandboxId = + (agentChat as any)?.sandboxId || (agentChat as any)?.sandbox_id; + const newSubChatSandboxUrl = newSubChatSandboxId + ? `https://3003-${newSubChatSandboxId}.e2b.app` + : null; + const isNewSubChatRemote = + !!(agentChat as any)?.isRemote || !!newSubChatSandboxId; console.log("[createNewSubChat] Transport selection", { newId: newId.slice(-8), isNewSubChatRemote, newSubChatSandboxId, newSubChatSandboxUrl, - }) + }); - let newSubChatTransport: IPCChatTransport | RemoteChatTransport | null = null + let newSubChatTransport: IPCChatTransport | RemoteChatTransport | null = + null; if (isNewSubChatRemote && newSubChatSandboxUrl) { // Remote sandbox chat: use HTTP SSE transport - const modelString = MODEL_ID_MAP[selectedModelId] || MODEL_ID_MAP["opus"] - console.log("[createNewSubChat] Using RemoteChatTransport", { model: modelString }) + const modelString = MODEL_ID_MAP[selectedModelId] || MODEL_ID_MAP["opus"]; + console.log("[createNewSubChat] Using RemoteChatTransport", { + model: modelString, + }); newSubChatTransport = new RemoteChatTransport({ chatId, subChatId: newId, @@ -5959,7 +6387,7 @@ Make sure to preserve all functionality from both branches when resolving confli sandboxUrl: newSubChatSandboxUrl, mode: subChatMode, model: modelString, - }) + }); } else if (worktreePath) { // Local worktree chat: use IPC transport newSubChatTransport = new IPCChatTransport({ @@ -5968,11 +6396,11 @@ Make sure to preserve all functionality from both branches when resolving confli cwd: worktreePath, projectPath, mode: newSubChatMode, - }) + }); } if (newSubChatTransport) { - const transport = newSubChatTransport + const transport = newSubChatTransport; const newChat = new Chat({ id: newId, @@ -5980,71 +6408,73 @@ Make sure to preserve all functionality from both branches when resolving confli transport, onError: () => { // Sync status to global store on error (allows queue to continue) - useStreamingStatusStore.getState().setStatus(newId, "ready") + useStreamingStatusStore.getState().setStatus(newId, "ready"); }, // Clear loading when streaming completes onFinish: () => { - clearLoading(setLoadingSubChats, newId) + clearLoading(setLoadingSubChats, newId); // Sync status to global store for queue processing (even when component unmounted) - useStreamingStatusStore.getState().setStatus(newId, "ready") + useStreamingStatusStore.getState().setStatus(newId, "ready"); // Check if this was a manual abort (ESC/Ctrl+C) - skip sound if so - const wasManuallyAborted = agentChatStore.wasManuallyAborted(newId) - agentChatStore.clearManuallyAborted(newId) + const wasManuallyAborted = agentChatStore.wasManuallyAborted(newId); + agentChatStore.clearManuallyAborted(newId); // Get CURRENT values at runtime (not stale closure values) const currentActiveSubChatId = - useAgentSubChatStore.getState().activeSubChatId - const currentSelectedChatId = appStore.get(selectedAgentChatIdAtom) + useAgentSubChatStore.getState().activeSubChatId; + const currentSelectedChatId = appStore.get(selectedAgentChatIdAtom); - const isViewingThisSubChat = currentActiveSubChatId === newId - const isViewingThisChat = currentSelectedChatId === chatId + const isViewingThisSubChat = currentActiveSubChatId === newId; + const isViewingThisChat = currentSelectedChatId === chatId; if (!isViewingThisSubChat) { setSubChatUnseenChanges((prev: Set) => { - const next = new Set(prev) - next.add(newId) - return next - }) + const next = new Set(prev); + next.add(newId); + return next; + }); } // Also mark parent chat as unseen if user is not viewing it if (!isViewingThisChat) { setUnseenChanges((prev: Set) => { - const next = new Set(prev) - next.add(chatId) - return next - }) + const next = new Set(prev); + next.add(chatId); + return next; + }); // Play completion sound only if NOT manually aborted and sound is enabled if (!wasManuallyAborted) { - const isSoundEnabled = appStore.get(soundNotificationsEnabledAtom) + const isSoundEnabled = appStore.get( + soundNotificationsEnabledAtom, + ); if (isSoundEnabled) { try { - const audio = new Audio("./sound.mp3") - audio.volume = 1.0 - audio.play().catch(() => {}) + const audio = new Audio("./sound.mp3"); + audio.volume = 1.0; + audio.play().catch(() => {}); } catch { // Ignore audio errors } } // Show native notification (desktop app, when window not focused) - notifyAgentComplete(agentChat?.name || "Agent") + notifyAgentComplete(agentChat?.name || "Agent"); } } // Refresh diff stats after agent finishes making changes - fetchDiffStatsRef.current() + fetchDiffStatsRef.current(); // Note: sidebar timestamp update is handled via optimistic update in handleSend // No need to refetch here as it would overwrite the optimistic update with stale data }, - }) - agentChatStore.set(newId, newChat, chatId) - agentChatStore.setStreamId(newId, null) // New chat has no active stream - forceUpdate({}) // Trigger re-render + }); + agentChatStore.set(newId, newChat, chatId); + agentChatStore.setStreamId(newId, null); // New chat has no active stream + forceUpdate({}); // Trigger re-render } }, [ worktreePath, @@ -6057,64 +6487,73 @@ Make sure to preserve all functionality from both branches when resolving confli notifyAgentComplete, agentChat?.isRemote, agentChat?.name, - ]) + ]); // Keyboard shortcut: New sub-chat // Web: Opt+Cmd+T (browser uses Cmd+T for new tab) // Desktop: Cmd+T useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { - const isDesktop = isDesktopApp() + const isDesktop = isDesktopApp(); // Desktop: Cmd+T (without Alt) if (isDesktop && e.metaKey && e.code === "KeyT" && !e.altKey) { - e.preventDefault() - handleCreateNewSubChat() - return + e.preventDefault(); + handleCreateNewSubChat(); + return; } // Web: Opt+Cmd+T (with Alt) if (e.altKey && e.metaKey && e.code === "KeyT") { - e.preventDefault() - handleCreateNewSubChat() + e.preventDefault(); + handleCreateNewSubChat(); } - } + }; - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) - }, [handleCreateNewSubChat]) + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [handleCreateNewSubChat]); // NOTE: Desktop notifications for pending questions are now triggered directly // in ipc-chat-transport.ts when the ask-user-question chunk arrives. // This prevents duplicate notifications from multiple ChatView instances. // Multi-select state for sub-chats (for Cmd+W bulk close) - const selectedSubChatIds = useAtomValue(selectedSubChatIdsAtom) - const isSubChatMultiSelectMode = useAtomValue(isSubChatMultiSelectModeAtom) - const clearSubChatSelection = useSetAtom(clearSubChatSelectionAtom) + const selectedSubChatIds = useAtomValue(selectedSubChatIdsAtom); + const isSubChatMultiSelectMode = useAtomValue(isSubChatMultiSelectModeAtom); + const clearSubChatSelection = useSetAtom(clearSubChatSelectionAtom); // Helper to add sub-chat to undo stack - const addSubChatToUndoStack = useCallback((subChatId: string) => { - const timeoutId = setTimeout(() => { - setUndoStack((prev) => prev.filter( - (item) => !(item.type === "subchat" && item.subChatId === subChatId) - )) - }, 10000) - - setUndoStack((prev) => [...prev, { - type: "subchat", - subChatId, - chatId, - timeoutId, - }]) - }, [chatId, setUndoStack]) + const addSubChatToUndoStack = useCallback( + (subChatId: string) => { + const timeoutId = setTimeout(() => { + setUndoStack((prev) => + prev.filter( + (item) => + !(item.type === "subchat" && item.subChatId === subChatId), + ), + ); + }, 10000); + + setUndoStack((prev) => [ + ...prev, + { + type: "subchat", + subChatId, + chatId, + timeoutId, + }, + ]); + }, + [chatId, setUndoStack], + ); // Keyboard shortcut: Close active sub-chat (or bulk close if multi-select mode) // Web: Opt+Cmd+W (browser uses Cmd+W to close tab) // Desktop: Cmd+W useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { - const isDesktop = isDesktopApp() + const isDesktop = isDesktopApp(); // Desktop: Cmd+W (without Alt) const isDesktopShortcut = @@ -6123,56 +6562,61 @@ Make sure to preserve all functionality from both branches when resolving confli e.code === "KeyW" && !e.altKey && !e.shiftKey && - !e.ctrlKey + !e.ctrlKey; // Web: Opt+Cmd+W (with Alt) - const isWebShortcut = e.altKey && e.metaKey && e.code === "KeyW" + const isWebShortcut = e.altKey && e.metaKey && e.code === "KeyW"; if (isDesktopShortcut || isWebShortcut) { - e.preventDefault() + e.preventDefault(); - const store = useAgentSubChatStore.getState() + const store = useAgentSubChatStore.getState(); // If multi-select mode, bulk close selected sub-chats if (isSubChatMultiSelectMode && selectedSubChatIds.size > 0) { - const idsToClose = Array.from(selectedSubChatIds) + const idsToClose = Array.from(selectedSubChatIds); const remainingOpenIds = store.openSubChatIds.filter( (id) => !idsToClose.includes(id), - ) + ); // Don't close all tabs via hotkey - user should use sidebar dialog for last tab if (remainingOpenIds.length > 0) { idsToClose.forEach((id) => { - store.removeFromOpenSubChats(id) - addSubChatToUndoStack(id) - }) + store.removeFromOpenSubChats(id); + addSubChatToUndoStack(id); + }); } - clearSubChatSelection() - return + clearSubChatSelection(); + return; } // Otherwise close active sub-chat - const activeId = store.activeSubChatId - const openIds = store.openSubChatIds + const activeId = store.activeSubChatId; + const openIds = store.openSubChatIds; // Only close if we have more than one tab open and there's an active tab // removeFromOpenSubChats automatically switches to the last remaining tab if (activeId && openIds.length > 1) { - store.removeFromOpenSubChats(activeId) - addSubChatToUndoStack(activeId) + store.removeFromOpenSubChats(activeId); + addSubChatToUndoStack(activeId); } } - } + }; - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) - }, [isSubChatMultiSelectMode, selectedSubChatIds, clearSubChatSelection, addSubChatToUndoStack]) + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [ + isSubChatMultiSelectMode, + selectedSubChatIds, + clearSubChatSelection, + addSubChatToUndoStack, + ]); // Keyboard shortcut: Navigate between sub-chats // Web: Opt+Cmd+[ and Opt+Cmd+] (browser uses Cmd+[ for back) // Desktop: Cmd+[ and Cmd+] useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { - const isDesktop = isDesktopApp() + const isDesktop = isDesktopApp(); // Check for previous sub-chat shortcut ([ key) const isPrevDesktop = @@ -6181,41 +6625,41 @@ Make sure to preserve all functionality from both branches when resolving confli e.code === "BracketLeft" && !e.altKey && !e.shiftKey && - !e.ctrlKey - const isPrevWeb = e.altKey && e.metaKey && e.code === "BracketLeft" + !e.ctrlKey; + const isPrevWeb = e.altKey && e.metaKey && e.code === "BracketLeft"; if (isPrevDesktop || isPrevWeb) { - e.preventDefault() + e.preventDefault(); - const store = useAgentSubChatStore.getState() - const activeId = store.activeSubChatId - const openIds = store.openSubChatIds + const store = useAgentSubChatStore.getState(); + const activeId = store.activeSubChatId; + const openIds = store.openSubChatIds; // Only navigate if we have multiple tabs - if (openIds.length <= 1) return + if (openIds.length <= 1) return; // If no active tab, select first one if (!activeId) { - store.setActiveSubChat(openIds[0]) - return + store.setActiveSubChat(openIds[0]); + return; } // Find current index - const currentIndex = openIds.indexOf(activeId) + const currentIndex = openIds.indexOf(activeId); if (currentIndex === -1) { // Current tab not found, select first - store.setActiveSubChat(openIds[0]) - return + store.setActiveSubChat(openIds[0]); + return; } // Navigate to previous tab (cycle to end if at start) const nextIndex = - currentIndex - 1 < 0 ? openIds.length - 1 : currentIndex - 1 - const nextId = openIds[nextIndex] + currentIndex - 1 < 0 ? openIds.length - 1 : currentIndex - 1; + const nextId = openIds[nextIndex]; if (nextId) { - store.setActiveSubChat(nextId) + store.setActiveSubChat(nextId); } } @@ -6226,47 +6670,47 @@ Make sure to preserve all functionality from both branches when resolving confli e.code === "BracketRight" && !e.altKey && !e.shiftKey && - !e.ctrlKey - const isNextWeb = e.altKey && e.metaKey && e.code === "BracketRight" + !e.ctrlKey; + const isNextWeb = e.altKey && e.metaKey && e.code === "BracketRight"; if (isNextDesktop || isNextWeb) { - e.preventDefault() + e.preventDefault(); - const store = useAgentSubChatStore.getState() - const activeId = store.activeSubChatId - const openIds = store.openSubChatIds + const store = useAgentSubChatStore.getState(); + const activeId = store.activeSubChatId; + const openIds = store.openSubChatIds; // Only navigate if we have multiple tabs - if (openIds.length <= 1) return + if (openIds.length <= 1) return; // If no active tab, select first one if (!activeId) { - store.setActiveSubChat(openIds[0]) - return + store.setActiveSubChat(openIds[0]); + return; } // Find current index - const currentIndex = openIds.indexOf(activeId) + const currentIndex = openIds.indexOf(activeId); if (currentIndex === -1) { // Current tab not found, select first - store.setActiveSubChat(openIds[0]) - return + store.setActiveSubChat(openIds[0]); + return; } // Navigate to next tab (cycle to start if at end) - const nextIndex = (currentIndex + 1) % openIds.length - const nextId = openIds[nextIndex] + const nextIndex = (currentIndex + 1) % openIds.length; + const nextId = openIds[nextIndex]; if (nextId) { - store.setActiveSubChat(nextId) + store.setActiveSubChat(nextId); } } - } + }; - window.addEventListener("keydown", handleKeyDown) - return () => window.removeEventListener("keydown", handleKeyDown) - }, []) + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, []); // Keyboard shortcut: Cmd + D to toggle diff sidebar useEffect(() => { @@ -6279,18 +6723,17 @@ Make sure to preserve all functionality from both branches when resolving confli !e.ctrlKey && e.code === "KeyD" ) { - e.preventDefault() - e.stopPropagation() + e.preventDefault(); + e.stopPropagation(); // Toggle diff sidebar - setIsDiffSidebarOpen(!isDiffSidebarOpen) + setIsDiffSidebarOpen(!isDiffSidebarOpen); } - } - - window.addEventListener("keydown", handleKeyDown, true) - return () => window.removeEventListener("keydown", handleKeyDown, true) - }, [isDiffSidebarOpen]) + }; + window.addEventListener("keydown", handleKeyDown, true); + return () => window.removeEventListener("keydown", handleKeyDown, true); + }, [isDiffSidebarOpen]); // Keyboard shortcut: Cmd + Shift + E to restore archived workspace useEffect(() => { @@ -6303,16 +6746,16 @@ Make sure to preserve all functionality from both branches when resolving confli e.code === "KeyE" ) { if (isArchived && !restoreWorkspaceMutation.isPending) { - e.preventDefault() - e.stopPropagation() - handleRestoreWorkspace() + e.preventDefault(); + e.stopPropagation(); + handleRestoreWorkspace(); } } - } + }; - window.addEventListener("keydown", handleKeyDown, true) - return () => window.removeEventListener("keydown", handleKeyDown, true) - }, [isArchived, restoreWorkspaceMutation.isPending, handleRestoreWorkspace]) + window.addEventListener("keydown", handleKeyDown, true); + return () => window.removeEventListener("keydown", handleKeyDown, true); + }, [isArchived, restoreWorkspaceMutation.isPending, handleRestoreWorkspace]); // Handle auto-rename for sub-chat and parent chat // Receives subChatId as param to avoid stale closure issues @@ -6320,8 +6763,8 @@ Make sure to preserve all functionality from both branches when resolving confli (userMessage: string, subChatId: string) => { // Check if this is the first sub-chat using agentSubChats directly // to avoid race condition with store initialization - const firstSubChatId = getFirstSubChatId(agentSubChats) - const isFirst = firstSubChatId === subChatId + const firstSubChatId = getFirstSubChatId(agentSubChats); + const isFirst = firstSubChatId === subChatId; autoRenameAgentChat({ subChatId, @@ -6329,25 +6772,28 @@ Make sure to preserve all functionality from both branches when resolving confli userMessage, isFirstSubChat: isFirst, generateName: async (msg) => { - return generateSubChatNameMutation.mutateAsync({ userMessage: msg, ollamaModel: selectedOllamaModel }) + return generateSubChatNameMutation.mutateAsync({ + userMessage: msg, + ollamaModel: selectedOllamaModel, + }); }, renameSubChat: async (input) => { - await renameSubChatMutation.mutateAsync(input) + await renameSubChatMutation.mutateAsync(input); }, renameChat: async (input) => { - await renameChatMutation.mutateAsync(input) + await renameChatMutation.mutateAsync(input); }, updateSubChatName: (subChatIdToUpdate, name) => { // Update local store useAgentSubChatStore .getState() - .updateSubChatName(subChatIdToUpdate, name) + .updateSubChatName(subChatIdToUpdate, name); // Also update query cache so init effect doesn't overwrite utils.agents.getAgentChat.setData({ chatId }, (old) => { - if (!old) return old + if (!old) return old; const existsInCache = old.subChats.some( (sc) => sc.id === subChatIdToUpdate, - ) + ); if (!existsInCache) { // Sub-chat not in cache yet (DB save still in flight) - add it return { @@ -6365,15 +6811,15 @@ Make sure to preserve all functionality from both branches when resolving confli chat_id: chatId, }, ], - } + }; } return { ...old, subChats: old.subChats.map((sc) => sc.id === subChatIdToUpdate ? { ...sc, name } : sc, ), - } - }) + }; + }); }, updateChatName: (chatIdToUpdate, name) => { // Optimistic update for sidebar (list query) @@ -6381,22 +6827,22 @@ Make sure to preserve all functionality from both branches when resolving confli utils.agents.getAgentChats.setData( { teamId: selectedTeamId }, (old) => { - if (!old) return old + if (!old) return old; return old.map((c) => c.id === chatIdToUpdate ? { ...c, name } : c, - ) + ); }, - ) + ); // Optimistic update for header (single chat query) utils.agents.getAgentChat.setData( { chatId: chatIdToUpdate }, (old) => { - if (!old) return old - return { ...old, name } + if (!old) return old; + return { ...old, name }; }, - ) + ); }, - }) + }); }, [ chatId, @@ -6409,676 +6855,708 @@ Make sure to preserve all functionality from both branches when resolving confli utils.agents.getAgentChats, utils.agents.getAgentChat, ], - ) + ); // Get or create Chat instance for active sub-chat const activeChat = useMemo(() => { if (!activeSubChatId || !agentChat) { - return null + return null; } - return getOrCreateChat(activeSubChatId) - }, [activeSubChatId, agentChat, getOrCreateChat, chatId, chatWorkingDir]) + return getOrCreateChat(activeSubChatId); + }, [activeSubChatId, agentChat, getOrCreateChat, chatId, chatWorkingDir]); // Check if active sub-chat is the first one (for renaming parent chat) // Use agentSubChats directly to avoid race condition with store initialization const isFirstSubChatActive = useMemo(() => { - if (!activeSubChatId) return false - return getFirstSubChatId(agentSubChats) === activeSubChatId - }, [activeSubChatId, agentSubChats]) + if (!activeSubChatId) return false; + return getFirstSubChatId(agentSubChats) === activeSubChatId; + }, [activeSubChatId, agentSubChats]); // Determine if chat header should be hidden const shouldHideChatHeader = subChatsSidebarMode === "sidebar" && isPreviewSidebarOpen && isDiffSidebarOpen && - !isMobileFullscreen + !isMobileFullscreen; // No early return - let the UI render with loading state handled by activeChat check below return ( - - {/* File Search Dialog (Cmd+P) */} - {worktreePath && ( - - )} -
- {/* Main content */} -
- {/* Chat Panel */} -
- {/* SubChatSelector header - absolute when sidebar open (desktop only), regular div otherwise */} - {!shouldHideChatHeader && ( + + {/* File Search Dialog (Cmd+P) */} + {worktreePath && ( + + )} +
+ {/* Main content */} +
+ {/* Chat Panel */}
- {/* Gradient background - only when not absolute */} - {(isMobileFullscreen || subChatsSidebarMode !== "sidebar") && ( -
- )} -
-
- {/* Mobile header - simplified with chat name as trigger */} - {isMobileFullscreen ? ( - - ) : ( - <> - {/* Header controls - desktop only */} - - setIsDiffSidebarOpen(true) : undefined} - canOpenDiff={canShowDiffButton} - isDiffSidebarOpen={isDiffSidebarOpen} - diffStats={diffStats} - onOpenTerminal={() => setIsTerminalSidebarOpen(true)} - canOpenTerminal={!!worktreePath} - isTerminalOpen={isTerminalSidebarOpen} - chatId={chatId} - /> - - {/* Open Locally button - desktop only, sandbox mode */} - {showOpenLocally && ( - - - - - - Continue this session on your local machine - - - )} - + {/* SubChatSelector header - absolute when sidebar open (desktop only), regular div otherwise */} + {!shouldHideChatHeader && ( +
- {/* Open Preview Button - shows when preview is closed (desktop only, local mode only) */} - {!isMobileFullscreen && - !isPreviewSidebarOpen && - sandboxId && - chatSourceMode === "local" && - (canOpenPreview ? ( - - - - - Open preview - - ) : ( - - - - - - ))} - {/* Overview/Terminal Button - shows when sidebar is closed and worktree/sandbox exists (desktop only) */} - {!isMobileFullscreen && - (worktreePath || sandboxId) && ( - isUnifiedSidebarEnabled ? ( - // Details button for unified sidebar - !isDetailsSidebarOpen && ( + > + {/* Gradient background - only when not absolute */} + {(isMobileFullscreen || + subChatsSidebarMode !== "sidebar") && ( +
+ )} +
+
+ {/* Mobile header - simplified with chat name as trigger */} + {isMobileFullscreen ? ( + + ) : ( + <> + {/* Header controls - desktop only */} + + setIsDiffSidebarOpen(true) + : undefined + } + canOpenDiff={canShowDiffButton} + isDiffSidebarOpen={isDiffSidebarOpen} + diffStats={diffStats} + onOpenTerminal={() => + setIsTerminalSidebarOpen(true) + } + canOpenTerminal={!!worktreePath} + isTerminalOpen={isTerminalSidebarOpen} + chatId={chatId} + /> + {/**/} + {/* Open Locally button - desktop only, sandbox mode */} + {showOpenLocally && ( + + + + + + Continue this session on your local machine + + + )} + + )} +
+ {/* Open Preview Button - shows when preview is closed (desktop only, local mode only) */} + {!isMobileFullscreen && + !isPreviewSidebarOpen && + sandboxId && + chatSourceMode === "local" && + (canOpenPreview ? ( - - View details - {toggleDetailsHotkey && {toggleDetailsHotkey}} - + Open preview - ) - ) : ( - // Terminal button for legacy sidebars - !isTerminalSidebarOpen && ( - - + ) : ( + + - - - Open terminal - {toggleTerminalHotkey && {toggleTerminalHotkey}} - - - ) - ) - )} - {/* Restore Button - shows when viewing archived workspace (desktop only) */} - {!isMobileFullscreen && isArchived && ( - - - - - - Restore workspace - ⇧⌘E - - - )} -
-
- )} - - {/* Chat Content - Keep-alive: render all open tabs, hide inactive with CSS */} - {tabsToRender.length > 0 && agentChat ? ( -
- {/* Loading gate: prevent getOrCreateChat() from caching empty messages before data is ready */} - {isLocalChatLoading ? ( -
- -
- ) : ( - tabsToRender.map(subChatId => { - const chat = getOrCreateChat(subChatId) - const isActive = subChatId === activeSubChatId - const isFirstSubChat = getFirstSubChatId(agentSubChats) === subChatId - - // Defense in depth: double-check workspace ownership - // Use agentSubChats (server data) as primary source, fall back to allSubChats for optimistic updates - // This fixes the race condition where allSubChats is empty after setChatId but before setAllSubChats - const belongsToWorkspace = agentSubChats.some(sc => sc.id === subChatId) || - allSubChats.some(sc => sc.id === subChatId) - - if (!chat || !belongsToWorkspace) return null - - return ( - - ) - }) - )} -
- ) : ( - <> - {/* Empty chat area - no loading indicator */} -
- - {/* Disabled input while loading */} -
-
-
- -
- Plan, @ for context, / for commands -
- -
- {/* Mode selector placeholder */} - - - {/* Model selector placeholder */} - -
-
- {/* Attach button placeholder */} + + + ))} + {/* Overview/Terminal Button - shows when sidebar is closed and worktree/sandbox exists (desktop only) */} + {!isMobileFullscreen && + (worktreePath || sandboxId) && + (isUnifiedSidebarEnabled + ? // Details button for unified sidebar + !isDetailsSidebarOpen && ( + + + + + + View details + {toggleDetailsHotkey && ( + {toggleDetailsHotkey} + )} + + + ) + : // Terminal button for legacy sidebars + !isTerminalSidebarOpen && ( + + + + + + Open terminal + {toggleTerminalHotkey && ( + {toggleTerminalHotkey} + )} + + + ))} + {/* Restore Button - shows when viewing archived workspace (desktop only) */} + {!isMobileFullscreen && isArchived && ( + + + + + Restore workspace + ⇧⌘E + + + )} +
+
+ )} - {/* Send button */} -
- {}} - /> -
+ {/* Chat Content - Keep-alive: render all open tabs, hide inactive with CSS */} + {tabsToRender.length > 0 && agentChat ? ( +
+ {/* Loading gate: prevent getOrCreateChat() from caching empty messages before data is ready */} + {isLocalChatLoading ? ( +
+ +
+ ) : ( + tabsToRender.map((subChatId) => { + const chat = getOrCreateChat(subChatId); + const isActive = subChatId === activeSubChatId; + const isFirstSubChat = + getFirstSubChatId(agentSubChats) === subChatId; + + // Defense in depth: double-check workspace ownership + // Use agentSubChats (server data) as primary source, fall back to allSubChats for optimistic updates + // This fixes the race condition where allSubChats is empty after setChatId but before setAllSubChats + const belongsToWorkspace = + agentSubChats.some((sc) => sc.id === subChatId) || + allSubChats.some((sc) => sc.id === subChatId); + + if (!chat || !belongsToWorkspace) return null; + + return ( + - - -
+ ); + }) + )}
-
- - )} -
+ ) : ( + <> + {/* Empty chat area - no loading indicator */} +
+ + {/* Disabled input while loading */} +
+
+
+ +
+ Plan, @ for context, / for commands +
+ +
+ {/* Mode selector placeholder */} + + + {/* Model selector placeholder */} + +
+
+ {/* Attach button placeholder */} + + + {/* Send button */} +
+ {}} + /> +
+
+
+
+
+
+
+ + )} +
- {/* Plan Sidebar - shows plan files on the right (leftmost right sidebar) */} - {/* Only show when we have an active sub-chat with a plan */} - {!isMobileFullscreen && activeSubChatIdForPlan && ( - setIsPlanSidebarOpen(false)} - widthAtom={agentsPlanSidebarWidthAtom} - minWidth={400} - maxWidth={800} - side="right" - animationDuration={0} - initialWidth={0} - exitWidth={0} - showResizeTooltip={true} - className="bg-tl-background border-l" - style={{ borderLeftWidth: "0.5px" }} - > - setIsPlanSidebarOpen(false)} - onBuildPlan={handleApprovePlanFromSidebar} - refetchTrigger={planEditRefetchTrigger} - mode={currentMode} - /> - - )} + {/* Plan Sidebar - shows plan files on the right (leftmost right sidebar) */} + {/* Only show when we have an active sub-chat with a plan */} + {!isMobileFullscreen && activeSubChatIdForPlan && ( + setIsPlanSidebarOpen(false)} + widthAtom={agentsPlanSidebarWidthAtom} + minWidth={400} + maxWidth={800} + side="right" + animationDuration={0} + initialWidth={0} + exitWidth={0} + showResizeTooltip={true} + className="bg-tl-background border-l" + style={{ borderLeftWidth: "0.5px" }} + > + setIsPlanSidebarOpen(false)} + onBuildPlan={handleApprovePlanFromSidebar} + refetchTrigger={planEditRefetchTrigger} + mode={currentMode} + /> + + )} - {/* Diff View - hidden on mobile fullscreen and when diff is not available */} - {/* Supports three display modes: side-peek (sidebar), center-peek (dialog), full-page */} - {/* Wrapped in DiffStateProvider to isolate diff state and prevent ChatView re-renders */} - {canOpenDiff && !isMobileFullscreen && ( - - - - )} + {/* Diff View - hidden on mobile fullscreen and when diff is not available */} + {/* Supports three display modes: side-peek (sidebar), center-peek (dialog), full-page */} + {/* Wrapped in DiffStateProvider to isolate diff state and prevent ChatView re-renders */} + {canOpenDiff && !isMobileFullscreen && ( + + + + )} - {/* Preview Sidebar - hidden on mobile fullscreen and when preview is not available */} - {canOpenPreview && !isMobileFullscreen && ( - setIsPreviewSidebarOpen(false)} - widthAtom={agentsPreviewSidebarWidthAtom} - minWidth={350} - side="right" - animationDuration={0} - initialWidth={0} - exitWidth={0} - showResizeTooltip={true} - className="bg-tl-background border-l" - style={{ borderLeftWidth: "0.5px" }} - > - {isQuickSetup ? ( -
- {/* Header with close button */} -
- -
- {/* Content */} -
-
- - - - - + {/* Preview Sidebar - hidden on mobile fullscreen and when preview is not available */} + {canOpenPreview && !isMobileFullscreen && ( + setIsPreviewSidebarOpen(false)} + widthAtom={agentsPreviewSidebarWidthAtom} + minWidth={350} + side="right" + animationDuration={0} + initialWidth={0} + exitWidth={0} + showResizeTooltip={true} + className="bg-tl-background border-l" + style={{ borderLeftWidth: "0.5px" }} + > + {isQuickSetup ? ( +
+ {/* Header with close button */} +
+ +
+ {/* Content */} +
+
+ + + + + +
+

+ Preview not available +

+

+ Set up this repository to enable live preview +

+
-

- Preview not available -

-

- Set up this repository to enable live preview -

-
-
- ) : ( - setIsPreviewSidebarOpen(false)} + /> + )} + + )} + + {/* File Viewer - opens when a file is clicked */} + {!isMobileFullscreen && + fileViewerPath && + worktreePath && + fileViewerDisplayMode === "side-peek" && ( + setFileViewerPath(null)} + widthAtom={fileViewerSidebarWidthAtom} + minWidth={350} + maxWidth={900} + side="right" + animationDuration={0} + initialWidth={0} + exitWidth={0} + showResizeTooltip={true} + className="bg-tl-background border-l" + style={{ borderLeftWidth: "0.5px" }} + > + setFileViewerPath(null)} + /> + + )} + {fileViewerPath && + worktreePath && + fileViewerDisplayMode === "center-peek" && ( + setFileViewerPath(null)} + > + setFileViewerPath(null)} + /> + + )} + {fileViewerPath && + worktreePath && + fileViewerDisplayMode === "full-page" && ( + setFileViewerPath(null)} + > + setFileViewerPath(null)} + /> + + )} + + {/* Terminal Sidebar - shows when worktree exists (desktop only) */} + {worktreePath && ( + setIsPreviewSidebarOpen(false)} + cwd={worktreePath} + workspaceId={chatId} /> )} - - )} - {/* File Viewer - opens when a file is clicked */} - {!isMobileFullscreen && fileViewerPath && worktreePath && fileViewerDisplayMode === "side-peek" && ( - setFileViewerPath(null)} - widthAtom={fileViewerSidebarWidthAtom} - minWidth={350} - maxWidth={900} - side="right" - animationDuration={0} - initialWidth={0} - exitWidth={0} - showResizeTooltip={true} - className="bg-tl-background border-l" - style={{ borderLeftWidth: "0.5px" }} - > - setFileViewerPath(null)} - /> - - )} - {fileViewerPath && worktreePath && fileViewerDisplayMode === "center-peek" && ( - setFileViewerPath(null)} - > - setFileViewerPath(null)} + {/* Open Locally Dialog - for importing sandbox chats to local */} + setOpenLocallyDialogOpen(false)} + remoteChat={remoteAgentChat ?? null} + matchingProjects={openLocallyMatchingProjects} + allProjects={projects ?? []} + remoteSubChatId={activeSubChatId} /> - - )} - {fileViewerPath && worktreePath && fileViewerDisplayMode === "full-page" && ( - setFileViewerPath(null)} - > - setFileViewerPath(null)} - /> - - )} - {/* Terminal Sidebar - shows when worktree exists (desktop only) */} - {worktreePath && ( - - )} - - {/* Open Locally Dialog - for importing sandbox chats to local */} - setOpenLocallyDialogOpen(false)} - remoteChat={remoteAgentChat ?? null} - matchingProjects={openLocallyMatchingProjects} - allProjects={projects ?? []} - remoteSubChatId={activeSubChatId} - /> - - {/* Unified Details Sidebar - combines all right sidebars into one (rightmost) */} - {/* Show for both local (worktreePath) and remote (sandboxId) chats */} - {isUnifiedSidebarEnabled && !isMobileFullscreen && (worktreePath || sandboxId) && ( - setIsTerminalSidebarOpen(true)} - onExpandPlan={() => setIsPlanSidebarOpen(true)} - onExpandDiff={() => setIsDiffSidebarOpen(true)} - onFileSelect={(filePath) => { - // Set the selected file path - setSelectedFilePath(filePath) - // Set filtered files to just this file - setFilteredDiffFiles([filePath]) - // Open the diff sidebar - setIsDiffSidebarOpen(true) - }} - remoteInfo={remoteInfo} - isRemoteChat={!!remoteInfo} - /> - )} -
+ {/* Unified Details Sidebar - combines all right sidebars into one (rightmost) */} + {/* Show for both local (worktreePath) and remote (sandboxId) chats */} + {isUnifiedSidebarEnabled && + !isMobileFullscreen && + (worktreePath || sandboxId) && ( + setIsTerminalSidebarOpen(true)} + onExpandPlan={() => setIsPlanSidebarOpen(true)} + onExpandDiff={() => setIsDiffSidebarOpen(true)} + onFileSelect={(filePath) => { + // Set the selected file path + setSelectedFilePath(filePath); + // Set filtered files to just this file + setFilteredDiffFiles([filePath]); + // Open the diff sidebar + setIsDiffSidebarOpen(true); + }} + remoteInfo={remoteInfo} + isRemoteChat={!!remoteInfo} + /> + )} +
- {/* Terminal Bottom Panel — renders below the main row when displayMode is "bottom" */} - {terminalDisplayMode === "bottom" && worktreePath && !isMobileFullscreen && ( - setIsTerminalSidebarOpen(false)} - heightAtom={terminalBottomHeightAtom} - minHeight={150} - maxHeight={500} - showResizeTooltip={true} - closeHotkey={toggleTerminalHotkey ?? undefined} - className="bg-background border-t" - style={{ borderTopWidth: "0.5px" }} - > - setIsTerminalSidebarOpen(false)} - /> - - )} -
- + {/* Terminal Bottom Panel — renders below the main row when displayMode is "bottom" */} + {terminalDisplayMode === "bottom" && + worktreePath && + !isMobileFullscreen && ( + setIsTerminalSidebarOpen(false)} + heightAtom={terminalBottomHeightAtom} + minHeight={150} + maxHeight={500} + showResizeTooltip={true} + closeHotkey={toggleTerminalHotkey ?? undefined} + className="bg-background border-t" + style={{ borderTopWidth: "0.5px" }} + > + setIsTerminalSidebarOpen(false)} + /> + + )} +
+ - ) + ); } diff --git a/src/renderer/features/agents/main/chat-input-area.tsx b/src/renderer/features/agents/main/chat-input-area.tsx index 37a6fe1e6..2671865c4 100644 --- a/src/renderer/features/agents/main/chat-input-area.tsx +++ b/src/renderer/features/agents/main/chat-input-area.tsx @@ -85,6 +85,13 @@ import { } from "../../../lib/hooks/use-voice-recording" import { getResolvedHotkey } from "../../../lib/hotkeys" import { customHotkeysAtom } from "../../../lib/atoms" +import { ProfileSelector, useEffectiveProfile, useEffectiveModel } from "../ui/profile-selector" +import { + modelProfilesAtom, + lastUsedProfileIdAtom, + selectedProfileModelIdAtom, + type ModelMapping, +} from "../../../lib/atoms" // Hook to get available models (including offline models if Ollama is available and debug enabled) function useAvailableModels() { @@ -177,6 +184,9 @@ export interface ChatInputAreaProps { onInputContentChange?: (hasContent: boolean) => void // Callback to send message with question answer (Enter sends immediately, not to queue) onSubmitWithQuestionAnswer?: () => void + // Per-workspace model profile (from chat data) + chatModelProfileId?: string | null + chatSelectedModelId?: string | null } /** @@ -372,12 +382,95 @@ export const ChatInputArea = memo(function ChatInputArea({ firstQueueItemId, onInputContentChange, onSubmitWithQuestionAnswer, + chatModelProfileId, + chatSelectedModelId, }: ChatInputAreaProps) { // Local state - changes here don't re-render parent const [hasContent, setHasContent] = useState(false) const [isFocused, setIsFocused] = useState(false) const [isDragOver, setIsDragOver] = useState(false) + // Get effective profile and model from per-workspace settings + const effectiveProfile = useEffectiveProfile(chatModelProfileId) + const effectiveModel = useEffectiveModel(effectiveProfile, chatSelectedModelId) + + // Derive available models from profile config (like settings do) + const profileModels = useMemo((): ModelMapping[] => { + if (!effectiveProfile?.config) return [] + const models: ModelMapping[] = [] + const config = effectiveProfile.config + + // Add main model + if (config.model) { + models.push({ + id: "main", + displayName: "Main", + modelId: config.model, + supportsThinking: true, + }) + } + + // Add Opus model + if (config.defaultOpusModel) { + models.push({ + id: "opus", + displayName: "Opus", + modelId: config.defaultOpusModel, + supportsThinking: true, + }) + } + + // Add Sonnet model + if (config.defaultSonnetModel) { + models.push({ + id: "sonnet", + displayName: "Sonnet", + modelId: config.defaultSonnetModel, + supportsThinking: true, + }) + } + + // Add Haiku model + if (config.defaultHaikuModel) { + models.push({ + id: "haiku", + displayName: "Haiku", + modelId: config.defaultHaikuModel, + supportsThinking: false, + }) + } + + // Add Subagent model + if (config.subagentModel) { + models.push({ + id: "subagent", + displayName: "Subagent", + modelId: config.subagentModel, + supportsThinking: false, + }) + } + + return models + }, [effectiveProfile?.config]) + + // Selected model within profile - use global atom so ipc-chat-transport can read it + const [selectedProfileModelId, setSelectedProfileModelId] = useAtom(selectedProfileModelIdAtom) + + // Get selected model from profile models + const selectedProfileModel = useMemo((): ModelMapping | null => { + if (profileModels.length === 0) return null + if (selectedProfileModelId) { + const found = profileModels.find((m) => m.id === selectedProfileModelId) + if (found) return found + } + return profileModels[0] ?? null + }, [profileModels, selectedProfileModelId]) + + // Reset selected model when profile changes + useEffect(() => { + setSelectedProfileModelId(null) + }, [effectiveProfile?.id]) + // Mention dropdown state const [showMentionDropdown, setShowMentionDropdown] = useState(false) const [mentionSearchText, setMentionSearchText] = useState("") @@ -1134,9 +1227,15 @@ export const ChatInputArea = memo(function ChatInputArea({
- - {/* Model selector - shows Ollama models when offline, Claude models when online */} + {/* Profile selector - per-workspace model profile */} + + + {/* Model selector - shows Ollama models when offline, profile models, or Claude models */} {availableModels.isOffline && availableModels.hasOllama ? ( // Offline mode: show Ollama model selector - ) : ( - // Online mode: show Claude model selector - // Model selector works with custom profiles - selections map to profile's model settings + ) : profileModels.length > 0 ? ( + // Profile mode: show models from selected profile config + + {profileModels.map((model) => { + const isSelected = model.id === selectedProfileModel?.id + return ( + setSelectedProfileModelId(model.id)} + className="gap-2 justify-between" + > +
+ + + {model.displayName}{" "} + + ({model.modelId}) + + +
+ {isSelected && ( + + )} +
+ ) + })} +
+
+ ) : ( + // Fallback: show Claude model selector (no profile or empty profile) + + + + {availableModels.models.map((model) => { const isSelected = selectedModel?.id === model.id @@ -1216,17 +1355,8 @@ export const ChatInputArea = memo(function ChatInputArea({ className="gap-2 justify-between" >
- {model.supportsThinking ? ( - - ) : ( - - )} - - {model.name} - {!hasCustomClaudeConfig && model.id !== "default" && ( - null - )} - + + {model.name}
{isSelected && ( diff --git a/src/renderer/features/agents/main/new-chat-form.tsx b/src/renderer/features/agents/main/new-chat-form.tsx index e38a3cd5f..5914aa389 100644 --- a/src/renderer/features/agents/main/new-chat-form.tsx +++ b/src/renderer/features/agents/main/new-chat-form.tsx @@ -9,6 +9,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, } from "../../../components/ui/dropdown-menu" import { @@ -57,6 +58,11 @@ import { customHotkeysAtom, chatSourceModeAtom, extendedThinkingEnabledAtom, + modelProfilesAtom, + lastUsedProfileIdAtom, + selectedProfileModelIdAtom, + type ModelProfile, + type ModelMapping, } from "../../../lib/atoms" // Desktop uses real tRPC import { toast } from "sonner" @@ -109,6 +115,7 @@ import { type DraftProject, } from "../lib/drafts" import { CLAUDE_MODELS } from "../lib/models" +import { Settings } from "lucide-react" // import type { PlanType } from "@/lib/config/subscription-plans" type PlanType = string @@ -235,6 +242,111 @@ export function NewChatForm({ const normalizedCustomClaudeConfig = normalizeCustomClaudeConfig(customClaudeConfig) const hasCustomClaudeConfig = Boolean(normalizedCustomClaudeConfig) + + // Model profile state for new chats + const profiles = useAtomValue(modelProfilesAtom) + const [lastUsedProfileId, setLastUsedProfileId] = useAtom(lastUsedProfileIdAtom) + + // Filter out offline profiles from display + const displayProfiles = profiles.filter((p) => !p.isOffline) + + // Get effective profile (lastUsed or first available) + const effectiveProfile = useMemo(() => { + if (lastUsedProfileId) { + const found = profiles.find((p) => p.id === lastUsedProfileId) + if (found) return found + } + return displayProfiles[0] ?? null + }, [profiles, displayProfiles, lastUsedProfileId]) + + // Selected model within profile - use global atom so ipc-chat-transport can read it + const [selectedProfileModelId, setSelectedProfileModelId] = useAtom(selectedProfileModelIdAtom) + + // Get effective model from profile + const effectiveProfileModel = useMemo((): ModelMapping | null => { + if (!effectiveProfile?.models || effectiveProfile.models.length === 0) { + return null + } + if (selectedProfileModelId) { + const found = effectiveProfile.models.find((m) => m.id === selectedProfileModelId) + if (found) return found + } + return effectiveProfile.models[0] ?? null + }, [effectiveProfile, selectedProfileModelId]) + + // Derive available models from profile config (like settings do) + const profileModels = useMemo((): ModelMapping[] => { + if (!effectiveProfile?.config) return [] + const models: ModelMapping[] = [] + const config = effectiveProfile.config + + // Add main model + if (config.model) { + models.push({ + id: "main", + displayName: "Main", + modelId: config.model, + supportsThinking: true, + }) + } + + // Add Opus model + if (config.defaultOpusModel) { + models.push({ + id: "opus", + displayName: "Opus", + modelId: config.defaultOpusModel, + supportsThinking: true, + }) + } + + // Add Sonnet model + if (config.defaultSonnetModel) { + models.push({ + id: "sonnet", + displayName: "Sonnet", + modelId: config.defaultSonnetModel, + supportsThinking: true, + }) + } + + // Add Haiku model + if (config.defaultHaikuModel) { + models.push({ + id: "haiku", + displayName: "Haiku", + modelId: config.defaultHaikuModel, + supportsThinking: false, + }) + } + + // Add Subagent model + if (config.subagentModel) { + models.push({ + id: "subagent", + displayName: "Subagent", + modelId: config.subagentModel, + supportsThinking: false, + }) + } + + return models + }, [effectiveProfile?.config]) + + // Get selected model from profile models + const selectedProfileModel = useMemo((): ModelMapping | null => { + if (profileModels.length === 0) return null + if (selectedProfileModelId) { + const found = profileModels.find((m) => m.id === selectedProfileModelId) + if (found) return found + } + return profileModels[0] ?? null + }, [profileModels, selectedProfileModelId]) + + // Reset selected model when profile changes + useEffect(() => { + setSelectedProfileModelId(null) + }, [effectiveProfile?.id]) const setSettingsDialogOpen = useSetAtom(agentsSettingsDialogOpenAtom) const setSettingsActiveTab = useSetAtom(agentsSettingsDialogActiveTabAtom) const setJustCreatedIds = useSetAtom(justCreatedIdsAtom) @@ -1559,7 +1671,49 @@ export function NewChatForm({
- {/* Model selector - shows Ollama models when offline, Claude models when online */} + {/* Profile selector - only show when profiles exist */} + {displayProfiles.length > 0 && ( + + + + + + {displayProfiles.map((profile) => { + const isSelected = profile.id === effectiveProfile?.id + return ( + setLastUsedProfileId(profile.id)} + className="gap-2 justify-between" + > + {profile.name} + {isSelected && } + + ) + })} + + { + setSettingsActiveTab("models") + setSettingsDialogOpen(true) + }} + className="gap-2" + > + + Manage Profiles... + + + + )} + + {/* Model selector - shows profile models, Ollama models when offline, or Claude models */} {availableModels.isOffline && availableModels.hasOllama ? ( // Offline mode: show Ollama model selector + ) : profileModels.length > 0 ? ( + // Profile mode: show models from selected profile config + + + + + + {profileModels.map((model) => { + const isSelected = model.id === selectedProfileModel?.id + return ( + setSelectedProfileModelId(model.id)} + className="gap-2 justify-between" + > +
+ + + {model.displayName}{" "} + + ({model.modelId}) + + +
+ {isSelected && ( + + )} +
+ ) + })} +
+
) : ( - // Online mode: show Claude model selector + // Fallback: show Claude model selector (no profile or empty profile) { diff --git a/src/renderer/features/agents/ui/profile-selector.tsx b/src/renderer/features/agents/ui/profile-selector.tsx new file mode 100644 index 000000000..0d49f5b3c --- /dev/null +++ b/src/renderer/features/agents/ui/profile-selector.tsx @@ -0,0 +1,165 @@ +import { useAtom, useAtomValue, useSetAtom } from "jotai" +import { ChevronDown, Settings } from "lucide-react" +import { useCallback } from "react" + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "../../../components/ui/dropdown-menu" +import { CheckIcon } from "../../../components/ui/icons" +import { + agentsSettingsDialogActiveTabAtom, + agentsSettingsDialogOpenAtom, + lastUsedProfileIdAtom, + modelProfilesAtom, + type ModelProfile, +} from "../../../lib/atoms" +import { trpc } from "../../../lib/trpc" +import { cn } from "../../../lib/utils" + +interface ProfileSelectorProps { + chatId: string + currentProfileId: string | null + disabled?: boolean + className?: string +} + +/** + * Dropdown selector for choosing a model profile per-chat. + * Updates the chat's modelProfileId in the database and syncs lastUsedProfileIdAtom. + */ +export function ProfileSelector({ + chatId, + currentProfileId, + disabled = false, + className, +}: ProfileSelectorProps) { + const profiles = useAtomValue(modelProfilesAtom) + const [lastUsedProfileId, setLastUsedProfileId] = useAtom(lastUsedProfileIdAtom) + const setSettingsOpen = useSetAtom(agentsSettingsDialogOpenAtom) + const setSettingsTab = useSetAtom(agentsSettingsDialogActiveTabAtom) + + const utils = trpc.useUtils() + const updateProfileMutation = trpc.chats.updateModelProfile.useMutation({ + onSuccess: () => { + utils.chats.get.invalidate({ id: chatId }) + }, + }) + + // Filter out offline profiles from display (they're auto-selected when offline) + const displayProfiles = profiles.filter((p) => !p.isOffline) + + // Get effective profile ID (chat's profile or fallback to lastUsed) + const effectiveProfileId = currentProfileId ?? lastUsedProfileId + + // Find current profile + const currentProfile = profiles.find((p) => p.id === effectiveProfileId) + + const handleProfileChange = useCallback( + (profileId: string) => { + // Update the chat's profile in database + updateProfileMutation.mutate({ id: chatId, modelProfileId: profileId }) + + // Update lastUsedProfileId so new chats inherit this + setLastUsedProfileId(profileId) + }, + [chatId, updateProfileMutation, setLastUsedProfileId], + ) + + const handleOpenSettings = useCallback(() => { + setSettingsTab("models") + setSettingsOpen(true) + }, [setSettingsTab, setSettingsOpen]) + + // Don't show if no custom profiles configured + if (displayProfiles.length === 0) { + return null + } + + return ( + + + + + + {displayProfiles.map((profile) => { + const isSelected = profile.id === effectiveProfileId + return ( + handleProfileChange(profile.id)} + className="gap-2 justify-between" + > + {profile.name} + {isSelected && } + + ) + })} + + + + Manage Profiles... + + + + ) +} + +/** + * Hook to get the effective profile for a chat. + * Returns the profile from chat.modelProfileId or falls back to lastUsedProfileId. + */ +export function useEffectiveProfile( + chatModelProfileId: string | null | undefined, +): ModelProfile | null { + const profiles = useAtomValue(modelProfilesAtom) + const lastUsedProfileId = useAtomValue(lastUsedProfileIdAtom) + + const effectiveProfileId = chatModelProfileId ?? lastUsedProfileId + + if (!effectiveProfileId) { + // Return first non-offline profile as default + return profiles.find((p) => !p.isOffline) ?? null + } + + return profiles.find((p) => p.id === effectiveProfileId) ?? null +} + +/** + * Hook to get the effective model for a chat within a profile. + * Returns the model from chat.selectedModelId or falls back to first model in profile. + */ +export function useEffectiveModel( + profile: ModelProfile | null, + chatSelectedModelId: string | null | undefined, +) { + if (!profile || !profile.models || profile.models.length === 0) { + return null + } + + // Try to find the selected model + if (chatSelectedModelId) { + const selectedModel = profile.models.find((m) => m.id === chatSelectedModelId) + if (selectedModel) { + return selectedModel + } + } + + // Fallback to first model + return profile.models[0] ?? null +} diff --git a/src/renderer/features/onboarding/api-key-onboarding-page.tsx b/src/renderer/features/onboarding/api-key-onboarding-page.tsx index 9fc7fe3d0..74d694068 100644 --- a/src/renderer/features/onboarding/api-key-onboarding-page.tsx +++ b/src/renderer/features/onboarding/api-key-onboarding-page.tsx @@ -115,7 +115,8 @@ export function ApiKeyOnboardingPage() { const trimmedToken = token.trim() const trimmedBaseUrl = baseUrl.trim() - if (!trimmedModel || !trimmedToken || !trimmedBaseUrl) return + // Only model and baseUrl are required - token is optional for proxy configs + if (!trimmedModel || !trimmedBaseUrl) return setIsSubmitting(true) @@ -167,8 +168,9 @@ export function ApiKeyOnboardingPage() { } } + // Token is optional if using a proxy (baseUrl) - proxy may handle auth const canSubmitCustomModel = Boolean( - model.trim() && token.trim() && baseUrl.trim() + model.trim() && baseUrl.trim() // Only model and baseUrl are required ) // Simple API key input mode @@ -299,7 +301,7 @@ export function ApiKeyOnboardingPage() { {/* API Token */}
- + { - const cliConfigData = cliConfig; - if (!cliConfigData) return; - - // Prompt user to save as profile - const shouldSave = window.confirm( - "Would you like to save this configuration as a profile for easy switching later?", - ); - - if (shouldSave) { - const newProfile: ModelProfile = { - id: crypto.randomUUID(), - name: "Detected CLI Config", - config: { - model: cliConfigData.model || "claude-3-7-sonnet-20250219", - token: cliConfigData.apiKey || "", - baseUrl: cliConfigData.baseUrl || "https://api.anthropic.com", - // Additional model config (if available in CLI config) - defaultOpusModel: cliConfigData.defaultOpusModel || undefined, - defaultSonnetModel: cliConfigData.defaultSonnetModel || undefined, - defaultHaikuModel: cliConfigData.defaultHaikuModel || undefined, - subagentModel: cliConfigData.subagentModel || undefined, - }, - }; - - setProfiles([...profiles, newProfile]); - setActiveProfileId(newProfile.id); - } - setIsContinuing(true); // Mark CLI config detected as shown so we don't show this page again setCliConfigDetectedShown(true); - // Set billing method to api-key since CLI config is being used - setBillingMethod("api-key"); - // Mark API key onboarding as complete - setApiKeyOnboardingCompleted(true); + // Navigate to custom-model form (api-key-onboarding-page in "custom-model" mode) + // This allows users to review/edit the pre-filled values before continuing + setBillingMethod("custom-model"); + // Note: apiKeyOnboardingCompleted is NOT set here - user needs to submit the form }; const handleUseOAuth = () => { @@ -203,13 +166,14 @@ export function CliConfigDetectedPage() {
{/* Primary Action - Use Existing Config */} + {/* Enable if we have API key OR proxy URL (proxies may handle auth) */} +
+
Connecting...
+
+ + +` +} + +/** + * Proxy request to Vite dev server + */ +function proxyToVite(req: IncomingMessage, res: ServerResponse): void { + // Map /app to / for Vite (Vite serves at root) + let path = req.url || "/" + if (path === "/app" || path.startsWith("/app?")) { + path = "/" + path.slice(4) // Remove "/app" prefix, keep query string + } + + const proxyReq = httpRequest( + { + hostname: "localhost", + port: VITE_DEV_PORT, + path: path, + method: req.method, + headers: { + ...req.headers, + host: `localhost:${VITE_DEV_PORT}`, + }, + }, + (proxyRes) => { + res.writeHead(proxyRes.statusCode || 200, proxyRes.headers) + proxyRes.pipe(res) + } + ) + + proxyReq.on("error", (err) => { + console.error("[WS] Proxy error:", err) + res.writeHead(502, { "Content-Type": "text/plain" }) + res.end("Vite dev server not available. Make sure bun run dev is running.") + }) + + req.pipe(proxyReq) +} + +/** + * Serve static files or PIN auth page + * In dev mode, proxy to Vite dev server for hot reload + */ +async function handleHttpRequest(req: IncomingMessage, res: ServerResponse): Promise { + const url = req.url || "/" + + // In dev mode, only serve PIN auth page directly + // All other requests proxy to Vite dev server + const isDev = process.env.NODE_ENV !== "production" + + if (isDev) { + // Root or /login -> PIN auth page (even in dev) + if (url === "/" || url === "/login") { + res.writeHead(200, { "Content-Type": "text/html" }) + res.end(getPinAuthPage()) + return + } + + // Proxy all other requests to Vite dev server + proxyToVite(req, res) + return + } + + // Production mode: serve static files + const rendererDir = getRendererDir() + + // Root or /login -> PIN auth page + if (url === "/" || url === "/login") { + res.writeHead(200, { "Content-Type": "text/html" }) + res.end(getPinAuthPage()) + return + } + + // /app -> serve renderer index.html + if (url === "/app" || url.startsWith("/app?")) { + const indexPath = join(rendererDir, "index.html") + try { + const content = await readFile(indexPath) + res.writeHead(200, { "Content-Type": "text/html" }) + res.end(content) + } catch { + res.writeHead(404) + res.end("Not found") + } + return + } + + // Static assets + let filePath = join(rendererDir, url.split("?")[0]) + + // Security: prevent directory traversal + if (!filePath.startsWith(rendererDir)) { + res.writeHead(403) + res.end("Forbidden") + return + } + + try { + const fileStat = await stat(filePath) + if (fileStat.isDirectory()) { + filePath = join(filePath, "index.html") + } + + const content = await readFile(filePath) + const ext = extname(filePath).toLowerCase() + const contentType = MIME_TYPES[ext] || "application/octet-stream" + + res.writeHead(200, { "Content-Type": contentType }) + res.end(content) + } catch { + res.writeHead(404) + res.end("Not found") + } +} + +/** + * Send message to a WebSocket client + */ +function send(ws: WebSocket, message: WSResponse): void { + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(message)) + } +} + +/** + * Broadcast to all authenticated clients + */ +export function broadcast(channel: string, data: unknown): void { + const message: WSResponse = { + id: null, + type: "subscription", + channel, + data, + } + + for (const [ws, client] of authenticatedClients) { + if (client.subscriptions.has(channel)) { + send(ws, message) + } + } +} + +/** + * Handle incoming WebSocket message + */ +async function handleMessage( + ws: WebSocket, + client: AuthenticatedClient | undefined, + message: WSRequest, + router: ReturnType +): Promise { + // Auth message + if (message.type === "auth") { + if (message.pin && validatePin(message.pin)) { + const clientId = randomUUID() + const newClient: AuthenticatedClient = { + ws, + id: clientId, + subscriptions: new Set(), + } + authenticatedClients.set(ws, newClient) + addClient(clientId) + send(ws, { id: message.id, type: "auth_success" }) + console.log(`[WS] Client authenticated: ${clientId}`) + } else { + send(ws, { id: message.id, type: "auth_failed", error: "Invalid PIN" }) + } + return + } + + // All other messages require authentication + if (!client) { + send(ws, { id: message.id, type: "auth_required" }) + return + } + + // tRPC call + if (message.type === "trpc" && message.method) { + try { + // Parse the method path (e.g., "claude.chat" or "external.getAppVersion") + const pathParts = message.method.split(".") + const routerName = pathParts[0] + const procedureName = pathParts[pathParts.length - 1] + + // Get the router + const routerObj = (router as any)[routerName] + if (!routerObj) { + throw new Error(`Router not found: ${routerName}`) + } + + // Handle subscriptions (streaming) + // For claude.chat, routerName is "claude" and procedureName is "chat" + if (routerName === "claude" && procedureName === "chat") { + console.log(`[WS] Starting claude.chat subscription: ${message.id}`) + console.log(`[WS] Chat params:`, JSON.stringify(message.params, null, 2)) + + // Access the claude router (procedures are direct properties, not in _def) + const claudeRouter = (router as any).claude + if (!claudeRouter) { + throw new Error(`claude router not found`) + } + + // The chat procedure is a direct property on the router + const chatProcedure = claudeRouter.chat + if (!chatProcedure) { + console.error(`[WS] Available procedures in claude router:`, Object.keys(claudeRouter).filter(k => !k.startsWith('_'))) + throw new Error(`chat procedure not found in claude router`) + } + + console.log(`[WS] Chat procedure found, type:`, typeof chatProcedure) + + // tRPC procedures store their implementation in _def.resolver + // For subscriptions, the resolver is the function that returns an Observable + const procedureDef = chatProcedure._def + if (!procedureDef) { + throw new Error(`chat procedure _def not found`) + } + + // For subscriptions, the implementation is in _def.resolver + const subscriptionFn = procedureDef.resolver + if (!subscriptionFn || typeof subscriptionFn !== 'function') { + console.error(`[WS] Procedure _def keys:`, Object.keys(procedureDef)) + throw new Error(`chat procedure resolver not found or not a function`) + } + + // Create context for the subscription + const ctx = { + getWindow: () => BrowserWindow.getFocusedWindow(), + } + + // Call the raw subscription function with { input, type, ctx, path } + // This bypasses the client-side checks in the procedure wrapper + let observable + try { + observable = subscriptionFn({ + input: message.params, + type: 'subscription', + ctx, + path: 'claude.chat', + }) + } catch (err) { + console.error(`[WS] Error calling claude.chat subscription:`, err) + send(ws, { + id: message.id, + type: "error", + error: err instanceof Error ? err.message : "Unknown error", + }) + return + } + + // Check if the result is actually an Observable + if (typeof observable?.subscribe !== 'function') { + console.error(`[WS] claude.chat did not return an Observable. Result:`, typeof observable) + if (observable) { + console.error(`[WS] Result constructor:`, observable.constructor?.name) + console.error(`[WS] Result keys:`, Object.keys(observable)) + } + send(ws, { + id: message.id, + type: "error", + error: `claude.chat returned ${typeof observable} instead of Observable`, + }) + return + } + + console.log(`[WS] Observable created, subscribing...`) + + const sub = observable.subscribe({ + next: (data: any) => { + console.log(`[WS] Chat data chunk:`, data.type || "no-type") + send(ws, { + id: message.id, + type: "subscription", + data: data, + }) + }, + error: (err: any) => { + console.error(`[WS] Chat subscription error:`, err) + send(ws, { + id: message.id, + type: "error", + error: err.message, + }) + }, + complete: () => { + console.log(`[WS] Chat subscription complete: ${message.id}`) + send(ws, { + id: message.id, + type: "result", + data: { completed: true }, + }) + } + }) + + // Store unsubscription for cleanup + const cleanupId = `sub-${message.id}` + const cleanup = () => sub.unsubscribe() + subscriptionCleanups.set(cleanupId, cleanup) + + ws.on("close", () => { + cleanup() + subscriptionCleanups.delete(cleanupId) + }) + + return + } + + // Create a caller for regular procedures + const caller = router.createCaller({ + getWindow: () => BrowserWindow.getFocusedWindow(), + }) + + // Navigate the caller to find the procedure + // Handle nested paths like "external.getAppVersion" or "projects.list" + let current: any = caller + + for (let i = 0; i < pathParts.length - 1; i++) { + current = current[pathParts[i]] + if (!current) { + throw new Error(`Router not found: ${pathParts.slice(0, i + 1).join(".")}`) + } + } + + if (!current[procedureName]) { + throw new Error(`Procedure not found: ${message.method}`) + } + + const result = await current[procedureName](message.params) + + send(ws, { + id: message.id, + type: "result", + data: superjson.serialize(result), + }) + } catch (error) { + send(ws, { + id: message.id, + type: "error", + error: error instanceof Error ? error.message : "Unknown error", + }) + } + return + } + + // Subscribe + if (message.type === "subscribe" && message.channel) { + client.subscriptions.add(message.channel) + send(ws, { id: message.id, type: "result", data: { subscribed: message.channel } }) + return + } + + // Unsubscribe + if (message.type === "unsubscribe" && message.channel) { + client.subscriptions.delete(message.channel) + send(ws, { id: message.id, type: "result", data: { unsubscribed: message.channel } }) + return + } + + // Unknown message type + send(ws, { id: message.id, type: "error", error: "Unknown message type" }) +} + +/** + * Start WebSocket server + * Returns the port number + */ +export function startWSServer(): Promise { + return new Promise((resolve, reject) => { + // Create HTTP server for WebSocket upgrade and static files + httpServer = createServer((req, res) => { + handleHttpRequest(req, res).catch((err) => { + console.error("[WS] HTTP request error:", err) + res.writeHead(500) + res.end("Internal Server Error") + }) + }) + + wss = new WebSocketServer({ server: httpServer }) + + // Create tRPC router + const router = createAppRouter(() => BrowserWindow.getFocusedWindow()) + + wss.on("connection", (ws: WebSocket, req: IncomingMessage) => { + console.log("[WS] New connection from:", req.socket.remoteAddress) + + // Send auth required message + send(ws, { id: null, type: "auth_required" }) + + ws.on("message", async (data: Buffer) => { + try { + const message = JSON.parse(data.toString()) as WSRequest + const client = authenticatedClients.get(ws) + await handleMessage(ws, client, message, router) + } catch (error) { + console.error("[WS] Message parse error:", error) + send(ws, { id: null, type: "error", error: "Invalid message format" }) + } + }) + + ws.on("close", () => { + const client = authenticatedClients.get(ws) + if (client) { + removeClient(client.id) + authenticatedClients.delete(ws) + console.log(`[WS] Client disconnected: ${client.id}`) + } + }) + + ws.on("error", (error) => { + console.error("[WS] WebSocket error:", error) + }) + }) + + // Listen on random available port + httpServer.listen(0, () => { + const address = httpServer?.address() + if (address && typeof address === "object") { + console.log(`[WS] Server listening on port ${address.port}`) + resolve(address.port) + } else { + reject(new Error("Failed to get server port")) + } + }) + + httpServer.on("error", reject) + }) +} + +/** + * Stop WebSocket server + */ +export function stopWSServer(): Promise { + return new Promise((resolve) => { + // Close all client connections + for (const [ws] of authenticatedClients) { + ws.close() + } + authenticatedClients.clear() + + // Clean up subscriptions + for (const cleanup of subscriptionCleanups.values()) { + cleanup() + } + subscriptionCleanups.clear() + + // Close servers + if (wss) { + wss.close(() => { + if (httpServer) { + httpServer.close(() => { + wss = null + httpServer = null + console.log("[WS] Server stopped") + resolve() + }) + } else { + wss = null + resolve() + } + }) + } else { + resolve() + } + }) +} + +/** + * Check if server is running + */ +export function isWSServerRunning(): boolean { + return httpServer !== null +} + +/** + * Get connected client count + */ +export function getConnectedClientCount(): number { + return authenticatedClients.size +} diff --git a/src/main/lib/trpc/routers/claude.ts b/src/main/lib/trpc/routers/claude.ts index ee6069e33..08748384c 100644 --- a/src/main/lib/trpc/routers/claude.ts +++ b/src/main/lib/trpc/routers/claude.ts @@ -553,7 +553,7 @@ export const claudeRouter = router({ customConfig: z .object({ model: z.string().min(1), - token: z.string().min(1), + token: z.string().optional(), // Token can be empty for proxy configs baseUrl: z.string().min(1), // Additional model configuration (optional) defaultOpusModel: z.string().optional(), diff --git a/src/main/lib/trpc/routers/model-profiles.ts b/src/main/lib/trpc/routers/model-profiles.ts new file mode 100644 index 000000000..5cc74c3da --- /dev/null +++ b/src/main/lib/trpc/routers/model-profiles.ts @@ -0,0 +1,184 @@ +import { eq } from "drizzle-orm" +import { z } from "zod" +import { publicProcedure, router } from "../index" +import { + modelProfiles, + modelProfileSettings, + getDatabase, + type NewModelProfile, +} from "../../db" + +/** + * Model Profiles Router + * Provides CRUD operations for model profiles stored in the database. + * This allows syncing profiles between desktop and web app. + */ + +// Schema for creating/updating a model profile +const modelProfileInputSchema = z.object({ + name: z.string().min(1, "Name is required"), + // CustomClaudeConfig structure + config: z.object({ + model: z.string().optional(), + token: z.string().optional(), // Token can be empty for proxy configs + baseUrl: z.string().url("Base URL must be a valid URL"), + defaultOpusModel: z.string().optional(), + defaultSonnetModel: z.string().optional(), + defaultHaikuModel: z.string().optional(), + subagentModel: z.string().optional(), + }), + // ModelMapping array + models: z.array(z.object({ + id: z.string(), + displayName: z.string(), + modelId: z.string(), + supportsThinking: z.boolean(), + })).optional(), + isOffline: z.boolean().optional().default(false), +}) + +export const modelProfilesRouter = router({ + /** + * Get all model profiles + */ + list: publicProcedure.query(async () => { + const db = getDatabase() + const profiles = await db.select().from(modelProfiles).orderBy(modelProfiles.createdAt) + + // Parse JSON fields + return profiles.map((profile) => ({ + ...profile, + config: JSON.parse(profile.config), + models: JSON.parse(profile.models), + })) + }), + + /** + * Get a single model profile by ID + */ + get: publicProcedure + .input(z.object({ id: z.string() })) + .query(async ({ input }) => { + const db = getDatabase() + const profile = await db.select().from(modelProfiles).where(eq(modelProfiles.id, input.id)).get() + + if (!profile) { + throw new Error("Model profile not found") + } + + return { + ...profile, + config: JSON.parse(profile.config), + models: JSON.parse(profile.models), + } + }), + + /** + * Create a new model profile + */ + create: publicProcedure + .input(modelProfileInputSchema) + .mutation(async ({ input }) => { + const db = getDatabase() + + const newProfile: NewModelProfile = { + name: input.name, + config: JSON.stringify(input.config), + models: JSON.stringify(input.models || []), + isOffline: input.isOffline || false, + } + + const result = await db.insert(modelProfiles).values(newProfile).returning() + const profile = result[0] + + return { + ...profile, + config: JSON.parse(profile.config), + models: JSON.parse(profile.models), + } + }), + + /** + * Update an existing model profile + */ + update: publicProcedure + .input(z.object({ + id: z.string(), + ...modelProfileInputSchema.partial(), + })) + .mutation(async ({ input }) => { + const db = getDatabase() + + // Get existing profile + const existing = await db.select().from(modelProfiles).where(eq(modelProfiles.id, input.id)).get() + if (!existing) { + throw new Error("Model profile not found") + } + + // Build update object + const update: Partial = {} + if (input.name !== undefined) update.name = input.name + if (input.config !== undefined) update.config = JSON.stringify(input.config) + if (input.models !== undefined) update.models = JSON.stringify(input.models) + if (input.isOffline !== undefined) update.isOffline = input.isOffline + + const result = await db.update(modelProfiles) + .set(update) + .where(eq(modelProfiles.id, input.id)) + .returning() + + const profile = result[0] + return { + ...profile, + config: JSON.parse(profile.config), + models: JSON.parse(profile.models), + } + }), + + /** + * Delete a model profile + */ + delete: publicProcedure + .input(z.object({ id: z.string() })) + .mutation(async ({ input }) => { + const db = getDatabase() + + await db.delete(modelProfiles).where(eq(modelProfiles.id, input.id)) + return { success: true } + }), + + /** + * Get or create model profile settings (last used profile, etc.) + */ + getSettings: publicProcedure.query(async () => { + const db = getDatabase() + let settings = await db.select().from(modelProfileSettings).where(eq(modelProfileSettings.id, "singleton")).get() + + // Create settings if they don't exist + if (!settings) { + const result = await db.insert(modelProfileSettings).values({}).returning() + settings = result[0] + } + + return settings + }), + + /** + * Update model profile settings (e.g., last used profile ID) + */ + updateSettings: publicProcedure + .input(z.object({ + lastUsedProfileId: z.string().optional(), + })) + .mutation(async ({ input }) => { + const db = getDatabase() + + // Update settings + const result = await db.update(modelProfileSettings) + .set(input) + .where(eq(modelProfileSettings.id, "singleton")) + .returning() + + return result[0] + }), +}) diff --git a/src/main/lib/trpc/routers/remote-access.ts b/src/main/lib/trpc/routers/remote-access.ts new file mode 100644 index 000000000..0f6514818 --- /dev/null +++ b/src/main/lib/trpc/routers/remote-access.ts @@ -0,0 +1,33 @@ +// src/main/lib/trpc/routers/remote-access.ts + +import { router, publicProcedure } from "../index" +import { + enableRemoteAccess, + disableRemoteAccess, + getRemoteAccessStatus, +} from "../../remote-access" + +export const remoteAccessRouter = router({ + /** + * Get current remote access status + */ + getStatus: publicProcedure.query(() => { + return getRemoteAccessStatus() + }), + + /** + * Enable remote access + */ + enable: publicProcedure.mutation(async () => { + await enableRemoteAccess() + return getRemoteAccessStatus() + }), + + /** + * Disable remote access + */ + disable: publicProcedure.mutation(async () => { + await disableRemoteAccess() + return getRemoteAccessStatus() + }), +}) diff --git a/src/renderer/components/dialogs/remote-access-dialog.tsx b/src/renderer/components/dialogs/remote-access-dialog.tsx new file mode 100644 index 000000000..7702d2355 --- /dev/null +++ b/src/renderer/components/dialogs/remote-access-dialog.tsx @@ -0,0 +1,165 @@ +// src/renderer/components/dialogs/remote-access-dialog.tsx + +import { useAtom } from "jotai" +import { useEffect, useCallback } from "react" +import { Globe, Copy, Loader2, X } from "lucide-react" +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog" +import { Button } from "../ui/button" +import { + remoteAccessStatusAtom, + remoteAccessDialogOpenAtom, + type RemoteAccessStatus, +} from "../../lib/atoms/remote-access" +import { toast } from "sonner" + +export function RemoteAccessDialog() { + const [open, setOpen] = useAtom(remoteAccessDialogOpenAtom) + const [status, setStatus] = useAtom(remoteAccessStatusAtom) + + // Fetch initial status and subscribe to changes + useEffect(() => { + if (!window.desktopApi) return + + // Get initial status + window.desktopApi.getRemoteAccessStatus?.().then(setStatus) + + // Subscribe to status changes + const unsubscribe = window.desktopApi.onRemoteAccessStatusChange?.(setStatus) + return unsubscribe + }, [setStatus]) + + const handleEnable = useCallback(async () => { + try { + await window.desktopApi?.enableRemoteAccess?.() + } catch (error) { + toast.error("Failed to enable remote access") + } + }, []) + + const handleDisable = useCallback(async () => { + try { + await window.desktopApi?.disableRemoteAccess?.() + } catch (error) { + toast.error("Failed to disable remote access") + } + }, []) + + const handleCopyUrl = useCallback(() => { + if (status.status === "active") { + navigator.clipboard.writeText(status.url) + toast.success("URL copied to clipboard") + } + }, [status]) + + const handleCopyPin = useCallback(() => { + if (status.status === "active") { + navigator.clipboard.writeText(status.pin) + toast.success("PIN copied to clipboard") + } + }, [status]) + + return ( + + + + + + Remote Access + + + +
+ {status.status === "disabled" && ( + <> +

+ Access your desktop from any browser. +

+ + + )} + + {status.status === "downloading" && ( +
+
+ + Downloading cloudflared... +
+
+
+
+
+ )} + + {status.status === "starting" && ( +
+ + Starting tunnel... +
+ )} + + {status.status === "active" && ( + <> +
+
+ Active +
+ +
+ +
+ + {status.url} + + +
+
+ +
+ +
+ + {status.pin} + + +
+
+ +
+ Connected clients: {status.clients} +
+ + + + )} + + {status.status === "error" && ( + <> +
+ + {status.message} +
+ + + )} +
+ +
+ ) +} diff --git a/src/renderer/components/model-profiles-sync.tsx b/src/renderer/components/model-profiles-sync.tsx new file mode 100644 index 000000000..688d118b5 --- /dev/null +++ b/src/renderer/components/model-profiles-sync.tsx @@ -0,0 +1,13 @@ +import { useEffect } from "react" +import { useSyncModelProfiles } from "../lib/atoms/model-profiles-sync" + +/** + * Component that initializes model profiles sync between localStorage and database. + * Should be rendered once in the app root. + */ +export function ModelProfilesSync() { + const { isLoading } = useSyncModelProfiles() + + // This component doesn't render anything - it just handles the sync + return null +} diff --git a/src/renderer/contexts/TRPCProvider.tsx b/src/renderer/contexts/TRPCProvider.tsx index f3d11be44..a0916b186 100644 --- a/src/renderer/contexts/TRPCProvider.tsx +++ b/src/renderer/contexts/TRPCProvider.tsx @@ -1,8 +1,8 @@ -import { useState } from "react" +import { useState, useEffect } from "react" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" -import { ipcLink } from "trpc-electron/renderer" import { trpc } from "../lib/trpc" import superjson from "superjson" +import { wsLink } from "../lib/remote-transport/ws-link" interface TRPCProviderProps { children: React.ReactNode @@ -15,6 +15,15 @@ export function getQueryClient(): QueryClient | null { return globalQueryClient } +/** + * Check if we're in remote/browser mode (no electronTRPC global) + * Must be called at runtime, not at module load time + */ +function checkIsRemoteMode(): boolean { + // @ts-ignore + return typeof window !== "undefined" && !window.electronTRPC +} + export function TRPCProvider({ children }: TRPCProviderProps) { const [queryClient] = useState(() => { const client = new QueryClient({ @@ -35,15 +44,75 @@ export function TRPCProvider({ children }: TRPCProviderProps) { return client }) - const [trpcClient] = useState(() => { - const client = trpc.createClient({ - links: [ipcLink({ transformer: superjson })], - }) - return client + // Combine loading and client state to ensure atomic updates + const [state, setState] = useState<{ + isLoading: boolean + trpcClient: ReturnType | null + }>({ + isLoading: true, + trpcClient: null, }) + // Log render state + console.log("[TRPCProvider] Render - isLoading:", state.isLoading, "trpcClient:", state.trpcClient) + + useEffect(() => { + let mounted = true + + async function initClient() { + try { + let link: any + + // Check at runtime to ensure electronTRPC has been set by preload + const isRemote = checkIsRemoteMode() + console.log("[TRPCProvider] isRemote:", isRemote, "electronTRPC:", !!(window as any).electronTRPC) + + if (isRemote) { + // Browser mode - use WebSocket link + link = wsLink() + } else { + // Electron mode - dynamically import ipcLink + console.log("[TRPCProvider] Loading ipcLink...") + const module = await import("trpc-electron/renderer") + console.log("[TRPCProvider] ipcLink module loaded:", module) + link = module.ipcLink({ transformer: superjson }) + console.log("[TRPCProvider] ipcLink created:", link) + } + + console.log("[TRPCProvider] Creating trpc client...") + const client = trpc.createClient({ links: [link] }) + console.log("[TRPCProvider] Client created:", client) + + if (mounted) { + setState({ isLoading: false, trpcClient: client }) + console.log("[TRPCProvider] State updated with client") + } + } catch (error) { + console.error("[TRPCProvider] Error initializing client:", error) + if (mounted) { + setState((prev) => ({ ...prev, isLoading: false })) + } + } + } + + initClient() + + return () => { + mounted = false + } + }, []) + + // Show loading state while initializing client + if (state.isLoading || !state.trpcClient) { + return ( +
+
Connecting...
+
+ ) + } + return ( - + {children} ) diff --git a/src/renderer/features/agents/lib/ipc-chat-transport.ts b/src/renderer/features/agents/lib/ipc-chat-transport.ts index 55240664b..47442ed54 100644 --- a/src/renderer/features/agents/lib/ipc-chat-transport.ts +++ b/src/renderer/features/agents/lib/ipc-chat-transport.ts @@ -217,7 +217,14 @@ export class IPCChatTransport implements ChatTransport { // Read active profile config (from profile system, supports multiple profiles) // Override the model in customConfig with the selected model from profile - const baseCustomConfig = appStore.get(activeConfigAtom) + let baseCustomConfig: any + try { + baseCustomConfig = appStore.get(activeConfigAtom) + console.log('[IPC Transport] baseCustomConfig:', baseCustomConfig ? 'FOUND' : 'NOT FOUND', baseCustomConfig) + } catch (error) { + console.error('[IPC Transport] ERROR getting activeConfigAtom:', error) + baseCustomConfig = undefined + } const customConfig = baseCustomConfig && modelString ? { ...baseCustomConfig, model: modelString } : baseCustomConfig diff --git a/src/renderer/lib/atoms/model-profiles-sync.ts b/src/renderer/lib/atoms/model-profiles-sync.ts new file mode 100644 index 000000000..1c1989cf7 --- /dev/null +++ b/src/renderer/lib/atoms/model-profiles-sync.ts @@ -0,0 +1,154 @@ +import { atom, useAtom, useAtomValue, useSetAtom } from "jotai" +import { useEffect, useState } from "react" +import { trpc } from "../../lib/trpc" +import { + localStorageModelProfilesAtom, + OFFLINE_PROFILE, + type ModelProfile, +} from "./index" +import { isRemoteMode } from "../remote-transport" + +/** + * Database-synced model profiles atom + * - Desktop mode: Uses localStorage (fast, offline-capable) and syncs to database + * - Web mode: Loads from database via tRPC + */ + +// Internal atom to track if database sync has been done (desktop only) +const hasSyncedToDatabaseAtom = atom(false) + +// Atom to hold database-loaded profiles (for web mode or after sync) +const databaseProfilesAtom = atom(null) + +// Helper to convert database profile to ModelProfile +function fromDBProfile(dbProfile: any): ModelProfile { + return { + id: dbProfile.id, + name: dbProfile.name, + config: typeof dbProfile.config === 'string' ? JSON.parse(dbProfile.config) : dbProfile.config, + models: typeof dbProfile.models === 'string' ? JSON.parse(dbProfile.models) : dbProfile.models, + isOffline: dbProfile.isOffline || false, + } +} + +// Helper to convert ModelProfile to database format +// Note: The tRPC router expects objects, Drizzle will handle serialization to JSON for TEXT columns +function toDBProfile(profile: ModelProfile): Omit { + return { + name: profile.name, + config: profile.config, + models: profile.models || [], + isOffline: profile.isOffline || false, + } +} + +/** + * Hook to sync model profiles between localStorage and database + * - Desktop: Syncs localStorage profiles to database on app start + * - Web: Loads profiles from database and stores in atom + */ +export function useSyncModelProfiles() { + const localStorageProfiles = useAtomValue(localStorageModelProfilesAtom) + const setDatabaseProfiles = useSetAtom(databaseProfilesAtom) + const [hasSynced, setHasSynced] = useAtom(hasSyncedToDatabaseAtom) + const isRemote = isRemoteMode() + + const { data: dbProfiles, isLoading: isLoadingDB } = trpc.modelProfiles.list.useQuery( + undefined, + { + enabled: isRemote || !hasSynced, // Always load if remote, or if desktop hasn't synced yet + staleTime: Infinity, // Don't refetch automatically + } + ) + + const createMutation = trpc.modelProfiles.create.useMutation() + const updateMutation = trpc.modelProfiles.update.useMutation() + const deleteMutation = trpc.modelProfiles.delete.useMutation() + + // Sync desktop localStorage profiles to database (one-time) + useEffect(() => { + if (!isRemote && !hasSynced && !isLoadingDB && dbProfiles) { + const syncToDatabase = async () => { + try { + // Get profiles from localStorage, excluding offline profile + const localProfiles = localStorageProfiles.filter(p => !p.isOffline) + + // For each local profile, check if it exists in database + for (const localProfile of localProfiles) { + const existsInDB = dbProfiles.find((db: any) => db.name === localProfile.name) + + if (!existsInDB) { + // Create new profile in database + const dbProfile = toDBProfile(localProfile) + await createMutation.mutateAsync(dbProfile) + console.log("[ModelProfilesSync] Created profile in DB:", localProfile.name) + } + // Note: We don't sync back from database to localStorage to avoid conflicts + // In the future, we can add proper conflict resolution with timestamps + } + + setHasSynced(true) + console.log("[ModelProfilesSync] Sync completed") + } catch (error) { + console.error("[ModelProfilesSync] Failed to sync:", error) + } + } + + syncToDatabase() + } + }, [isRemote, hasSynced, isLoadingDB, dbProfiles, localStorageProfiles, createMutation]) + + // In web mode, use database profiles directly + useEffect(() => { + if (isRemote && !isLoadingDB && dbProfiles) { + // Add offline profile to database profiles + const profiles = [ + OFFLINE_PROFILE, + ...dbProfiles.map(fromDBProfile), + ] + setDatabaseProfiles(profiles) + } + }, [isRemote, isLoadingDB, dbProfiles, setDatabaseProfiles]) + + return { + isLoading: isLoadingDB, + dbProfiles, + createProfile: createMutation.mutateAsync, + updateProfile: updateMutation.mutateAsync, + deleteProfile: deleteMutation.mutateAsync, + } +} + +/** + * Combined model profiles atom + * - Desktop: Returns localStorage profiles, writes to localStorage + * - Web: Returns database profiles, writes are not supported (web mode is read-only) + */ +export const modelProfilesAtom = atom( + (get) => { + const isRemote = isRemoteMode() + const localStorageProfiles = get(localStorageModelProfilesAtom) + const databaseProfiles = get(databaseProfilesAtom) + + if (isRemote) { + // In web mode, use database profiles (with offline profile added) + return databaseProfiles || [OFFLINE_PROFILE] + } + + // In desktop mode, use localStorage profiles + return localStorageProfiles + }, + (get, set, newValue) => { + const isRemote = isRemoteMode() + + if (isRemote) { + // In web mode, writes are not supported (read-only) + // In the future, we could add tRPC mutations to update database + console.warn("[modelProfilesAtom] Cannot modify profiles in web mode") + return + } + + // In desktop mode, write to localStorage + set(localStorageModelProfilesAtom, newValue) + } +) diff --git a/src/renderer/lib/atoms/remote-access.ts b/src/renderer/lib/atoms/remote-access.ts new file mode 100644 index 000000000..9ee83f687 --- /dev/null +++ b/src/renderer/lib/atoms/remote-access.ts @@ -0,0 +1,14 @@ +// src/renderer/lib/atoms/remote-access.ts + +import { atom } from "jotai" + +export type RemoteAccessStatus = + | { status: "disabled" } + | { status: "downloading"; progress: number } + | { status: "starting" } + | { status: "active"; url: string; pin: string; clients: number } + | { status: "error"; message: string } + +export const remoteAccessStatusAtom = atom({ status: "disabled" }) + +export const remoteAccessDialogOpenAtom = atom(false) diff --git a/src/renderer/lib/remote-transport/get-link.ts b/src/renderer/lib/remote-transport/get-link.ts new file mode 100644 index 000000000..2b07a9629 --- /dev/null +++ b/src/renderer/lib/remote-transport/get-link.ts @@ -0,0 +1,62 @@ +// src/renderer/lib/remote-transport/get-link.ts +// Conditional tRPC link - returns wsLink for browser, ipcLink for Electron + +import { wsLink } from "./ws-link" +import superjson from "superjson" + +// Cache the link to avoid re-creation +let cachedLink: any = null +let ipcLinkModule: any = null + +// Dynamically import ipcLink only in Electron +// This avoids importing the module in browser where it would throw +async function loadIpcLink() { + if (ipcLinkModule) return ipcLinkModule + + // Check if we're in Electron (electronTRPC global exists) + // @ts-ignore + if (typeof window !== "undefined" && window.electronTRPC) { + try { + // Only import if electronTRPC exists + const { ipcLink } = await import("trpc-electron/renderer") + ipcLinkModule = ipcLink + } catch (error) { + // If import fails (e.g., in web bundle), fall back to null + console.warn("[get-link] Failed to load ipcLink, using wsLink fallback:", error) + } + } + + return ipcLinkModule +} + +/** + * Get the appropriate tRPC link based on environment + * - Browser (remote mode): WebSocket link + * - Electron: IPC link + * + * Note: Uses dynamic import to avoid loading trpc-electron in browser + */ +export async function getLink() { + if (cachedLink) return cachedLink + + // Check if remote mode (no desktopApi means browser/remote) + // @ts-ignore + const isRemote = typeof window !== "undefined" && !window.desktopApi + + if (isRemote) { + // Browser mode - always use wsLink + cachedLink = wsLink() + } else { + // Electron mode - try to dynamically import ipcLink + const ipc = await loadIpcLink() + if (ipc) { + cachedLink = ipc({ transformer: superjson }) + } else { + // Fallback to wsLink if ipcLink failed to load + console.warn("[get-link] ipcLink not available, using wsLink fallback") + cachedLink = wsLink() + } + } + + return cachedLink +} diff --git a/src/renderer/lib/remote-transport/index.ts b/src/renderer/lib/remote-transport/index.ts new file mode 100644 index 000000000..9a3a8d5cd --- /dev/null +++ b/src/renderer/lib/remote-transport/index.ts @@ -0,0 +1,327 @@ +// src/renderer/lib/remote-transport/index.ts +// WebSocket Transport Adapter for Remote Access +// Replaces IPC calls with WebSocket when running in browser + +import superjson from "superjson" + +type WSMessageType = "auth" | "trpc" | "api" | "subscribe" | "unsubscribe" + +interface WSRequest { + id: string + type: WSMessageType + method?: string + params?: unknown + pin?: string + channel?: string +} + +interface WSResponse { + id: string | null + type: "auth_required" | "auth_success" | "auth_failed" | "result" | "error" | "subscription" + data?: unknown + error?: string + channel?: string +} + +type PendingRequest = { + resolve: (data: unknown) => void + reject: (error: Error) => void +} + +let ws: WebSocket | null = null +let isAuthenticated = false +let connectionPromise: Promise | null = null +const pendingRequests = new Map() +const subscriptionHandlers = new Map void>>() +let requestId = 0 + +/** + * Check if we're running in remote browser mode + * Robust check that works even after desktopApi is mocked + */ +export function isRemoteMode(): boolean { + if (typeof window === "undefined") return false; + + // If we have a stored PIN, we are definitely in remote mode + if (sessionStorage.getItem("remote_ws_auth")) return true; + + // Otherwise check if desktopApi is missing (initial load) + return !window.desktopApi; +} + +/** + * Get stored PIN from sessionStorage + */ +function getStoredPin(): string | null { + return sessionStorage.getItem("remote_ws_auth") +} + +/** + * Connect to WebSocket server and authenticate + */ +export function connect(): Promise { + if (connectionPromise) return connectionPromise + if (ws?.readyState === WebSocket.OPEN && isAuthenticated) { + return Promise.resolve() + } + + connectionPromise = new Promise((resolve, reject) => { + const pin = getStoredPin() + if (!pin) { + // Redirect to login page + window.location.href = "/login" + reject(new Error("No PIN stored")) + return + } + + const protocol = location.protocol === "https:" ? "wss:" : "ws:" + ws = new WebSocket(`${protocol}//${location.host}`) + + ws.onopen = () => { + // Authenticate with stored PIN + const authRequest: WSRequest = { + id: `auth-${Date.now()}`, + type: "auth", + pin, + } + ws!.send(JSON.stringify(authRequest)) + } + + ws.onmessage = (event) => { + const message: WSResponse = JSON.parse(event.data) + + // Handle auth response + if (message.type === "auth_success") { + console.log("[RemoteTransport] Authentication successful") + isAuthenticated = true + connectionPromise = null + resolve() + return + } + + if (message.type === "auth_failed") { + console.error("[RemoteTransport] Authentication failed") + isAuthenticated = false + sessionStorage.removeItem("remote_ws_auth") + window.location.href = "/login" + reject(new Error("Authentication failed")) + return + } + + if (message.type === "auth_required") { + console.log("[RemoteTransport] Auth required, waiting for success/fail...") + // Don't redirect immediately, give it a chance to process the auth we just sent + return + } + + // Handle subscription messages + if (message.type === "subscription" && message.channel) { + const handlers = subscriptionHandlers.get(message.channel) + if (handlers) { + handlers.forEach((handler) => handler(message.data)) + } + return + } + + // Handle request responses + if (message.id) { + const pending = pendingRequests.get(message.id) + if (pending) { + pendingRequests.delete(message.id) + if (message.type === "error") { + pending.reject(new Error(message.error || "Unknown error")) + } else { + // Deserialize superjson data + const data = message.data + if (data && typeof data === "object" && "json" in data) { + pending.resolve(superjson.deserialize(data as any)) + } else { + pending.resolve(data) + } + } + } + } + } + + ws.onerror = (error) => { + console.error("[RemoteTransport] WebSocket error:", error) + connectionPromise = null + reject(new Error("WebSocket connection failed")) + } + + ws.onclose = () => { + console.log("[RemoteTransport] WebSocket closed") + ws = null + isAuthenticated = false + connectionPromise = null + } + }) + + return connectionPromise +} + +/** + * Send a request and wait for response + */ +async function sendRequest(type: WSMessageType, method?: string, params?: unknown): Promise { + await connect() + + return new Promise((resolve, reject) => { + const id = `req-${++requestId}-${Date.now()}` + const request: WSRequest = { id, type, method, params } + + pendingRequests.set(id, { resolve, reject }) + + // Timeout after 30 seconds + setTimeout(() => { + if (pendingRequests.has(id)) { + pendingRequests.delete(id) + reject(new Error("Request timeout")) + } + }, 30000) + + ws!.send(JSON.stringify(request)) + }) +} + +/** + * Call a tRPC procedure + */ +export async function trpcCall(method: string, params?: unknown): Promise { + return sendRequest("trpc", method, params) +} + +/** + * Subscribe to a channel + */ +export async function subscribe(channel: string, handler: (data: unknown) => void): Promise<() => void> { + await connect() + + // Register handler + if (!subscriptionHandlers.has(channel)) { + subscriptionHandlers.set(channel, new Set()) + // Send subscribe request + await sendRequest("subscribe", undefined, undefined) + const id = `sub-${++requestId}` + const request: WSRequest = { id, type: "subscribe", channel } + ws!.send(JSON.stringify(request)) + } + subscriptionHandlers.get(channel)!.add(handler) + + // Return unsubscribe function + return () => { + const handlers = subscriptionHandlers.get(channel) + if (handlers) { + handlers.delete(handler) + if (handlers.size === 0) { + subscriptionHandlers.delete(channel) + // Send unsubscribe request + if (ws?.readyState === WebSocket.OPEN) { + const id = `unsub-${++requestId}` + const request: WSRequest = { id, type: "unsubscribe", channel } + ws.send(JSON.stringify(request)) + } + } + } + } +} + +/** + * Disconnect from server + */ +export function disconnect(): void { + if (ws) { + ws.close() + ws = null + } + isAuthenticated = false + pendingRequests.clear() + subscriptionHandlers.clear() +} + +/** + * Create a remote desktopApi that bridges to WebSocket + */ +export function createRemoteDesktopApi(): Partial { + return { + platform: "darwin" as NodeJS.Platform, // Will be overridden by server + arch: "arm64", + + // These will use tRPC calls over WebSocket + getVersion: async () => { + const result = await trpcCall("external.getAppVersion") + return result as string + }, + + isPackaged: async () => true, + + // Window controls - no-op in browser + windowMinimize: async () => {}, + windowMaximize: async () => {}, + windowClose: async () => { window.close() }, + windowIsMaximized: async () => false, + windowToggleFullscreen: async () => {}, + windowIsFullscreen: async () => false, + setTrafficLightVisibility: async () => {}, + + // Clipboard + clipboardWrite: async (text: string) => { + await navigator.clipboard.writeText(text) + }, + clipboardRead: async () => { + return navigator.clipboard.readText() + }, + + // Open external links + openExternal: async (url: string) => { + window.open(url, "_blank") + }, + + // Remote access - return disabled status (we're already remote) + getRemoteAccessStatus: async () => ({ status: "disabled" as const }), + enableRemoteAccess: async () => ({ status: "disabled" as const }), + disableRemoteAccess: async () => ({ status: "disabled" as const }), + onRemoteAccessStatusChange: () => () => {}, + + // Notifications + showNotification: async (options: { title: string; body: string }) => { + if ("Notification" in window && Notification.permission === "granted") { + new Notification(options.title, { body: options.body }) + } + }, + + // Auth - no-op in remote mode + getUser: async () => null, + isAuthenticated: async () => false, + logout: async () => {}, + startAuthFlow: async () => {}, + submitAuthCode: async () => {}, + getAuthToken: async () => null, + + // Events - return no-op unsubscribe functions + onFullscreenChange: () => () => {}, + onFocusChange: () => () => {}, + onUpdateChecking: () => () => {}, + onUpdateAvailable: () => () => {}, + onUpdateNotAvailable: () => () => {}, + onUpdateProgress: () => () => {}, + onUpdateDownloaded: () => () => {}, + onUpdateError: () => () => {}, + onUpdateManualCheck: () => () => {}, + onAuthSuccess: () => () => {}, + onAuthError: () => () => {}, + onShortcutNewAgent: () => () => {}, + onFileChanged: () => () => {}, + onGitStatusChanged: () => () => {}, + + // Git Watcher + subscribeToGitWatcher: async () => {}, + unsubscribeFromGitWatcher: async () => {}, + + // Storage + getAppDataPath: async () => "/tmp/1code-remote", + + // System + setAnalyticsOptOut: async () => {}, + } +} diff --git a/src/renderer/lib/remote-transport/init.ts b/src/renderer/lib/remote-transport/init.ts new file mode 100644 index 000000000..905bc0403 --- /dev/null +++ b/src/renderer/lib/remote-transport/init.ts @@ -0,0 +1,56 @@ +// src/renderer/lib/remote-transport/init.ts +// Initialize remote transport when running in browser + +import { isRemoteMode, connect, createRemoteDesktopApi } from "./index" + +/** + * Initialize remote mode if running in browser without Electron + */ +export async function initRemoteTransport(): Promise { + if (!isRemoteMode()) { + console.log("[RemoteTransport] Running in Electron, skipping remote init") + return false + } + + console.log("[RemoteTransport] Running in remote browser mode") + + // Check if we have stored auth + const pin = sessionStorage.getItem("remote_ws_auth") + if (!pin) { + // Redirect to login if not on login page + if (!window.location.pathname.includes("/login") && window.location.pathname !== "/") { + window.location.href = "/login" + } + return false + } + + try { + // Connect to WebSocket + await connect() + + // Create and expose remote desktopApi + const remoteApi = createRemoteDesktopApi() + ;(window as any).desktopApi = remoteApi + + // Also expose webUtils stub + ;(window as any).webUtils = { + getPathForFile: () => "", + } + + console.log("[RemoteTransport] Remote transport initialized") + return true + } catch (error) { + console.error("[RemoteTransport] Failed to initialize:", error) + // Clear auth and redirect to login + sessionStorage.removeItem("remote_ws_auth") + window.location.href = "/login" + return false + } +} + +/** + * Check if remote mode is active + */ +export function isRemoteModeActive(): boolean { + return isRemoteMode() && sessionStorage.getItem("remote_ws_auth") !== null +} diff --git a/src/renderer/lib/remote-transport/trpc-client.ts b/src/renderer/lib/remote-transport/trpc-client.ts new file mode 100644 index 000000000..dfba497e1 --- /dev/null +++ b/src/renderer/lib/remote-transport/trpc-client.ts @@ -0,0 +1,39 @@ +// src/renderer/lib/remote-transport/trpc-client.ts +// tRPC Client for Remote Access via WebSocket + +import { trpcCall } from "./index" + +type TRPCProxy = { + [key: string]: TRPCProxy & { + query: (input?: unknown) => Promise + mutate: (input?: unknown) => Promise + } +} + +/** + * Create a proxy that intercepts tRPC calls and sends them via WebSocket + */ +function createTRPCProxy(path: string[] = []): TRPCProxy { + return new Proxy(() => {}, { + get(_, prop: string) { + if (prop === "query" || prop === "mutate") { + return async (input?: unknown) => { + const method = path.join(".") + return trpcCall(method, input) + } + } + return createTRPCProxy([...path, prop]) + }, + apply(_, __, args) { + // Handle direct calls like trpc.projects.list() + const method = path.join(".") + return trpcCall(method, args[0]) + }, + }) as TRPCProxy +} + +/** + * Remote tRPC client + * Usage: remoteTrpc.projects.list.query() or remoteTrpc.chats.create.mutate({ ... }) + */ +export const remoteTrpcClient = createTRPCProxy() diff --git a/src/renderer/lib/remote-transport/ws-link.ts b/src/renderer/lib/remote-transport/ws-link.ts new file mode 100644 index 000000000..daadc6d98 --- /dev/null +++ b/src/renderer/lib/remote-transport/ws-link.ts @@ -0,0 +1,274 @@ +// src/renderer/lib/remote-transport/ws-link.ts +// WebSocket tRPC Link for Remote Access + +import { TRPCLink } from "@trpc/client" +import { observable } from "@trpc/server/observable" +import type { AppRouter } from "../../../main/lib/trpc/routers" +import superjson from "superjson" + +type WSMessageType = "auth" | "trpc" | "api" | "subscribe" | "unsubscribe" + +interface WSRequest { + id: string + type: WSMessageType + method?: string + params?: unknown + pin?: string + channel?: string +} + +interface WSResponse { + id: string | null + type: "auth_required" | "auth_success" | "auth_failed" | "result" | "error" | "subscription" + data?: unknown + error?: string + channel?: string +} + +type PendingRequest = { + resolve: (data: unknown) => void + reject: (error: Error) => void +} + +type SubscriptionHandler = { + onData: (data: unknown) => void + onError: (error: Error) => void + onComplete: () => void +} + +let ws: WebSocket | null = null +let isAuthenticated = false +let connectionPromise: Promise | null = null +const pendingRequests = new Map() +const subscriptionHandlers = new Map() +let requestId = 0 + +/** + * Get stored PIN from sessionStorage + */ +function getStoredPin(): string | null { + return sessionStorage.getItem("remote_ws_auth") +} + +/** + * Connect to WebSocket server and authenticate + */ +function connect(): Promise { + if (connectionPromise) return connectionPromise + if (ws?.readyState === WebSocket.OPEN && isAuthenticated) { + return Promise.resolve() + } + + connectionPromise = new Promise((resolve, reject) => { + const pin = getStoredPin() + if (!pin) { + window.location.href = "/login" + reject(new Error("No PIN stored")) + return + } + + const protocol = location.protocol === "https:" ? "wss:" : "ws:" + ws = new WebSocket(`${protocol}//${location.host}`) + + ws.onopen = () => { + const authRequest: WSRequest = { + id: `auth-${Date.now()}`, + type: "auth", + pin, + } + ws!.send(JSON.stringify(authRequest)) + } + + ws.onmessage = (event) => { + const message: WSResponse = JSON.parse(event.data) + + // Handle auth response + if (message.type === "auth_success") { + console.log("[ws-link] Authentication successful") + isAuthenticated = true + connectionPromise = null + resolve() + return + } + + if (message.type === "auth_failed") { + console.error("[ws-link] Authentication failed") + isAuthenticated = false + sessionStorage.removeItem("remote_ws_auth") + window.location.href = "/login" + reject(new Error("Authentication failed")) + return + } + + if (message.type === "auth_required") { + console.log("[ws-link] Auth required, waiting for success/fail...") + return + } + + // Handle subscription messages + if (message.id) { + const subHandler = subscriptionHandlers.get(message.id) + if (subHandler) { + if (message.type === "subscription" && message.data !== undefined) { + console.log("[ws-link] Subscription data:", message.id, message.data) + subHandler.onData(message.data) + } else if (message.type === "error") { + console.error("[ws-link] Subscription error:", message.id, message.error) + subHandler.onError(new Error(message.error || "Subscription error")) + } else if (message.type === "result") { + console.log("[ws-link] Subscription complete:", message.id) + subHandler.onComplete() + subscriptionHandlers.delete(message.id) + } + return + } + + // Handle normal query/mutation responses + const pending = pendingRequests.get(message.id) + if (pending) { + pendingRequests.delete(message.id) + if (message.type === "error") { + pending.reject(new Error(message.error || "Unknown error")) + } else { + const data = message.data + if (data && typeof data === "object" && "json" in data) { + pending.resolve(superjson.deserialize(data as any)) + } else { + pending.resolve(data) + } + } + } + } + } + + ws.onerror = () => { + connectionPromise = null + reject(new Error("WebSocket connection failed")) + } + + ws.onclose = () => { + ws = null + isAuthenticated = false + connectionPromise = null + // Clear all pending requests + for (const [id, pending] of pendingRequests) { + pending.reject(new Error("Connection closed")) + } + pendingRequests.clear() + // Clear all subscriptions + for (const handler of subscriptionHandlers.values()) { + handler.onComplete() + } + subscriptionHandlers.clear() + } + }) + + return connectionPromise +} + +/** + * Send a tRPC request via WebSocket (for queries/mutations) + */ +async function sendTRPCRequest(method: string, params?: unknown): Promise { + await connect() + + return new Promise((resolve, reject) => { + const id = `trpc-${++requestId}-${Date.now()}` + const request: WSRequest = { id, type: "trpc", method, params } + + pendingRequests.set(id, { resolve, reject }) + + setTimeout(() => { + if (pendingRequests.has(id)) { + pendingRequests.delete(id) + reject(new Error("Request timeout")) + } + }, 30000) + + ws!.send(JSON.stringify(request)) + }) +} + +/** + * Create a WebSocket-based tRPC link + */ +export function wsLink(): TRPCLink { + return () => { + return ({ op }) => { + return observable((observer) => { + const { path, input, type } = op + + // Convert path to method name (e.g., "projects.list") + const method = path + + console.log("[ws-link] tRPC call:", { type, path: method }) + + // Handle subscriptions differently from queries/mutations + if (type === "subscription") { + console.log("[ws-link] Starting subscription:", method) + + const id = `sub-${++requestId}-${Date.now()}` + + // Set up subscription handler + const handler: SubscriptionHandler = { + onData: (data) => { + console.log("[ws-link] Subscription data received:", method, data) + observer.next({ + result: { + type: "data", + data: data, + }, + }) + }, + onError: (error) => { + console.error("[ws-link] Subscription error:", method, error) + observer.error(error) + }, + onComplete: () => { + console.log("[ws-link] Subscription complete:", method) + observer.complete() + }, + } + + subscriptionHandlers.set(id, handler) + + // Wait for connection, then send subscription request + connect().then(() => { + const request: WSRequest = { id, type: "trpc", method, params: input } + ws!.send(JSON.stringify(request)) + console.log("[ws-link] Subscription request sent:", id, method) + }).catch((error) => { + console.error("[ws-link] Failed to connect for subscription:", error) + subscriptionHandlers.delete(id) + observer.error(error) + }) + + // Return cleanup function + return () => { + console.log("[ws-link] Unsubscribing:", method, id) + subscriptionHandlers.delete(id) + } + } + + // Regular query/mutation + sendTRPCRequest(method, input) + .then((result) => { + observer.next({ + result: { + type: "data", + data: result, + }, + }) + observer.complete() + }) + .catch((error) => { + observer.error(error) + }) + + return () => { + // Cleanup for query/mutation (none needed) + } + }) + } + } +} diff --git a/src/renderer/lib/trpc.ts b/src/renderer/lib/trpc.ts index 684d64c03..36b79c224 100644 --- a/src/renderer/lib/trpc.ts +++ b/src/renderer/lib/trpc.ts @@ -1,17 +1,96 @@ import { createTRPCReact } from "@trpc/react-query" import { createTRPCProxyClient } from "@trpc/client" -import { ipcLink } from "trpc-electron/renderer" import type { AppRouter } from "../../main/lib/trpc/routers" import superjson from "superjson" +import { wsLink } from "./remote-transport/ws-link" + +/** + * Check if we're in remote/browser mode (no electronTRPC global) + * Must be called at runtime, not at module load time + */ +function checkIsRemoteMode(): boolean { + // @ts-ignore + return typeof window !== "undefined" && !window.electronTRPC +} /** * React hooks for tRPC */ export const trpc = createTRPCReact() +// Vanilla client - initialized lazily +let _trpcClient: ReturnType> | null = null +let _clientPromise: Promise>> | null = null +let _initStarted = false + +/** + * Initialize the vanilla client + */ +async function initVanillaClient(): Promise>> { + if (_trpcClient) return _trpcClient + if (_clientPromise) return _clientPromise + + _clientPromise = (async () => { + const isRemote = checkIsRemoteMode() + console.log("[trpc] Initializing vanilla client, isRemote:", isRemote) + + let link: any + if (isRemote) { + link = wsLink() + } else { + const { ipcLink } = await import("trpc-electron/renderer") + link = ipcLink({ transformer: superjson }) + } + + _trpcClient = createTRPCProxyClient({ links: [link] }) + console.log("[trpc] Vanilla client initialized") + return _trpcClient + })() + + return _clientPromise +} + +// Start initialization immediately (will run after module loads) +if (typeof window !== "undefined") { + // Use setTimeout to ensure this runs after preload has set up electronTRPC + setTimeout(() => { + if (!_initStarted) { + _initStarted = true + initVanillaClient().catch(console.error) + } + }, 0) +} + +/** + * Get vanilla tRPC client (async to handle dynamic link loading) + */ +export async function getTrpcClient() { + if (_trpcClient) return _trpcClient + if (!_initStarted) { + _initStarted = true + return initVanillaClient() + } + return _clientPromise! +} + /** * Vanilla client for use outside React components (stores, utilities) + * Returns a proxy that delegates to the real client once initialized */ -export const trpcClient = createTRPCProxyClient({ - links: [ipcLink({ transformer: superjson })], +export const trpcClient = new Proxy({} as ReturnType>, { + get(_target, prop) { + if (_trpcClient) { + return (_trpcClient as any)[prop] + } + // Return a proxy that waits for initialization + return new Proxy({} as any, { + get(_t, subProp) { + // This handles trpcClient.git.query(...) pattern + return async (...args: any[]) => { + const client = await getTrpcClient() + return (client as any)[prop][subProp](...args) + } + }, + }) + }, }) From 68ca699caa84b2fb8071d1ba3c642b5119bad863 Mon Sep 17 00:00:00 2001 From: Yogi Date: Mon, 2 Feb 2026 13:09:29 +0700 Subject: [PATCH 06/16] feat: add chat state sync between desktop and web clients Implement shared subscription system so all clients receive chat updates: - Shared subscriptions per subChatId in WebSocket server - IPC clients can register listeners to receive broadcasts - Desktop IPC transport subscribes to WebSocket server broadcasts - When web sends message, desktop receives it via broadcast - When desktop sends message, web receives it via shared subscription Key changes: - ws-server.ts: Add sharedChatSubscriptions map with clients + listeners - ws-server.ts: Add subscribeToChatData() for IPC clients - main.ts: Add chat:subscribe IPC handler - preload/index.ts: Add subscribeToChatData to desktopApi - ipc-chat-transport.ts: Subscribe to broadcasts when creating stream This ensures bidirectional sync: desktop and web clients see all messages from each other in real-time. Co-Authored-By: Claude --- src/main/lib/remote-access/ws-server.ts | 298 ++++++++++++------ src/main/windows/main.ts | 31 ++ src/preload/index.ts | 49 +++ .../features/agents/lib/ipc-chat-transport.ts | 46 +++ 4 files changed, 335 insertions(+), 89 deletions(-) diff --git a/src/main/lib/remote-access/ws-server.ts b/src/main/lib/remote-access/ws-server.ts index bc72bffa0..76ad4461f 100644 --- a/src/main/lib/remote-access/ws-server.ts +++ b/src/main/lib/remote-access/ws-server.ts @@ -46,6 +46,16 @@ const authenticatedClients = new Map() // Subscription handlers - map of channel pattern to active subscriptions const subscriptionCleanups = new Map void>() +// Shared chat subscriptions - one Observable per subChatId, broadcast to all clients +interface SharedChatSubscription { + observable: any + subscription: any + clients: Set + // Event emitter for IPC clients to listen + listeners: Set<(data: any) => void> +} +const sharedChatSubscriptions = new Map() + // MIME types for static files const MIME_TYPES: Record = { ".html": "text/html", @@ -400,6 +410,75 @@ export function broadcast(channel: string, data: unknown): void { } } +/** + * Broadcast chat data to all clients subscribed to a specific subChat + * This is called when chat updates come from IPC (desktop) or WebSocket (web) + */ +export function broadcastChatData(subChatId: string, data: any): void { + const sharedSub = sharedChatSubscriptions.get(subChatId) + if (!sharedSub) { + // No subscription exists for this chat + return + } + + console.log(`[WS] Broadcasting chat data to ${sharedSub.clients.size} WebSocket clients and ${sharedSub.listeners.size} IPC listeners for subChatId: ${subChatId}`) + + // Broadcast to WebSocket clients + for (const clientWs of sharedSub.clients) { + send(clientWs, { + id: null, + type: "subscription", + data: data, + }) + } + + // Broadcast to IPC listeners (desktop clients) + for (const listener of sharedSub.listeners) { + try { + listener(data) + } catch (err) { + console.error(`[WS] Error in IPC listener:`, err) + } + } +} + +/** + * Subscribe to chat data from IPC (desktop client) + * Returns unsubscribe function + */ +export function subscribeToChatData(subChatId: string, callback: (data: any) => void): () => void { + let sharedSub = sharedChatSubscriptions.get(subChatId) + + if (!sharedSub) { + // Create a placeholder subscription without an Observable + // This allows IPC clients to register listeners before any WebSocket client connects + sharedSub = { + observable: null, + subscription: null, + clients: new Set(), + listeners: new Set(), + } + sharedChatSubscriptions.set(subChatId, sharedSub) + } + + sharedSub.listeners.add(callback) + console.log(`[WS] IPC listener added for subChatId: ${subChatId}. Total listeners: ${sharedSub.listeners.size}`) + + // Return unsubscribe function + return () => { + sharedSub!.listeners.delete(callback) + console.log(`[WS] IPC listener removed for subChatId: ${subChatId}. Remaining listeners: ${sharedSub!.listeners.size}`) + + // Clean up if no clients or listeners left + if (sharedSub!.clients.size === 0 && sharedSub!.listeners.size === 0) { + if (sharedSub!.subscription) { + sharedSub!.subscription.unsubscribe() + } + sharedChatSubscriptions.delete(subChatId) + } + } +} + /** * Handle incoming WebSocket message */ @@ -451,115 +530,156 @@ async function handleMessage( // Handle subscriptions (streaming) // For claude.chat, routerName is "claude" and procedureName is "chat" if (routerName === "claude" && procedureName === "chat") { - console.log(`[WS] Starting claude.chat subscription: ${message.id}`) - console.log(`[WS] Chat params:`, JSON.stringify(message.params, null, 2)) - - // Access the claude router (procedures are direct properties, not in _def) - const claudeRouter = (router as any).claude - if (!claudeRouter) { - throw new Error(`claude router not found`) + const subChatId = message.params?.subChatId + if (!subChatId) { + throw new Error(`subChatId is required for chat subscription`) } - // The chat procedure is a direct property on the router - const chatProcedure = claudeRouter.chat - if (!chatProcedure) { - console.error(`[WS] Available procedures in claude router:`, Object.keys(claudeRouter).filter(k => !k.startsWith('_'))) - throw new Error(`chat procedure not found in claude router`) - } + console.log(`[WS] Starting claude.chat subscription for subChatId: ${subChatId}`) - console.log(`[WS] Chat procedure found, type:`, typeof chatProcedure) + // Check if there's already a shared subscription for this subChatId + let sharedSub = sharedChatSubscriptions.get(subChatId) - // tRPC procedures store their implementation in _def.resolver - // For subscriptions, the resolver is the function that returns an Observable - const procedureDef = chatProcedure._def - if (!procedureDef) { - throw new Error(`chat procedure _def not found`) - } + if (!sharedSub) { + // Create new shared subscription + console.log(`[WS] Creating new shared subscription for ${subChatId}`) - // For subscriptions, the implementation is in _def.resolver - const subscriptionFn = procedureDef.resolver - if (!subscriptionFn || typeof subscriptionFn !== 'function') { - console.error(`[WS] Procedure _def keys:`, Object.keys(procedureDef)) - throw new Error(`chat procedure resolver not found or not a function`) - } + // Access the claude router + const claudeRouter = (router as any).claude + if (!claudeRouter) { + throw new Error(`claude router not found`) + } - // Create context for the subscription - const ctx = { - getWindow: () => BrowserWindow.getFocusedWindow(), - } + const chatProcedure = claudeRouter.chat + if (!chatProcedure) { + throw new Error(`chat procedure not found`) + } - // Call the raw subscription function with { input, type, ctx, path } - // This bypasses the client-side checks in the procedure wrapper - let observable - try { - observable = subscriptionFn({ + const procedureDef = chatProcedure._def + if (!procedureDef) { + throw new Error(`chat procedure _def not found`) + } + + const subscriptionFn = procedureDef.resolver + if (!subscriptionFn || typeof subscriptionFn !== 'function') { + throw new Error(`chat procedure resolver not found`) + } + + const ctx = { + getWindow: () => BrowserWindow.getFocusedWindow(), + } + + // Create the Observable + const observable = subscriptionFn({ input: message.params, type: 'subscription', ctx, path: 'claude.chat', }) - } catch (err) { - console.error(`[WS] Error calling claude.chat subscription:`, err) - send(ws, { - id: message.id, - type: "error", - error: err instanceof Error ? err.message : "Unknown error", - }) - return - } - // Check if the result is actually an Observable - if (typeof observable?.subscribe !== 'function') { - console.error(`[WS] claude.chat did not return an Observable. Result:`, typeof observable) - if (observable) { - console.error(`[WS] Result constructor:`, observable.constructor?.name) - console.error(`[WS] Result keys:`, Object.keys(observable)) + if (typeof observable?.subscribe !== 'function') { + throw new Error(`chat subscription did not return Observable`) } - send(ws, { - id: message.id, - type: "error", - error: `claude.chat returned ${typeof observable} instead of Observable`, + + // Subscribe and broadcast to all clients (WebSocket + IPC) + const subscription = observable.subscribe({ + next: (data: any) => { + console.log(`[WS] Broadcasting chat data to ${sharedSub?.clients.size || 0} WS clients, ${sharedSub?.listeners.size || 0} IPC listeners:`, data.type || "no-type") + // Broadcast to all WebSocket clients subscribed to this subChat + if (sharedSub) { + for (const clientWs of sharedSub.clients) { + send(clientWs, { + id: null, + type: "subscription", + data: data, + }) + } + // Also notify IPC listeners (desktop clients) + for (const listener of sharedSub.listeners) { + try { + listener(data) + } catch (err) { + console.error(`[WS] Error in IPC listener:`, err) + } + } + } + }, + error: (err: any) => { + console.error(`[WS] Chat subscription error:`, err) + if (sharedSub) { + for (const clientWs of sharedSub.clients) { + send(clientWs, { + id: null, + type: "error", + error: err.message, + }) + } + for (const listener of sharedSub.listeners) { + try { + listener({ type: "error", error: err.message }) + } catch (err) { + console.error(`[WS] Error in IPC listener:`, err) + } + } + } + }, + complete: () => { + console.log(`[WS] Chat subscription complete for ${subChatId}`) + if (sharedSub) { + for (const clientWs of sharedSub.clients) { + send(clientWs, { + id: null, + type: "result", + data: { completed: true }, + }) + } + for (const listener of sharedSub.listeners) { + try { + listener({ type: "complete" }) + } catch (err) { + console.error(`[WS] Error in IPC listener:`, err) + } + } + } + } }) - return - } - console.log(`[WS] Observable created, subscribing...`) - - const sub = observable.subscribe({ - next: (data: any) => { - console.log(`[WS] Chat data chunk:`, data.type || "no-type") - send(ws, { - id: message.id, - type: "subscription", - data: data, - }) - }, - error: (err: any) => { - console.error(`[WS] Chat subscription error:`, err) - send(ws, { - id: message.id, - type: "error", - error: err.message, - }) - }, - complete: () => { - console.log(`[WS] Chat subscription complete: ${message.id}`) - send(ws, { - id: message.id, - type: "result", - data: { completed: true }, - }) + // Create shared subscription object with listeners set + // Preserve existing listeners if this is being recreated + const existingListeners = sharedChatSubscriptions.get(subChatId)?.listeners || new Set() + sharedSub = { + observable, + subscription, + clients: new Set(), + listeners: existingListeners, } - }) + sharedChatSubscriptions.set(subChatId, sharedSub) + } - // Store unsubscription for cleanup - const cleanupId = `sub-${message.id}` - const cleanup = () => sub.unsubscribe() - subscriptionCleanups.set(cleanupId, cleanup) + // Add this client to the shared subscription + sharedSub.clients.add(ws) + console.log(`[WS] Client added to shared subscription ${subChatId}. Total clients: ${sharedSub.clients.size}`) + // Send success response + send(ws, { + id: message.id, + type: "result", + data: { subscribed: true, subChatId }, + }) + + // Clean up client from subscription on disconnect ws.on("close", () => { - cleanup() - subscriptionCleanups.delete(cleanupId) + if (sharedSub) { + sharedSub.clients.delete(ws) + console.log(`[WS] Client removed from shared subscription ${subChatId}. Remaining clients: ${sharedSub.clients.size}`) + + // Clean up subscription if no clients left + if (sharedSub.clients.size === 0) { + console.log(`[WS] No clients left for ${subChatId}, cleaning up subscription`) + sharedSub.subscription.unsubscribe() + sharedChatSubscriptions.delete(subChatId) + } + } }) return diff --git a/src/main/windows/main.ts b/src/main/windows/main.ts index 141f726d8..05b8e3749 100644 --- a/src/main/windows/main.ts +++ b/src/main/windows/main.ts @@ -452,6 +452,37 @@ function registerIpcHandlers(): void { // Register VS Code theme scanner IPC handlers registerThemeScannerIPC() + + // Remote access IPC handlers + ipcMain.handle("remote-access:get-status", async () => { + const { getRemoteAccessStatus } = await import("../lib/remote-access") + return getRemoteAccessStatus() + }) + + ipcMain.handle("remote-access:enable", async () => { + const { enableRemoteAccess, getRemoteAccessStatus } = await import("../lib/remote-access") + await enableRemoteAccess() + return getRemoteAccessStatus() + }) + + ipcMain.handle("remote-access:disable", async () => { + const { disableRemoteAccess, getRemoteAccessStatus } = await import("../lib/remote-access") + await disableRemoteAccess() + return getRemoteAccessStatus() + }) + + // Chat data sync - subscribe to broadcasts from WebSocket server + // This allows desktop clients to receive messages from web clients + ipcMain.handle("chat:subscribe", (event, subChatId: string) => { + const { subscribeToChatData } = require("../lib/remote-access/ws-server") + const unsubscribe = subscribeToChatData(subChatId, (data: any) => { + // Send chat data to this renderer process + event.sender.send("chat:data", subChatId, data) + }) + + // Return unsubscribe function for cleanup + return unsubscribe + }) } /** diff --git a/src/preload/index.ts b/src/preload/index.ts index 5152745df..6364076b4 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -221,6 +221,43 @@ contextBridge.exposeInMainWorld("desktopApi", { // VS Code theme scanning scanVSCodeThemes: () => ipcRenderer.invoke("vscode:scan-themes"), loadVSCodeTheme: (themePath: string) => ipcRenderer.invoke("vscode:load-theme", themePath), + + // Remote access + getRemoteAccessStatus: () => ipcRenderer.invoke("remote-access:get-status"), + enableRemoteAccess: () => ipcRenderer.invoke("remote-access:enable"), + disableRemoteAccess: () => ipcRenderer.invoke("remote-access:disable"), + onRemoteAccessStatusChange: (callback: (status: any) => void) => { + const handler = (_event: unknown, status: any) => callback(status) + ipcRenderer.on("remote-access:status", handler) + return () => ipcRenderer.removeListener("remote-access:status", handler) + }, + + // Chat data sync - subscribe to broadcasts from WebSocket server + subscribeToChatData: (subChatId: string, callback: (data: any) => void) => { + const dataHandler = (_event: unknown, chatId: string, data: any) => { + if (chatId === subChatId) { + callback(data) + } + } + ipcRenderer.on("chat:data", dataHandler) + + // Register subscription with main process + ipcRenderer.invoke("chat:subscribe", subChatId).then((unsubscribe) => { + // Store unsubscribe function for cleanup + ;(ipcRenderer as any).__chatUnsubscribeMap = (ipcRenderer as any).__chatUnsubscribeMap || new Map() + ;(ipcRenderer as any).__chatUnsubscribeMap.set(subChatId, unsubscribe) + }) + + // Return cleanup function + return () => { + ipcRenderer.removeListener("chat:data", dataHandler) + const unsubscribe = (ipcRenderer as any).__chatUnsubscribeMap?.get(subChatId) + if (unsubscribe) { + unsubscribe() + ;(ipcRenderer as any).__chatUnsubscribeMap.delete(subChatId) + } + } + }, }) // Type definitions @@ -354,6 +391,18 @@ export interface DesktopApi { // VS Code theme scanning scanVSCodeThemes: () => Promise loadVSCodeTheme: (themePath: string) => Promise + // Remote access + getRemoteAccessStatus: () => Promise<{ + status: "disabled" | "downloading" | "starting" | "active" | "error" + progress?: number + url?: string + pin?: string + clients?: number + message?: string + }> + enableRemoteAccess: () => Promise + disableRemoteAccess: () => Promise + onRemoteAccessStatusChange: (callback: (status: any) => void) => () => void } declare global { diff --git a/src/renderer/features/agents/lib/ipc-chat-transport.ts b/src/renderer/features/agents/lib/ipc-chat-transport.ts index 47442ed54..717a1b6c9 100644 --- a/src/renderer/features/agents/lib/ipc-chat-transport.ts +++ b/src/renderer/features/agents/lib/ipc-chat-transport.ts @@ -514,8 +514,54 @@ export class IPCChatTransport implements ChatTransport { }, ) + // Also subscribe to broadcasts from WebSocket server (for sync with web clients) + // This allows desktop to receive messages sent from web clients + let broadcastUnsubscribe: (() => void) | null = null + if (window.desktopApi?.subscribeToChatData) { + try { + broadcastUnsubscribe = window.desktopApi.subscribeToChatData( + this.config.subChatId, + (data: any) => { + // Received broadcast from web client, forward to controller + console.log(`[SD] Broadcast from web:`, data.type || "no-type") + if (data.type === "complete") { + try { + controller.close() + } catch { + // Already closed + } + } else if (data.type === "error") { + controller.error(new Error(data.error || "Unknown error")) + } else { + // Regular data chunk + try { + controller.enqueue(data) + } catch { + // Controller closed, ignore + } + } + } + ) + } catch (err) { + console.error(`[SD] Failed to subscribe to chat broadcasts:`, err) + } + } + // Handle abort options.abortSignal?.addEventListener("abort", () => { + console.log(`[SD] R:ABORT sub=${subId} n=${chunkCount} last=${lastChunkType}`) + sub.unsubscribe() + // Clean up broadcast subscription + if (broadcastUnsubscribe) { + broadcastUnsubscribe() + } + // trpcClient.claude.cancel.mutate({ subChatId: this.config.subChatId }) + try { + controller.close() + } catch { + // Already closed + } + }) console.log(`[SD] R:ABORT sub=${subId} n=${chunkCount} last=${lastChunkType}`) sub.unsubscribe() // trpcClient.claude.cancel.mutate({ subChatId: this.config.subChatId }) From 300a621719574cf3d5f162c06c33c71d1bcdee99 Mon Sep 17 00:00:00 2001 From: Yogi Date: Mon, 2 Feb 2026 13:13:46 +0700 Subject: [PATCH 07/16] feat: hide Settings & Remote Access icons from web view Add isRemoteModeAtom that detects when running in web browser (not desktop). Hide Settings and Remote Access buttons in sidebar when in remote mode. Remote mode detection: - Check for remote_ws_auth in sessionStorage (set after login) - Check if window.desktopApi is missing (web browser) This prevents web users from seeing desktop-only features. Co-Authored-By: Claude --- .../features/sidebar/agents-sidebar.tsx | 62 ++++++++++++++----- src/renderer/lib/atoms/index.ts | 41 +++++++++++- 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/src/renderer/features/sidebar/agents-sidebar.tsx b/src/renderer/features/sidebar/agents-sidebar.tsx index c6915f536..f9927ca36 100644 --- a/src/renderer/features/sidebar/agents-sidebar.tsx +++ b/src/renderer/features/sidebar/agents-sidebar.tsx @@ -20,6 +20,7 @@ import { clearAgentChatSelectionAtom, selectedAgentChatsCountAtom, isDesktopAtom, + isRemoteModeAtom, isFullscreenAtom, showOfflineModeFeaturesAtom, chatSourceModeAtom, @@ -28,6 +29,7 @@ import { showWorkspaceIconAtom, betaKanbanEnabledAtom, betaAutomationsEnabledAtom, + remoteAccessDialogOpenAtom, } from "../../lib/atoms" import { useRemoteChats, @@ -39,7 +41,7 @@ import { useRenameRemoteChat, } from "../../lib/hooks/use-remote-chats" import { ArchivePopover } from "../agents/ui/archive-popover" -import { ChevronDown, MoreHorizontal, Columns3, ArrowUpRight } from "lucide-react" +import { ChevronDown, MoreHorizontal, Columns3, ArrowUpRight, Globe } from "lucide-react" import { useQuery } from "@tanstack/react-query" import { remoteTrpc } from "../../lib/remote-trpc" // import { useRouter } from "next/navigation" // Desktop doesn't use next/navigation @@ -1137,6 +1139,26 @@ const ArchiveButton = memo(forwardRef + + + + Remote Access + + ) +}) + const KanbanButton = memo(function KanbanButton() { const kanbanEnabled = useAtomValue(betaKanbanEnabledAtom) const setSelectedChatId = useSetAtom(selectedAgentChatIdAtom) @@ -1742,6 +1764,7 @@ export function AgentsSidebar({ // Global desktop/fullscreen state from atoms (initialized in AgentsLayout) const isDesktop = useAtomValue(isDesktopAtom) + const isRemoteMode = useAtomValue(isRemoteModeAtom) const isFullscreen = useAtomValue(isFullscreenAtom) // Multi-select state @@ -3469,22 +3492,24 @@ export function AgentsSidebar({ >
- {/* Settings Button */} - - - - - Settings{settingsHotkey && <> {settingsHotkey}} - + {/* Settings Button - hide in remote mode */} + {!isRemoteMode && ( + + + + + Settings{settingsHotkey && <> {settingsHotkey}} + + )} {/* Help Button - isolated component to prevent sidebar re-renders */} @@ -3492,6 +3517,9 @@ export function AgentsSidebar({ {/* Kanban View Button - isolated component */} + {/* Remote Access Button - hide in remote mode */} + {!isRemoteMode && } + {/* Archive Button - isolated component to prevent sidebar re-renders */}
diff --git a/src/renderer/lib/atoms/index.ts b/src/renderer/lib/atoms/index.ts index 8f09adbf6..8b6e86fc2 100644 --- a/src/renderer/lib/atoms/index.ts +++ b/src/renderer/lib/atoms/index.ts @@ -302,14 +302,21 @@ export const openaiApiKeyAtom = atomWithStorage( { getOnInit: true }, ) -// New: Model profiles storage -export const modelProfilesAtom = atomWithStorage( +// New: Model profiles storage (localStorage-based for desktop) +// Renamed to localStorageModelProfilesAtom - use modelProfilesAtom from model-profiles-sync.ts instead +export const localStorageModelProfilesAtom = atomWithStorage( "agents:model-profiles", [OFFLINE_PROFILE], // Start with offline profile undefined, { getOnInit: true }, ) +// Re-export the database-synced model profiles atom +// This atom automatically handles: +// - Desktop mode: Uses localStorage, syncs to database +// - Web mode: Loads from database via tRPC +export { modelProfilesAtom, useSyncModelProfiles } from "./model-profiles-sync" + // Migration: add models array to existing profiles that don't have it if (typeof window !== "undefined") { const profilesKey = "agents:model-profiles" @@ -425,10 +432,18 @@ export const activeConfigAtom = atom((get) => { const networkOnline = get(networkOnlineAtom) const autoOffline = get(autoOfflineModeAtom) + console.log('[activeConfigAtom] Debug:', { + activeProfileId, + profilesCount: profiles.length, + legacyConfigModel: legacyConfig.model, + legacyConfigBaseUrl: legacyConfig.baseUrl, + }) + // If auto-offline enabled and no internet, use offline profile if (!networkOnline && autoOffline) { const offlineProfile = profiles.find(p => p.isOffline) if (offlineProfile) { + console.log('[activeConfigAtom] Using offline profile') return offlineProfile.config } } @@ -437,16 +452,21 @@ export const activeConfigAtom = atom((get) => { if (activeProfileId) { const profile = profiles.find(p => p.id === activeProfileId) if (profile) { + console.log('[activeConfigAtom] Using profile:', profile.name, 'with model:', profile.config.model) return profile.config + } else { + console.log('[activeConfigAtom] Profile ID set but not found in profiles') } } // Fallback to legacy config if set const normalized = normalizeCustomClaudeConfig(legacyConfig) if (normalized) { + console.log('[activeConfigAtom] Using normalized legacy config') return normalized } + console.log('[activeConfigAtom] No custom config found, returning undefined') // No custom config return undefined }) @@ -803,6 +823,17 @@ export const updateInfoAtom = atom(null) // Whether app is running in Electron desktop environment export const isDesktopAtom = atom(false) +// Whether app is running in remote mode (web browser, not desktop) +// In remote mode, certain desktop-only features should be hidden +export const isRemoteModeAtom = atom(() => { + // Check if running in remote mode (no desktopApi or has remote auth token) + if (typeof window === "undefined") return false + // If we have a stored remote auth PIN, we're in remote mode + if (sessionStorage.getItem("remote_ws_auth")) return true + // If desktopApi is missing, we're in remote mode + return !window.desktopApi +}) + // Fullscreen state - null means not initialized yet // null = not yet loaded, false = not fullscreen, true = fullscreen export const isFullscreenAtom = atom(null) @@ -946,3 +977,9 @@ export const mcpApprovalDialogOpenAtom = atom(false) // Pending MCP approvals to show in the dialog export const pendingMcpApprovalsAtom = atom([]) + +// ============================================ +// REMOTE ACCESS ATOMS +// ============================================ + +export * from "./remote-access" From 5a5ab7e41117edfcc5e30cd3b3d04b8d84ef21d8 Mon Sep 17 00:00:00 2001 From: Yogi Date: Mon, 2 Feb 2026 13:16:22 +0700 Subject: [PATCH 08/16] feat: sync workspace and chat lists between desktop and web Add polling to projects and chats queries when running in remote mode (web browser). When in remote mode, the following queries now poll every 5 seconds: - trpc.projects.list - workspace list - trpc.chats.list - local chat list - trpc.chats.listArchived - archived chats - api.agents.getAgentChats - team chats - api.teams.getUserTeams - teams list This ensures that web clients see updates made on desktop (new chats, new workspaces, etc.) within 5 seconds. Co-Authored-By: Claude --- .../features/agents/lib/ipc-chat-transport.ts | 9 --------- .../features/agents/ui/agents-content.tsx | 17 +++++++++++++++-- .../features/sidebar/agents-sidebar.tsx | 15 ++++++++++++--- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/renderer/features/agents/lib/ipc-chat-transport.ts b/src/renderer/features/agents/lib/ipc-chat-transport.ts index 717a1b6c9..05084958e 100644 --- a/src/renderer/features/agents/lib/ipc-chat-transport.ts +++ b/src/renderer/features/agents/lib/ipc-chat-transport.ts @@ -562,15 +562,6 @@ export class IPCChatTransport implements ChatTransport { // Already closed } }) - console.log(`[SD] R:ABORT sub=${subId} n=${chunkCount} last=${lastChunkType}`) - sub.unsubscribe() - // trpcClient.claude.cancel.mutate({ subChatId: this.config.subChatId }) - try { - controller.close() - } catch { - // Already closed - } - }) }, }) } diff --git a/src/renderer/features/agents/ui/agents-content.tsx b/src/renderer/features/agents/ui/agents-content.tsx index 1c7a8d7e1..274eb8b30 100644 --- a/src/renderer/features/agents/ui/agents-content.tsx +++ b/src/renderer/features/agents/ui/agents-content.tsx @@ -32,6 +32,7 @@ import { betaKanbanEnabledAtom, betaAutomationsEnabledAtom, chatSourceModeAtom, + isRemoteModeAtom, } from "../../../lib/atoms" import { NewChatForm } from "../main/new-chat-form" import { KanbanView } from "../../kanban" @@ -166,20 +167,32 @@ export function AgentsContent() { } }, [activeSubChatName]) + // Check if running in remote mode (web browser) + const isRemoteMode = useAtomValue(isRemoteModeAtom) + // Fetch teams for header const { data: teams } = api.teams.getUserTeams.useQuery(undefined, { enabled: !!selectedTeamId, + // Poll every 5 seconds in remote mode to sync with desktop changes + refetchInterval: isRemoteMode ? 5000 : false, }) const selectedTeam = teams?.find((t: any) => t.id === selectedTeamId) as any // Fetch agent chats for keyboard navigation and mobile view const { data: agentChats } = api.agents.getAgentChats.useQuery( { teamId: selectedTeamId! }, - { enabled: !!selectedTeamId }, + { + enabled: !!selectedTeamId, + // Poll every 5 seconds in remote mode to sync with desktop changes + refetchInterval: isRemoteMode ? 5000 : false, + }, ) // Fetch all projects for git info (like sidebar does) - const { data: projects } = trpc.projects.list.useQuery() + const { data: projects } = trpc.projects.list.useQuery( + // Poll every 5 seconds in remote mode to sync with desktop changes + isRemoteMode ? { refetchInterval: 5000 } : undefined, + ) // Create map for quick project lookup by id const projectsMap = useMemo(() => { diff --git a/src/renderer/features/sidebar/agents-sidebar.tsx b/src/renderer/features/sidebar/agents-sidebar.tsx index f9927ca36..0bc943e85 100644 --- a/src/renderer/features/sidebar/agents-sidebar.tsx +++ b/src/renderer/features/sidebar/agents-sidebar.tsx @@ -1873,7 +1873,10 @@ export function AgentsSidebar({ }, []) // Fetch all local chats (no project filter) - const { data: localChats } = trpc.chats.list.useQuery({}) + const { data: localChats } = trpc.chats.list.useQuery( + // Poll every 5 seconds in remote mode to sync with desktop changes + isRemoteMode ? { refetchInterval: 5000 } : {}, + ) // Fetch user's teams (same as web) - always enabled to allow merged list const { data: teams, isLoading: isTeamsLoading, isError: isTeamsError } = useUserTeams(true) @@ -2015,7 +2018,10 @@ export function AgentsSidebar({ ) // Fetch all projects for git info - const { data: projects } = trpc.projects.list.useQuery() + const { data: projects } = trpc.projects.list.useQuery( + // Poll every 5 seconds in remote mode to sync with desktop changes + isRemoteMode ? { refetchInterval: 5000 } : undefined, + ) // Auto-import hook for "Open Locally" functionality const { getMatchingProjects, autoImport, isImporting } = useAutoImport() @@ -2027,7 +2033,10 @@ export function AgentsSidebar({ }, [projects]) // Fetch all archived chats (to get count) - const { data: archivedChats } = trpc.chats.listArchived.useQuery({}) + const { data: archivedChats } = trpc.chats.listArchived.useQuery( + // Poll every 5 seconds in remote mode to sync with desktop changes + isRemoteMode ? { refetchInterval: 5000 } : {}, + ) const archivedChatsCount = archivedChats?.length ?? 0 // Get utils outside of callbacks - hooks must be called at top level From a53814b72ed880c8cc147f5597157d62cdbcd195 Mon Sep 17 00:00:00 2001 From: Yogi Date: Mon, 2 Feb 2026 13:24:47 +0700 Subject: [PATCH 09/16] feat: add connection error overlay for web app when disconnected Add visual feedback when web client loses connection to desktop app: Connection state tracking: - Add onConnectionStateChange() callback for connection state updates - States: "connecting" | "connected" | "disconnected" - Notify listeners on auth success, connection close, and errors UI components: - remoteConnectionStateAtom - tracks connection state in Jotai - useRemoteConnection() - hook that subscribes to state changes - ConnectionErrorOverlay - full-screen overlay when disconnected Overlay features: - Shows "Connection Lost" message with WiFiOff icon - Displays current connection state - "Reconnect" button that reloads the page - Backdrop blur effect with centered card Only active in remote mode (web browser), not in desktop app. Co-Authored-By: Claude --- src/renderer/App.tsx | 42 ++++++++++++++- .../ui/connection-error-overlay.tsx | 54 +++++++++++++++++++ src/renderer/lib/atoms/remote-access.ts | 4 ++ .../lib/hooks/use-remote-connection.ts | 29 ++++++++++ src/renderer/lib/remote-transport/index.ts | 51 ++++++++++++++++++ 5 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 src/renderer/components/ui/connection-error-overlay.tsx create mode 100644 src/renderer/lib/hooks/use-remote-connection.ts diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 641c43f4d..3d73e3179 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,6 +1,6 @@ import { Provider as JotaiProvider, useAtomValue, useSetAtom } from "jotai"; import { ThemeProvider, useTheme } from "next-themes"; -import { useEffect, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Toaster } from "sonner"; import { TooltipProvider } from "./components/ui/tooltip"; import { TRPCProvider } from "./contexts/TRPCProvider"; @@ -32,6 +32,11 @@ import { appStore } from "./lib/jotai-store"; import { VSCodeThemeProvider } from "./lib/themes/theme-provider"; import { useTrayEvents } from "./lib/tray-events"; import { trpc } from "./lib/trpc"; +import { initRemoteTransport } from "./lib/remote-transport/init"; +import { isRemoteMode } from "./lib/remote-transport"; +import { ModelProfilesSync } from "./components/model-profiles-sync"; +import { ConnectionErrorOverlay } from "./components/ui/connection-error-overlay"; +import { useRemoteConnection } from "./lib/hooks/use-remote-connection"; /** * Custom Toaster that adapts to theme @@ -125,6 +130,8 @@ function AppContent() { const { data: projects, isLoading: isLoadingProjects } = trpc.projects.list.useQuery(); + const isRemote = useMemo(() => isRemoteMode(), []); + // Validated project - only valid if exists in DB const validatedProject = useMemo(() => { if (!selectedProject) return null; @@ -136,6 +143,12 @@ function AppContent() { return exists ? selectedProject : null; }, [selectedProject, projects, isLoadingProjects]); + // If in remote mode, bypass all onboarding and go straight to layout + // The desktop app handles auth/config, and we can select projects from the sidebar + if (isRemote) { + return ; + } + // Determine which page to show: // 1. No billing method selected -> BillingMethodPage OR CliConfigDetectedPage // 2. Claude subscription selected but not completed -> AnthropicOnboardingPage @@ -179,6 +192,22 @@ function AppContent() { } export function App() { + const [remoteReady, setRemoteReady] = useState(!isRemoteMode()); + + // Track remote connection state (for connection error overlay) + useRemoteConnection(); + + // Initialize remote transport if running in browser + useEffect(() => { + if (isRemoteMode()) { + initRemoteTransport().then((success) => { + if (success) { + setRemoteReady(true); + } + }); + } + }, []); + // Listen for tray events (settings, workspace selection) useTrayEvents(); @@ -217,6 +246,15 @@ export function App() { }; }, []); + // Show loading while remote transport initializes + if (!remoteReady) { + return ( +
+
Connecting...
+
+ ); + } + return ( @@ -224,6 +262,8 @@ export function App() { + +
+
+ {/* Icon */} +
+ +
+ + {/* Text */} +
+

Connection Lost

+

+ Unable to connect to the desktop app. Please make sure the desktop app is running and remote access is enabled. +

+
+ + {/* Status */} +
+
+ {connectionState} +
+ + {/* Refresh button */} + +
+
+ ) +} diff --git a/src/renderer/lib/atoms/remote-access.ts b/src/renderer/lib/atoms/remote-access.ts index 9ee83f687..e685fa147 100644 --- a/src/renderer/lib/atoms/remote-access.ts +++ b/src/renderer/lib/atoms/remote-access.ts @@ -12,3 +12,7 @@ export type RemoteAccessStatus = export const remoteAccessStatusAtom = atom({ status: "disabled" }) export const remoteAccessDialogOpenAtom = atom(false) + +// Connection state for remote mode (web client) +export type ConnectionState = "connecting" | "connected" | "disconnected" +export const remoteConnectionStateAtom = atom("connected") diff --git a/src/renderer/lib/hooks/use-remote-connection.ts b/src/renderer/lib/hooks/use-remote-connection.ts new file mode 100644 index 000000000..f2fb710d3 --- /dev/null +++ b/src/renderer/lib/hooks/use-remote-connection.ts @@ -0,0 +1,29 @@ +// src/renderer/lib/hooks/use-remote-connection.ts + +import { useEffect } from "react" +import { useSetAtom } from "jotai" +import { remoteConnectionStateAtom } from "../atoms/remote-access" +import { onConnectionStateChange, isRemoteMode } from "../remote-transport" + +/** + * Hook to track remote connection state + * Only active in remote mode (web browser) + */ +export function useRemoteConnection(): void { + const setConnectionState = useSetAtom(remoteConnectionStateAtom) + + useEffect(() => { + // Only track connection in remote mode + if (!isRemoteMode()) return + + // Subscribe to connection state changes + const unsubscribe = onConnectionStateChange((state) => { + console.log("[useRemoteConnection] State changed:", state) + setConnectionState(state) + }) + + return () => { + unsubscribe() + } + }, [setConnectionState]) +} diff --git a/src/renderer/lib/remote-transport/index.ts b/src/renderer/lib/remote-transport/index.ts index 9a3a8d5cd..4fdcf8f0d 100644 --- a/src/renderer/lib/remote-transport/index.ts +++ b/src/renderer/lib/remote-transport/index.ts @@ -35,6 +35,49 @@ const pendingRequests = new Map() const subscriptionHandlers = new Map void>>() let requestId = 0 +// Connection state listeners +type ConnectionState = "connecting" | "connected" | "disconnected" +const connectionStateListeners = new Set<(state: ConnectionState) => void>() +let currentConnectionState: ConnectionState = "connecting" + +/** + * Notify listeners of connection state change + */ +function notifyConnectionStateChange(state: ConnectionState) { + currentConnectionState = state + for (const listener of connectionStateListeners) { + try { + listener(state) + } catch (err) { + console.error("[RemoteTransport] Error in connection state listener:", err) + } + } +} + +/** + * Subscribe to connection state changes + * Returns unsubscribe function + */ +export function onConnectionStateChange(callback: (state: ConnectionState) => void): () => void { + connectionStateListeners.add(callback) + // Immediately call with current state + try { + callback(currentConnectionState) + } catch (err) { + console.error("[RemoteTransport] Error in initial connection state callback:", err) + } + return () => { + connectionStateListeners.delete(callback) + } +} + +/** + * Get current connection state + */ +export function getConnectionState(): ConnectionState { + return currentConnectionState +} + /** * Check if we're running in remote browser mode * Robust check that works even after desktopApi is mocked @@ -65,10 +108,14 @@ export function connect(): Promise { return Promise.resolve() } + // Notify we're connecting + notifyConnectionStateChange("connecting") + connectionPromise = new Promise((resolve, reject) => { const pin = getStoredPin() if (!pin) { // Redirect to login page + notifyConnectionStateChange("disconnected") window.location.href = "/login" reject(new Error("No PIN stored")) return @@ -94,6 +141,7 @@ export function connect(): Promise { if (message.type === "auth_success") { console.log("[RemoteTransport] Authentication successful") isAuthenticated = true + notifyConnectionStateChange("connected") connectionPromise = null resolve() return @@ -102,6 +150,7 @@ export function connect(): Promise { if (message.type === "auth_failed") { console.error("[RemoteTransport] Authentication failed") isAuthenticated = false + notifyConnectionStateChange("disconnected") sessionStorage.removeItem("remote_ws_auth") window.location.href = "/login" reject(new Error("Authentication failed")) @@ -145,6 +194,7 @@ export function connect(): Promise { ws.onerror = (error) => { console.error("[RemoteTransport] WebSocket error:", error) + notifyConnectionStateChange("disconnected") connectionPromise = null reject(new Error("WebSocket connection failed")) } @@ -154,6 +204,7 @@ export function connect(): Promise { ws = null isAuthenticated = false connectionPromise = null + notifyConnectionStateChange("disconnected") } }) From da3b02102ab33162b5ae982ee0bbc9747600e83e Mon Sep 17 00:00:00 2001 From: Yogi Date: Mon, 2 Feb 2026 13:48:10 +0700 Subject: [PATCH 10/16] fix: resolve circular dependency and WebSocket routing issues Fix several issues that were preventing the app from running: 1. Circular dependency fix: - Move ModelProfile, OFFLINE_PROFILE, CustomClaudeConfig, ModelMapping to separate model-profile-types.ts file - This avoids circular dependency between atoms/index.ts and model-profiles-sync.ts 2. WebSocket server routing fix: - Access router directly instead of through caller for nested paths - Fix "Router not found: external" error by using router instead of caller - Caller may not expose nested routers properly 3. Import fix in main.ts: - Import subscribeToChatData at module level instead of dynamic require - Fix "Cannot find module '../lib/remote-access/ws-server'" error Co-Authored-By: Claude --- src/main/lib/remote-access/ws-server.ts | 4 +- src/main/windows/main.ts | 2 +- src/renderer/lib/atoms/index.ts | 55 +++---------------- src/renderer/lib/atoms/model-profile-types.ts | 47 ++++++++++++++++ src/renderer/lib/atoms/model-profiles-sync.ts | 3 +- 5 files changed, 59 insertions(+), 52 deletions(-) create mode 100644 src/renderer/lib/atoms/model-profile-types.ts diff --git a/src/main/lib/remote-access/ws-server.ts b/src/main/lib/remote-access/ws-server.ts index 76ad4461f..9223b3cab 100644 --- a/src/main/lib/remote-access/ws-server.ts +++ b/src/main/lib/remote-access/ws-server.ts @@ -690,9 +690,9 @@ async function handleMessage( getWindow: () => BrowserWindow.getFocusedWindow(), }) - // Navigate the caller to find the procedure + // Navigate to the procedure using the router directly (caller may not expose nested routers) // Handle nested paths like "external.getAppVersion" or "projects.list" - let current: any = caller + let current: any = router for (let i = 0; i < pathParts.length - 1; i++) { current = current[pathParts[i]] diff --git a/src/main/windows/main.ts b/src/main/windows/main.ts index 05b8e3749..d7f50b323 100644 --- a/src/main/windows/main.ts +++ b/src/main/windows/main.ts @@ -16,6 +16,7 @@ import { getAuthManager, handleAuthCode, getBaseUrl } from "../index" import { registerGitWatcherIPC } from "../lib/git/watcher" import { registerThemeScannerIPC } from "../lib/vscode-theme-scanner" import { windowManager } from "./window-manager" +import { subscribeToChatData } from "../lib/remote-access/ws-server" // Helper to get window from IPC event function getWindowFromEvent( @@ -474,7 +475,6 @@ function registerIpcHandlers(): void { // Chat data sync - subscribe to broadcasts from WebSocket server // This allows desktop clients to receive messages from web clients ipcMain.handle("chat:subscribe", (event, subChatId: string) => { - const { subscribeToChatData } = require("../lib/remote-access/ws-server") const unsubscribe = subscribeToChatData(subChatId, (data: any) => { // Send chat data to this renderer process event.sender.send("chat:data", subChatId, data) diff --git a/src/renderer/lib/atoms/index.ts b/src/renderer/lib/atoms/index.ts index 8b6e86fc2..56b2d300b 100644 --- a/src/renderer/lib/atoms/index.ts +++ b/src/renderer/lib/atoms/index.ts @@ -1,6 +1,12 @@ import { atom } from "jotai" import { atomWithStorage } from "jotai/utils" import { desktopViewAtom as _desktopViewAtom } from "../../features/agents/atoms" +import { + type ModelProfile, + OFFLINE_PROFILE, + type CustomClaudeConfig, + type ModelMapping, +} from "./model-profile-types" // ============================================ // RE-EXPORT FROM FEATURES/AGENTS/ATOMS (source of truth) @@ -204,33 +210,8 @@ export const agentsSettingsDialogOpenAtom = atom( } ) -export type CustomClaudeConfig = { - model: string - token: string - baseUrl: string - // Additional model configuration (optional) - defaultOpusModel?: string // ANTHROPIC_DEFAULT_OPUS_MODEL - defaultSonnetModel?: string // ANTHROPIC_DEFAULT_SONNET_MODEL - defaultHaikuModel?: string // ANTHROPIC_DEFAULT_HAIKU_MODEL - subagentModel?: string // CLAUDE_CODE_SUBAGENT_MODEL -} - -// Model mapping within a profile - maps display names to actual model IDs -export type ModelMapping = { - id: string // Internal reference ID (e.g., "opus", "sonnet", "haiku") - displayName: string // User-facing name (e.g., "Opus", "Sonnet 3.5") - modelId: string // Actual model ID sent to API (e.g., "glm-4.7", "claude-3-opus-20240229") - supportsThinking?: boolean // Whether this model supports extended thinking -} - -// Model profile system - support multiple configs -export type ModelProfile = { - id: string - name: string - config: CustomClaudeConfig - models: ModelMapping[] // Available models for this profile - isOffline?: boolean // Mark as offline/Ollama profile -} +// Re-export from model-profile-types.ts for convenience +export { CustomClaudeConfig, ModelMapping, ModelProfile, OFFLINE_PROFILE } from "./model-profile-types" // Selected Ollama model for offline mode export const selectedOllamaModelAtom = atomWithStorage( @@ -260,26 +241,6 @@ export const getOfflineProfile = (modelName?: string | null): ModelProfile => ({ ], }) -// Predefined offline profile for Ollama (legacy, uses default model) -export const OFFLINE_PROFILE: ModelProfile = { - id: 'offline-ollama', - name: 'Offline (Ollama)', - isOffline: true, - config: { - model: 'qwen2.5-coder:7b', - token: 'ollama', - baseUrl: 'http://localhost:11434', - }, - models: [ - { - id: 'default', - displayName: 'Default', - modelId: 'qwen2.5-coder:7b', - supportsThinking: false, - }, - ], -} - // Legacy single config (deprecated, kept for backwards compatibility) export const customClaudeConfigAtom = atomWithStorage( "agents:claude-custom-config", diff --git a/src/renderer/lib/atoms/model-profile-types.ts b/src/renderer/lib/atoms/model-profile-types.ts new file mode 100644 index 000000000..2f0433139 --- /dev/null +++ b/src/renderer/lib/atoms/model-profile-types.ts @@ -0,0 +1,47 @@ +// src/renderer/lib/atoms/model-profile-types.ts +// Shared types and constants for model profiles (to avoid circular dependencies) + +export type ModelMapping = { + id: string // Internal reference ID (e.g., "opus", "sonnet", "haiku") + displayName: string // User-facing name (e.g., "Opus", "Sonnet 3.5") + modelId: string // Actual model ID sent to API (e.g., "glm-4.7", "claude-3-opus-20240229") + supportsThinking?: boolean // Whether this model supports extended thinking +} + +export type CustomClaudeConfig = { + model: string + token?: string + baseUrl: string + defaultOpusModel?: string + defaultSonnetModel?: string + defaultHaikuModel?: string + subagentModel?: string +} + +export type ModelProfile = { + id: string + name: string + config: CustomClaudeConfig + models: ModelMapping[] + isOffline?: boolean +} + +export const OFFLINE_PROFILE: ModelProfile = { + id: 'offline-ollama', + name: 'Offline (Ollama)', + isOffline: true, + config: { + model: 'qwen2.5-coder:7b', + token: 'ollama', + baseUrl: 'http://localhost:11434', + }, + models: [ + { + id: 'default', + displayName: 'Default', + modelId: 'qwen2.5-coder:7b', + supportsThinking: false, + }, + ], +} + diff --git a/src/renderer/lib/atoms/model-profiles-sync.ts b/src/renderer/lib/atoms/model-profiles-sync.ts index 1c1989cf7..25c326f81 100644 --- a/src/renderer/lib/atoms/model-profiles-sync.ts +++ b/src/renderer/lib/atoms/model-profiles-sync.ts @@ -3,9 +3,8 @@ import { useEffect, useState } from "react" import { trpc } from "../../lib/trpc" import { localStorageModelProfilesAtom, - OFFLINE_PROFILE, - type ModelProfile, } from "./index" +import { OFFLINE_PROFILE, type ModelProfile } from "./model-profile-types" import { isRemoteMode } from "../remote-transport" /** From 9c5f2710c5f3119b93b6eab6f8970015a95b07c8 Mon Sep 17 00:00:00 2001 From: Yogi Date: Mon, 2 Feb 2026 14:10:11 +0700 Subject: [PATCH 11/16] fix: resolve circular dependency by keeping types inline in atoms/index.ts - Deleted model-profile-types.ts (was causing Vite HMR issues) - Restored ModelProfile, CustomClaudeConfig, OFFLINE_PROFILE types inline - Updated model-profiles-sync.ts to import from ./index Co-Authored-By: Claude --- src/renderer/lib/atoms/index.ts | 53 ++++++++++++++++--- src/renderer/lib/atoms/model-profile-types.ts | 47 ---------------- src/renderer/lib/atoms/model-profiles-sync.ts | 2 +- 3 files changed, 46 insertions(+), 56 deletions(-) delete mode 100644 src/renderer/lib/atoms/model-profile-types.ts diff --git a/src/renderer/lib/atoms/index.ts b/src/renderer/lib/atoms/index.ts index 56b2d300b..c78f80b84 100644 --- a/src/renderer/lib/atoms/index.ts +++ b/src/renderer/lib/atoms/index.ts @@ -1,12 +1,6 @@ import { atom } from "jotai" import { atomWithStorage } from "jotai/utils" import { desktopViewAtom as _desktopViewAtom } from "../../features/agents/atoms" -import { - type ModelProfile, - OFFLINE_PROFILE, - type CustomClaudeConfig, - type ModelMapping, -} from "./model-profile-types" // ============================================ // RE-EXPORT FROM FEATURES/AGENTS/ATOMS (source of truth) @@ -210,8 +204,51 @@ export const agentsSettingsDialogOpenAtom = atom( } ) -// Re-export from model-profile-types.ts for convenience -export { CustomClaudeConfig, ModelMapping, ModelProfile, OFFLINE_PROFILE } from "./model-profile-types" +// Model profile types (defined here to avoid circular dependencies) +export type ModelMapping = { + id: string // Internal reference ID (e.g., "opus", "sonnet", "haiku") + displayName: string // User-facing name (e.g., "Opus", "Sonnet 3.5") + modelId: string // Actual model ID sent to API (e.g., "glm-4.7", "claude-3-opus-20240229") + supportsThinking?: boolean // Whether this model supports extended thinking +} + +export type CustomClaudeConfig = { + model: string + token?: string + baseUrl: string + // Additional model configuration (optional) + defaultOpusModel?: string // ANTHROPIC_DEFAULT_OPUS_MODEL + defaultSonnetModel?: string // ANTHROPIC_DEFAULT_SONNET_MODEL + defaultHaikuModel?: string // ANTHROPIC_DEFAULT_HAIKU_MODEL + subagentModel?: string // CLAUDE_CODE_SUBAGENT_MODEL +} + +export type ModelProfile = { + id: string + name: string + config: CustomClaudeConfig + models: ModelMapping[] + isOffline?: boolean +} + +export const OFFLINE_PROFILE: ModelProfile = { + id: 'offline-ollama', + name: 'Offline (Ollama)', + isOffline: true, + config: { + model: 'qwen2.5-coder:7b', + token: 'ollama', + baseUrl: 'http://localhost:11434', + }, + models: [ + { + id: 'default', + displayName: 'Default', + modelId: 'qwen2.5-coder:7b', + supportsThinking: false, + }, + ], +} // Selected Ollama model for offline mode export const selectedOllamaModelAtom = atomWithStorage( diff --git a/src/renderer/lib/atoms/model-profile-types.ts b/src/renderer/lib/atoms/model-profile-types.ts deleted file mode 100644 index 2f0433139..000000000 --- a/src/renderer/lib/atoms/model-profile-types.ts +++ /dev/null @@ -1,47 +0,0 @@ -// src/renderer/lib/atoms/model-profile-types.ts -// Shared types and constants for model profiles (to avoid circular dependencies) - -export type ModelMapping = { - id: string // Internal reference ID (e.g., "opus", "sonnet", "haiku") - displayName: string // User-facing name (e.g., "Opus", "Sonnet 3.5") - modelId: string // Actual model ID sent to API (e.g., "glm-4.7", "claude-3-opus-20240229") - supportsThinking?: boolean // Whether this model supports extended thinking -} - -export type CustomClaudeConfig = { - model: string - token?: string - baseUrl: string - defaultOpusModel?: string - defaultSonnetModel?: string - defaultHaikuModel?: string - subagentModel?: string -} - -export type ModelProfile = { - id: string - name: string - config: CustomClaudeConfig - models: ModelMapping[] - isOffline?: boolean -} - -export const OFFLINE_PROFILE: ModelProfile = { - id: 'offline-ollama', - name: 'Offline (Ollama)', - isOffline: true, - config: { - model: 'qwen2.5-coder:7b', - token: 'ollama', - baseUrl: 'http://localhost:11434', - }, - models: [ - { - id: 'default', - displayName: 'Default', - modelId: 'qwen2.5-coder:7b', - supportsThinking: false, - }, - ], -} - diff --git a/src/renderer/lib/atoms/model-profiles-sync.ts b/src/renderer/lib/atoms/model-profiles-sync.ts index 25c326f81..251e903a3 100644 --- a/src/renderer/lib/atoms/model-profiles-sync.ts +++ b/src/renderer/lib/atoms/model-profiles-sync.ts @@ -4,7 +4,7 @@ import { trpc } from "../../lib/trpc" import { localStorageModelProfilesAtom, } from "./index" -import { OFFLINE_PROFILE, type ModelProfile } from "./model-profile-types" +import { OFFLINE_PROFILE, type ModelProfile } from "./index" import { isRemoteMode } from "../remote-transport" /** From 9956ae958539ac00819a2152e43b42acfac06f95 Mon Sep 17 00:00:00 2001 From: Yogi Date: Mon, 2 Feb 2026 14:20:10 +0700 Subject: [PATCH 12/16] fix: move activeConfigAtom to model-profiles-sync.ts to break circular dependency - Moved activeConfigAtom and normalizeCustomClaudeConfig to model-profiles-sync.ts - Updated index.ts to re-export activeConfigAtom from model-profiles-sync.ts - This breaks the circular dependency where: - index.ts imports modelProfilesAtom from model-profiles-sync.ts - model-profiles-sync.ts imports types from index.ts - index.ts defines activeConfigAtom which reads modelProfilesAtom Co-Authored-By: Claude --- bun.lockb | Bin 483005 -> 493891 bytes docs/plans/2026-02-01-remote-access-design.md | 266 ++++ ...2026-02-01-remote-access-implementation.md | 1342 +++++++++++++++++ drizzle/0010_cold_masked_marvel.sql | 18 + drizzle/meta/0010_snapshot.json | 541 +++++++ drizzle/meta/_journal.json | 9 +- package.json | 4 +- src/main/index.ts | 18 +- src/main/lib/db/schema/index.ts | 33 + src/main/lib/tray.ts | 7 +- src/main/lib/trpc/routers/index.ts | 4 + .../features/agents/ui/profile-selector.tsx | 42 +- .../features/layout/agents-layout.tsx | 2 + .../onboarding/api-key-onboarding-page.tsx | 51 +- src/renderer/lib/atoms/index.ts | 61 +- src/renderer/lib/atoms/model-profiles-sync.ts | 72 +- 16 files changed, 2384 insertions(+), 86 deletions(-) create mode 100644 docs/plans/2026-02-01-remote-access-design.md create mode 100644 docs/plans/2026-02-01-remote-access-implementation.md create mode 100644 drizzle/0010_cold_masked_marvel.sql create mode 100644 drizzle/meta/0010_snapshot.json diff --git a/bun.lockb b/bun.lockb index 6601c57c89350cbf3ed855a809ebd553dfbc7cfc..2f16a73c3d459f6830227c6318e826cb130c3cb1 100755 GIT binary patch delta 109801 zcmeFadz?*W|Np=Cm|-tw*bBqRnPkM2^K681%9&ANkQif|8RldTn4}q%(m`RTO9ywU z6qUOqcT_4$krcX>V^Jw8MdgmV)$jGX4tuZ7)aSmxkI(n}&(C^fUi*4pr}y=~j%)2T z)5eVr-&)*g;pIt{EX#WOqt0I@KQ?Ll<~u%|GU3Z^OFXfMzuR~1gR!ZpCl{tZ-DB_C z`Bjk0vYuEpx^da_uSCm$`VnhckQ2x#F3iuHV5{)U(AY9W#8$Pe8qjno zSb0$(cSfKP73QZGLaeK+SynA@KeReD35x&9o8t$vl-JXmnO`6+Sq|$Q@HEuQbScmT z=vin@Xd7s4=+h{yj1^AG&(8^@=UKVw#Ti+sc@(T+f=b$;)kwsOCI=>!WWu0a-NGlq znZKYgKeI4UR5Yb1DKM+JFuf0Ir-%Gd8ZfmeKW|EQPQWxQGoSv1cWYT?(z3hJy!uFZ z5azL^zax?vZ-x~ts2q^tC}=!%Ab$9uKj`#7AWY9}N0=>t0e)u#mLW|3IKotuZVAG0 zLU~C|ud3kgI>A9vjmkcNo5-gHN@vi(HT6`zuc92*_pH{1P^NqL5|#aRC>@avPJ6FI zfc9=jqZlqtFS#)X(hfYS=;< z@R}}f4U`RdN^5peQf5y6Bx}-TD*q$kRm+gzO8~ldJ^?hprLuHVVR}ZO$g;jdn7mD* zs_=Q zpenx-$`St}lzeiI9B@{8At#t+orzIa`~YSCo*h)V@Y~F+^x|>WQAE(5!a%MZL{mGe z_BHLKCVOot4e>(Rz7m+39hhv*;6y|Pb33c}ozRAe$I4WanPpjhItMq$Tvlf4ACF>L zOi~xMc5H=mpuG-d^-nOeUjMxcC#1Km~n z2|X+eL#eF14?bX)lw>p&ZKlV}K15g_Hwp7f7r}2S~*^i90aO2gg!qYH_Ys)U(0nhYTj;}O8 z8F1swY^e5z2Wj2h)*7&JnBc7 zUAYV4IOy1G;D1(FkX~36!0-v=6=T^56b(QG7P#_GP`2#gwW`Hip)}}49d3g7c!aMW zsSLad$_oAo<(O!V3RzKZdf_yTNGkzhmS;g3|J`V;L>wHQ)0Cz6K$&3gXjM`7F=}$< zAPpP+FLXkxgEZUYwPLZE|K-HV3lf>G-v^oVQxD#xImwxrcB96FGdEs!v=3KO&OMvUl>T5S`^Hz>Xfy~QDtPK zOxlo}U!0wv2hZgy8}5eE*tt+yM^R2T{A68^^t92GZN?e4gxqxOg6r~CqjS>pGP6r^ ztyu+@)ePw~pd7c0rpsO~D{qFb<#?zKWn)Gl0ln27$_igBQjK~TS{LC`D7~Hy^+BIN zMQq$%nwxNcoxh}nd$MIcr_{xN>^hUvOQ)hdYbh$A2Pb!6b(U4>M%9q7OI7)kJ0xY~=j0cj4u^}g%W?x2 zB5(#xFTHQJYRP^ipld&X(g4I4XTz`1KzdQYdJdcwZPxKG>+o`|ugy`Oi@I5PN`TXo z3$)$_rN?GNgT=8|)jUwH5{#d#DttC6xH-0S)P4EEUt=4U8MDgKKKdse%Ix>wrnKff zWzJOaIu-CVoxb&a)sc0e904A!r*Bu~p14De&i&9@h<{gUS@}AA;D~ra>mse?P>zIr zty^_L58kaDJ^`E^Ib3TBlnrR7^>0Yej&G*J&*^Y29gdbd8H+U{*dbr(3O>>Liq5c5 z>n&P~wPr$@ZxB4ditb;iG&d&)uEzm!3LgaIg*@NUS+7L}&wU=9~s zUEm3};D$$)L8(w0GA%oAa#H>j>sKAVbUYIlmt4&vgX+Y~$YU*x+vWx0Gqj+{6?lH5A zi`K0QR`oS%pYp77Tt*gd911Nf4cWOQw0cfCrZ7V`(|R6Zw)4s7f@6J+$_5}A-O&=imC-H$5S-OBvUbn1P2)_Vr3GIvwY{B-|&C_ZOy<@xT znn{|cK${_7BdzDqATA+`cj)u-K@FBe8zSOPC|y+urDw)N>B5(_t6Lz~bT|5x1;u+*-**0|;&Z^+ zXCwEh3SUK-1-$gJa_!_#REzpTn}7$w*}}K?s+K|0auY-D81Cqr4m zFlZg9U+cJoD!z}_s!+Bt3CeT}4=KaHgK{<;fXcy*czUoF${~MRHz-i{nQGBq=p~34 z0;MMwApr~g^stK0)Zs6VsPI;3eZ;SWvH=~wP}B59C=F^3<#ekKWyOE{QgzKQpR0zg zKt9HoJ%kUO|HCUqw%m^}JyT-ztmm{6-mM(tkHmt#d|m3QH#BrWaV|mc`s@ zm^+WNNKX$nfC05R|KI&y71-tn<%vq#!qz8MUlpd$Oq!HGx%6{{>8UQK)bzu{*5VTE z)~yj{fs+I3DdvH4wGWi!6=dTnt2L>#n9qMKAM!KZAE<|8Vro9lon?qP0l+~Kn1!1R zJOg{+XVs_kpiEc-WnX1OxpNokhi~I`_-ZH(3t(B#E-Wf;fG{_rTpVzdCQq`apwukX z6DUs4gjn5l`dU!=7*+1uC51U709e7tFo@b#C**#Ug!6skSyj<=HV4<=5N5i1zp61{ zLuu$Y;B4s~2y+Ws@|zk}&S1RN$K#O;PFpCaS5qi0pORiwoK#Yn{R-04u&n&Ne5U`wqdagx>*7i(-%%)M z(F`a(m6Ja+Cp$B%I2U0y_?9w!pvzKYJkp|GP*$`N1#xA3yRt{F3!STY_^Ysc1(qdKk z1sK4-s~YE#E-ab}zv7ya&AVR=rlLtD**TM~5}z{g1}F^~0Hp!#wKmXNS?kFfD*gZz zhLx4Sqdt_s2&H9z(|VWI8CoZ49j3Lb*2}ckg0ibDttYCh@P4gZp`2y^(E6lQ%>Tt2 zlxr=_&&eq%z^YYWx%l@>R11$lx#Desa)oSQaAoE=?SD}ESZmb(kea=ve=@=wYssT-J-T{!tD0H;G2RKNtUvk<_8RWxaxU zu1oi|Q+zg*LoOFeem#`4dMFfT;8`v{uwmv-U_l3!!Q2s;JBYd9tSGxkK6J3;Hw19# zrJa-^^`JET`~y7*a`z#j!K8InJ(`g!VRAs?uNCP3b~tU5?Fp_`nKhLs{_5tCY6T z;j;*97t0}J7SPALt1)I4QmmPHI%Zh~C3&S8`I7^wFo0dyR~z~4-^xApau$+7sW9;~x;#P$7@0fm8LxtX8`@C<5lAQSN%JQJ^0hHQdz z(7z48keBvX24_Rb^N=Sgv$UXmfJ&baWxDc#X7ZFNR~O}HOoQjFfrC^yUnj`T&dV-H zFDf!O?q=3aKs+ljv#!|?Rp1D4dZdrmKpxD-o#q+Dle4@V@gTC3j{l=srdZ703SVCIG$x^;6{9A#?0)=#aY2OnmT2p zF^JfN%b~P^M>y6oK!;5iG(ebjnT6)W4QjnQh?g!jltK`Y)iE zj%DShk!B%CpQI}Kvvt5M$(3Oc=kw%zJdVH_%FG(G;ypB3_0bb7voGDn2dS+NoFvr`4weF*t*!z`?FO&)>NhDm|~76 z|I@ML|L`Jyeb`SL^~Pdl>GJ@ly-g!`Lc%1GGQ{_C}d- zGhC90@FP$@uDAor1v3lEh3tAD5Ftfl6Emb9%6%B7&WF+O}XPR}xEL7*O58?G7*uKRLDQo*SYPoWE z+5UxJ@BOsi!nf}?dP#f>XKwRr-|zCT;@E1X!9_`(zxcL)-?B|zg0J=J-T%I`RiX#Z zS%2?U+t)tUVt!OGC9z49J0JOYbo8j$H)l1--g#vH4L>&MP}ARH=cUWWJlp)6xOp>P zTRSmvt0&m@a{tW8k;8{B2);gO-5-ZPz5ejXuTpQUG<4*HFCSZZ zSB;N9u3S)~&(#NNbU)#*Q|qfCiSq|+YO&?1Yg^Wz8Xer(Gtqm=ncNer1~dNFESe*fBAN1O`%M3FN340WhJ8N$FX;ZW9QEOKYOz;F34)O-yTx0tx zTUKZEYmDqo{0QzElx*J#IttVibVgmtq@ujF$F5PuvO0ibgOyh>b%@{TdLE1l?i!Np z!#*oJL z1Wf{kSi#f}+QQ0cM)+<6xegK0x}f0BA%5Rpz}|qGS;3vd{mvy=`)FiSO{0U079=}4 zpng!?|EmktCfjd;rhrz0S;^9@`dH1c2B{*NzEDXbXDU!PAf3^QlfHNnv?CL0T!jTX zUxCt@rkWaJS5=SSi#aw7G$vH2_i3QMK*91s=wkTeTYw(SiPClF)bQ9oEE89OV!q1D zBh3yND%OmtR4aIJSR4CAAUfP5MihEF7`Hi}0}pcLSN)iQHGp-6OI?)a_X4I72G>+F?L|(PT zh~}Q;xVsSUWl9Y$8p$q-7Ab8p^%0Yd7&CRn&T5`6K5Qf`+qBD~Y4#Y<0iaQ#4zQO4 z4Fjrd`p!NF(ienn3=IUkR~0epp&LaL_6(A3=P|I z7^t_j#a(l=1l4cG=u+DY!f_N6t~h0z?E`HKYW#rZaN+aRiukUrrupC@9lLsbOKMDYXDpE;u1CI-lt5CSg@M z{v14ikZW3S*FQqN>Wl&9sM2LxQNcw|Cfg5z(g0SW(v}~#y@2$GC+u{%(}~6dmu|q} zIe?k&3Itt2<9QqXp;r15N(^9(5cYSOBG;7ib6psvx(aoVDpYEb2nzZaL- zUci1xTO-snO!_|P1ryQhZO~lVYj&rYG=R3jp+myMG6>>#*;$9yd!sJJ-8`tt=wt!7 zTP{1vc@mVyMCqpcxG37kLFx7=?m+Aa-{mbLy(#-E2e2=ag?r}(5IMEX!eM`}No7-% z)8;bOUD3KqHH~sWX`V4eO^ubBMo0MS6liaz4Ud5iEmih#p}PXf&MZ)NmMK&%`7eUX zK^0-ZNnJt}O_c#%6IBz#2L}5NAQoE1w9(lF;&v^D+VEuOoK9y(3Mzlk?`x%3E?o;7 zmh8J5lx~aBR34wU0diaOXoynLr23afb{Nde!l4S3X-z}1Ze}Ih&w{3~gwXJlbMIH+ zt6c@lb7o3wwYHerYRiF81j?yJXM|Q9dle{0TqSdSWwFi=ni?A_$u+Ab&Hx-EoPMDa zVr~K*6duKQ*j|7Oi~QYC?E*R3obfo9CbN1?o6}V13s7#^8e(n5G8o_S3d`#5qSCma z^J;5p@^r+qE5oB^1V;a}pj;&@!-*l2odckBg)8k1$+m;D1B^uTM%)leYfl7ae}>NY zvbJYHS%R?}4jSe6hLVm5`^Ra7`gx{bDkUHmqvTrb??%DxBl zno~P+yuJxEK$c)`F1ld3PXh_9O7ez4T#FauG{za2eqs5yU zxM9I`l7)ut#Hkt(LnFMf+xLO6d$2kU3whW12(%BVX{1N&O2csq-WUps`xAL_PAA(3 zK!<_Ga6J^|xL5L`XW2Kd16Ba&2B7egZO7oqaZMx|eF1V-0i`jOu2aqgJ08WSl7^fR z0O8o?FbuWBehHLa7(TJvCxGBE7#F%_yA&$~ckgOuMYrdG$XY|?--7v^hcvDRoCKN} zLZZAOT8>3H3&<1MlkWi!VaiZ(byDzJaELS!t-Jto7Kf6|g-O@TB)a<-kFmXA*O7%r zsof4EOePDX1wd0HQ~d%oDgrq}(ZOShv}fh;wg+P{gd4@VYCj8fUUBjL+^H6yvAqD{ z^5s2EB$6dH#kMmk!V2H5K(gK1ir~U!e)}Cj%y0P1J@U_jveUy8XJCK5>xbL^3eZ3# z3iYM09;UwZnW=$C&3ggm>ZSUO6DXFFHN~1tWK^C zatLfhP&wW_%CJuXp=({YuLH4>bH1ouyqQ!)Gr0Q;^;uxGQnZ_bevyQ z=YUut9}9&ShQpv(<6O(Vop7zHRQKr{wikc{!Yf-S-mHp#2fB{sbCxc7%+o3wq150G z*Kzy-5Sc`Gu-Y}g0Ya6bdlW^l8>!4S=RP$Bmx8jfcG$Du0dYMF4Sio<^a$H;&H};I z8~k<<5Sv!H6VAC6h>ZxZ#ZH~kkxMF%amn$&fVz*HvS2)qeW9DrD+I2>H7_&Oji zUf~H?W1LyC&45Vx#P$MmJVb|9)gLqo8)tXG@=1Ry^gthUGYDp`s|EH}AP!PjzUt%8 z-_x8NAg*Rn#v{BJv!4Q`v97$FC-jlDUu_*AlMUfi@x27n)>W81*nxEfGcK#bBcS7- zpd49CR~sc3<>FzdrWT(*$)W#1uE&lA#qL&KiD`q2?qh^}vgt2fRE>1ybk0vhr@$leXgrdBn55YucDd}&VqB4sQND!K=La>JZR}ztGyd5Cq+a#lsv!zXwsHz?{Fgg;hcg;Dg4`LD?r^ zb30)(>J1W_bk0l=KZ(vM`@`M_+Fs`5=& z$sua23`cSde2!RlO|-5PyCWXzHNrN_Ipzs1eaK-$}S2~F(Mfg6O_ew!WLM?qp1}D)Z5R zQ>8$)E5hmaL!dAh9qD-iU#UE@tc~4sx|+D*4bgcX2$Lt&VUD~D>t2ZWA#NvuGDWcw&+JJ#tQcy^r`dW#Bq zLvBAl;EqTft=%CH1h#W-+tWc=G9zaTLb}(iN1ZMI3{jJWY)eKKUJs{WvfOs@Hwot9?)>HY*Cd zfzP7l5#Lv;9Brl|dH@g5hl933LD*pjg{G|?f1?^STp+?U9h45yhb$~%UJyTtNynQv zoho#LArHh&XX{yDQp)9FG^ot4nKsKyOXv!@9!1e;3*H!gzi7@YN+Y6wYWsVYeUO?VBpE##MWDB%qa?f$v->Qxf z#@<-DIOl@W3XjHU8@~Nm0m^y}#iSmT>>L2)sUk|J#iEa4S^GBCxbTi_4+D}%icovy zU|$B@4f!kS{7q&4y*htXI5Ad5?>sg1^jSJ(k|ft5cf_=Eo<};mHf$-DVf(nwt-fJu zGNG04c6E?7b6*LjkBcd7-&v`0O* z&H)|BLPOcWxO48C)q{auuIcTV5C)T94!QZB}KUO;&d_JO9Z=~(M-wXuip z&Ye+)ge+o#PlNV!)s3ZRcyjHpKzo5!ZEO-_1@(O>zs+-6-lHPJmeDg=hK84fRY0!6)hN^K61Esi{fMNQNWT(l!kqyJv zjv+k()IInVedvO;bj`jBX~KPx#-cXtz9S6{HFdEe#)Go{4=YgR)+Fjyhkx7; zpdFCbONuX6Sfjk1w3{tf!(jrhc*HF}tnzn<|Er!aPd{2r@`u$OvoS&W z>QhiQ&QR$q=h8>i8e|p+P<*qx1C*PC*#_vrn9G;J*{*X!sqF*lgJfo;Sn@DgZw2r* zuG}&35wB|Sz52rJ7o_O#N@0mzc+>8M}(s zd{lYVG{+JSUTCQT%H6^|>G1vofaBHx$NQKnt+F%}KjbFW1C+_aPoSLpfO-QViM&*` zpFQTfxdpE-XGfjok&g}3dO94Gqu%&dRka?J&4~`3oSg50Xtl9Py`eYfarN-SR`pxB z1;D1U1M~_OD&~w)ul@Oc0~*E>bwYV`8u)}6^X}DU2`Jl%vq1Q}LT4YSpJnO-U_Pd? z^Q3B2RkJ=VzSZ`Er68s9)8b4Cm^J(chdc^wMmpI@U7~cJeFl_?U0!axLKSGbJUZw+ z>h}S1A2a481)Vv5-&3kVW>i;}cOe~~R+AkSgzrL}0?>XaAXb+kp9MKvKvf=3P+ z?@DDs=%Ivczy?rGSYsFOt&)AefOZ6p(&h4{LZ{OzpqzAu%3a{V zRx2MzdVUzFdoarE&h4Ob*lRCic?~4Ty#(3?g-UM*o%h0<>j}iLuR}|IOsBuY0O5?Q z)J=Bqtq8IsOjpXu>1+dKw;PIKb(`P&ytc?aX}rThS%c{cM*whec(sW&)Z{}+f^^x zhhy&!P&oubS)EERs^gF`Tdh(ZK-mkSS6DyO#{^}#IwO6e)}z*H2fXbZx@gb zYfV?X*PZF0Nhr>&dl(4s_?y+FtQNehoOUnUH@gh)8NaHP;z@bXn!V0$A#$&pd9g$&%Td?a=oj@vT^aH z;N;p@tXHGStO8>ZV{S=mKY&PDQ(@$%h#ZDU2fnFpEM-Zq2zxOiu0am{1!hVQJo*4i1M-A!mh9^`seM?lTznnb z2Sn$YNiFY+om!jK_rQkQ!l~}@R|5^3`_E9f$L>Q!PRL$YM8CJx*V@wFo#BX>)rjb6 zz8B&u7WW&-L}N&B#2)1(-t@n%PUiBU78*XzhrpaR(JY%6zov|h+oBT__&$~EYQEKA-}I?j!=Zo!7n&{+te604QVAC z@+#=0P%+wGJaeh`o}2FrwZpj^5xjSf(&f~U$L82~fG38^F(dVRyV`dAEmOnY1N8w! zaIK0wO!)QyU+pTM2fx_*@4E_jJ7G2=ZZM_0ydAR-5#!y%Dz@_u_09~IZ2PY@oWX}1g+FfeN)>!y5V#K5V8i1)tY6hk}$ky1S8X1w_Hyw18t5gm$=Uu=d zE~pM^`#)B1^SBCtN9TRwTBBeDx8dZNqo6nNI97}8oJ zbFr^t82k>((W#ru@1*d^ug9nQ_DN%T=jodb>fYYu)77^Cd%KF2-RQ)87I}X!-zs+I zf^upZ6J$^Negx%qYp8l?dsuaufvVX1LEV1j%dyGMub}RssM4k$QE81ARN6m4Bhxw? z{WU&6|7iyv8?ORn_nQ3h7e;OI7iy;o+r_)OH$l~`cO4I%=r1GvCBNtJ4FP3yO}QLP z_S1k|yj=H7M}TOlNhfb@oaCeD*Uw>JavoJ}T?=~N0L3$6&sUKHRF3fYp`b%)fbMar zp%=a%LM(gU*a8ouq|Y@qd0^r2 z3mgv$o}&d{=C zXV^E9GYhAue0OZD%<$64l;-zLS42IY)VbvZwU&dYJj5p&h~F?bFh3(lb!kwcXC$16}L z+%fw1@Yo_}1Y$ZP59{T{#}3JHcYt;XmE=kv_X=WqTo9A8T2{fy5#bta)v@2Zjdc}e z-;S7G$bqwV_O}Npzv@NvRp8#Pq!Vj+Oey1!eg$2qxX?5EN#C+kmm^Xs-m5Cy5^F2SBx6JjolTJGj z_7QB5mt2Fqxu6|E%_9cia)9;##$N6@_D6uMM174cJJ9=+8Vp|9f+qL0@&U9(n##I- zxfVE!K!<=<(NsSAaSnm9k7G2&PJyTDKkG>pdT?O>0>lv)e&*2YbmTgSoLEWk1WjQ& zS9!_KHc(n*I)t?(J7;y;Fy*hG{Y!ga7l3DkGyOFH>1>ly>>TCk;sumL-Sk}Sw;;(n zSuizxG`E}mqFgDhm+vh5K-?C|n`q}YPP=VqX<;ib0M~ujwsa84J!A082LZJfcXW#>vKIM5+p1RHf9oSx zsM_*>*ggYw1En+UdqJgxLrJmF*zbeV!C`~!n&)&=!k)MW=qi~i^n~Cw_?+lv6X} zWgGh`P-b?0Kc;hQV=OuSeRV8P#DH9~5Wn*b)&qIX3ZPnZn@(xkD1S-Ye#xX&OBCPF zX7(#-kgf@@g$+sej`n!uu{qS5rvbQShE_7?Gzd2=Q?Fb=>~2vWy;ADY#is^7K=;6@ zCEtNq584g|MC+RI(p6S+%yH1(@-9Fut>S4L(;9z>PKWn0yaM4A6)(g8MtDFt?DUOB zdX!o%Tq*|r?Vx@d8!<><2bDuAM6nO~00$ze&Trw1z-7s`$Hl0mrnOuOgA4zuo_{St zB=H1?j5T9{DW6RbUoS>IDfu2A$w4a9A6sQ&rXdku^UCIzBStIUsHz5ec1v z_+x$-Po}{upKSZ_cL6z-xO;?_Bc~Yj z3Q}EyI#GWN>fQt8*~J-9U5yn}Sv`5uyAOXSa3o4_Q@;;z_<1nd>EL+ef!duE-^^A> zcT`9(saa78nHAEPKnGu7!ygsWLA4^swrX0iLVB=5+M;$vCFE5|U#^f^@fDRYutK^R zwEqQd+gBlNQm3M{nHAE%gK|NN?4h43r2e`UrJYkD-CQB9li-o>{6_2Q)ce8y`f55x z(g#5~%p&Rcpqv09s&4ihc;pRLh;r$69tXX!gkL}-N|0%9h8&Z zRofZAuaR0RTo5L_0(xFj(7KJ)Iu)7rM$q)gVv+X11!+gD^81=pl=M;13oH9KC|A$O z$}*aIp)?JY z`(Hhs31>2VS$h(cTTl3C-?ybl-Vob59Tr?XK3W3G85@z?sou(CO*^0ZNElK#O~$WG`VhypUDs&wE;!k@29yUNb9R=G-F^V%q0hKOK9=_NX{`z{RDOqD z2FR0c6?2PVzYD@;o!?*96^|u)5}i72aO`kZf*XB2#v27Hj}_V?c}lbI1?8~5s2qcRH0sS5alEBWvotU z29dlO?0`SK))z^uL>4*|n01AYoc8k|^drBz3hkWsub>=4VY7W#U#Ywpb~xU7xCKz! zu3aIY2sxkXq{bq7!1rB_zkf=P8yZ|V2ETu=9Bdelu~-MTgLXtpQc4vUU-+#kRkgyH)T zr&|Zzwc)m7$}9(^0b$QMr+{ddr#<<{%%s;Gwe{)lfT_XFiPFt*Bv+{*#oBSEbkM?q;rgbRG#x@pUFx$3boAaAfk zzgcFV17SP8=AP8FEi-@5A)+-f0jh?^Y2CufWa|pmb39h5<{A?+7THrYpnj%ksz9NKtdm80N^L z^{@8Y{SZq7L%&Il*WRjt^#%(~viAYFkD69sv%({troEJFjScwAOZWJ_fPI-Xyuj}R zV)usk+pfJMC#@Q_#hUU#o$8ngcx3^Q4Lxrte+R5L4c7vn)JG+TuN0N<_4q)$pa^pt zEgxRlPlC$Zvxp_p>D^bg-E<9Zndfw=uGYk`Tk$2~J|yf#!$K5ihL}tGA$xE~mV9E% zKlj8iQn}pmEKmm^oCMTcHF72T6x2OX)Sgl2YPFghYvg;|b`dCtL})#Ac7ezzTw!C- zL9O~nc91+{IQN3`j@p!|*1W@@EWs4OA)o9d5747SWBd_EysHSv>P^3^$E`;|dGA(L zCzS=*iw5emOK2h=0O5)i`Xa!oJ4h`{W_00s5?gZ^n4kg0KJf zgVK4%BKhlUcFnOo(_ zF}H#8xq{hNYx3)HFQ9x>6q=&WS)gR3^XcsB0a!WySoC>=cQX)IIrZ(q#DZ4dRDG&Z zuM|B}mP8_58YzG}2%HK+i$in9-UY%nm8Y&yd3MW@ z%Jt!2mGjO5VrQGR+AlW$v%I(%r zwT+D#tB(inuJNt{<%~D|;G6)E{h+O}gNri!-f_yqEJ=2&+`vdVwD&r&Xs^uXt6_Cv_x6s(# z@N2XSLFqT1io?V7O;C1EsFl*|m2Xg94-JI)6p)BLiLMeOM|nEfOA*Bf?${k%w>gz2 zs5|>`lbq2&EF-)%V1U?%E4ny#em50`!W=CEj-2k8i34ipnN^(X}~^!=J2GxlKBRd zcPBnQc-6_G@#IKrRoV%l+=Gk<)wEj!%0onWk&6$g16la}dnX-;Zi?2`sCoPvC{G5V zIWKlq@g&Ed1?C>c{tKptU#qm+PEiF%Y#8?4pd5s*LzE%m+5*Ym7MbCR%L5&P?0J~t zI*u;~m2*jFRg2RRQ1_lJ*9WI%mb%q2!vy}o!=_Q7azN`6a304Ol6Qczw(#t;-vHui zwq?iwe$k_09i6)tk5Ep#Y&D`ydm77C>sinX5A$`WDr&64s)Dy*CW6Y=>LO}V@i#^A zXMz50*G8oK8v|G5AaDHFh#g8t=_?ew_@G^T+MmR6A&8&*m$4uGWo-SN$ZrrrwaSvG zAeK$Pz$O{{{GZ0c!?pi3k|y`c{gZ-mH&y%3;$)>C{bg)}ygw;d<_-K=>;t%Qs*OlK z1F5*Jr~f>5ja@tcud?3ym$C05w%?ytTchAlV&yvuwf{U8e;MFUW9bX~Q{?T1da>v} z8QN*QS58-}e`wd83Bcvdcm?|hUPE|ACky>P2#a*;7pkS(EL?aq<9)oO2Fg{^EXkNC zn5b((CxTYhhhF@(gCbRR#2Y@r*MOBn9cBbvkwX`WPq=NXB6N@)!?{pmO^U z-ypP{rPi5~@IYxAEVmrH*^SCRxm!Je-v#gjaI>iuZo?uVo;1SeV0#~sY^f>UZaiC$ z9z&e*nwW)Gdd_|rlno8vYJ3RN9>n$M+dVg_JtNi>X7>PLqR_7D6lh|KSG(`ypgfm_ zZ%0xJ@OMMBb3<*4tvpA0m)(k21VZD&?uiKL%23s}g2;mw@1(`VLeHgT0p#L8=FY}H z?#xs1kC$7?@s*0aDpGFrxjLT8wiIYBg0in>;~x($bMcSm-Gcwq4n%$kDHF_>p}$sE zc$ZE`WrYiLn92(7)nO{*7wK?VQ9lzd))8(c58@y7ejQ(tvS6X(-O7ARHK+1)_5}XX z(@*0cucxoW4<=lxbrqBsmHZj4&q8@o$)95Y7nL)2Ed#iuirivPV?4sKtabQD{RjTh zVe55x1C$q)@vmro70N3@#l~U}|0ihppCa=IE-IU`3ICXVGybvoxABh`m3#{WxTp-j z!vHQS({IH;@@@FXW6@{$M}7qVc)7I9iUIfv|LE`I_{ZzNQ5O6y{?YIs@Q)Xj2ApI7 z7nSAz!~m|aHdhIM24sfQjKvk9;#`TRaT)75D2d&uRS~%FC^6ZzU$y z%8$|b!SX6=_3C&kKic>|8A9X-399M@R8GN~I!xuqS~^VS$J+S83hF>Pt{M_*C2y?5 zRDNut!&Ex1xkt~aOLc-4I>BEl3vP*Y)K)tEf1*r!Iet)E>wK-t@Sz4kR}HANtUHty z^n@}hg+H{irTuk0=MB>hhBCvcSphE336 zx6%OIY|6{6M7UR!<>2;Cj^#{fEI7_GTJ!=80Fl#}5; z%@=B21f>B%txKRjgjYa$QEB)}DDyw3^#!Tu|Ce-vm!bI2TF)O^`Edh&aG8Gt%7QjS z>7nh6)Jg+(=rEOocMp^v-4A8d0UbUJWqDsg`In=t@9}|_{Rm~>p4H)VP!+l+_Yc+pK>pCd&uh-!X za@7ae0KkjNf?m;KDqFZ&hyN30{-aK$;OBSngF|RLlr7#xsFfLZ>oAoc z|A`;udo-ukM){M?=U^pdVwMvcLRN64sU@n-Fr}8W#$8w5g$Ssw_7LpPn0eBNT;W= z!cTOVN<;SPFqP@|t8kh8zyt?%#37x~tz55_wWQAUr)zV`LUr6Q|ZZOP}b1`%JMGL@otq1ODjaMg4Q}g zMaqJ%(D77$Y_G$ArL4GvPFIo2`OgR@?5Gn`Sz)pcQ`w@cpiI|Y^ZyfN{vNuVp3(ZW zf)BFb6eug|2gQHZK+Oj+fU6>9)G!_Y-zW_k9xdC?4{9eF31wG|)&)}OiLp9NW&HJ8 zZ-6pt5`Hv?&eQx3&F_Y?qI)#I7s`vukBjBT$`1%JVGzm+mS`1F8t{+~KLX`NC4Usk ze2?k)$2DJ}btRM+mE}GICHgyl(9>mW@qq=r1Z9CQL-C*Ws@6B4%(zL1w?LV0E0p=R z>G1nd7Pt$_^dCWaQTg!`tp}jYcMysNtjzjCgD;^h_-iQsv%b~g@1ZR4B$S)|zZj{N z2S^WUWxi*{a=C@X3VWdTj0 zycuf;rA?hQPlhsGS14~m`snc0I(`5Y|5-!HIsQ@^(#nq`@q;ZL2W3U$bvOgcN3_$S zte{xyOeilZ`HfKKyGe5@8#o6_bPIkk-8^U+SF}a=z?-JOLAlgE1!Vy%p>*+D9bN~m zhwukFyc5bz=o77b-L!Wq0B$OGW^dzS_D8#?}xJD2Xy#B zC@XwO>%&l9RHj>|^-(BW^n})@p}eT{=<`sP^8%FlUew{W(fAN8R?o+MBOe58)BHVR zgg=0?;U7VH{gtx(f9iDqi8B2jr01S@supKyB*hmqcqK{ z^HFWl{Vp_{X2c`NW?rJTF_g77hw`GbKa-$z-<6tEnZK!^v8^Xzd2& z1yvgW0W)^j5mXw~TZjKjX+R&HzaNz0tD$VbAf5lu74qA>2SV|r!u~vOoIXqrt2`32?}+X%5aemyOq3Hb1K6n zI!vV}Z`5Hb4W9$$R4uy?A1=X%RXV}{V=eo?T5)|e>}}n!iWU7|am4>4&hh`>7{Jx+ zeYA*!^aI^8D*OCH9j0=+?$dlfl<5ybn?Zkvaw%+W%>p>9$F?dGy-5mV;wp(D80%g@u|^87_u0ec4PM;Z|BUOLHm{&emZn<8Ok} z_2rtomGN^mcPlHtRp+|{igabxe4Ws(EchPHsmyq<%3$3GrDq<{VF9Iq4?%fRnf?(S zzf5x~(>sLq7mP*zZxTiba`UPZ@yb-Y^{Z-cX(YPR10@j+fxCWzGus%!36 z#@7Jnu2vh$>34}vM`eo}=`fWQHPKp8ttDfm>NoE6u44U#`Pc4#IXiT#>R% zIwGE$tkY5XF-3<{%FG9s(&F9-v%)@5hWqL8)jA!O@%^<9(40!chHIXxIhE;0XuSr? za?7s62fA=9loyo|*XuBq;Tv?=t>hCmr?Q+WP_{T*YmSb0D-Fq2yv%%%%7{Fj!L8)^ z;M4*gPvsaW(R_yHRE~igHJ`2ZCLQlq`6clj9YJLUH$&NixjKA{4$sr!+o7!BE+{>4 zkJfvkybF2~%IiN;j=%pX;s2#{a{SW(wtR)Q+^uZk)8Ope)jFO^{yda}?H@Y4Uh6AR zUQ~9`8&H=2rq)eR=6}n~e|(S$-qyMW${WPpP!{ls=6kj7gYt4Kcg-)rxzv6SWf%Pj zW&U5F?9yMMyr{e}ty~rDr)5=;o9fkSYptra8k856>0-6UX-*}tuESJ@YiRXp1(Gir z1)wxAoBn6Qsrbjst>n`rtyt;8Tpdqk3k$RsLAk=s#y@u5T>N8sx8NTyqh2xcl_E&Eh;j^_$M?9nTSwdV?7W^CoxTqZUYZ<^L_2QdG$CWoC-bCVn`4;~;)cJ-H zFDl)6k^x*)_UBIw;0kMVmGI)5M#?db;Waw034ds%<1W5wbn#82i*FiTeA7sssV=^0 zbn#82i*FinU&PfO%H6VqR`(l5jAxkf7vD6ZH*mM0{Z^6kEH_8T|0hbnUVPJNnI2&m z-!!`Trjd2=O{0r%8eM$T=;E73eB+3ZB`>~dbn#6iJbUHSv5RjS!3h`NG`jev5uU^I z;X1DWWxe>O(Zx57@MZwM6yu%g#W#&EzG=iaj=1+;eADRSn?}|3n*na+I&tw$ql<4E zU3}B%f8!0M|G&R!l&KCL|JUC%5~Z(s-WM%4MGf{;5_5m_j26c>dg8?R=23&AVl46b zfJ&pp($_)y4XiZSQ$=*yh$I8v09dxsGkB<82eI^FbIePvwLj|v-~5*{yMEu|$ENwE z$Jf4Iqu=DsA70b^mXWvByuZcK;x|%1ernpY|M-3VmW6GPz4FYNEC2rO_7h_h%BziP z@kDiT;tfxNIE`{W)kLq?QPIjbQPGOm0pi3dg0xNeH1Z9A8e;hy0N%|2F>eAmV#J#O z>j~Bo)DoUe09kJV1U3Q0i!}rZZv(_{2B<62Hv?>a+f!Yaf%@AeSneg z0bD7TzX#ym0T8ntpq&`89bi4dI)VA;4aOJp?^P)13hGcLU7Z3D8UIBJh6% z&}kPyA2D|qz%hcO1pP$2-2hAf39w{0K!0(BV89-Lejfo06pKCrI74unV6f=*Pk@yl z1FZNbz)*3DAng-?k$V7!i{*O&yn6v+J_Z;eMtlsgo?sopwZiiWK-NBhz$XAB#TtTy z{Q&WM0Y;1Tz4)<}U<<(*;p_vLbpW7bAHX=VZ9hQbL4c$K0OLjJ0f4;(hX^K!O+Nt4 zKLoIYS<*#4X7PUtFlRqNhG=yV;26Pzg8%`sk6`I%0NoD(WQscv0Sq_{aDpIPbomtE z48gKb0j7!L1S^jK4E+osS3LL`K-%X3=Lqt}z{3FEF922_2AD3+609c}e*~aNtU3aa z^(8>u=Kv*Q%;x|JM*%hx%oO$)09y%izW^u|8wh571<>$IfY~DZOMt|$0d^3~5%rD& z>?N3U6rfCOBba{-An7ZBxuWzd0RM4-Lj<>qR$l`gBUtb?z&x=Jz;nB3cMNiexDz4< ze2eH4#}Iv|=yDw348gMF0C$Vy1S`J-82Sys0`cHC0BI)x&Jo-v27U|R{T^WTw*ZU8 zS%UQhgTcLHFE7;^$3;UvID0wL`00k#t4eh=`V*g!Dr6hOlt03H_E z?E1tX0a}~{SSF^Q1lUWkhu~4s^c2ASp8)2a0$46~5%_-w==3AN6JnoSlnJ{31h7Kf z`4dtsJq>Vz;Azq2XMh3!0$BDlz$$T^;0(dg(*Vzk2Tud6`~~0~!D=z^UjS)m09OAC z;CXSDzoB_!C6<{O5I$@s$NcasP_bkB6Vgtcef`-2W zY!KPM0?hh1zz%{}MZMnu63+q5`3+#B*ha9IAnD%#Z-~-=1I+&&;1Izk(drz4{||r# z=K$Uk`v{H^bpIV-i@5W5&*&vfqu>sP-xXc{K!O1tfMtIGY!k-`&HyYKifwNDk_V&U zj3@-pA-F>f^dOv83F%jR06r9F3B1t&j~m20emFJR07C~0oX{eN7&H- z36%kIqX9k<8wj=%G>ie*C$eJzW>o>$L2y9Ss|=9n1(;JA;E>oxu$LgI3czQgvzzgt&*hg@Tpt}ulRNQF;EUgA`g5Yb>r7FOHSb$|!0gj8~1ZN0_ zRs;A}JXj52WgNgcf)iq3EI?XyfYq@8KZvsg-WmYo;{Z;HRdE383F4{){3OOy2gvdP zY$P}>>>2R`IS#-%v5jCaK~hbC zKSXIwfcdon4go}YJmT_NQKN)E9%Ml+kV+o0pX3-x_u9x5Bkrt?OiSwkoFKpnCLUlw zU4Uir0JbUIH*a0l*Qf5&+f{ z#MJ|+CC1bP$f^&pksw~!mjEO*0LZ-rpsv_Ju$7=;eSmr*yFS3Ih5$PV>Wg{}01_Jk z%xM77P;4XEOOVtMps^@z2r$1fz#)RBqE#aRe-nTOjR2a9eFVn{x;F-BA?|Dpu(T<_ z34)fQOA~+r%>b4)0ca(T6PzI!+7zI*c(5tJ%H{y)2$ICWW&mlI0<3NZaHTj);B5i0 z4hKgZJv^5JtS1Ou3eZ8UA;`K6Aif1aCz0L)AfY9|7J_8qTn4a}pyV=uE@BhGtVDno zEdjcT=`8^gTLJ7L=q{Qj0_-K2mk7{P>>`+dIY6gY0KLTARsjCi07nV>h<29)93xnA zIY2*ggkWhKfPSq3`in)a0R|)ioF*73dbI&KL$IO^z+iETVC5A6Ba;AziseZFX;%Wo zTmdj#jJN{8+ZJFQ!3g2G5@0<+;7WjN#TtUFb^!5h0Y-}Swg3t30k#l~#{WzQU@JjM zJAg4_6Tz$w04>@Bj1$w_10;3?*h4U0H0=PemtbB8fC*w3!Te4DojL-fi@6;E{G9=g z5@d*WodAvzEa?Og5Jw1>CIj^A43H@nbp{yV2RKcTEqWyboFQ0|3@}ZcB3Ri4V5A=) zS1k7fq;&;|=>m{1Msxx2b^};PFkN`M0<0$pbOk69YY4Kg0*LPhP$JU10VH$>*g`N< zI9CB|B`CQHpj2!knAHQIMR$PNVtRLg#GU|q2j{sOF(nkO!Tnn&;;AP=l1F)5#i}9@3-GF#el0-aNPs;A8%5LW z0QM5hyAI$Dv5R2-D1c5Q0XB)bBLV!Q0ge*9CEASwI7YB!6u=g7gkWhJK)=xd?}|mE z0S1f#I8Crk^hyIbL$D$ZV7oX)uyQQG$T0vr#PTr!Y2yGI9>F&Dp+`(Tf^E!uJxKgv zkX;_JnPfdli_bwm@`x8d1IZc>lFKxEJmS|wAPF~sjQ7EW!DN5Xx4swj-1W8Pk_+}uVspNqKrh`<85b)!UKHpo_zJtXy_M6;{m4{4OHxuLmbP}fs63PHZ<^v>) z<@o?x31SKWx`+`40JF*g))90Qp6LLIa{)G@XK_HW3jy{L7WwaIcZTGn@O}OF;^v)s&U^fvnKQGK9fZ8S z5h|Mgy%8!6N4O@TvdP&8;gy8(eGsaeOA?|+AQbP5P~D8~i_mB!!b1r)P2qkBKBExk z_Cu&`?n&4xp?ZIWx@Km7gm$A5-b$!%Du0FGHwIzNR|pNwYYB%XG#`M_*sK_U&~Ge4 zf`JH4P2+(GImRLElF;158H8|ALgzsUEzLFwqsAko9gGlW+7Ctuoq%vm!WSl01j1bj zgCY>Z&5sghOhm{ViO|;ck3^_63E`TA_9o{LgjW*A4?*Z?E=h=*j8J?iLT592C_ zqY+r3Na*(sLe?<|EKtTE5B(Ojki*QlG@UaLiP$Z0+fe<_nfd$GqgwUA?wJwGjk$>&pd?slMp7G zg_97rN^nj_m}+WIMrb!5VY7tk#&ZgS-vWg2DG0`FkZ@Rn?^J}DCTuD~zl8_~B+NER zrXl25gwT5$!d$aY!bJ&Lrz6Za-KQgrT8wZ`!a|er8-&m$2*bZYSZq#7xGN#pAS^XQ z48n}12)89HH=|wcu?*qi41^V?@Ju{jNtinmVU@WjA!<27^;rmO%*QcyG-{52p1)sld#8RT!=7gEyD1H2>Z+_38Cu{f)^p| zH$xU7+?8-!!T}Sw7-7bGgz1YB4w;)0Ds4b0zXah&GkFQZD+wu6_FGtuaVY7rY#xn|`-DZUFD1>ung9N|t5PVl4 zTrgoP5DrT?AmNfpvJ#=+7KGj_5w4hh5^`)s$hr#Qn(4j@;i80d5^k7`s}V+RLm0jq z;ifqyA#^)J@EU|)&5$(+cO~4GaN7i~MVPSzVftEx-^@)3m3AVOUx#qdOkRiZO2P{X zznjwQ5u$b>EM1TA&^(pUXg5Or4G53T!VL&Mdk~x(5uTdb8xgij*eu}>YKxq4S+WVd%rn`9UZ%=UY$7kS2y1+PqCn1FoUfC3 znL4|4zLv6CN-{6w*o|*;FVjTU*UM~_P2pwY?!l(?GOcC(yv%mlR9+_OUTkVF(?K?k zm-$}S-^=vahfV8cev(b+Wiow_O;6orGf;QgjMRNUHWPK1%}m{8vrzXR91V0|&lWP(kcpD-aNOs0_8CKGCs9KjSe?J=g`MH=ha5&E;JNp(~`ehFdFQH0{= zM+DCjCgU+oNz-4ZlsP3++T=WrDPxAnlr@)R%9+3unDS<{Oa*gOrlKi)5>v@cmZ@y+ z$y70=PhqN>nKISPQ<>_f@@Y&Bvrwj{c`Z}R)INi$ZC1$CF`j2JbxmWLdS-)6eG}&# zrhy5QX=t{|G%`ufV;YDm_(%h73YYJb%v@?@s+M9ba9Zcz~n2u(qOegbHrn9MhjRtOX zp9WrYjRx*&7T!hh`5nP|523rMeGg%)gv}Cq8qfO(?H(Y6-$&?eHc0S$h~WD>LSGa1 zJHlZJ2PE`2Ngg2ddxX&Y0m1;YPeP8z2w5K@3^LsxB3zVkPC|sq_y}Rt6NKT95Qdmj z5<;IM1V2Xj+6;M&a96@@3Byg`6NDMh5T-vt7-?=wsPqRy`KJh@&E%&DuOz&XFxHfQ zh7k1}Vd*o3@#d+7MlTTR|A8>kEc^q(=T8LZbA-vJ_H%@-5;jYiYCK;cw0nsV{sLjT z*&xC16@u@d2*!l{iEvoL0SPlrl9vemUL*8=i7?yjlaS*tgsiU+=9=!W5H3nMCt<$H z_!?o<-w4BBBk+BTgwQt#!G9qvHbeeGxGUkdgrz3%Z-g0d5vKo*u-x2~Q0W~)`8NnF z%;Yx+uOz&Xu*#HvixBl5Vd-0hHRdV8^hRDZ$2<1dnT79g@bN@&zDL+#YQIO=Dq*vP zP18NSXbp!~g6ZLW@4k8Z26p|t2=?`4e~SrI_+beLBy2ND90>iq5qdiic9?wlWv^iF_q#q5)i zBQZkOgb3G6_k;)+C7hFR!(>c^Fe(Yc@I(l#0wjbcMF>uez$zdy!d(fsC9n!ef-oZ) z!t^8vtO6udN{&!IDFUm2qzJDhypX^uAQ?iGFT&Df2&@7mG)jR`KRLoBx z-)5hL9BB};rb2jYx~D?8DB+xh_aDg(mObO;H| zQwfbSBGgZhkjN}dkKmID!I=ReiK(3dVXK7A5|SCuj0o*ABZOx}@HHDG_+>%x&4iHB zgk?fFEa8BJR3=Gggnn5OdS^yRWA;hNkqseh7KF5>dlrO?63$6TZ!%^@7?mAicvggr z=9Glc9083CxZ#BPYW2>6{2rxe=D;M96ENN@$b^p?)red}d)T1fRSJ&fExrrgm-5W-TDg`5yFMv?l zOfG=%O2P{XRZZ!F2vH#jOA8`YH%}!rDuhr!7@?+F7>wW(ir@@EsBLP8AZ(SeSwdao zSqPzBVTABP2=&bd34TQod_xf$ny^rW!x9ciXl#-c_R<2Qy-ZWHPo|kkRRq)AbeCyi zew1lxG8VpN%QTkhZ8l(xUwPHC zES2hO!pf?a5)Me{Z<3Tl=vM)ucR7RsW}k!{6%n$QM;K(fmq)lL;hcmBld%HAs7eUK zDQaa-Tc#sCCXB()9&`G50o99 zvh0T7ngM%nlIy26UA}5^J@jzb!DC7~JFN6fU3CaA zNB(fzf-hq#lWe7`L2-D3r34XN@($+*UrhQF>!xD~3m}yeg zLg-f;VNflE+2%(HIqD$ft&K3(^skL@QNlF|^G(h=^wUsG@i(Or!qh6dX?;3f<^>kiO&rgJ!z|Lg0 zpoy1{Z|;n)-<^!qALw^z->bcz-0Q-9bg!vOEN+kRo?Scj3HKP4c}v2kUMak{Oq%1h z#1Z{}D|eoyx{~LJ23A4#;Lz1;lBZHgu&$l!H%TM6VG|pQdBi1mYu~0WJYW{)iAazNwc5}xt;CJ>$enfqN~=?C6Ja<+Lm@* zy{g4_-kHp=CQ7_v@~`*ub{?C~Z&qu+qAOyn=_Hn!SJ}jsHo#T54n4zrenCTwnrTIK z$*IcCcKz^M79nb38m!7|+IDO%*e4IJW)BgjTR0-^Hr5Z1+XIKvnfqZ2=4S5YRd*^!{O_hqby78lfp7cp{pLd+@1M+nO7sns1{pl zEccq?NR_sor+&fKJsiV2>X+_3T$(E5>QxQ;EXtB&Tk(}%Wxf8RSrS;Uaf^)CgCeV9 z?lF_#KdPX_+J2(gmajczgLq45{OF7qtn>0w;ngtVTN1DLn)Hi$ZEDgd+x3Sa{Hm?q zufQj6G4tIN?*b9;-n&22G7v=PxPDeI=Dy|~J?8pkw0!hC8Tvtaee@y{#Z$^DCGg2? zY5FT*{ra{(Su9Pz#-|^J=ab^Y+e`E}aTz4>$!3O3^-dO%-SXEHyIEZU{Gh3&(E%RW zEY1Ga0S2Q-c1zPcPE>CF2x4wa%WG+>OCC$hXQij#wGFVe{BAA6^`e||mKbO`GR8d0 zTbf>sqKc~_s#sb9G-a%xlWk;a!IqYc{ic>y$kOx%F8$1;K83CHeAymm|8i>)%Td3) zdEWZV!D5!C);VPFyw6wHnV=b+crRh~$6D-Ysu?k0WkMWkKm#e6N8DKP;hDJ3s{&{4C zNXxI5m0m<9MAzHyu(d5QGuyQ+$2yjl1+5j^`qZ^FjhA?qR?pJ1q3QKO>O#GUMU~Td zxvWz0X<%tN*uHAN=&m86!gIpUmSbbfF&EkhH1)OK+@b_E*cPcj_%yXN4YsjXA2+kK zyl8zuecc>QNoxT9Wp!~&y*fq-=7SfOs9H+P4}V%(n56}x{eh-&qSwVJ!60~L`L(gM z0%(6*+Lu<`f@r^4T3bsC*55NcwZwLo7=m^UO_{Vu=DLan(5Qo@(X1tuEWO(1Njgm5}1rgH%@BWoWKn?yisM zhp2(K+;VKdb{jI*z>Bi9hHQUn6=sE{>F}$U8tS9>5-F3$5N2tsEUgLJ6D#A@md0T^ z!u5OO>aR7H*oEb|b5HdoArtw1$?p z&(hkW*+16(y)^ZIJMh6#eY4+kY|nN^GSTM;G___2$ZBZ^Ex(Rv*)8pmrFBAkWi|g{ zOY4mG!qR@Uv@U274~VS3`NRt*cf&LO% zt#;Y+>(6#wG<~jE+E;8hvHY&0xz0ZW5OW}^DX&|O1KECtrkdZdG|m5amiDuyY5w0u zQ;lv~nz~disKWnZX_0K}x6M^}`(0T>*lw-CAn}&vIF#)+mUi3HzD84ms^uL^8^(4~ zG}ZDqG5=O?Cl}!Vx$PXW!XOw7 zdMlRVeGP4)Bea8-pfz1xs0@{$0+fN`Pzn;mcTVzx77Ui=E%~n%Xd$OHS`8=-WuO$4 zgt(wZUR`67N{^H;b9x8V-_28Tdz>D>o= zLBGej6L!IF*aLcm;Wy!wG!9LTdQiVq94N(xDPMk5!{4_pw|&S1FfvJ zkbVGn;TAlHd+-)waS)6L2_PXPg2Z}rkPjPrw~;?KEohb99Q10^dys~0y&Fj@?YNK# z-ciu^;K}x%*az?uZotp*1fIe(NP$mE;0}#PDo73fuz@sJ>kzYwjh(O-_P|Ei1nXcu zY=-Y(8*GOyuoZT|Uf2iU!+ux;-@-1~4f^$)@=VOHu-%{s^n&it6T0ZlVO^myw5C)o zu&p2r^uDu-p!c9vhDxA!q?Lp3m_l3N28Rq?M?8UR@CeSsukagOfZyRN+=a(*3+};1 zh`7mrw?QwldjQwr5Ag2AARBVAn$ zg9y;2gD!-)$Z=hr=>jMcbbZqSIzlg9f9evi4e0Vu*M(J}G8BLWkPs34Ca__@0`-2M@MA!2w6vKL)F`{wvL>FAL?MJXC;Z z@CQ7H7w{*%gjaA3j>8E!38&yRoPo1YfrKkTWvBwxp$621RFE8s;#UlEY5l*F!YzZv zupTzTYFG!WU^bVMp zpzWYNl%bG+Q;?*@bAmTKW4ROwLA0C=E$LZw)mt z1L~1L184~0B=jZR;TWNJr0PYjqiNz%pleNC80jioi)&qHUV}OC8YaL*m;{qSuetq> zLgd%{4?qZnypRU;YU)Bz7>YnB6osqQR4?A11e0M5jD>NaEAR*K5FWwrpr2oE0=Xdw zN<(7w_Ee@Lbb`*h{_g@^p&M|w+#?}Q@3B9?0XPJ|!#%hOzrt92x`8h9Ye70l51Akv zY^QdA(xtit_lCD@dtskqpMYMAwh|(8QnOr;8Ojn+4k|)+G8s<%uVEyNfFbY$L=bTx zjD^n7750HH>1V@8(A9W-Xbidv9}K$K(uKws#mHwQf@!LIDACX%kWb%mWBGC()z z2%SLh&U=GzED9C}GD8Z+OG@yARG>G^Hb>XXdk27CYphoor>AnoU?N?hD|20mzlJ{@ z^#4mX9zy{F`w@8*7?=UGU^dK$_V{#!PS6=@LjeeZ9PpY-=yyTNV@FZY(GW?YD?xdv z3@6|S_~VlnQmaYQz#PPu*cOn7n&gI@kPWg!F3{C2pdmDZT%b1sz9mB+3Ys3@ zClu%@bb(@^x6daTGh~8l=wa9|ARO|OK{2#4 zkO%rhT=aO*5%RGgADaO7gWkP$k}Py5UN^;iKxv4hH~0NT|LB#)k6}9;fXt8!l0Xys z?G{~gMo^Q}5D6n7fZ7JbTEcX>uX`AOkjXVjh^{-a9`F}tH5smgHLw*UA&B) zsi4;p&w-B}7g7klZaOz%ttm`WxI%{YL3dYmH`NKcfqDQtj@nKm!*5_5EwLPyz-IUk zCc_jM2EEk(wIDg91V2aty7iZwk|%?0Bpja_9V6q^(2z_@LOrMpV+bDyo5=7PaXsnh z_t-n6p*wk}@%e`R`CwokOo0UuF@ygmz)YA53t=(Lf<-VJCc-qB1CwAbOoz#WOozc> zGSV%=-Ea_e%TIUnW`JJqsJnQ&YuA7{y`eVvX#PJ!ROIgPoLaPn2W;Q4P*_FK4Y1Ro z8(c?0H?_`yZe$&U^Kck)lCbVssnx2mFPgI79EO0>)x9d+hpOpA|L0&s?^<{Z3RHx@ z-~~K`=kN}0!A+3QUAPP?nC>gJhBlym-4-eVr9iiXlA_0ghiohD4s3UVwooMs&i7xk=Q^S7?iGS>=Ep18LVZomdnjR)20UEK&|7>lIAJ1?+9N<>hfNf`8z-@sMEKuGjzS78M6s=$v+ab)YtNV zH0aV^t1Yd(%0UIFM|Negb+DzerJy8~fa0LzQbEu`G&TfkIhdafUj}_XYyfzJ7ijtS z5VZU|3P+$iQ~@m;wR|iCT26+7ZYj_NuGG7?Yg!ME3Le(?%kNcV)iyq;gQ`IS$O~B@59EOC zkPR|JLP!spAOrckzGzE=O$3QSfhi#c_(F0>2C2Xg(n1E2p(rTf!cffGE`==% zrJ;;v%VVoRW%75GR*BRAechl;Ye7w@4I0Bj&8#s!7v?}q=mr|=Z9ylw;V=MN!B@}= znnMrh3SB@q=D&c}5T^cb!bU@A42_@_C~+lh_j3!jl~6N~ubpW)+ik4kb5j`$0aIY1FVC!um)Dc3Q*We%dWEQdhBl41>b@)lCQ1p zWLs%%f$v~5s37k2Xxr`VY=f;9JFv<~DABzjO$lpX`hHNN=ine5fbU@+C>{B$fZA62 z3fH#c{a|gM!Ddn4+KC-y`v@q)VfYCYQH4{&KZ0s;2<%K$2>IIkww0zrC@on#kJD_Q z0_AfOB2Mt1o#An|kAa<8%#7q?CvF$=bNeb(O#hhMX9-g)ieJ_LSJ}`&xd3((UyQM> z=Dq@#LCvi~s;Sk7mp}Z7ABWrf%20_s02S;$ zr~uM_2eryl&`fw_SU^Nt#IY_m*u1U*pK*-$Q#Q^i72u%jO7_!I6EVIk*xaotzl2Q+-%!ZR)f#3m8`w(hmW@PZBGz{ zfUe*QKtX*SREUjIPy&jBZqw;@T@fe@idYiLf}Ln(wkv=)3FpJ=G%c+XRQyQSel@5H zO6Moo1M^{~`hN}^y7cc1Q=t=d1Qn<~v;*B^XbY_&3|c`;r~x%W_pDk#eW(q!pbqGM zR$Wkh`80(l&={IQqX_nNQ7{E2!$jx@!=MLDf}St| z#zQw42_s-Qd<_wO`EMu;fk+835cipBl#gnv#O;Df)Apyrl+iaZ4W@&ESuhi3 zd$wgkY31uJ0%M43~&x!797 z=0aEiioO)qfReSdQ4ypI`PDvDReuTrI`Em|>9Gp7XYQmgh z=QNyvQ_vjkBv$%a(6+*^!cDjV*FkZV&}ESCCAbJWTwTDPhdgZ0fSF*YaRuESm&vAx ziXcJTw(~V>-_GD?_Eiv-UVh3{iT{qh1;4^Ca3Ai$UHA=d!yQniQ3pP8bz}c{QArIt+ zT#!SzT641DOA7^ID`V9PML@SugCRDN3u8kqP5Xt|E^6)DK4sXq5B_!WsRwmHcOJ@H zVHIQeRA##Y`G2^mi$LfHRY6TX(Aw{hRiPS!?(g=6KF}L_L3QW}-Ju(F1>IuRec=w! z2--ng2!}TC1%yFFEBO(!K2FgT*PyuQ}4QpST##=3{rh8p%9kBQ9 zc(zp~>}?7b;Yzeg3{ib$JIJc^%A`dMA3NMm%=U@7-`a|&^laU3#prZt|1YhCV-b^0hBJIv1fll$s(Wu^()1LXX1ffQyZB42H*?_(xtZ`&=?k8s;p zV?^UZKGN+m^cC9?wo}Z(WIGSQ!S35ZY!3z{Fa*Aap%4kSCM!)=i~tVluG@k3%+UVG zXg%V?l+m4P11+O{WZKw?D$;*ASmCqK=fiAJ3(SNW5VPQ) zif3!8h|;k0nZvfU2xUHxow*jDDs{|6>@KmpVF~dRXAyj=3u5})zKiiyOVO$kpNynH zWuSyV)y#G+)OGTU*%Hgx|5QQjI2#Gy0Pcd({wvw}c!0$$=?cPCpeRt{8gLp98gy!z z|CX8JsS@`1i8-d$qp#Kc*GXp$b`{vATy5$0zI{X8cq$+qL*_mH1p&YQRp^ z`Clbb)5a{srxO17;PY=5I0{D~Cxi7c_IuDc&@m<*{J{Qx(6+AQ4{81%L^uE$ z5yekz{}{u`c2=S>b?wJYOc^V~lW+o#gTieed;2t+U8pL$wmyTPhl7$qQh3h3o=6HJ z13jS>%KoqL3*3aC;Rc)stp(3vwOrHob+)g;Rk#3GATeBqOK=f%k*M@u!4=K_$55Kc zcd>dpNvGULZ0pG-JprYso&IG187Psb@Bm_=zra2LWp*D__&?wt{04X6HrxWmldsZI zUdkuJ7F7sks)UqyRsxlv5>lqW!$VLcJC4HbLaFA;T;WPXEue7KQZ1zlDEuWT{nwC= z{5{^Vp>L}H#{LD~Y!l%6=2KzPKi2#R?+EhKvs-$Gs}tMm*!TGRv-u9IaOtvMgelzC zmBu^G|F@Q-GSkCmu|Xx&lWD4r`cMyh`3z^|V_iup?_b2=YTd$P0R| zCnscwte`=e4VwdUK^_QzKqvsEL-;RPkZM6-)AhiaoykP@^?iOQG-Z1N=u4Rqpi$57 z865Xc6QTvNcJ--)tDZrq1vQ}tREKI%6{xZ>xp(M-}h*LFbo1cF4hl}!B@~9l*mA=&~_wNg;ZuEuw!5p zjE1o=4pu-E)Wkm_nJ;5I0d@j5VhaCBoM<^r!gjVCCtG3jvATsZ18d+Lm=4omD%k!r z*`5bF=+42;h1sCH2(z%#O0&HbE1yN`|Ap)0hGC#RxP1h zf_BrZ^;W?Tupf5AF4zR?z3BgSY^;X0um)7>jo?dS8?c+L(vwao@;&%#r0*-^m zj9TU-(3CDtW6yxnQ@XnQWT&B)@khHz_yv$2q0(H2t8fLb!wtwwU>0xpbh1aUrlO{j z5=uuxzv7n(`wR9as8C8&KAMIqoSvTy!C%ixzhz%x*+2!nt^U7-7@xquvA@G#@ETr$ z3ic;FgQxHq9>GJn4|kw231xx1Z2tz@zK2z}D!m7mrZDY40o(6)^}oay@EraC4IVq9 z>`TxISvAtr-zuyR#D;f-saEn+p`B>n-~}qM?x(82@8J!o&~_ZPj;EeWcYqR5)5Kys z4(RW9^mjbcOQYLUW;y$sF?QyvogPJ31(dNKLD!?`dL%6g=+SgdZ#^ZZftw1e$657= zx(0KEo>!NncKoq=>N6wg9;vhpZ0ljkbdU*@h;&(vd~GKsgOON0FRQ0V6_x{=9kM}I zP`Ip~EX@rnKpy?Iu^zU~2SFew4KCeV)%?{VSo1fK7O)?t*Ms%C!>6Y=i-TrG5v(4{ z)kC_P6?#xt;d)57B z2DT<>eWPVeZA^dg{KWY*%AD0R(CEh9dSy>?bGa11+E>^dqb{ zwion-9?%`Sd7B~^yvs!NWjl+NhHhuyg8^vDtPN<@po0znTxZtdgbjg6(Ad-8NfaWm zE9lH?TisGW4Z{fk8is;28--C3x{sRM^IpdS9XB+$7Z5?mg$dX!pyPuE_-M9u92pPe zK;1MJ##r0hm(M(y1Bx>ht7FD^P}+KMTZfI=>b`00D3YAwB2LEYxS=PzH3c>0)XH{7 z_B7Yn7)aP47ywhO{Ve$ESfWB`tj&g5p!kY26J~(Y{zl_uIw%u47;u2LRdU(6FyGpb zk0xGu>e7$%Ozc>u=_T*1ME5Wem%PvEiOJ zK09L4=!-^DiTpF~`p8;$uJy$)Fn>V7fS~+6&HT&W1=0-0Au$dq`rb*Be$uIfI0OX; z1O|k9OfU~|;8%_^ULn0@rsx&+)|;;E1?xL426JSu;mz{JEq(2X!!v(CP(V;HCk8^2 z6EfzlGjYi)eY-n6TjUSOA5b9J(cc(W83)lafJjb>QY|DM%w#`ngWN!uh$JsaHn!++acKFg4u_I=W}yKehF!8U;HL} z&&lS$Ay*SCiy)4DCeby@p!t{*hYf#R`Fh=*yIrCas!51Scct_20~40N`q~N!WvOhM z6E%$nlNxHkperZZCO@>&n;h-vhTW8AJyIpxbf@cdx4=GT^xj68S=o69vAm&XEywO=;)a1xHE1G6b=X+Y&ze>Z;V-ZlX6ZqJ1}XMS_LfO8}LiR zd)sf}9!%TQFzd}@IsAYF$Mwi(%bM+&Ry;*v9H_e-PMI4oy;GTDzj&wSCoUWQLfW^@ z*k6dF$J{mRn{6A{^Ne5Yq{Ly?1u!08nLRj!#ERu^?e5P0Rl}a1pXVxM{(wSEIYN}( zh7=VhBzQVo!;S7%)NHX#!e71pQuXcWI?k|Os61%vgksET9a0Jh8d(`QO%gH@U6em8xrPDc+HPqh{<{ z-GJ?Su6_SkqE+payDLn4_aP)9aZd&(uh;nM^Vmw7rlN4=%(Y)#gYD(7-qrd2Gc)*v z)6dZ|j%jtvdzNEK9P{L&)6ZXjv97ME-0SyggOVK^?#h$evG9&#^56Cja(KlxZEkyi z>4+cK9KG%B=Lm~y?x2UHVhkx}(EIPw)^Lu|FbX7NVrI5NrZ!maS!t=~PFIMlz4FF0 zHST!(g@odvQV%=!cdMz{oBV0Tr2{JvqA{OlcY@*%pAY_?5LJg%7sfYp?oggQ3Cu~Q zQHn0nQDRZT8QXe~-%%<$jYfo!Ol0QdyXVfoaO#1TKWV&9VAB0Y9c;Tip~*1D>1R6p z=AGKnAdwmI8x;*rVjkfXQZ|WukiWT5#ZkWA@lhnBZl{o;geZg1DnC66nw`3zx4I)R zfUj!XCpE?Hdi!o+7>BM<8042l?buM`;Me$H~IM=!Gl6Y&P$%4N5YXKzQcnf ziH7Laq-MfBs{A;qiI4Sn(7HN&B}(-$Ab3W@P%AA?KPi)$Qu2&UW}4iiq3xJslbI;( z4^D2b%2xI@iSH{#Uz6)T`(sm>7VL+7zRuG*R;kX9yggPQdem?&d2$p}r!GoiRuk9% z^L1aJ!u%|c#}rzvv*XRGBqa+jX-gU!ZnWs!q$c_Al;gM`FTte$Hl#A^e<#N7)b1Hy z``6kL<>GApix@gPlk1_>=9XeuB{%sW(EUf!m}(CY%lVtg2UM@RznR58XYXSVm`9cg zGoBBP=YQj@C=jr2tC zbSA?i$~71dO`Hu0(%l+8ennnaO^C{X*tgj%ijz+*teDW)B?I z?NneHAf|h#HhkHl#B<$;7cUoq6;e`%^meg#B6lSq{%X@zr@JeR|XAF>N(2 zy&3nIT-;UjH9H=AcQj3&(3(~q91Aj-Nl)+~Mvf;OHSFJ0%n`WsM4G$&Otrd^vm-YK zGP#?jbiBRc zdP=#{W;F@NF|SuY^-k^II-A=)?%bP~hsFN20PaEVad0b}`Q<4Icg=3{KVt;n$?oR=8KP3)shbO0PHFzG5&uTvJMnUzC#aelQ5bLlsIENXmxP@}Mn{jNLX&ZK4{_%z@ z7gv{5v_kgenSHrveZMM%=-^*K-Dr+JqqwequjcT{Wopp>A+2(`4;~|9=bTr4@y6w@ z2!R1Cum72ejw;MukKc2dIe&Q13i;>A7_33h$heu?U4?Rq)-Ufh{^S^k=U_DhBY?5e z={d#dn8z$&%!G8M88n3Jw|emYuSxvMxcgK|Gkh|HHL)uVjkl0#dEKYF*}hxuryjnu zBXL>5D*si4DAn@`3wKHCTeUJF)HF+-~0qh)vhr<*TcDY>3LyAu)E>2L4UA&Z$p zy141w@vEnOHZ{~T%{nXZCPYIoRif!F;&&LMwRiz{(;hBtb}Bc!fgX|2hw|+&VkX6< z2v>@j5^r#NQp6`Gd~Tz^aEWlI?U*ksnS z#Pk~fETY}gcFFCU*|BZiDvo1oN%Q7ij2d4qW%9r07!tF%{&z~bCr}*n$HoG0{=P5mn|LsieW_1@iq0y!T_pKrF5>y8b>%++C%GyvZk1- zW>@)2S<{Vu|7XPDOT5T!9dad#^cZ?GI)!9gGT^rKjxPRxH-1^2)$-=kjoWsQmsb$D|2bA9ZY zVB6sp&3Y%xqNx?#2isK)ud_K%FYbyMY&i*%E(eue3s-GW;|1m1K`f(p2ZS|wVgna%8^7$!m9pJ3- z=Kpy|J5#F+*3memc6Il?p?9O+ADFPCmwWl+I?Z+?M5p^=f1ECNe?gElx@AXJH)}bP zhb+b;IUe~&MeUojq_~Op*g}X75D6m(w^(Fir zE&q@Vb==po=Zn|!uhP1P#I{0XJP=`2D^yVa;wen?|=$6KbLm-!dr<1=JlGK!`PN ztky7hUdLDzxeMv%f0hKb-psw?i`#$pUc8uS)_UOM;@j1;>h9mAp?k4cqJ?AU*~dP* zka4deKXjMf4OWBr4JSrA2c-cT)MRkvsZ{Ajm!h3IIfYqqn)w4Pufv@WtjVJdLd3A z#T%OpzNBc^?{m|uOGC3$exLAnO(MIQi=}WD{CIBq`!#X*&gi=tQkQG?{7iH+XKP~i zrXX2+VPzM5SQC?w^_*i)6VnXq@59z7DUu=>~bSjD$XTD4BP_*2t~ zX`g0~_OMnUAqVkDf=B6~Rhc*M^qmv!ae)x6YikS`HNv@gkFIE~)NeL5*U3L*TQm1X zMc(q|Lj5i^h_P__eB4ho)hCZ!R@0lG{hUd&__cJO(#BXfVh8vY{j2@RdS52?v_pEf zG$~R!gZ!W4se@>p5@XuVU3O6w)|C*~T0X5zn^e}697H2NXl0J3q7h%Wa(Bali>qdj z?f8t9A(u9Vb=4U=?3>g$_=dS_xpDZFt?ARG?icNlCCoe-*$DMO`ATgjdvPn zP{+^=S+;TpW0y^S{(MI{^&^hkEv_T0JAJ@^wP)CJKS7N8)G46oo39prJYIR*eky= zrg=JNb-oo?mCl*U!LT@(j$u)*t^2@KAnJi<+2zyTs$cjDfvG;Gt;s*c>1%SPcNX$L zgPWSUl<$ohMfUKL;}1hVM>{hxJ)@^%d-D@Eq*r_QLJ$Lg(Iyg)aG)McbNo)Ar& z2icYdG%PUNpAf$H(|r!p-c-xLAo+Y@{5zURJREsCnOPZ}x%_u_a^Kzm=eXb5$^4wb znZc2|vvFi}=E`DUc7N=d+|v%p-r3a3NK*{y>@MG&EInGs8`VI9+w5;&G_>yYKdIJM}W0+Z6f+LWfhkS_QhAE}1xPRO#v-?-7wlThBVP zemh0bL7&EJM2ONDnt%S@SBnllBBT&sn{tyTtgG2cT>lPu=r-cU+g?=;_e*`r)x54n zNn}^^Boh^!Xr-|sRsF`vVg;7PgS2(iY(`g8A~OSsPVAAH&RpEp?9J@_(y_X$$&m$p zb63+$ws+oS{5PK!{F8wenR>bFwrQ{Iz8eq%DGYF=0E6j(@i;*$3;d9qT+ zOg-F3?J3`Xf62F266$DsxJ&HkU)(BLnVUOH2IXGUBC)FzwKl*be>R3t`<`YkX@&If z={~;umCE+ISkT2|jC_@Y79C@SR7e@?($)KZoO|P+wJ#_ z_V_3l|Ig=QdH$DjZ13Yfi&alwtm>)!A-i4u#cgDszv^R-QWyX8cqF16Nh|xzyc<3? zqsxPH5zi;}`6fG+JcvVL90qSYIXn084vBOocZ~^;e@5lLs$z0a(bULJXIo9WWmb0Q zc2EBS{oMmF#qbcHg;~5h(DeA~GpxVqmXo~3ES%b^fit znMYdYaxP~L$HW09S8l}B{Y?v5J8)%gCiQXB(h_6bqPTOqPg!0%v1d?*ycv|!&Ozqq z+?2;L*!-2-*&^g|gu7k-*@A&qjDME+b%dFa$LSZ>5$SIA7yZq8_N}7$IkxmSxAHiv z`Tz6&uMR8vT!}Q*@^XS~KEz$l*9kIh_ARyGiNmuxipE5n0(rZI^=ju);6~hU-wgio zCkC-q9(RS)m=k%ODeYQ++{_<}VmJS%BK@P493NVV-Z~xN%;m`WwRsodtmi**fV-wQ zJ7vANv`T+UW1s4$4KrQx(XV0F;cNe)kd3XrDRqRYt>Y*|HtBHp==o~k;WSb6Py9e! zYe7cboy7J3Z*k2NrQT$O`$Ra)yXU~yTet6xzGc$Z3Q0D*N2BY_&)$s=={3TX$WPp% zcxc3JNVqbzSh~LlM0-rKLbCnVD4<}mp`17E)A76!W^e&Yw%YQT&^>+F&;&EM@nU;y zA7S(IEZ%=-4 z^`|P)A(Ka%JVBIfF&;X}99sQ#cJeL)Hel*kYbeks<%;=2(KTaj#Z3~kb9JQqa;7yly;Ic$C?^S>+>CE zb$ZC>JN@&+(9XrS?t(k)UE5lPa15Pf+7;xWHEoiaQ;<`7g30E7L1(0+?G)1~*jX;i zoT;uK(TXfG%{_O%eVy*|q{w+&^{j)w<<9R>ewsNO?5yTBZ5n_4y}+5wOj_vl_FBMR z>G{rNu?uk`TEot@`OcL7j_K~%dpBxKqVwltbz=rvSH{+NmTBYRq0|1IT3>lpuRn7O z9;BcLc&zk&oxvWBzVR@7<~dV-#yga?fSn~T2=_4Q=Q>jsN@m=*<_ZoRKjYcJjd~Eq zm2!|rK|=VtHL~$nOLo2Ry}s5R63RKgE|EJE*{)7X2en-=*Xfrwj5wNYsb6(|xT0#N zF?O8bnC0w7^v8-moQ*j1oMpWX8#U)S3m5v#cXcdcKL3r^KkAhB_6+xMEZTcnngQO6 zUf89`?_st7hXUK(<7>vuclv**&j&dj1<(7*uMc_rtV*C+y}%h{JAX*@v#n6TBW#w3 z$+wVZ|9ppjJft{KyGNAWgw{}^sf}6a3@lW1wtMtE`C(AEITw!Vg5P?mpd2AO%XXb{ zBGKSc)pYBRYX*jvN3Gc&<~`-;*cunzDEWO=_|kc&=d%{I2bTTXypj2T%jGv=USC3j zxVYhjF=mcC*#c9(_-fzW1ss+Kp(G%cZ?MUgTG6nuAA@SdAx{JqGT-ed@I7WA(*-+g*GxPH>4DTdlV z31;VPc=(sH$v+$`w=Om73zPjmJhVCsNc3>z`CGS)>rpG${60#Enyq*HwPz}pukb87 z?xm&X9dUzy!$arBXB}!x$uRQQz0n>otdJwALespu7?8#N09S#60fiozszscs{JoaB zr{b=on=AOttk5(%YSLvUvIqssgojQ9P1pN03U5EG6JJmVa#h6jQb9u0F4>ZKU!1q9 z?Rq87(wA1RxXc_Uu76WJeDR2E*=%C$#V516vkxr51Io*cqbS!)!|~AVK4;r19dE?T zeN!F;^O?KM)GF%q3)+lFDm=#Q*_Qaq)nA9p!@cM|ZiVD5x4%@915vLP!ouCdbi3#6 zZ%!8F>V5Na^MFMB6GXXNx83W3k=`XYbLz2IFMd%bLovE6dz2|sjNwrv%5>3wr6@D1 z7+u>a$~-~$Z%qoCCH|TJIKCvXfo@3U58%sWp8tq4C5khka<4GsiW8F*ma*@@d4)U1 zm{!^Ay*ytqVf2vXfuDpW@Z7h;g!FZ+VpIb_3rF`7xgM-=&6##qNad6MaheS{&w~T5>qjcvXT6y~8fN1S z@6_fyn%HFM;Pj2HqlHJNWhQ?qsxfbqdrZ~M*R6M|{tG8@ai9xqJl1S7HA+*wU7O6{ zQrtK=gQvz;;J5B^viNA_!-dR>#;%IH=3a$w%~8^HSTW7JQqH6$>}WHHraANE@4oXx zhj(q@>JwK{)@*jS{mNOJuH0Xoov)It#~VH_LR>qoC0op#(#|06-JB@xw4a*#$0MbEh!O_{`* zjH>YU34dm>!(7GFKiy9EI%wLti=FGW9`!srtx`Kp;_{TEH6A>S75T%fNo#ZGyB=F< z1oPz%e-~(lw7dLOijrTX`NNfZekC8Z)3i|BBbG}HZ18Iv9a3Vq8Ax3JHkQYcdu`ti`z7rc@(6KF!7;nddZoS|kL;8zXY3mHS7q+b zm55!k3xwn(WPR!2R`UzqEUu6sSH2#5%sZu?dyji1{4CkVQ-1NE{o;!2x@ASLwyNfu za3^cvSeee%+MxLOKRSQEbr+W`_JKO>UK3dnkD$HoA@r*EgLB&o&KT{=%+*00e@H`f z_jpAPbGP=I4walaOs-1Kxc*D_xnrF#+OA;i?tj*Fby{KF_u8?~)T+era=Qm;&$iYB#3C%L_P|J8@?(#MGYzjIBVL9^ta4aZ7D z^U9;ak7jK(cdNS&0iT|N8c$ZMd?>xnN!1xO|FxAqRe%48pWLUAZBI96N`H0&kNxpP zAFI?tAGPa;&ibz<3$#lXGV+LfF#Hr}%#wjeiu8A-s~e6UA&mWGjQvE@^Y(6;x?fU- zxaPNVoI}f6eUv>^X)QX-H!sX{E(FcIA7~y4>({(IFq7=b}TX(?Fef?RCIM=VAZM zC*4nE)mh*FMeb9B^+2J%LSS8El_%o!<@vZJqjUXyNk49n#GWN7f!1!Hse_XsyArcc zns4i|1i!_RUrU4?1#T}Jx${O**D>7n5O38}rq+6@yWo^LzAi?!gHD@T^*MD_KJC7S z_|8!o6L+!7U2uPMUZ*tpLsti_py6YHZ);DqH`Jclfx-;!wi5dN0ls4{FQ>;11KfkN) z3zhhB!%O(LOaGFA&OMAm^hMsQrW+piFz_#nr>5D?mrGywzucI=v9umy@MwC~?3Slp z6stmZZ~Q+?_HkcZB{d0KI`e#J-AUI>xt4sz^3OrWU!sL@DVG1rHTUJoikfLp&6#p~ zhwG@|`kTK8*Uajc6xX)@zNbDe+D8+~ZmJJ6rrHg2T{FpUnD85>S{VD*RQVU(^tl{9 zPWj>u^X@BB{;28yeMa^mwC223V(ZZ0ngaiiF=d1W1d|D z`&eiXdHa~?9xxv|{QptI|10jg!=gI6KD&DFA|geR=7QJ}m8Px=1T`v(yioBv-qY$Q*WZ0X}e&(~oMrf|dVaHCCUpYN$S4HGOzD`D<}LV5W->5BYQkw2<=i zt-E$9XVzLMTz;1R`y^4-wgyh4BXW;i~vARnb4}ak?S& z?>+59ruq-e@4vHt{%cK1*8*y)5M2pByhf^5wXTY*_XgcqFBx0!2B#00jdWSAF8IU7 z_YY5ikJ5@$lsO9lbGqdI)>LIf=>ufeq?H;Po_d*5USH{NjF9Xcn5Td5BLDYbelA`J zVBwtV1WZ%6!em=yf!+9mi>-p}l?trNnUl$A=`$J(gmQQYO-z92d-F5d^ZmYEqoc@S zkGAU5A@TyWS>yw;-YXLXAi!w^mkSFKr4lBSBc*`hIe1_ED|yYq_>cI_DR}>n;x|#E z6W~>=kUHCvuqi0*)o;Y=bIjZ0DLEyor&|48j^O+!5;%vlhH`02g5`~{XFIoQ_7?ft zNO_T5lTS{#c z1(#7eZ~gl89IMovQ*br`KaX0#?j0PqlfM`n@ z%}x@$)J2K%UdDy}zByq5K2B2Pq2!@^SVo0OY!mEX2O$YceJnqZzVln7-{Sc1yZ<$GK?U0i}~YA!)tbBV!` zn2fY|NmG}?hs?ELpyJ@~ktz;cgO}>L)XuNuoQ00wH9z~wDtP(fgq6AyuNC!XAeiRr zS-%`pw&FfE_H_t;D~0meD=J$C9lr#U=_&5}l=S|CuCtf}NMcU6ILoz1?p3n?qjamo!@PV zD(e4&ELyf4JE%Nq&8KhVO|xm!%&hPFv}c=DtYE0VC{S3%s?t^$G+Xdwn?#Y+$B*`F zW&&Xjgsac#@Tbd$u}w?9zFy2JsV&Zj$JQTqTF;AXKgQx(0Oh(j6#O}!Iz@At@7~3? zbQd_a!89br%Muz2$SN1Y?ht86Tb2ugPP}2wO3qSU!d$}|t4diB@#QzW%*&i6?Ptj@ zYX?sGIrH(bDF-*;@RTm-UDvw{E6I8#hK#;d6t)se{D3NXs;L_{e%_6Hm56QC9g6Zh zBo9!(?Nk2lR=%BGJb~OWb@y?jf|WvqLlcEUQmWng)pd#&WoJqV4;E zX|x6u=BM8s-zaxy*1Vr23TPdTqG?i!&_=DB01QnHRFEP#y-W68a52d8{=k@g<>UrVIA`g|ZIJ#b9>W3kdTysT;jo{lAq_OBr=ZfH8 z9mivo!bBnM9UzdwbuIjuR`qDcTER&nYgoCh9_6nUrl_l5?tqrUHvBNWoo%P>zYg2* zzM`3&!{&IhQFZRo*qHO3Z+J>!Hj)kF4hkFRY*O3KD&F_@nLb1((Tp~yVia8i1e?Gk zkNNEGAWnW#o9(|My35mO2dDg;I^(zWu74cYQ;wKX33%1#IYQIF;7IzKL$mdW+qh$h zApJQIthsO7Gk0RCaf_>Z1pBKpMoF%{cS4Y*7p@c1rQ5)qrZC=?K(Ll|UNxBfo=(o! zBfL1p_VsIzp%*pbddfg^IssmFC`S}6i171By?siLh%=`$CVd%4gj!eBF>h@3haRz+ zQ@-#nX*Timjmdh-L38q558iV?ux7itk$cXyUp^h9M-*{N+etR6-%n5U(^JaKX$5%I zqJ`YB>)G`DWph^Cw|a!yg7TPjPmYKR-d1&U^|9`HL?2G^w45{ILC(-nJtfeB%r-!J zED-j{Sx$JaYsbbTF6a?B@%Qlt7>E6A71GYoaVzWk<5q1*s;%h2q8Bh8M%YpcWN4Cr zKom*hkd39K-L_t-)FaYCQG;^R^LW+gzl~JrDc{)AUB+9)dB1K^dz|~zMGf?b*PPP1 zEPjxg#gZ#}ik%&`-H5z<+bN{DikE>K=5|QCSfxh<+EFkNngk%6kiNyL=A{!>bXV#T zX`nO#<-wOR%?@ANx>ry6#*VTWZ!t%-h*%i7py&nkt}AyXC~O;N!-c!rxm!;5)l<~= zR3YWeUPc7mJ(<3we7?ONG1{KoH$i$d5KJ>kz5K5{&dqPHN2~;e?Z7$A9;A4ja3fJq z$+V}rjQ1?(HGg=;qiw~d3O(Wh=S_~@)T%hyzn-2_X-@^TVwLT1x}d zATzzWJR+nX>9^gu(;0Z|*{FYB3ZijwnV-@1=9HF(prc|S zSQ{GOf(ju^T~oMdJ9pgoqTViSR3B<7&l*P?rgz!vcnp&o8$sA{qmwPkeK+!XsU?+c z#}h%1We4!Q-BRncLs+O#-)W_g&P}b_(0jsfjWXCBBy5y}nWsKZc{@>*#~iWx$q-?c z+mN|Hu%H1TN-_temL^OeQ_cnSz1PsaJgpNH#zv zSA-wxw`?O@Yk4Bz5a#VoXUbx-s#h6~$M7n9?o1DNp-EM%Ittw_q$zV+r|toF&2_?0 zB?>4IZbumiu%_l}x`O|DU<$o8=hcM6Eo?3+6{qoqDgSLTl4VMn2#Gx%R~-y+-Mn)>~6JO_KrBrJ9|;K^KNqUZ93LIYSY$Mjinq*`9UJyonl1+GQ-{oMK)jP>?=BmhD}QW#q21Ku!#NVh0#aScWJLqPI?LW~ z#KWWQ^JQ6*Iib5XP0p7mj}!IO2PS(^;6Bvj01vsg;!UeQavXHDwv>j{{KkU9YRTH! zMX~0-`ng1r+U{fou!C0}4+NXbw_eGacExPUN(muFZ>;j5`#`+AyNzpy<-#=!b%N;C zge*vy-(JrDktOLzdreKo?Qe5={UQQ38wkz&5wH>hGu!9jWvO5C_VcFroe1DUcaT~U zq*rDM5o!aGj4bFAymlQa5n%zE<{f1XpS|?#@Z6yO%&EfD?{`IBl#M=FDx}?gF0Un} z?NMDwfwzX9?#IgAKq{=$p*71v2>*{d4>$l_<4l@a9_pf6*Vpwi&C^bDZ)nxzt8ahU zn0f*hH`4*`D(l%;j65{oyqqgWRL4iD-NO;Oz%v2c&ZPAgSG7r z4`VWfxR>FQKBd-lqBEI&4UNBF<18^WX``>Z$fLxpw20&tbNzRM7l&Kfe1q8LvEWso z0)k2ZbG}JoSBvflBMw9q5v$O3xV?%=ZyP1A7;T?cy7pTYiuhpEbK|Y9fbZjd(Oi(LoiuQ+EBYV^R96)FFrolhpncJNr zZVK(_`yGNne#eEvclXOyzbX}3W+&<4qMh1@`kw%AVjmjyqtJ$4 z6qL{l2-ZnMt(vY6&vwLAA}IlFu4eTN9z+%3RdQu0g9ed%K9n*QvMeFXIrmInQtb}x z*adIyTq%^0kE6=@gQ);;W!)k4Bp)+D?V)l_J)eDI&Z8}FSWPiU9(tH|C=EOf!KTCH zOd2okp}Mzy8{(pMf^CLT(rLrJl#e8Q&LYhjAxVi+=bV8GHwRGZ8I(RFK(6TVSKW^M zw8%0Rg8A^41BwmGvTE174hucwpYlwhr%fGBK4+1AJ~`8rvp7l7e1t5h=FzYN5Uj=4 z8~2CvjIb-ATp(JT?+D6;K#e~TtSWMcMxItyUSs1l%M8X5H;klg6nq<{$ez};=WS3} zTZV=LMv&<_!Pd3r?7q+Y$w(S{4o;Q#DLScsqv#H#s)vq}t(^UlnKud=G__;+j*JbL#vuA0IYvvs&8cH3 zfu+`K45gk&KkNl0vxBC8RcsX3%5DOXd??-Z3H=0w(tiZmUJx$pECBWUOCITj2WEV} zlB4VX@GPP?(vXWnKZMVJc~NMuz7Qy@D9^rycswZhJ}(aDZmjA`E<(*$fndd1WHB}> zYM~+x2y_58K^F&-(Y|Jxz6g;KFJH$`$RD*9)F>m$e?2zS@A5fqQ?LX%rGlX5Rrh(}$eDLw#-xqp|G0DvaXruHFGH_UB7;SwaK_UxrMB+ZK|wU^AYwE+h3H z#?$%B80B?d3N(Vg-y@B`-_LH#FWa`Ii{Ik{hiQ>=HAFh})qH*H&Bsz3lA3j5WNIP$ z;aUJ#t(fg9*g1rX+33RBJ$h<6D6B^`H1i+x>4YEH?i)@mGEEvyq=AJ%nNttRwmt$yHiHQK{{(>F7=(;E0#Y>*Gpe! zPNj_3kh@dnJ;-^_|B=`@VaM<<^t|WH$>%C^Rt5y?qeFcQ+o&BZcj*zL1x*7&qvkHl zOuxu+(Op}m=@Ew9oN~HwX6Axd*?2nLx+?S>rHqzGlb)O8&BMPRHfAU4-ajuq4m_;8 zO~jLpGrjWyDZXKwXfy@-?8Hg|Ab6GeG4$DWIM<%xJ3Hp}@11KL z;ZQway3^Y2+;s>45m47jckaA_7kgP4E7yzZnqC2x3vcz--^#i)+rxTF zb}S{_zyW(ml{$}ggD3r+llz>KN-2%IEWHCa1RIuc4G^4umx^uP!>`Evqvy;Uf-3~5 z&|h#Q_nYisl4LY{D(mg0jW^K|VZ&c$PuaL`NH#wRow$X4S$TOTIo-l+v_t;i22D+O zWqZE&=LASsZ*lwnhzgJ`?nI3ND;i@g8yg19lB-PVJF2PI09#+k;D!;muQfgV<#B&q z&Nuy!sOt=rQjvITHXL=fF>KV&R)sM#dtBWBi$uiTpJ2i3y(^7ar}}>$4f*(NjXA zhRukMoHCxe-4$BVgu8;fva17ayel|1Z3bsOvnr{w{l`(+{pxR0$lK<+za?nqNVgeI z(@y?gG(3LC$V?}@?s#Xj!hDr=l?ha^2_V)xwWqcxevFEZx8pEHn`S*?NxjvC{ z`13Y@zGhG0?eBAbJUy1D%P}A4mmRD)J$T4hoAA!^`DjK`q(zv2(N4TK!28ZgpDd5) z-CaGGmfjZ{&+7x{xdlGE#2jm$a^t&IqwvmPe>U#Dxp`8yOBvR`Y=vR-?5c6`)jz&h z^pv+f%Vv7DxIFFWQFeG|bi1NO*}-Rf>~`VwjMi73nyf$JTF4c=DOn{c+{7ogv#*nf zQ%vOS@Tn1PwXGfqofO9GKUiYiX2xj8Jrs5-DJe^I)G8heUn|Mymgr3rwIZC*C&flo zc0?5DJ3rBk7Q7Li-o3ig))6XGDjlH;qeDLnq0Td7+}c52I}FdvcQ9l8?9kYVi16sx z8Ie)3Ga}k`ZWk946&V&ndBLIs4g5r<(mFg9?%B{ug{WGWFFvHLmf}dNs1nX;&nd*) z<`mgn)kyoNrPx}j9cCrQdTQVJi9^~^aFwW{T9u+>>u#YTq2W<(Qc?KtX(7=uxWGmx zd^$BOyj?_e$YeSdEUIP39+hGn3Q?$RsqtIUgf3Tz^J!_NxQ1p`i0w(GM2V#Y$|{h` zmI|>RO?)f%q}bQU^!C?cpz)085VYEuaBb5!;wS~}e=7zM18bXBi6)}9{|xbliY!K` zjA`f`u`y+jRJEkCbx>cgWYJYSWRBR%n1Z8#U%6EbS8AIii0%sQXN$y>O1i&PG}R_1 zi>+%>elRkUy-YOG{;^zKW5pymYU^(oO9gteM}!rSB|0cNP+^8>Wjrl9a&qL1$T;%O z5c^Te?}9Py8KLr`vTpz!l_L%&RhDSY(R6W-Xi7=`&5hWAmr2S!^xLl>(eOSz{P2T$M00KWE%AC2SutLMYVr`%zqF6el%6d%C8r3ec+5zZJ5@au>S*tcR1Fl3KOGp+ PJ8o#qK$<_b*82YikIr46 delta 107029 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�v|(st&xp*+r_WxNIQwlewG-G#kIWo#h6rgc{`v3p>`6s zILWR+_Dh;^&NBDJyvwmtyQB9CmPWfx*Z$;SQPB!n;w&7=_-fO;+hLPk^iyZKF=BTy z{mophfP(4Poen(?Dw6hmT@crF?%P-zLgvI3Z9Uq0=O(Dh+&dqrs@g-@->}rgD$1Tq z^s6)xs163^SG=N~##l>ZQX|kD?9T>j{nu1-&YNA-TS|aB*bbt{uv8WM&0@z>?CK`> zgPgodpjy=Ywn^kgtjvR|SMSt}ZO0piWh=;It%=Q8s!R1I`59{0c0EYnXRNECIb+Sk z3bu5mGAna*8#HL}lwWmEbGI4IQrsI$QG%j^v6z@aKz9PPL!>t6__I*WGBu5>t!x?5)z)vO#q+V$5!z8Q7pCY20)h=T zs8l3;hMi~tyHyPaGJ5~K($@waEivxv>9f?#Au*aoN9;B z=q1o#>IV*akGQC2c6#5rD0Z&po@E=sSc!A6GOq&7^60IF%72C`zk9Y5O9NN6Nv$K% zW@potw5=(=Sbo>JA#s%T-v9Cb--a-p+>X}aOtnu!GJGp#aUPBBs zbcx~`!1QwT2LkqOzj)6H=?R?razcOU4z6WO1<6ITY$cw7smz1qyHR~K8In0jX0|Eh}H@R}PcD7{|vnVR0w z)UQG16O{z$M6C{25_4Z8b`LZdn_g-I^!^1_ad!GNoo2V=BNV~@m77rx9ZXL8hR)vO zxD=|L1)#q+Z||=pY8BOVh%I$g{^)J{cN0_v1I*6bSZ$j43VQN>^tj^m9Tn};Htyy7 zQT`ZRd_SmcJ4~-BgK}u_ZAv=Zzd^NVRClcIjGE}E8tJt)()VkmN6oA$--;UPmKtfD zS?O;*(zO^_BfYvt`p+8ap(W|BJ<|D3tC8MaBi&OIjrA&Z2YVWrKXu5;!}%<72~_K% znFhaqQ!kFKiLPGIDrW{wtkLZB>X}-s6Xj5?Wx;A_ej7Ws9jXOYD^;!ZfUiHt-ifVe z?pMvgQnA@YN{3D=vOZkj}pMPejWji5QI zwWn6H;P)1>r?|rPsGwRS6MWpJy0$Nn$6yvKp(==Imz5Jh<%0?Tv4X?(8K53 zsoEep;M%+h#vtPbC3ufwsgCxR!%tYlu=q7E{;JY3*Fc;4putL{`Tk=EV*lA?!oGYW&XaZci4G$k&T*k6R{L#N{?UVEVYD| zMUCC-*A ze5tcDRq-x!JX6Il=Ph04!k5|q?I9D`jIVSNGF1teJ1*5Z>jvIxxmCRNTE$!ORy(}~ z)k`XVtJB+1y>3(e6|hDYFR7-<-7&5u_@3m$RkH;B#kZs^abDE&alU*Q5&n z(#88KSHWSTd9DCSQ=Ufy9iS8X3m!CV{>Oq)n+XnZ{@VL3;$25 zeA^OFdT7}86(6t+_x#p;VzxDA$E6XXQ~|S1y_N2sG{_7@%m`2s^{m-ag(pnmSzqx=~l&T03R2f9 (3fFEjsgww`o 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 aTredq(bFf}A$qMf<1_U9Ir1&mS@A!*o^!4M diff --git a/docs/plans/2026-02-01-remote-access-design.md b/docs/plans/2026-02-01-remote-access-design.md new file mode 100644 index 000000000..9f5fc3fb6 --- /dev/null +++ b/docs/plans/2026-02-01-remote-access-design.md @@ -0,0 +1,266 @@ +# Remote Access Feature Design + +## Overview + +Enable users to access their desktop 1Code app from any browser via Cloudflare Tunnel with PIN authentication. + +## Architecture + +``` +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ Web Browser │◄═══════►│ Cloudflare │◄═══════►│ Desktop App │ +│ (WebSocket) │ WSS │ Tunnel │ WS │ (WS server + │ +│ │ │ │ │ cloudflared) │ +└─────────────────┘ └──────────────────┘ └─────────────────┘ +``` + +**Key Properties:** +- Desktop initiates outbound connection (no firewall issues) +- Random URL changes each session (private) +- PIN prevents unauthorized access +- Cloudflare handles SSL automatically +- All communication via WebSocket (real-time) + +## User Flow + +1. User clicks "Enable Remote Access" in desktop +2. Desktop starts WebSocket server on `localhost:PORT` +3. Desktop runs `cloudflared tunnel --url http://localhost:PORT` +4. Cloudflare generates random URL: `https://abc-xyz.trycloudflare.com` +5. Desktop generates 6-digit PIN +6. Desktop displays URL + PIN to user +7. User opens URL in browser, enters PIN +8. Web client loads (same renderer UI), all tRPC calls via WebSocket + +## Transport Adapter + +Same renderer UI, different transport based on environment: + +``` +Desktop App Browser +┌──────────────┐ ┌──────────────┐ +│ Renderer UI │ │ Renderer UI │ ← Same build +│ (Electron) │ │ (Browser) │ +├──────────────┤ ├──────────────┤ +│ desktopApi │ │ wsAdapter │ ← Different transport +│ (IPC) │ │ (WebSocket) │ +└──────────────┘ └──────────────┘ + ↓ ↓ +┌──────────────┐ ┌──────────────┐ +│ Main Process │◄═══════════════════│ WS Server │ +│ (tRPC) │ tunnel │ (cloudflared)│ +└──────────────┘ └──────────────┘ +``` + +## WebSocket Protocol + +### Message Types + +```typescript +// Client → Server +interface WSRequest { + id: string // Request ID for matching response + type: "trpc" | "api" // trpc call or desktopApi call + method: string // e.g., "claude.sendMessage" or "getUser" + params: unknown +} + +// Server → Client +interface WSResponse { + id: string // Match request ID + type: "result" | "error" | "stream" + data: unknown +} + +// Server → Client (Push/Subscription) +interface WSPush { + id: null // No request ID + type: "subscription" + channel: string // e.g., "claude.onMessage", "terminal.onOutput" + data: unknown +} +``` + +### Connection Flow + +``` +1. Client connect → WS handshake +2. Server ← { type: "auth_required" } +3. Client → { type: "auth", pin: "123456" } +4. Server ← { type: "auth_success" } or { type: "auth_failed" } +5. Client → { id: "1", type: "trpc", method: "projects.list", params: {} } +6. Server ← { id: "1", type: "result", data: [...] } +``` + +### Subscriptions (Real-time) + +``` +// Client subscribe +→ { id: "2", type: "trpc", method: "claude.onMessage", params: { subChatId: "xxx" } } + +// Server sends stream continuously +← { id: null, type: "subscription", channel: "claude.onMessage:xxx", data: { ... } } +← { id: null, type: "subscription", channel: "claude.onMessage:xxx", data: { ... } } + +// Client unsubscribe +→ { id: "3", type: "unsubscribe", channel: "claude.onMessage:xxx" } +``` + +## Cloudflared Management + +### Binary Download + +Download on first use, store in userData: + +```typescript +const CLOUDFLARED_URLS = { + darwin_arm64: "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-arm64.tgz", + darwin_x64: "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-amd64.tgz", + win32_x64: "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe", + linux_x64: "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64" +} + +// Storage location: +// ~/Library/Application Support/1Code/bin/cloudflared +``` + +### Lifecycle + +``` +1. Check cloudflared binary exists + - If missing → download + extract + - If exists → continue + +2. Start WebSocket server on localhost:random_port + +3. Spawn cloudflared process + cloudflared tunnel --url http://localhost:PORT + +4. Parse output for URL + "https://random-words.trycloudflare.com" + +5. Generate 6-digit PIN + +6. Display URL + PIN to user +``` + +### Cleanup + +```typescript +async function stopRemoteAccess() { + cloudflaredProcess.kill() + wsServer.close() + pin = null +} + +app.on('before-quit', stopRemoteAccess) +``` + +## File Structure + +### New Files (Main Process) + +``` +src/main/lib/remote-access/ +├── index.ts # Main export +├── ws-server.ts # WebSocket server + tRPC adapter +├── cloudflared.ts # Manage cloudflared process +└── session.ts # PIN auth, connected clients +``` + +### New Files (Renderer) + +``` +src/renderer/lib/transport/ +├── index.ts # Auto-detect and export transport +├── electron.ts # IPC transport (existing logic) +└── websocket.ts # WebSocket transport (new) +``` + +## UI Components + +### Location + +Bottom sidebar icon bar, add Remote Access button: + +``` +┌─────────────────────┐ +│ ⚙️ ❓ 📅 │ ← Existing icon bar +│ [🌐] │ ← Add Remote Access icon +│ [Feedback] │ +└─────────────────────┘ +``` + +### State Atom + +```typescript +remoteAccessStateAtom: + | { status: "disabled" } + | { status: "starting" } + | { status: "active", url: string, pin: string, clients: number } + | { status: "error", message: string } +``` + +### Dialog (Active State) + +``` +┌────────────────────────────────────────────┐ +│ 🌐 Remote Access [X] │ +├────────────────────────────────────────────┤ +│ │ +│ Status: ● Active │ +│ │ +│ URL: │ +│ ┌──────────────────────────────────────┐ │ +│ │ https://abc-xyz.trycloudflare.com │ │ +│ │ [Copy] │ │ +│ └──────────────────────────────────────┘ │ +│ │ +│ PIN: │ +│ ┌──────────────────────────────────────┐ │ +│ │ 1 2 3 4 5 6 │ │ +│ └──────────────────────────────────────┘ │ +│ │ +│ Connected clients: 1 │ +│ │ +│ [Disable Remote Access] │ +│ │ +└────────────────────────────────────────────┘ +``` + +### Dialog (Disabled State) + +``` +┌────────────────────────────────────────────┐ +│ 🌐 Remote Access [X] │ +├────────────────────────────────────────────┤ +│ │ +│ Access your desktop from any browser. │ +│ │ +│ [Enable Remote Access] │ +│ │ +└────────────────────────────────────────────┘ +``` + +## Real-time Features + +All desktop real-time features must work in web client: + +| Feature | Implementation | +|---------|----------------| +| Claude streaming | Subscribe `claude.onMessage` → forward via WS | +| Terminal output | Subscribe `terminal.onOutput` → forward via WS | +| Git status changes | Subscribe `git.onStatusChange` → forward via WS | +| File watcher | Subscribe `files.onFileChange` → forward via WS | +| Thinking indicator | Real-time status updates | +| Tool execution | Read, Write, Bash output streaming | +| File change badges | +157 -180 badge updates | +| Chat list updates | New chats appear instantly | + +## Security + +- PIN required for authentication +- PIN expires with session +- Session ends when tunnel closes or user disables +- No persistent credentials stored +- Random URL per session diff --git a/docs/plans/2026-02-01-remote-access-implementation.md b/docs/plans/2026-02-01-remote-access-implementation.md new file mode 100644 index 000000000..6c5034097 --- /dev/null +++ b/docs/plans/2026-02-01-remote-access-implementation.md @@ -0,0 +1,1342 @@ +# Remote Access Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Enable users to access their desktop 1Code app from any browser via Cloudflare Tunnel with PIN authentication. + +**Architecture:** Desktop starts a WebSocket server, runs cloudflared to create a public tunnel, generates a PIN. Browser loads the same renderer UI but uses WebSocket transport instead of IPC. All tRPC calls and desktopApi calls are bridged through WebSocket. + +**Tech Stack:** ws (WebSocket server), cloudflared binary, existing tRPC routers, existing renderer UI + +--- + +## Task 1: Create Session Manager + +**Files:** +- Create: `src/main/lib/remote-access/session.ts` + +**Step 1: Create session types and state** + +```typescript +// src/main/lib/remote-access/session.ts + +import { randomInt } from "crypto" + +export interface RemoteSession { + pin: string + url: string | null + createdAt: Date + clients: Set // client IDs +} + +let currentSession: RemoteSession | null = null + +/** + * Generate a 6-digit PIN + */ +export function generatePin(): string { + return randomInt(100000, 999999).toString() +} + +/** + * Start a new remote session + */ +export function createSession(): RemoteSession { + currentSession = { + pin: generatePin(), + url: null, + createdAt: new Date(), + clients: new Set(), + } + return currentSession +} + +/** + * Get current session + */ +export function getSession(): RemoteSession | null { + return currentSession +} + +/** + * Set tunnel URL after cloudflared starts + */ +export function setSessionUrl(url: string): void { + if (currentSession) { + currentSession.url = url + } +} + +/** + * Validate PIN + */ +export function validatePin(pin: string): boolean { + return currentSession?.pin === pin +} + +/** + * Add a connected client + */ +export function addClient(clientId: string): void { + currentSession?.clients.add(clientId) +} + +/** + * Remove a connected client + */ +export function removeClient(clientId: string): void { + currentSession?.clients.delete(clientId) +} + +/** + * Get connected client count + */ +export function getClientCount(): number { + return currentSession?.clients.size ?? 0 +} + +/** + * End current session + */ +export function endSession(): void { + currentSession = null +} + +/** + * Check if session is active + */ +export function isSessionActive(): boolean { + return currentSession !== null +} +``` + +**Step 2: Commit** + +```bash +git add src/main/lib/remote-access/session.ts +git commit -m "feat(remote-access): add session manager with PIN generation" +``` + +--- + +## Task 2: Create Cloudflared Manager + +**Files:** +- Create: `src/main/lib/remote-access/cloudflared.ts` + +**Step 1: Create cloudflared download and process manager** + +```typescript +// src/main/lib/remote-access/cloudflared.ts + +import { spawn, ChildProcess } from "child_process" +import { createWriteStream, existsSync, chmodSync } from "fs" +import { mkdir } from "fs/promises" +import { join } from "path" +import { app } from "electron" +import { pipeline } from "stream/promises" +import { createGunzip } from "zlib" +import { extract } from "tar" + +const CLOUDFLARED_VERSION = "2024.1.5" + +const DOWNLOAD_URLS: Record = { + "darwin-arm64": `https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-darwin-arm64.tgz`, + "darwin-x64": `https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-darwin-amd64.tgz`, + "win32-x64": `https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-windows-amd64.exe`, + "linux-x64": `https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64`, +} + +let cloudflaredProcess: ChildProcess | null = null + +/** + * Get the path where cloudflared binary should be stored + */ +export function getCloudflaredPath(): string { + const binDir = join(app.getPath("userData"), "bin") + const ext = process.platform === "win32" ? ".exe" : "" + return join(binDir, `cloudflared${ext}`) +} + +/** + * Check if cloudflared is already downloaded + */ +export function isCloudflaredInstalled(): boolean { + return existsSync(getCloudflaredPath()) +} + +/** + * Download cloudflared binary for current platform + */ +export async function downloadCloudflared( + onProgress?: (percent: number) => void +): Promise { + const platform = process.platform + const arch = process.arch + const key = `${platform}-${arch}` + + const url = DOWNLOAD_URLS[key] + if (!url) { + throw new Error(`Unsupported platform: ${key}`) + } + + const binDir = join(app.getPath("userData"), "bin") + await mkdir(binDir, { recursive: true }) + + const cloudflaredPath = getCloudflaredPath() + const isTgz = url.endsWith(".tgz") + + console.log(`[Cloudflared] Downloading from ${url}`) + + const response = await fetch(url) + if (!response.ok) { + throw new Error(`Failed to download: ${response.statusText}`) + } + + const contentLength = parseInt(response.headers.get("content-length") || "0") + let downloaded = 0 + + if (isTgz) { + // For .tgz files, extract directly + const tempPath = join(binDir, "cloudflared.tgz") + const fileStream = createWriteStream(tempPath) + + const reader = response.body?.getReader() + if (!reader) throw new Error("No response body") + + while (true) { + const { done, value } = await reader.read() + if (done) break + fileStream.write(value) + downloaded += value.length + if (contentLength && onProgress) { + onProgress(Math.round((downloaded / contentLength) * 100)) + } + } + fileStream.end() + + // Wait for file to be written + await new Promise((resolve) => fileStream.on("finish", resolve)) + + // Extract tar.gz + await extract({ + file: tempPath, + cwd: binDir, + }) + + // Clean up temp file + const { unlink } = await import("fs/promises") + await unlink(tempPath) + } else { + // For direct executables (Windows, Linux) + const fileStream = createWriteStream(cloudflaredPath) + const reader = response.body?.getReader() + if (!reader) throw new Error("No response body") + + while (true) { + const { done, value } = await reader.read() + if (done) break + fileStream.write(value) + downloaded += value.length + if (contentLength && onProgress) { + onProgress(Math.round((downloaded / contentLength) * 100)) + } + } + fileStream.end() + await new Promise((resolve) => fileStream.on("finish", resolve)) + } + + // Make executable on Unix + if (process.platform !== "win32") { + chmodSync(cloudflaredPath, 0o755) + } + + console.log(`[Cloudflared] Downloaded to ${cloudflaredPath}`) +} + +/** + * Start cloudflared tunnel + * Returns the public URL when ready + */ +export function startTunnel(localPort: number): Promise { + return new Promise((resolve, reject) => { + const cloudflaredPath = getCloudflaredPath() + + if (!existsSync(cloudflaredPath)) { + reject(new Error("Cloudflared not installed")) + return + } + + console.log(`[Cloudflared] Starting tunnel to localhost:${localPort}`) + + cloudflaredProcess = spawn(cloudflaredPath, [ + "tunnel", + "--url", + `http://localhost:${localPort}`, + ]) + + let urlFound = false + + cloudflaredProcess.stderr?.on("data", (data: Buffer) => { + const output = data.toString() + console.log(`[Cloudflared] ${output}`) + + // Parse URL from output + // Example: "INF +-----------------------------------------------------------+" + // followed by "INF | https://random-words.trycloudflare.com |" + const urlMatch = output.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/i) + if (urlMatch && !urlFound) { + urlFound = true + resolve(urlMatch[0]) + } + }) + + cloudflaredProcess.on("error", (err) => { + console.error("[Cloudflared] Process error:", err) + reject(err) + }) + + cloudflaredProcess.on("exit", (code) => { + console.log(`[Cloudflared] Process exited with code ${code}`) + cloudflaredProcess = null + if (!urlFound) { + reject(new Error(`Cloudflared exited with code ${code}`)) + } + }) + + // Timeout after 30 seconds + setTimeout(() => { + if (!urlFound) { + stopTunnel() + reject(new Error("Timeout waiting for tunnel URL")) + } + }, 30000) + }) +} + +/** + * Stop cloudflared tunnel + */ +export function stopTunnel(): void { + if (cloudflaredProcess) { + console.log("[Cloudflared] Stopping tunnel") + cloudflaredProcess.kill() + cloudflaredProcess = null + } +} + +/** + * Check if tunnel is running + */ +export function isTunnelRunning(): boolean { + return cloudflaredProcess !== null +} +``` + +**Step 2: Commit** + +```bash +git add src/main/lib/remote-access/cloudflared.ts +git commit -m "feat(remote-access): add cloudflared download and tunnel manager" +``` + +--- + +## Task 3: Create WebSocket Server with tRPC Bridge + +**Files:** +- Create: `src/main/lib/remote-access/ws-server.ts` + +**Step 1: Create WebSocket message types** + +```typescript +// src/main/lib/remote-access/ws-server.ts + +import { WebSocketServer, WebSocket } from "ws" +import { createServer, Server, IncomingMessage } from "http" +import { parse } from "url" +import { randomUUID } from "crypto" +import { validatePin, addClient, removeClient } from "./session" +import { createAppRouter } from "../trpc/routers" +import { BrowserWindow } from "electron" +import superjson from "superjson" + +// Message types +interface WSRequest { + id: string + type: "auth" | "trpc" | "api" | "subscribe" | "unsubscribe" + method?: string + params?: unknown + pin?: string + channel?: string +} + +interface WSResponse { + id: string | null + type: "auth_required" | "auth_success" | "auth_failed" | "result" | "error" | "subscription" + data?: unknown + error?: string + channel?: string +} + +interface AuthenticatedClient { + ws: WebSocket + id: string + subscriptions: Set +} + +let httpServer: Server | null = null +let wss: WebSocketServer | null = null +const authenticatedClients = new Map() + +// Subscription handlers - map of channel pattern to active subscriptions +const subscriptionCleanups = new Map void>() + +/** + * Send message to a WebSocket client + */ +function send(ws: WebSocket, message: WSResponse): void { + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(message)) + } +} + +/** + * Broadcast to all authenticated clients + */ +export function broadcast(channel: string, data: unknown): void { + const message: WSResponse = { + id: null, + type: "subscription", + channel, + data, + } + + for (const [ws, client] of authenticatedClients) { + if (client.subscriptions.has(channel)) { + send(ws, message) + } + } +} + +/** + * Handle incoming WebSocket message + */ +async function handleMessage( + ws: WebSocket, + client: AuthenticatedClient | undefined, + message: WSRequest, + router: ReturnType +): Promise { + // Auth message + if (message.type === "auth") { + if (message.pin && validatePin(message.pin)) { + const clientId = randomUUID() + const newClient: AuthenticatedClient = { + ws, + id: clientId, + subscriptions: new Set(), + } + authenticatedClients.set(ws, newClient) + addClient(clientId) + send(ws, { id: message.id, type: "auth_success" }) + console.log(`[WS] Client authenticated: ${clientId}`) + } else { + send(ws, { id: message.id, type: "auth_failed", error: "Invalid PIN" }) + } + return + } + + // All other messages require authentication + if (!client) { + send(ws, { id: message.id, type: "auth_required" }) + return + } + + // tRPC call + if (message.type === "trpc" && message.method) { + try { + const [routerName, procedureName] = message.method.split(".") + + // Get the router and procedure + const routerObj = (router as any)[routerName] + if (!routerObj) { + throw new Error(`Router not found: ${routerName}`) + } + + // Create a caller for the procedure + const caller = router.createCaller({ + getWindow: () => BrowserWindow.getFocusedWindow(), + }) + + // Call the procedure + const routerCaller = (caller as any)[routerName] + if (!routerCaller || !routerCaller[procedureName]) { + throw new Error(`Procedure not found: ${message.method}`) + } + + const result = await routerCaller[procedureName](message.params) + + send(ws, { + id: message.id, + type: "result", + data: superjson.serialize(result), + }) + } catch (error) { + send(ws, { + id: message.id, + type: "error", + error: error instanceof Error ? error.message : "Unknown error", + }) + } + return + } + + // Subscribe + if (message.type === "subscribe" && message.channel) { + client.subscriptions.add(message.channel) + send(ws, { id: message.id, type: "result", data: { subscribed: message.channel } }) + return + } + + // Unsubscribe + if (message.type === "unsubscribe" && message.channel) { + client.subscriptions.delete(message.channel) + send(ws, { id: message.id, type: "result", data: { unsubscribed: message.channel } }) + return + } + + // Unknown message type + send(ws, { id: message.id, type: "error", error: "Unknown message type" }) +} + +/** + * Start WebSocket server + * Returns the port number + */ +export function startWSServer(): Promise { + return new Promise((resolve, reject) => { + // Create HTTP server for WebSocket upgrade + httpServer = createServer((req, res) => { + // Serve static files for web client + // TODO: Implement static file serving in Task 6 + res.writeHead(200, { "Content-Type": "text/plain" }) + res.end("1Code Remote Access") + }) + + wss = new WebSocketServer({ server: httpServer }) + + // Create tRPC router + const router = createAppRouter(() => BrowserWindow.getFocusedWindow()) + + wss.on("connection", (ws: WebSocket, req: IncomingMessage) => { + console.log("[WS] New connection from:", req.socket.remoteAddress) + + // Send auth required message + send(ws, { id: null, type: "auth_required" }) + + ws.on("message", async (data: Buffer) => { + try { + const message = JSON.parse(data.toString()) as WSRequest + const client = authenticatedClients.get(ws) + await handleMessage(ws, client, message, router) + } catch (error) { + console.error("[WS] Message parse error:", error) + send(ws, { id: null, type: "error", error: "Invalid message format" }) + } + }) + + ws.on("close", () => { + const client = authenticatedClients.get(ws) + if (client) { + removeClient(client.id) + authenticatedClients.delete(ws) + console.log(`[WS] Client disconnected: ${client.id}`) + } + }) + + ws.on("error", (error) => { + console.error("[WS] WebSocket error:", error) + }) + }) + + // Listen on random available port + httpServer.listen(0, () => { + const address = httpServer?.address() + if (address && typeof address === "object") { + console.log(`[WS] Server listening on port ${address.port}`) + resolve(address.port) + } else { + reject(new Error("Failed to get server port")) + } + }) + + httpServer.on("error", reject) + }) +} + +/** + * Stop WebSocket server + */ +export function stopWSServer(): Promise { + return new Promise((resolve) => { + // Close all client connections + for (const [ws] of authenticatedClients) { + ws.close() + } + authenticatedClients.clear() + + // Clean up subscriptions + for (const cleanup of subscriptionCleanups.values()) { + cleanup() + } + subscriptionCleanups.clear() + + // Close servers + wss?.close(() => { + httpServer?.close(() => { + wss = null + httpServer = null + console.log("[WS] Server stopped") + resolve() + }) + }) + }) +} + +/** + * Check if server is running + */ +export function isWSServerRunning(): boolean { + return httpServer !== null +} + +/** + * Get connected client count + */ +export function getConnectedClientCount(): number { + return authenticatedClients.size +} +``` + +**Step 2: Commit** + +```bash +git add src/main/lib/remote-access/ws-server.ts +git commit -m "feat(remote-access): add WebSocket server with tRPC bridge" +``` + +--- + +## Task 4: Create Remote Access Controller + +**Files:** +- Create: `src/main/lib/remote-access/index.ts` + +**Step 1: Create main controller that orchestrates all components** + +```typescript +// src/main/lib/remote-access/index.ts + +import { + createSession, + endSession, + getSession, + setSessionUrl, + isSessionActive, + getClientCount, +} from "./session" +import { + isCloudflaredInstalled, + downloadCloudflared, + startTunnel, + stopTunnel, + isTunnelRunning, +} from "./cloudflared" +import { + startWSServer, + stopWSServer, + isWSServerRunning, + getConnectedClientCount, + broadcast, +} from "./ws-server" +import { BrowserWindow } from "electron" + +export type RemoteAccessStatus = + | { status: "disabled" } + | { status: "downloading"; progress: number } + | { status: "starting" } + | { status: "active"; url: string; pin: string; clients: number } + | { status: "error"; message: string } + +let currentStatus: RemoteAccessStatus = { status: "disabled" } +let statusListeners: ((status: RemoteAccessStatus) => void)[] = [] + +/** + * Get current status + */ +export function getRemoteAccessStatus(): RemoteAccessStatus { + if (isSessionActive() && isTunnelRunning()) { + const session = getSession() + if (session?.url) { + return { + status: "active", + url: session.url, + pin: session.pin, + clients: getConnectedClientCount(), + } + } + } + return currentStatus +} + +/** + * Set status and notify listeners + */ +function setStatus(status: RemoteAccessStatus): void { + currentStatus = status + for (const listener of statusListeners) { + listener(status) + } + // Also notify via IPC to all windows + for (const win of BrowserWindow.getAllWindows()) { + if (!win.isDestroyed()) { + win.webContents.send("remote-access:status", status) + } + } +} + +/** + * Subscribe to status changes + */ +export function onStatusChange( + listener: (status: RemoteAccessStatus) => void +): () => void { + statusListeners.push(listener) + return () => { + statusListeners = statusListeners.filter((l) => l !== listener) + } +} + +/** + * Enable remote access + */ +export async function enableRemoteAccess(): Promise { + if (isSessionActive()) { + throw new Error("Remote access already enabled") + } + + try { + // Check if cloudflared is installed + if (!isCloudflaredInstalled()) { + setStatus({ status: "downloading", progress: 0 }) + await downloadCloudflared((progress) => { + setStatus({ status: "downloading", progress }) + }) + } + + setStatus({ status: "starting" }) + + // Create session + const session = createSession() + + // Start WebSocket server + const port = await startWSServer() + + // Start cloudflared tunnel + const url = await startTunnel(port) + setSessionUrl(url) + + setStatus({ + status: "active", + url, + pin: session.pin, + clients: 0, + }) + + console.log(`[RemoteAccess] Enabled - URL: ${url}, PIN: ${session.pin}`) + } catch (error) { + await disableRemoteAccess() + const message = error instanceof Error ? error.message : "Unknown error" + setStatus({ status: "error", message }) + throw error + } +} + +/** + * Disable remote access + */ +export async function disableRemoteAccess(): Promise { + stopTunnel() + await stopWSServer() + endSession() + setStatus({ status: "disabled" }) + console.log("[RemoteAccess] Disabled") +} + +/** + * Refresh client count (called periodically or on connection change) + */ +export function refreshClientCount(): void { + if (currentStatus.status === "active") { + setStatus({ + ...currentStatus, + clients: getConnectedClientCount(), + }) + } +} + +// Re-export broadcast for use by other modules +export { broadcast } + +// Re-export for type checking +export type { RemoteAccessStatus as RemoteAccessState } +``` + +**Step 2: Commit** + +```bash +git add src/main/lib/remote-access/index.ts +git commit -m "feat(remote-access): add main controller" +``` + +--- + +## Task 5: Add tRPC Router for Remote Access + +**Files:** +- Create: `src/main/lib/trpc/routers/remote-access.ts` +- Modify: `src/main/lib/trpc/routers/index.ts` + +**Step 1: Create remote access router** + +```typescript +// src/main/lib/trpc/routers/remote-access.ts + +import { z } from "zod" +import { router, publicProcedure } from "../index" +import { + enableRemoteAccess, + disableRemoteAccess, + getRemoteAccessStatus, +} from "../../remote-access" + +export const remoteAccessRouter = router({ + /** + * Get current remote access status + */ + getStatus: publicProcedure.query(() => { + return getRemoteAccessStatus() + }), + + /** + * Enable remote access + */ + enable: publicProcedure.mutation(async () => { + await enableRemoteAccess() + return getRemoteAccessStatus() + }), + + /** + * Disable remote access + */ + disable: publicProcedure.mutation(async () => { + await disableRemoteAccess() + return getRemoteAccessStatus() + }), +}) +``` + +**Step 2: Add router to app router** + +Edit `src/main/lib/trpc/routers/index.ts`: + +```typescript +// Add import at top +import { remoteAccessRouter } from "./remote-access" + +// Add to router object in createAppRouter function +export function createAppRouter(getWindow: () => BrowserWindow | null) { + return router({ + // ... existing routers ... + remoteAccess: remoteAccessRouter, // Add this line + }) +} +``` + +**Step 3: Commit** + +```bash +git add src/main/lib/trpc/routers/remote-access.ts src/main/lib/trpc/routers/index.ts +git commit -m "feat(remote-access): add tRPC router for remote access control" +``` + +--- + +## Task 6: Add IPC Handlers for Remote Access Events + +**Files:** +- Modify: `src/main/windows/main.ts` +- Modify: `src/preload/index.ts` + +**Step 1: Add IPC handlers in main.ts** + +Add to `src/main/windows/main.ts` in the IPC handlers section: + +```typescript +// Remote access IPC handlers +ipcMain.handle("remote-access:get-status", () => { + const { getRemoteAccessStatus } = require("../lib/remote-access") + return getRemoteAccessStatus() +}) + +ipcMain.handle("remote-access:enable", async () => { + const { enableRemoteAccess, getRemoteAccessStatus } = require("../lib/remote-access") + await enableRemoteAccess() + return getRemoteAccessStatus() +}) + +ipcMain.handle("remote-access:disable", async () => { + const { disableRemoteAccess, getRemoteAccessStatus } = require("../lib/remote-access") + await disableRemoteAccess() + return getRemoteAccessStatus() +}) +``` + +**Step 2: Add to preload** + +Add to `src/preload/index.ts` in the desktopApi object: + +```typescript +// Remote access +getRemoteAccessStatus: () => ipcRenderer.invoke("remote-access:get-status"), +enableRemoteAccess: () => ipcRenderer.invoke("remote-access:enable"), +disableRemoteAccess: () => ipcRenderer.invoke("remote-access:disable"), +onRemoteAccessStatusChange: (callback: (status: any) => void) => { + const handler = (_event: unknown, status: any) => callback(status) + ipcRenderer.on("remote-access:status", handler) + return () => ipcRenderer.removeListener("remote-access:status", handler) +}, +``` + +**Step 3: Add types to DesktopApi interface** + +Add to the DesktopApi interface in `src/preload/index.ts`: + +```typescript +// Remote access +getRemoteAccessStatus: () => Promise<{ + status: "disabled" | "downloading" | "starting" | "active" | "error" + progress?: number + url?: string + pin?: string + clients?: number + message?: string +}> +enableRemoteAccess: () => Promise +disableRemoteAccess: () => Promise +onRemoteAccessStatusChange: (callback: (status: any) => void) => () => void +``` + +**Step 4: Commit** + +```bash +git add src/main/windows/main.ts src/preload/index.ts +git commit -m "feat(remote-access): add IPC handlers and preload bridge" +``` + +--- + +## Task 7: Create Remote Access UI Atom + +**Files:** +- Create: `src/renderer/lib/atoms/remote-access.ts` +- Modify: `src/renderer/lib/atoms/index.ts` + +**Step 1: Create remote access atoms** + +```typescript +// src/renderer/lib/atoms/remote-access.ts + +import { atom } from "jotai" + +export type RemoteAccessStatus = + | { status: "disabled" } + | { status: "downloading"; progress: number } + | { status: "starting" } + | { status: "active"; url: string; pin: string; clients: number } + | { status: "error"; message: string } + +export const remoteAccessStatusAtom = atom({ status: "disabled" }) + +export const remoteAccessDialogOpenAtom = atom(false) +``` + +**Step 2: Export from index** + +Add to `src/renderer/lib/atoms/index.ts`: + +```typescript +export * from "./remote-access" +``` + +**Step 3: Commit** + +```bash +git add src/renderer/lib/atoms/remote-access.ts src/renderer/lib/atoms/index.ts +git commit -m "feat(remote-access): add UI atoms for remote access state" +``` + +--- + +## Task 8: Create Remote Access Dialog Component + +**Files:** +- Create: `src/renderer/components/dialogs/remote-access-dialog.tsx` + +**Step 1: Create dialog component** + +```typescript +// src/renderer/components/dialogs/remote-access-dialog.tsx + +import { useAtom } from "jotai" +import { useEffect, useCallback } from "react" +import { Globe, Copy, Check, Loader2, X } from "lucide-react" +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog" +import { Button } from "../ui/button" +import { + remoteAccessStatusAtom, + remoteAccessDialogOpenAtom, + type RemoteAccessStatus, +} from "../../lib/atoms/remote-access" +import { toast } from "sonner" + +export function RemoteAccessDialog() { + const [open, setOpen] = useAtom(remoteAccessDialogOpenAtom) + const [status, setStatus] = useAtom(remoteAccessStatusAtom) + + // Fetch initial status and subscribe to changes + useEffect(() => { + if (!window.desktopApi) return + + // Get initial status + window.desktopApi.getRemoteAccessStatus?.().then(setStatus) + + // Subscribe to status changes + const unsubscribe = window.desktopApi.onRemoteAccessStatusChange?.(setStatus) + return unsubscribe + }, [setStatus]) + + const handleEnable = useCallback(async () => { + try { + await window.desktopApi?.enableRemoteAccess?.() + } catch (error) { + toast.error("Failed to enable remote access") + } + }, []) + + const handleDisable = useCallback(async () => { + try { + await window.desktopApi?.disableRemoteAccess?.() + } catch (error) { + toast.error("Failed to disable remote access") + } + }, []) + + const handleCopyUrl = useCallback(() => { + if (status.status === "active") { + navigator.clipboard.writeText(status.url) + toast.success("URL copied to clipboard") + } + }, [status]) + + const handleCopyPin = useCallback(() => { + if (status.status === "active") { + navigator.clipboard.writeText(status.pin) + toast.success("PIN copied to clipboard") + } + }, [status]) + + return ( + + + + + + Remote Access + + + +
+ {status.status === "disabled" && ( + <> +

+ Access your desktop from any browser. +

+ + + )} + + {status.status === "downloading" && ( +
+
+ + Downloading cloudflared... +
+
+
+
+
+ )} + + {status.status === "starting" && ( +
+ + Starting tunnel... +
+ )} + + {status.status === "active" && ( + <> +
+
+ Active +
+ +
+ +
+ + {status.url} + + +
+
+ +
+ +
+ + {status.pin} + + +
+
+ +
+ Connected clients: {status.clients} +
+ + + + )} + + {status.status === "error" && ( + <> +
+ + {status.message} +
+ + + )} +
+ +
+ ) +} +``` + +**Step 2: Commit** + +```bash +git add src/renderer/components/dialogs/remote-access-dialog.tsx +git commit -m "feat(remote-access): add remote access dialog component" +``` + +--- + +## Task 9: Add Remote Access Button to Sidebar + +**Files:** +- Modify: `src/renderer/features/sidebar/agents-sidebar.tsx` + +**Step 1: Find the sidebar footer icons section** + +Search for the icon bar area (near settings, help icons) and add remote access button: + +```typescript +// Import at top +import { Globe } from "lucide-react" +import { useSetAtom } from "jotai" +import { remoteAccessDialogOpenAtom } from "../../lib/atoms/remote-access" + +// In the component, add: +const setRemoteAccessDialogOpen = useSetAtom(remoteAccessDialogOpenAtom) + +// In the icon bar section (footer), add button: + +``` + +**Step 2: Import dialog in layout** + +Add to `src/renderer/features/layout/agents-layout.tsx`: + +```typescript +// Import at top +import { RemoteAccessDialog } from "../../components/dialogs/remote-access-dialog" + +// Add inside the return, after ClaudeLoginModal: + +``` + +**Step 3: Commit** + +```bash +git add src/renderer/features/sidebar/agents-sidebar.tsx src/renderer/features/layout/agents-layout.tsx +git commit -m "feat(remote-access): add remote access button to sidebar" +``` + +--- + +## Task 10: Add Cleanup on App Quit + +**Files:** +- Modify: `src/main/index.ts` + +**Step 1: Add cleanup in before-quit handler** + +Find the `app.on("before-quit", ...)` section and add: + +```typescript +// Import at top +import { disableRemoteAccess } from "./lib/remote-access" + +// In before-quit handler, add before other cleanup: +app.on("before-quit", async () => { + console.log("[App] Shutting down...") + isQuitting = true + ;(global as any).__isAppQuitting = () => true + + // Disable remote access first + try { + await disableRemoteAccess() + } catch (error) { + console.warn("[App] Failed to disable remote access:", error) + } + + destroyTray() + // ... rest of cleanup +}) +``` + +**Step 2: Commit** + +```bash +git add src/main/index.ts +git commit -m "feat(remote-access): add cleanup on app quit" +``` + +--- + +## Task 11: Install ws Package + +**Files:** +- Modify: `package.json` + +**Step 1: Add ws dependency** + +```bash +bun add ws +bun add -d @types/ws +``` + +**Step 2: Commit** + +```bash +git add package.json bun.lockb +git commit -m "feat(remote-access): add ws package for WebSocket server" +``` + +--- + +## Task 12: Test Remote Access Feature + +**Step 1: Run the app in dev mode** + +```bash +bun run dev +``` + +**Step 2: Test enable remote access** + +1. Click the Globe icon in sidebar footer +2. Click "Enable Remote Access" +3. Wait for cloudflared to download (first time only) +4. Wait for tunnel to start +5. Verify URL and PIN are displayed + +**Step 3: Test web client access** + +1. Open the URL in another browser +2. Verify you see a page (for now just "1Code Remote Access" text) +3. Test WebSocket connection (will implement full UI in future tasks) + +**Step 4: Test disable** + +1. Click "Disable Remote Access" +2. Verify status returns to disabled +3. Verify tunnel is stopped + +--- + +## Summary + +| Task | Description | Files | +|------|-------------|-------| +| 1 | Session Manager | `session.ts` | +| 2 | Cloudflared Manager | `cloudflared.ts` | +| 3 | WebSocket Server | `ws-server.ts` | +| 4 | Remote Access Controller | `index.ts` | +| 5 | tRPC Router | `remote-access.ts`, `routers/index.ts` | +| 6 | IPC Handlers | `main.ts`, `preload/index.ts` | +| 7 | UI Atoms | `atoms/remote-access.ts` | +| 8 | Dialog Component | `remote-access-dialog.tsx` | +| 9 | Sidebar Button | `agents-sidebar.tsx`, `agents-layout.tsx` | +| 10 | App Cleanup | `index.ts` | +| 11 | Dependencies | `package.json` | +| 12 | Testing | Manual test | + +--- + +## Future Tasks (Not in This Plan) + +1. **Web Client Transport** - Create WebSocket transport adapter for renderer +2. **Static File Serving** - Serve built renderer UI through WS server +3. **Real-time Subscriptions** - Bridge Claude streaming, terminal, git status +4. **PIN Auth UI in Browser** - Create PIN entry page for web client +5. **Session Timeout** - Auto-disable after inactivity diff --git a/drizzle/0010_cold_masked_marvel.sql b/drizzle/0010_cold_masked_marvel.sql new file mode 100644 index 000000000..43e0e4faf --- /dev/null +++ b/drizzle/0010_cold_masked_marvel.sql @@ -0,0 +1,18 @@ +CREATE TABLE `model_profile_settings` ( + `id` text PRIMARY KEY DEFAULT 'singleton' NOT NULL, + `last_used_profile_id` text, + `updated_at` integer +); +--> statement-breakpoint +CREATE TABLE `model_profiles` ( + `id` text PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `config` text NOT NULL, + `models` text DEFAULT '[]' NOT NULL, + `is_offline` integer DEFAULT false NOT NULL, + `created_at` integer, + `updated_at` integer +); +--> statement-breakpoint +ALTER TABLE `chats` ADD `model_profile_id` text;--> statement-breakpoint +ALTER TABLE `chats` ADD `selected_model_id` text; \ No newline at end of file diff --git a/drizzle/meta/0010_snapshot.json b/drizzle/meta/0010_snapshot.json new file mode 100644 index 000000000..c87c66a2f --- /dev/null +++ b/drizzle/meta/0010_snapshot.json @@ -0,0 +1,541 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "8fe29cf3-6ce4-4de5-b897-767c62effb23", + "prevId": "b2d2d602-5de1-43b1-ada8-c9ed3edde22d", + "tables": { + "anthropic_accounts": { + "name": "anthropic_accounts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "oauth_token": { + "name": "oauth_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connected_at": { + "name": "connected_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "desktop_user_id": { + "name": "desktop_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "anthropic_settings": { + "name": "anthropic_settings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false, + "default": "'singleton'" + }, + "active_account_id": { + "name": "active_account_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "chats": { + "name": "chats", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "archived_at": { + "name": "archived_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "worktree_path": { + "name": "worktree_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "base_branch": { + "name": "base_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "model_profile_id": { + "name": "model_profile_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "selected_model_id": { + "name": "selected_model_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "chats_worktree_path_idx": { + "name": "chats_worktree_path_idx", + "columns": [ + "worktree_path" + ], + "isUnique": false + } + }, + "foreignKeys": { + "chats_project_id_projects_id_fk": { + "name": "chats_project_id_projects_id_fk", + "tableFrom": "chats", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "claude_code_credentials": { + "name": "claude_code_credentials", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false, + "default": "'default'" + }, + "oauth_token": { + "name": "oauth_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connected_at": { + "name": "connected_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "model_profile_settings": { + "name": "model_profile_settings", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false, + "default": "'singleton'" + }, + "last_used_profile_id": { + "name": "last_used_profile_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "model_profiles": { + "name": "model_profiles", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "models": { + "name": "models", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "is_offline": { + "name": "is_offline", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "git_remote_url": { + "name": "git_remote_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "git_provider": { + "name": "git_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "git_owner": { + "name": "git_owner", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "git_repo": { + "name": "git_repo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_path": { + "name": "icon_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "projects_path_unique": { + "name": "projects_path_unique", + "columns": [ + "path" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sub_chats": { + "name": "sub_chats", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "chat_id": { + "name": "chat_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'agent'" + }, + "messages": { + "name": "messages", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "sub_chats_chat_id_chats_id_fk": { + "name": "sub_chats_chat_id_chats_id_fk", + "tableFrom": "sub_chats", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 75aa3f60b..861180bf1 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -71,6 +71,13 @@ "when": 1769999999999, "tag": "0009_add_selected_model_id", "breakpoints": true + }, + { + "idx": 10, + "version": "6", + "when": 1769994872133, + "tag": "0010_cold_masked_marvel", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/package.json b/package.json index 1fc8d526b..4c561c561 100644 --- a/package.json +++ b/package.json @@ -109,6 +109,7 @@ "tailwindcss-animate": "^1.0.7", "trpc-electron": "^0.1.2", "unique-names-generator": "^4.7.1", + "ws": "^8.19.0", "xterm": "^5.3.0", "zod": "^3.24.1", "zustand": "^5.0.3" @@ -116,18 +117,19 @@ "devDependencies": { "@electron-toolkit/preload": "^3.0.1", "@electron-toolkit/utils": "^4.0.0", + "@electron/rebuild": "^4.0.3", "@types/better-sqlite3": "^7.6.13", "@types/diff": "^8.0.0", "@types/node": "^20.17.50", "@types/react": "^19.0.7", "@types/react-dom": "^19.0.3", + "@types/ws": "^8.18.1", "@vitejs/plugin-react": "^4.3.4", "@welldone-software/why-did-you-render": "^10.0.1", "autoprefixer": "^10.4.20", "drizzle-kit": "^0.31.8", "electron": "~39.4.0", "electron-builder": "^25.1.8", - "@electron/rebuild": "^4.0.3", "electron-vite": "^3.0.0", "postcss": "^8.5.1", "tailwindcss": "^3.4.17", diff --git a/src/main/index.ts b/src/main/index.ts index a7f67d21b..9729cfe79 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -853,12 +853,13 @@ if (gotTheLock) { console.error("[App] Failed to initialize database:", error) } - // Initialize system tray - initTray() - - // Create main window + // Create main window FIRST (before tray initialization) + // This prevents tray from creating a duplicate window createMainWindow() + // Initialize system tray (after main window exists) + initTray() + // Initialize auto-updater (production only) if (app.isPackaged) { await initAutoUpdater(getAllWindows) @@ -916,6 +917,15 @@ if (gotTheLock) { console.log("[App] Shutting down...") isQuitting = true ;(global as any).__isAppQuitting = () => true + + // Disable remote access first + try { + const { disableRemoteAccess } = await import("./lib/remote-access") + await disableRemoteAccess() + } catch (error) { + console.warn("[App] Failed to disable remote access:", error) + } + destroyTray() cancelAllPendingOAuth() await cleanupGitWatchers() diff --git a/src/main/lib/db/schema/index.ts b/src/main/lib/db/schema/index.ts index 2f5410766..cae063987 100644 --- a/src/main/lib/db/schema/index.ts +++ b/src/main/lib/db/schema/index.ts @@ -131,6 +131,35 @@ export const anthropicSettings = sqliteTable("anthropic_settings", { ), }) +// ============ MODEL PROFILES ============ +// Stores model profiles for Claude API configuration +// Allows syncing profiles between desktop and web app +export const modelProfiles = sqliteTable("model_profiles", { + id: text("id").primaryKey().$defaultFn(() => createId()), + name: text("name").notNull(), + // JSON string of CustomClaudeConfig (model, token, baseUrl, etc.) + config: text("config").notNull(), + // JSON string of ModelMapping[] (available models in this profile) + models: text("models").notNull().default("[]"), + // Whether this is an offline/Ollama profile + isOffline: integer("is_offline", { mode: "boolean" }).notNull().default(false), + createdAt: integer("created_at", { mode: "timestamp" }).$defaultFn( + () => new Date(), + ), + updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn( + () => new Date(), + ), +}) + +// Settings for model profiles (stores last used profile ID, etc.) +export const modelProfileSettings = sqliteTable("model_profile_settings", { + id: text("id").primaryKey().default("singleton"), // Single row + lastUsedProfileId: text("last_used_profile_id"), // References modelProfiles.id + updatedAt: integer("updated_at", { mode: "timestamp" }).$defaultFn( + () => new Date(), + ), +}) + // ============ TYPE EXPORTS ============ export type Project = typeof projects.$inferSelect export type NewProject = typeof projects.$inferInsert @@ -143,3 +172,7 @@ export type NewClaudeCodeCredential = typeof claudeCodeCredentials.$inferInsert export type AnthropicAccount = typeof anthropicAccounts.$inferSelect export type NewAnthropicAccount = typeof anthropicAccounts.$inferInsert export type AnthropicSettings = typeof anthropicSettings.$inferSelect +export type ModelProfile = typeof modelProfiles.$inferSelect +export type NewModelProfile = typeof modelProfiles.$inferInsert +export type ModelProfileSettings = typeof modelProfileSettings.$inferSelect +export type NewModelProfileSettings = typeof modelProfileSettings.$inferInsert diff --git a/src/main/lib/tray.ts b/src/main/lib/tray.ts index 874c9d1b8..ce5dd2a4a 100644 --- a/src/main/lib/tray.ts +++ b/src/main/lib/tray.ts @@ -153,7 +153,12 @@ export async function buildTrayMenu(): Promise { let windows = getAllWindows() let window = windows.find(w => !w.isDestroyed()) - // If no window, create a hidden one to read localStorage + // First try to get the main window by ID + if (!window) { + window = getWindow() + } + + // If still no window, create one temporarily let createdHiddenWindow = false if (!window) { console.log("[Tray] No window available, creating hidden window to read profiles") diff --git a/src/main/lib/trpc/routers/index.ts b/src/main/lib/trpc/routers/index.ts index 833026a98..de79abcfa 100644 --- a/src/main/lib/trpc/routers/index.ts +++ b/src/main/lib/trpc/routers/index.ts @@ -18,6 +18,8 @@ import { commandsRouter } from "./commands" import { voiceRouter } from "./voice" import { pluginsRouter } from "./plugins" import { createGitRouter } from "../../git" +import { remoteAccessRouter } from "./remote-access" +import { modelProfilesRouter } from "./model-profiles" import { BrowserWindow } from "electron" /** @@ -46,6 +48,8 @@ export function createAppRouter(getWindow: () => BrowserWindow | null) { plugins: pluginsRouter, // Git operations - named "changes" to match Superset API changes: createGitRouter(), + remoteAccess: remoteAccessRouter, + modelProfiles: modelProfilesRouter, }) } diff --git a/src/renderer/features/agents/ui/profile-selector.tsx b/src/renderer/features/agents/ui/profile-selector.tsx index 0d49f5b3c..be6784b2f 100644 --- a/src/renderer/features/agents/ui/profile-selector.tsx +++ b/src/renderer/features/agents/ui/profile-selector.tsx @@ -19,6 +19,7 @@ import { } from "../../../lib/atoms" import { trpc } from "../../../lib/trpc" import { cn } from "../../../lib/utils" +import { isRemoteMode } from "../../../lib/remote-transport" interface ProfileSelectorProps { chatId: string @@ -70,14 +71,21 @@ export function ProfileSelector({ ) const handleOpenSettings = useCallback(() => { + // In web mode, settings dialog is not available + if (isRemoteMode()) { + console.log("[ProfileSelector] Settings not available in web mode") + return + } setSettingsTab("models") setSettingsOpen(true) }, [setSettingsTab, setSettingsOpen]) - // Don't show if no custom profiles configured - if (displayProfiles.length === 0) { - return null - } + // For web app or when no custom profiles: show "Default" option + // This ensures the selector is always visible in remote/web mode + const showDefaultOption = displayProfiles.length === 0 || isRemoteMode() + + // In web mode, hide the "Manage Profiles" option since settings dialog is desktop-only + const showManageOption = !isRemoteMode() return ( @@ -97,6 +105,20 @@ export function ProfileSelector({ + {/* Show "Default" option for web app or when no custom profiles */} + {showDefaultOption && ( + { + // Clear the profile to use default + updateProfileMutation.mutate({ id: chatId, modelProfileId: null }) + setLastUsedProfileId("") + }} + className="gap-2 justify-between" + > + Default + {!effectiveProfileId && } + + )} {displayProfiles.map((profile) => { const isSelected = profile.id === effectiveProfileId return ( @@ -110,11 +132,13 @@ export function ProfileSelector({ ) })} - - - - Manage Profiles... - + {showDefaultOption && displayProfiles.length > 0 && } + {showManageOption && ( + + + Manage Profiles... + + )} ) diff --git a/src/renderer/features/layout/agents-layout.tsx b/src/renderer/features/layout/agents-layout.tsx index 7d651cf16..cdd21fa33 100644 --- a/src/renderer/features/layout/agents-layout.tsx +++ b/src/renderer/features/layout/agents-layout.tsx @@ -18,6 +18,7 @@ import { trpc } from "../../lib/trpc" import { useAgentsHotkeys } from "../agents/lib/agents-hotkeys-manager" import { toggleSearchAtom } from "../agents/search" import { ClaudeLoginModal } from "../../components/dialogs/claude-login-modal" +import { RemoteAccessDialog } from "../../components/dialogs/remote-access-dialog" import { TooltipProvider } from "../../components/ui/tooltip" import { ResizableSidebar } from "../../components/ui/resizable-sidebar" import { AgentsSidebar } from "../sidebar/agents-sidebar" @@ -241,6 +242,7 @@ export function AgentsLayout() { {/* Global queue processor - handles message queues for all sub-chats */} +
{/* Windows Title Bar (only shown on Windows with frameless window) */} diff --git a/src/renderer/features/onboarding/api-key-onboarding-page.tsx b/src/renderer/features/onboarding/api-key-onboarding-page.tsx index 74d694068..e3315b840 100644 --- a/src/renderer/features/onboarding/api-key-onboarding-page.tsx +++ b/src/renderer/features/onboarding/api-key-onboarding-page.tsx @@ -110,7 +110,7 @@ export function ApiKeyOnboardingPage() { } // Submit for custom model mode (all fields) - const submitCustomModel = () => { + const submitCustomModel = async () => { const trimmedModel = model.trim() const trimmedToken = token.trim() const trimmedBaseUrl = baseUrl.trim() @@ -129,24 +129,57 @@ export function ApiKeyOnboardingPage() { defaultHaikuModel: defaultHaikuModel.trim() || undefined, subagentModel: subagentModel.trim() || undefined, } - + + // Build ModelMapping array from config (unique, non-empty model IDs) + const modelMappings: Array<{ id: string; displayName: string; modelId: string; supportsThinking: boolean }> = [] + + const addModel = (id: string, displayName: string, modelId: string) => { + if (modelId) { + modelMappings.push({ id, displayName, modelId, supportsThinking: false }) + } + } + + // Add default model + addModel("default", "Default", trimmedModel) + + // Add optional models + if (defaultOpusModel.trim()) addModel("opus", "Opus", defaultOpusModel.trim()) + if (defaultSonnetModel.trim()) addModel("sonnet", "Sonnet", defaultSonnetModel.trim()) + if (defaultHaikuModel.trim()) addModel("haiku", "Haiku", defaultHaikuModel.trim()) + if (subagentModel.trim()) addModel("subagent", "Subagent", subagentModel.trim()) + // Save to legacy config atom setStoredConfig(config) - - // Also create a profile in modelProfilesAtom so it shows in settings + + // Create profile data for database and localStorage + const profileData = { + name: "Default", + config, + models: modelMappings, + isOffline: false, + } + + // Save to database (works in both desktop and remote mode) + try { + await trpc.modelProfiles.create.mutate(profileData) + console.log("[ApiKeyOnboarding] Created Default profile in database") + } catch (error) { + console.error("[ApiKeyOnboarding] Failed to save profile to database:", error) + } + + // Also create a profile in modelProfilesAtom so it shows in settings immediately const newProfileId = crypto.randomUUID() const newProfile = { id: newProfileId, - name: "Default", // Saved as Default as requested - config, + ...profileData, } - + // Add new profile (keep existing profiles like offline profile) setProfiles([...profiles, newProfile]) - + // Set as active profile setActiveProfileId(newProfileId) - + setApiKeyOnboardingCompleted(true) setIsSubmitting(false) diff --git a/src/renderer/lib/atoms/index.ts b/src/renderer/lib/atoms/index.ts index c78f80b84..1289bdb14 100644 --- a/src/renderer/lib/atoms/index.ts +++ b/src/renderer/lib/atoms/index.ts @@ -313,7 +313,7 @@ export const localStorageModelProfilesAtom = atomWithStorage( // This atom automatically handles: // - Desktop mode: Uses localStorage, syncs to database // - Web mode: Loads from database via tRPC -export { modelProfilesAtom, useSyncModelProfiles } from "./model-profiles-sync" +export { modelProfilesAtom, useSyncModelProfiles, activeConfigAtom } from "./model-profiles-sync" // Migration: add models array to existing profiles that don't have it if (typeof window !== "undefined") { @@ -410,65 +410,6 @@ export const showOfflineModeFeaturesAtom = atomWithStorage( // Network status (updated from main process) export const networkOnlineAtom = atom(true) -export function normalizeCustomClaudeConfig( - config: CustomClaudeConfig, -): CustomClaudeConfig | undefined { - const model = config.model.trim() - const token = config.token.trim() - const baseUrl = config.baseUrl.trim() - - if (!model || !token || !baseUrl) return undefined - - return { model, token, baseUrl } -} - -// Get active config (considering network status and auto-fallback) -export const activeConfigAtom = atom((get) => { - const activeProfileId = get(activeProfileIdAtom) - const profiles = get(modelProfilesAtom) - const legacyConfig = get(customClaudeConfigAtom) - const networkOnline = get(networkOnlineAtom) - const autoOffline = get(autoOfflineModeAtom) - - console.log('[activeConfigAtom] Debug:', { - activeProfileId, - profilesCount: profiles.length, - legacyConfigModel: legacyConfig.model, - legacyConfigBaseUrl: legacyConfig.baseUrl, - }) - - // If auto-offline enabled and no internet, use offline profile - if (!networkOnline && autoOffline) { - const offlineProfile = profiles.find(p => p.isOffline) - if (offlineProfile) { - console.log('[activeConfigAtom] Using offline profile') - return offlineProfile.config - } - } - - // If specific profile is selected, use it - if (activeProfileId) { - const profile = profiles.find(p => p.id === activeProfileId) - if (profile) { - console.log('[activeConfigAtom] Using profile:', profile.name, 'with model:', profile.config.model) - return profile.config - } else { - console.log('[activeConfigAtom] Profile ID set but not found in profiles') - } - } - - // Fallback to legacy config if set - const normalized = normalizeCustomClaudeConfig(legacyConfig) - if (normalized) { - console.log('[activeConfigAtom] Using normalized legacy config') - return normalized - } - - console.log('[activeConfigAtom] No custom config found, returning undefined') - // No custom config - return undefined -}) - // Preferences - Extended Thinking // When enabled, Claude will use extended thinking for deeper reasoning (128K tokens) export const extendedThinkingEnabledAtom = atomWithStorage( diff --git a/src/renderer/lib/atoms/model-profiles-sync.ts b/src/renderer/lib/atoms/model-profiles-sync.ts index 251e903a3..16af6026e 100644 --- a/src/renderer/lib/atoms/model-profiles-sync.ts +++ b/src/renderer/lib/atoms/model-profiles-sync.ts @@ -3,10 +3,27 @@ import { useEffect, useState } from "react" import { trpc } from "../../lib/trpc" import { localStorageModelProfilesAtom, + activeProfileIdAtom, + customClaudeConfigAtom, + networkOnlineAtom, + autoOfflineModeAtom, } from "./index" -import { OFFLINE_PROFILE, type ModelProfile } from "./index" +import { OFFLINE_PROFILE, type ModelProfile, type CustomClaudeConfig } from "./index" import { isRemoteMode } from "../remote-transport" +// Helper to normalize custom config (moved from index.ts to break circular dependency) +function normalizeCustomClaudeConfig( + config: CustomClaudeConfig, +): CustomClaudeConfig | undefined { + const model = config.model.trim() + const token = config.token.trim() + const baseUrl = config.baseUrl.trim() + + if (!model || !token || !baseUrl) return undefined + + return { model, token, baseUrl } +} + /** * Database-synced model profiles atom * - Desktop mode: Uses localStorage (fast, offline-capable) and syncs to database @@ -151,3 +168,56 @@ export const modelProfilesAtom = atom( set(localStorageModelProfilesAtom, newValue) } ) + +/** + * Get active config (considering network status and auto-fallback) + * - If auto-offline enabled and no internet, use offline profile + * - If specific profile is selected, use it + * - Fallback to legacy config if set + * - Returns undefined if no custom config found + */ +export const activeConfigAtom = atom((get) => { + const activeProfileId = get(activeProfileIdAtom) + const profiles = get(modelProfilesAtom) + const legacyConfig = get(customClaudeConfigAtom) + const networkOnline = get(networkOnlineAtom) + const autoOffline = get(autoOfflineModeAtom) + + console.log('[activeConfigAtom] Debug:', { + activeProfileId, + profilesCount: profiles.length, + legacyConfigModel: legacyConfig.model, + legacyConfigBaseUrl: legacyConfig.baseUrl, + }) + + // If auto-offline enabled and no internet, use offline profile + if (!networkOnline && autoOffline) { + const offlineProfile = profiles.find(p => p.isOffline) + if (offlineProfile) { + console.log('[activeConfigAtom] Using offline profile') + return offlineProfile.config + } + } + + // If specific profile is selected, use it + if (activeProfileId) { + const profile = profiles.find(p => p.id === activeProfileId) + if (profile) { + console.log('[activeConfigAtom] Using profile:', profile.name, 'with model:', profile.config.model) + return profile.config + } else { + console.log('[activeConfigAtom] Profile ID set but not found in profiles') + } + } + + // Fallback to legacy config if set + const normalized = normalizeCustomClaudeConfig(legacyConfig) + if (normalized) { + console.log('[activeConfigAtom] Using normalized legacy config') + return normalized + } + + console.log('[activeConfigAtom] No custom config found, returning undefined') + // No custom config + return undefined +}) From 079aee7a16af09e27addfc2967f1f788fae7aa0d Mon Sep 17 00:00:00 2001 From: Yogi Date: Mon, 2 Feb 2026 14:35:19 +0700 Subject: [PATCH 13/16] fix: resolve circular dependency and IPC cloning error - Move activeConfigAtom and normalizeCustomClaudeConfig to model-profiles-sync.ts to break circular dependency (modelProfilesAtom is not defined error) - Fix IPC cloning error by not returning functions through Electron IPC - Store chat subscriptions in a Map in main process - Add chat:unsubscribe IPC handler for cleanup - Update preload to call chat:unsubscribe instead of storing function Co-Authored-By: Claude --- src/main/windows/main.ts | 26 +++++++++++++++++-- src/preload/index.ts | 15 +++++------ src/renderer/lib/atoms/index.ts | 2 +- src/renderer/lib/atoms/model-profiles-sync.ts | 2 +- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/main/windows/main.ts b/src/main/windows/main.ts index d7f50b323..9248eaba6 100644 --- a/src/main/windows/main.ts +++ b/src/main/windows/main.ts @@ -474,14 +474,36 @@ function registerIpcHandlers(): void { // Chat data sync - subscribe to broadcasts from WebSocket server // This allows desktop clients to receive messages from web clients + const chatSubscriptions = new Map void>() + ipcMain.handle("chat:subscribe", (event, subChatId: string) => { + // Clean up existing subscription if any + const existing = chatSubscriptions.get(subChatId) + if (existing) { + existing() + chatSubscriptions.delete(subChatId) + } + const unsubscribe = subscribeToChatData(subChatId, (data: any) => { // Send chat data to this renderer process event.sender.send("chat:data", subChatId, data) }) - // Return unsubscribe function for cleanup - return unsubscribe + // Store subscription for later cleanup + chatSubscriptions.set(subChatId, unsubscribe) + + // Return success (not the function - functions can't be cloned over IPC) + return { success: true, subChatId } + }) + + ipcMain.handle("chat:unsubscribe", (_event, subChatId: string) => { + const unsubscribe = chatSubscriptions.get(subChatId) + if (unsubscribe) { + unsubscribe() + chatSubscriptions.delete(subChatId) + return { success: true } + } + return { success: false, error: "No subscription found" } }) } diff --git a/src/preload/index.ts b/src/preload/index.ts index 6364076b4..a3653bf6c 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -242,20 +242,17 @@ contextBridge.exposeInMainWorld("desktopApi", { ipcRenderer.on("chat:data", dataHandler) // Register subscription with main process - ipcRenderer.invoke("chat:subscribe", subChatId).then((unsubscribe) => { - // Store unsubscribe function for cleanup - ;(ipcRenderer as any).__chatUnsubscribeMap = (ipcRenderer as any).__chatUnsubscribeMap || new Map() - ;(ipcRenderer as any).__chatUnsubscribeMap.set(subChatId, unsubscribe) + ipcRenderer.invoke("chat:subscribe", subChatId).catch((err) => { + console.error("[preload] Failed to subscribe to chat:", err) }) // Return cleanup function return () => { ipcRenderer.removeListener("chat:data", dataHandler) - const unsubscribe = (ipcRenderer as any).__chatUnsubscribeMap?.get(subChatId) - if (unsubscribe) { - unsubscribe() - ;(ipcRenderer as any).__chatUnsubscribeMap.delete(subChatId) - } + // Call unsubscribe IPC handler (functions can't be passed over IPC) + ipcRenderer.invoke("chat:unsubscribe", subChatId).catch((err) => { + console.error("[preload] Failed to unsubscribe from chat:", err) + }) } }, }) diff --git a/src/renderer/lib/atoms/index.ts b/src/renderer/lib/atoms/index.ts index 1289bdb14..887f81fae 100644 --- a/src/renderer/lib/atoms/index.ts +++ b/src/renderer/lib/atoms/index.ts @@ -313,7 +313,7 @@ export const localStorageModelProfilesAtom = atomWithStorage( // This atom automatically handles: // - Desktop mode: Uses localStorage, syncs to database // - Web mode: Loads from database via tRPC -export { modelProfilesAtom, useSyncModelProfiles, activeConfigAtom } from "./model-profiles-sync" +export { modelProfilesAtom, useSyncModelProfiles, activeConfigAtom, normalizeCustomClaudeConfig } from "./model-profiles-sync" // Migration: add models array to existing profiles that don't have it if (typeof window !== "undefined") { diff --git a/src/renderer/lib/atoms/model-profiles-sync.ts b/src/renderer/lib/atoms/model-profiles-sync.ts index 16af6026e..c4ab1dc67 100644 --- a/src/renderer/lib/atoms/model-profiles-sync.ts +++ b/src/renderer/lib/atoms/model-profiles-sync.ts @@ -12,7 +12,7 @@ import { OFFLINE_PROFILE, type ModelProfile, type CustomClaudeConfig } from "./i import { isRemoteMode } from "../remote-transport" // Helper to normalize custom config (moved from index.ts to break circular dependency) -function normalizeCustomClaudeConfig( +export function normalizeCustomClaudeConfig( config: CustomClaudeConfig, ): CustomClaudeConfig | undefined { const model = config.model.trim() From 831df5605bf14adeedbf38d6f5a2cd28ce644d12 Mon Sep 17 00:00:00 2001 From: Yogi Date: Mon, 2 Feb 2026 17:51:43 +0700 Subject: [PATCH 14/16] fix: web chat subscription and sync issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix sharedSub.clients type (Set → Map) for subscription ID tracking - Fix ws-link to handle subscription confirmation without deleting handler - Fix tRPC query syntax (refetchInterval in options, not input) - Add WebSocket chat relay implementation plan This fixes web app not receiving chat responses and ensures desktop and web stay in sync with shared subscription pattern. Co-Authored-By: Claude --- docs/plans/2025-02-02-websocket-chat-relay.md | 658 ++++++++++++++++++ src/main/lib/remote-access/ws-server.ts | 32 +- .../features/agents/ui/agents-content.tsx | 1 + .../features/sidebar/agents-sidebar.tsx | 12 +- src/renderer/lib/remote-transport/ws-link.ts | 13 +- 5 files changed, 696 insertions(+), 20 deletions(-) create mode 100644 docs/plans/2025-02-02-websocket-chat-relay.md diff --git a/docs/plans/2025-02-02-websocket-chat-relay.md b/docs/plans/2025-02-02-websocket-chat-relay.md new file mode 100644 index 000000000..d2258c3f7 --- /dev/null +++ b/docs/plans/2025-02-02-websocket-chat-relay.md @@ -0,0 +1,658 @@ +# WebSocket Chat Relay Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Simplify web-desktop communication so web client only sends/receives via WebSocket, with desktop as relay/proxy to tRPC. + +**Architecture:** Web client sends messages through WebSocket → Desktop app forwards to tRPC → Response broadcast to subscribed WebSocket clients. Single source of truth via shared subscription Observable. + +**Tech Stack:** WebSocket (ws), tRPC, Electron IPC, Observable pattern + +--- + +## Current Flow (Complex) + +``` +Web Client + ├─→ Direct tRPC queries (projects.list, chats.list) + ├─→ Direct tRPC mutations (sendMessage) + └─→ tRPC subscription (claude.chat) +``` + +## Target Flow (Simplified) + +``` +Web Client + ├─→ WebSocket.send(chatId, message) → Desktop → tRPC + └─→ WebSocket.subscribe(chatId) ← Desktop ← tRPC +``` + +**Benefits:** +- Single channel for all communication +- Desktop controls everything (no direct tRPC access from web) +- Simpler client code +- Better security (web can't bypass desktop) + +--- + +## Task 1: Simplify WebSocket Message Protocol + +**Files:** +- Modify: `src/main/lib/remote-access/ws-server.ts` + +**Step 1: Define new simplified message types** + +Add to existing `WSRequest` interface: + +```typescript +interface WSRequest { + id: string + type: "auth" | "send" | "subscribe" | "unsubscribe" + chatId?: string // For subscribe/unsubscribe/send + message?: any // For send (chat message content) + pin?: string // For auth +} + +interface WSResponse { + id: string | null + type: "auth_required" | "auth_success" | "auth_failed" | "subscribed" | "error" | "data" + chatId?: string // Which chat this data is for + data?: unknown + error?: string +} +``` + +**Step 2: Remove "trpc" type handling** + +Delete the large `if (message.type === "trpc" && message.method)` block in `handleMessage()`. This will be replaced with simpler "send" handling. + +**Step 3: Commit** + +```bash +git add src/main/lib/remote-access/ws-server.ts +git commit -m "refactor: remove direct tRPC access from WebSocket" +``` + +--- + +## Task 2: Implement Chat Message Send Handler + +**Files:** +- Modify: `src/main/lib/remote-access/ws-server.ts` + +**Step 1: Add send message handler** + +In `handleMessage()`, add after auth check: + +```typescript +// Send chat message +if (message.type === "send" && message.chatId && message.message) { + try { + const { chatId, message: msgContent } = message + + console.log(`[WS] Received message for chat ${chatId}:`, msgContent) + + // Forward to tRPC via desktop's tRPC router + const router = createAppRouter(() => BrowserWindow.getFocusedWindow()) + const caller = router.createCaller({ + getWindow: () => BrowserWindow.getFocusedWindow(), + }) + + // Call claude.sendMessage (this will trigger the subscription to broadcast) + const result = await caller.claude.sendMessage({ + subChatId: chatId, + message: msgContent, + }) + + // Send success response + send(ws, { + id: message.id, + type: "data", + chatId, + data: { sent: true }, + }) + + // Note: Actual response will come through subscription broadcast + return + } catch (error) { + send(ws, { + id: message.id, + type: "error", + error: error instanceof Error ? error.message : "Failed to send message", + }) + return + } +} +``` + +**Step 2: Commit** + +```bash +git add src/main/lib/remote-access/ws-server.ts +git commit -m "feat: add WebSocket send message handler" +``` + +--- + +## Task 3: Update Subscribe Handler to Use New Protocol + +**Files:** +- Modify: `src/main/lib/remote-access/ws-server.ts` + +**Step 1: Simplify subscribe handler** + +Replace the complex `if (routerName === "claude" && procedureName === "chat")` block with: + +```typescript +// Subscribe to chat +if (message.type === "subscribe" && message.chatId) { + const subChatId = message.chatId + + console.log(`[WS] Starting subscription for subChatId: ${subChatId}`) + + // Check if there's already a shared subscription + let sharedSub = sharedChatSubscriptions.get(subChatId) + + if (!sharedSub) { + // Create new shared subscription + console.log(`[WS] Creating new shared subscription for ${subChatId}`) + + const router = createAppRouter(() => BrowserWindow.getFocusedWindow()) + const claudeRouter = (router as any).claude + const chatProcedure = claudeRouter.chat + const subscriptionFn = chatProcedure._def.resolver + + const ctx = { + getWindow: () => BrowserWindow.getFocusedWindow(), + } + + const observable = subscriptionFn({ + input: { subChatId }, + type: 'subscription', + ctx, + path: 'claude.chat', + }) + + if (typeof observable?.subscribe !== 'function') { + throw new Error(`chat subscription did not return Observable`) + } + + // Subscribe and broadcast + const subscription = observable.subscribe({ + next: (data: any) => { + if (sharedSub) { + for (const [clientWs, subId] of sharedSub.clients) { + send(clientWs, { + id: subId, + type: "data", + chatId: subChatId, + data: data, + }) + } + // Also notify IPC listeners (desktop) + for (const listener of sharedSub.listeners) { + try { + listener(data) + } catch (err) { + console.error(`[WS] Error in IPC listener:`, err) + } + } + } + }, + error: (err: any) => { + console.error(`[WS] Chat subscription error:`, err) + if (sharedSub) { + for (const [clientWs, subId] of sharedSub.clients) { + send(clientWs, { + id: subId, + type: "error", + chatId: subChatId, + error: err.message, + }) + } + } + }, + complete: () => { + console.log(`[WS] Chat subscription complete for ${subChatId}`) + if (sharedSub) { + for (const [clientWs, subId] of sharedSub.clients) { + send(clientWs, { + id: subId, + type: "data", + chatId: subChatId, + data: { completed: true }, + }) + } + } + } + }) + + sharedSub = { + observable, + subscription, + clients: new Map(), + listeners: sharedChatSubscriptions.get(subChatId)?.listeners || new Set(), + } + sharedChatSubscriptions.set(subChatId, sharedSub) + } + + // Add client to shared subscription + sharedSub.clients.set(ws, message.id) + + send(ws, { + id: message.id, + type: "subscribed", + chatId: subChatId, + }) + + // Clean up on disconnect + ws.on("close", () => { + if (sharedSub) { + sharedSub.clients.delete(ws) + if (sharedSub.clients.size === 0 && sharedSub.listeners.size === 0) { + sharedSub.subscription.unsubscribe() + sharedChatSubscriptions.delete(subChatId) + } + } + }) + + return +} +``` + +**Step 2: Commit** + +```bash +git add src/main/lib/remote-access/ws-server.ts +git commit -m "refactor: simplify subscribe handler" +``` + +--- + +## Task 4: Add Unsubscribe Handler + +**Files:** +- Modify: `src/main/lib/remote-access/ws-server.ts` + +**Step 1: Add unsubscribe handler** + +```typescript +// Unsubscribe from chat +if (message.type === "unsubscribe" && message.chatId) { + const subChatId = message.chatId + const sharedSub = sharedChatSubscriptions.get(subChatId) + + if (sharedSub) { + sharedSub.clients.delete(ws) + + // Clean up if no clients left + if (sharedSub.clients.size === 0 && sharedSub.listeners.size === 0) { + sharedSub.subscription.unsubscribe() + sharedChatSubscriptions.delete(subChatId) + } + + send(ws, { + id: message.id, + type: "data", + chatId: subChatId, + data: { unsubscribed: true }, + }) + } + + return +} +``` + +**Step 2: Commit** + +```bash +git add src/main/lib/remote-access/ws-server.ts +git commit -m "feat: add unsubscribe handler" +``` + +--- + +## Task 5: Update Client-Side ws-link + +**Files:** +- Modify: `src/renderer/lib/remote-transport/ws-link.ts` + +**Step 1: Simplify message types** + +```typescript +type WSMessageType = "auth" | "send" | "subscribe" | "unsubscribe" + +interface WSRequest { + id: string + type: WSMessageType + chatId?: string + message?: any + pin?: string +} + +interface WSResponse { + id: string | null + type: "auth_required" | "auth_success" | "auth_failed" | "subscribed" | "error" | "data" + chatId?: string + data?: unknown + error?: string +} +``` + +**Step 2: Add send message function** + +```typescript +/** + * Send a chat message via WebSocket + */ +export async function sendChatMessage(chatId: string, message: any): Promise { + await connect() + + return new Promise((resolve, reject) => { + const id = `send-${++requestId}-${Date.now()}` + const request: WSRequest = { id, type: "send", chatId, message } + + const timeout = setTimeout(() => { + pendingRequests.delete(id) + reject(new Error("Send timeout")) + }, 30000) + + pendingRequests.set(id, { + resolve: (data) => { + clearTimeout(timeout) + resolve(data) + }, + reject: (error) => { + clearTimeout(timeout) + reject(error) + }, + }) + + ws!.send(JSON.stringify(request)) + }) +} +``` + +**Step 3: Simplify subscription handler** + +Replace complex tRPC subscription with: + +```typescript +/** + * Subscribe to chat updates via WebSocket + */ +export function subscribeToChat(chatId: string, onData: (data: any) => void): () => void { + const id = `sub-${++requestId}-${Date.now()}` + + const handler: SubscriptionHandler = { + onData: (data) => { + console.log("[ws-link] Chat data received:", chatId, data) + onData(data) + }, + onError: (error) => { + console.error("[ws-link] Chat error:", chatId, error) + }, + onComplete: () => { + console.log("[ws-link] Chat complete:", chatId) + }, + } + + subscriptionHandlers.set(id, handler) + + // Wait for connection, then send subscribe request + connect().then(() => { + const request: WSRequest = { id, type: "subscribe", chatId } + ws!.send(JSON.stringify(request)) + console.log("[ws-link] Subscribe request sent:", id, chatId) + }).catch((error) => { + console.error("[ws-link] Failed to connect for subscription:", error) + subscriptionHandlers.delete(id) + }) + + // Return unsubscribe function + return () => { + console.log("[ws-link] Unsubscribing:", chatId, id) + subscriptionHandlers.delete(id) + if (ws?.readyState === WebSocket.OPEN) { + const unsubscribeRequest: WSRequest = { id: `unsub-${Date.now()}`, type: "unsubscribe", chatId } + ws.send(JSON.stringify(unsubscribeRequest)) + } + } +} +``` + +**Step 4: Remove old tRPC link code** + +Delete the entire `wsLink()` function and related `sendTRPCRequest()`. The new API is just `sendChatMessage()` and `subscribeToChat()`. + +**Step 5: Commit** + +```bash +git add src/renderer/lib/remote-transport/ws-link.ts +git commit -m "refactor: simplify ws-link for send/subscribe API" +``` + +--- + +## Task 6: Create Chat Transport Hook + +**Files:** +- Create: `src/renderer/lib/hooks/use-chat-transport.ts` + +**Step 1: Write the hook** + +```typescript +import { useEffect, useRef } from "react" +import { subscribeToChat, sendChatMessage } from "../remote-transport/ws-link" + +/** + * Hook for chat communication via WebSocket + * @param chatId - The sub-chat ID to communicate with + * @returns Object with sendMessage function + */ +export function useChatTransport(chatId: string | null) { + const unsubscribeRef = useRef<(() => void) | null>(null) + + useEffect(() => { + if (!chatId) return + + // Subscribe to chat updates + unsubscribeRef.current = subscribeToChat(chatId, (data) => { + console.log("[useChatTransport] Received data:", chatId, data) + // Data will be handled by the existing chat subscription system + }) + + return () => { + if (unsubscribeRef.current) { + unsubscribeRef.current() + unsubscribeRef.current = null + } + } + }, [chatId]) + + const sendMessage = async (message: any) => { + if (!chatId) { + throw new Error("No active chat") + } + return sendChatMessage(chatId, message) + } + + return { sendMessage } +} +``` + +**Step 2: Commit** + +```bash +git add src/renderer/lib/hooks/use-chat-transport.ts +git commit -m "feat: add useChatTransport hook" +``` + +--- + +## Task 7: Update Active Chat to Use New Transport + +**Files:** +- Modify: `src/renderer/features/agents/main/active-chat.tsx` + +**Step 1: Find where messages are sent** + +Search for `trpc.claude.sendMessage` or similar mutation calls. + +**Step 2: Replace with new transport** + +```typescript +import { useChatTransport } from "@/lib/hooks/use-chat-transport" +import { useAtomValue } from "jotai" +import { selectedSubChatIdAtom } from "@/lib/atoms" + +// Inside component +const selectedSubChatId = useAtomValue(selectedSubChatIdAtom) +const { sendMessage } = useChatTransport(selectedSubChatId?.id) + +// Replace mutation call with: +const handleSend = async (message: string) => { + try { + await sendMessage({ message }) + } catch (error) { + console.error("Failed to send message:", error) + } +} +``` + +**Step 3: Commit** + +```bash +git add src/renderer/features/agents/main/active-chat.tsx +git commit -m "refactor: use WebSocket transport for chat messages" +``` + +--- + +## Task 8: Remove Old tRPC Remote Links + +**Files:** +- Modify: `src/renderer/lib/trpc.ts` or wherever tRPC client is configured +- Modify: `src/renderer/contexts/TRPCProvider.tsx` + +**Step 1: Remove WebSocket link from tRPC client** + +If tRPC client has `wsLink` mixed in, remove it for remote mode. Desktop still uses IPC, web uses WebSocket transport directly. + +**Step 2: Commit** + +```bash +git add src/renderer/lib/trpc.ts +git commit -m "refactor: remove WebSocket from tRPC client" +``` + +--- + +## Task 9: Test Web Client Send Message + +**Files:** +- Test: Manual testing via browser + +**Step 1: Start desktop app** + +```bash +bun run dev +``` + +**Step 2: Open web app** + +Navigate to `http://localhost:PORT/app` and enter PIN. + +**Step 3: Send message from web** + +Type a message in chat input and send. + +**Expected:** +- Message appears in web app chat +- Message appears in desktop app chat simultaneously +- No errors in console + +**Step 4: Verify logs** + +Desktop terminal should show: +``` +[WS] Received message for chat ml3xxx: { message: "hello" } +[WS] Broadcasting chat data to 1 WS clients, 1 IPC listeners +``` + +**Step 5: Debug if fails** + +Check: +- WebSocket connection established +- Subscription active before send +- tRPC mutation succeeds +- Broadcast reaches both clients + +--- + +## Task 10: Test Desktop Still Works + +**Files:** +- Test: Manual testing + +**Step 1: Send message from desktop** + +Type in desktop chat input and send. + +**Expected:** +- Message appears in desktop app +- Message appears in web app simultaneously + +**Step 2: Verify no regression** + +- Desktop chat still works via IPC +- Desktop and web stay in sync +- No performance issues + +--- + +## Task 11: Cleanup Unused Code + +**Files:** +- Modify: Multiple files + +**Step 1: Remove unused imports and functions** + +After confirming everything works: +- Remove old `sendTRPCRequest` if fully replaced +- Remove complex tRPC subscription handling in ws-link +- Clean up any commented-out code + +**Step 2: Final commit** + +```bash +git add -A +git commit -m "chore: cleanup unused WebSocket code" +``` + +--- + +## Testing Checklist + +- [ ] Web client can subscribe to chat +- [ ] Web client can send messages +- [ ] Messages appear in both web and desktop simultaneously +- [ ] Desktop can still send messages +- [ ] Desktop messages appear in web +- [ ] Unsubscribe works correctly +- [ ] Reconnection after disconnect works +- [ ] No memory leaks (subscriptions cleaned up) +- [ ] Console shows expected logs + +--- + +## Rollback Plan + +If issues arise: +```bash +git revert HEAD # Undo last commit +# Or +git reset --hard +``` + +The old code is preserved in git history for easy rollback. diff --git a/src/main/lib/remote-access/ws-server.ts b/src/main/lib/remote-access/ws-server.ts index 9223b3cab..870e2ad14 100644 --- a/src/main/lib/remote-access/ws-server.ts +++ b/src/main/lib/remote-access/ws-server.ts @@ -50,7 +50,7 @@ const subscriptionCleanups = new Map void>() interface SharedChatSubscription { observable: any subscription: any - clients: Set + clients: Map // Map of WebSocket to subscription ID // Event emitter for IPC clients to listen listeners: Set<(data: any) => void> } @@ -424,9 +424,9 @@ export function broadcastChatData(subChatId: string, data: any): void { console.log(`[WS] Broadcasting chat data to ${sharedSub.clients.size} WebSocket clients and ${sharedSub.listeners.size} IPC listeners for subChatId: ${subChatId}`) // Broadcast to WebSocket clients - for (const clientWs of sharedSub.clients) { + for (const [clientWs, subId] of sharedSub.clients) { send(clientWs, { - id: null, + id: subId, type: "subscription", data: data, }) @@ -455,7 +455,7 @@ export function subscribeToChatData(subChatId: string, callback: (data: any) => sharedSub = { observable: null, subscription: null, - clients: new Set(), + clients: new Map(), listeners: new Set(), } sharedChatSubscriptions.set(subChatId, sharedSub) @@ -587,9 +587,9 @@ async function handleMessage( console.log(`[WS] Broadcasting chat data to ${sharedSub?.clients.size || 0} WS clients, ${sharedSub?.listeners.size || 0} IPC listeners:`, data.type || "no-type") // Broadcast to all WebSocket clients subscribed to this subChat if (sharedSub) { - for (const clientWs of sharedSub.clients) { + for (const [clientWs, subId] of sharedSub.clients) { send(clientWs, { - id: null, + id: subId, type: "subscription", data: data, }) @@ -607,9 +607,9 @@ async function handleMessage( error: (err: any) => { console.error(`[WS] Chat subscription error:`, err) if (sharedSub) { - for (const clientWs of sharedSub.clients) { + for (const [clientWs, subId] of sharedSub.clients) { send(clientWs, { - id: null, + id: subId, type: "error", error: err.message, }) @@ -626,9 +626,9 @@ async function handleMessage( complete: () => { console.log(`[WS] Chat subscription complete for ${subChatId}`) if (sharedSub) { - for (const clientWs of sharedSub.clients) { + for (const [clientWs, subId] of sharedSub.clients) { send(clientWs, { - id: null, + id: subId, type: "result", data: { completed: true }, }) @@ -650,14 +650,14 @@ async function handleMessage( sharedSub = { observable, subscription, - clients: new Set(), + clients: new Map(), listeners: existingListeners, } sharedChatSubscriptions.set(subChatId, sharedSub) } - // Add this client to the shared subscription - sharedSub.clients.add(ws) + // Add this client to the shared subscription with their subscription ID + sharedSub.clients.set(ws, message.id) console.log(`[WS] Client added to shared subscription ${subChatId}. Total clients: ${sharedSub.clients.size}`) // Send success response @@ -690,9 +690,9 @@ async function handleMessage( getWindow: () => BrowserWindow.getFocusedWindow(), }) - // Navigate to the procedure using the router directly (caller may not expose nested routers) - // Handle nested paths like "external.getAppVersion" or "projects.list" - let current: any = router + // Navigate to the procedure using the caller + // Handle nested paths like "external.getAppVersion" or "projects.list" or "chats.list" + let current: any = caller for (let i = 0; i < pathParts.length - 1; i++) { current = current[pathParts[i]] diff --git a/src/renderer/features/agents/ui/agents-content.tsx b/src/renderer/features/agents/ui/agents-content.tsx index 274eb8b30..31861b15b 100644 --- a/src/renderer/features/agents/ui/agents-content.tsx +++ b/src/renderer/features/agents/ui/agents-content.tsx @@ -190,6 +190,7 @@ export function AgentsContent() { // Fetch all projects for git info (like sidebar does) const { data: projects } = trpc.projects.list.useQuery( + undefined, // no input // Poll every 5 seconds in remote mode to sync with desktop changes isRemoteMode ? { refetchInterval: 5000 } : undefined, ) diff --git a/src/renderer/features/sidebar/agents-sidebar.tsx b/src/renderer/features/sidebar/agents-sidebar.tsx index 0bc943e85..b1d0328d5 100644 --- a/src/renderer/features/sidebar/agents-sidebar.tsx +++ b/src/renderer/features/sidebar/agents-sidebar.tsx @@ -1873,11 +1873,19 @@ export function AgentsSidebar({ }, []) // Fetch all local chats (no project filter) - const { data: localChats } = trpc.chats.list.useQuery( + const { data: localChats, isLoading: isChatsLoading, error: chatsError } = trpc.chats.list.useQuery( + {}, // empty input // Poll every 5 seconds in remote mode to sync with desktop changes isRemoteMode ? { refetchInterval: 5000 } : {}, ) + // Debug: Log chat data in remote mode + useEffect(() => { + if (isRemoteMode) { + console.log('[AgentsSidebar] Remote mode - localChats:', localChats, 'isLoading:', isChatsLoading, 'error:', chatsError) + } + }, [isRemoteMode, localChats, isChatsLoading, chatsError]) + // Fetch user's teams (same as web) - always enabled to allow merged list const { data: teams, isLoading: isTeamsLoading, isError: isTeamsError } = useUserTeams(true) @@ -2019,6 +2027,7 @@ export function AgentsSidebar({ // Fetch all projects for git info const { data: projects } = trpc.projects.list.useQuery( + undefined, // no input // Poll every 5 seconds in remote mode to sync with desktop changes isRemoteMode ? { refetchInterval: 5000 } : undefined, ) @@ -2034,6 +2043,7 @@ export function AgentsSidebar({ // Fetch all archived chats (to get count) const { data: archivedChats } = trpc.chats.listArchived.useQuery( + undefined, // no input // Poll every 5 seconds in remote mode to sync with desktop changes isRemoteMode ? { refetchInterval: 5000 } : {}, ) diff --git a/src/renderer/lib/remote-transport/ws-link.ts b/src/renderer/lib/remote-transport/ws-link.ts index daadc6d98..ff53dce20 100644 --- a/src/renderer/lib/remote-transport/ws-link.ts +++ b/src/renderer/lib/remote-transport/ws-link.ts @@ -116,9 +116,16 @@ function connect(): Promise { console.error("[ws-link] Subscription error:", message.id, message.error) subHandler.onError(new Error(message.error || "Subscription error")) } else if (message.type === "result") { - console.log("[ws-link] Subscription complete:", message.id) - subHandler.onComplete() - subscriptionHandlers.delete(message.id) + // Check if this is the actual completion or just the initial subscription confirmation + const resultData = message.data as { completed?: boolean; subscribed?: boolean } | undefined + if (resultData?.completed) { + console.log("[ws-link] Subscription complete:", message.id) + subHandler.onComplete() + subscriptionHandlers.delete(message.id) + } else if (resultData?.subscribed) { + // Initial subscription confirmation - keep the handler active + console.log("[ws-link] Subscription confirmed:", message.id) + } } return } From d4ba8e151b40bba464d749645b57f352f6ee5a0a Mon Sep 17 00:00:00 2001 From: Yogi Date: Mon, 2 Feb 2026 18:33:19 +0700 Subject: [PATCH 15/16] docs: update plan with push-based sync protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Auth success → push projects + model profiles - Subscribe to project → push chat list - Subscribe to chat → push message history + stream updates - Send message → WebSocket → Desktop → tRPC Co-Authored-By: Claude --- docs/plans/2025-02-02-websocket-chat-relay.md | 830 ++++++++++++------ 1 file changed, 572 insertions(+), 258 deletions(-) diff --git a/docs/plans/2025-02-02-websocket-chat-relay.md b/docs/plans/2025-02-02-websocket-chat-relay.md index d2258c3f7..d9ad98a80 100644 --- a/docs/plans/2025-02-02-websocket-chat-relay.md +++ b/docs/plans/2025-02-02-websocket-chat-relay.md @@ -1,127 +1,216 @@ -# WebSocket Chat Relay Implementation Plan +# WebSocket Chat Relay Implementation Plan (Push-Based Sync) > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. -**Goal:** Simplify web-desktop communication so web client only sends/receives via WebSocket, with desktop as relay/proxy to tRPC. +**Goal:** Simplify web-desktop communication with push-based data sync. Web client subscribes to resources, server pushes data proactively. -**Architecture:** Web client sends messages through WebSocket → Desktop app forwards to tRPC → Response broadcast to subscribed WebSocket clients. Single source of truth via shared subscription Observable. +**Architecture:** Server pushes data on subscription events - no polling, no direct tRPC queries from web. **Tech Stack:** WebSocket (ws), tRPC, Electron IPC, Observable pattern --- -## Current Flow (Complex) +## Target Flow (Push-Based) ``` -Web Client - ├─→ Direct tRPC queries (projects.list, chats.list) - ├─→ Direct tRPC mutations (sendMessage) - └─→ tRPC subscription (claude.chat) -``` - -## Target Flow (Simplified) - -``` -Web Client - ├─→ WebSocket.send(chatId, message) → Desktop → tRPC - └─→ WebSocket.subscribe(chatId) ← Desktop ← tRPC +┌─────────────────────────────────────────────────────────────┐ +│ AUTH SUCCESS │ +├─────────────────────────────────────────────────────────────┤ +│ Web → Server: auth with PIN │ +│ Server → Web: auth_success + { projects, modelProfiles } │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ SUBSCRIBE TO PROJECT │ +├─────────────────────────────────────────────────────────────┤ +│ Web → Server: subscribe(projectId) │ +│ Server → Web: subscribed + { chats: [] } │ +│ Server → Web: (push) new chat created │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ SUBSCRIBE TO CHAT │ +├─────────────────────────────────────────────────────────────┤ +│ Web → Server: subscribe(chatId) │ +│ Server → Web: subscribed + { messages: [] } │ +│ Server → Web: (push) new message from Claude │ +└─────────────────────────────────────────────────────────────┘ + +┌─────────────────────────────────────────────────────────────┐ +│ SEND MESSAGE │ +├─────────────────────────────────────────────────────────────┤ +│ Web → Server: send(chatId, message) │ +│ Server → Desktop: claude.sendMessage() │ +│ Server → Web: (push via subscription) response │ +└─────────────────────────────────────────────────────────────┘ ``` **Benefits:** - Single channel for all communication -- Desktop controls everything (no direct tRPC access from web) +- Server pushes data proactively (no polling) +- Desktop controls everything (security) - Simpler client code -- Better security (web can't bypass desktop) +- Real-time sync guaranteed --- -## Task 1: Simplify WebSocket Message Protocol +## Task 1: Define New Message Protocol **Files:** - Modify: `src/main/lib/remote-access/ws-server.ts` -**Step 1: Define new simplified message types** - -Add to existing `WSRequest` interface: +**Step 1: Define message types** ```typescript +// Resource types for subscription +type ResourceType = "project" | "chat" | "modelProfiles" + interface WSRequest { id: string type: "auth" | "send" | "subscribe" | "unsubscribe" - chatId?: string // For subscribe/unsubscribe/send - message?: any // For send (chat message content) - pin?: string // For auth + resource?: ResourceType // What to subscribe to + resourceId?: string // ID of the resource (projectId/chatId) + message?: any // For send (chat message content) + pin?: string // For auth } interface WSResponse { id: string | null type: "auth_required" | "auth_success" | "auth_failed" | "subscribed" | "error" | "data" - chatId?: string // Which chat this data is for + resource?: ResourceType // Which resource this data is for + resourceId?: string // ID of the resource data?: unknown error?: string } ``` -**Step 2: Remove "trpc" type handling** +**Step 2: Add resource subscription tracking** -Delete the large `if (message.type === "trpc" && message.method)` block in `handleMessage()`. This will be replaced with simpler "send" handling. +```typescript +// Track subscriptions per client +interface ClientSubscription { + resource: ResourceType + resourceId: string + ws: WebSocket +} + +interface AuthenticatedClient { + ws: WebSocket + id: string + subscriptions: Map // key: `${resource}:${resourceId}` +} +``` **Step 3: Commit** ```bash git add src/main/lib/remote-access/ws-server.ts -git commit -m "refactor: remove direct tRPC access from WebSocket" +git commit -m "refactor: define new WebSocket message protocol" ``` --- -## Task 2: Implement Chat Message Send Handler +## Task 2: Auth Success - Push Initial Data **Files:** - Modify: `src/main/lib/remote-access/ws-server.ts` -**Step 1: Add send message handler** - -In `handleMessage()`, add after auth check: +**Step 1: Fetch projects and model profiles on auth** ```typescript -// Send chat message -if (message.type === "send" && message.chatId && message.message) { - try { - const { chatId, message: msgContent } = message +// In handleMessage(), auth success section +if (message.pin && validatePin(message.pin)) { + const clientId = randomUUID() + const newClient: AuthenticatedClient = { + ws, + id: clientId, + subscriptions: new Map(), + } + authenticatedClients.set(ws, newClient) + addClient(clientId) - console.log(`[WS] Received message for chat ${chatId}:`, msgContent) + // Fetch initial data + const router = createAppRouter(() => BrowserWindow.getFocusedWindow()) + const caller = router.createCaller({ + getWindow: () => BrowserWindow.getFocusedWindow(), + }) - // Forward to tRPC via desktop's tRPC router - const router = createAppRouter(() => BrowserWindow.getFocusedWindow()) - const caller = router.createCaller({ - getWindow: () => BrowserWindow.getFocusedWindow(), - }) + // Get projects and model profiles in parallel + const [projects, modelProfiles] = await Promise.all([ + caller.projects.list(), + caller.modelProfiles.list().catch(() => []), + ]) - // Call claude.sendMessage (this will trigger the subscription to broadcast) - const result = await caller.claude.sendMessage({ - subChatId: chatId, - message: msgContent, - }) + send(ws, { + id: message.id, + type: "auth_success", + data: { + projects, + modelProfiles, + }, + }) - // Send success response - send(ws, { - id: message.id, - type: "data", - chatId, - data: { sent: true }, - }) + console.log(`[WS] Client authenticated: ${clientId}, sent ${projects.length} projects`) + return +} +``` - // Note: Actual response will come through subscription broadcast - return - } catch (error) { - send(ws, { - id: message.id, - type: "error", - error: error instanceof Error ? error.message : "Failed to send message", - }) +**Step 2: Commit** + +```bash +git add src/main/lib/remote-access/ws-server.ts +git commit -m "feat: send projects and model profiles on auth" +``` + +--- + +## Task 3: Subscribe to Project - Push Chat List + +**Files:** +- Modify: `src/main/lib/remote-access/ws-server.ts` + +**Step 1: Add project subscription handler** + +```typescript +// Subscribe to project (get chats) +if (message.type === "subscribe" && message.resource === "project" && message.resourceId) { + const projectId = message.resourceId + const client = authenticatedClients.get(ws) + + if (!client) { + send(ws, { id: message.id, type: "error", error: "Not authenticated" }) return } + + console.log(`[WS] Subscribing to project: ${projectId}`) + + // Fetch chats for this project + const router = createAppRouter(() => BrowserWindow.getFocusedWindow()) + const caller = router.createCaller({ + getWindow: () => BrowserWindow.getFocusedWindow(), + }) + + const chats = await caller.chats.list({ projectId }) + + // Store subscription + const subKey = `project:${projectId}` + client.subscriptions.set(subKey, { + resource: "project", + resourceId: projectId, + ws, + }) + + // Send initial data + send(ws, { + id: message.id, + type: "subscribed", + resource: "project", + resourceId: projectId, + data: { chats }, + }) + + console.log(`[WS] Subscribed to project ${projectId}, sent ${chats.length} chats`) + return } ``` @@ -129,28 +218,32 @@ if (message.type === "send" && message.chatId && message.message) { ```bash git add src/main/lib/remote-access/ws-server.ts -git commit -m "feat: add WebSocket send message handler" +git commit -m "feat: add project subscription with chat list" ``` --- -## Task 3: Update Subscribe Handler to Use New Protocol +## Task 4: Subscribe to Chat - Push Message List + Stream Updates **Files:** - Modify: `src/main/lib/remote-access/ws-server.ts` -**Step 1: Simplify subscribe handler** - -Replace the complex `if (routerName === "claude" && procedureName === "chat")` block with: +**Step 1: Add chat subscription handler with message history** ```typescript -// Subscribe to chat -if (message.type === "subscribe" && message.chatId) { - const subChatId = message.chatId +// Subscribe to chat (get messages + stream) +if (message.type === "subscribe" && message.resource === "chat" && message.resourceId) { + const subChatId = message.resourceId + const client = authenticatedClients.get(ws) + + if (!client) { + send(ws, { id: message.id, type: "error", error: "Not authenticated" }) + return + } - console.log(`[WS] Starting subscription for subChatId: ${subChatId}`) + console.log(`[WS] Subscribing to chat: ${subChatId}`) - // Check if there's already a shared subscription + // Check if shared subscription exists let sharedSub = sharedChatSubscriptions.get(subChatId) if (!sharedSub) { @@ -181,11 +274,13 @@ if (message.type === "subscribe" && message.chatId) { const subscription = observable.subscribe({ next: (data: any) => { if (sharedSub) { - for (const [clientWs, subId] of sharedSub.clients) { + console.log(`[WS] Broadcasting to ${sharedSub.clients.size} clients for ${subChatId}:`, data.type) + for (const [clientWs] of sharedSub.clients) { send(clientWs, { - id: subId, + id: null, // Streaming data doesn't have request ID type: "data", - chatId: subChatId, + resource: "chat", + resourceId: subChatId, data: data, }) } @@ -202,11 +297,12 @@ if (message.type === "subscribe" && message.chatId) { error: (err: any) => { console.error(`[WS] Chat subscription error:`, err) if (sharedSub) { - for (const [clientWs, subId] of sharedSub.clients) { + for (const [clientWs] of sharedSub.clients) { send(clientWs, { - id: subId, + id: null, type: "error", - chatId: subChatId, + resource: "chat", + resourceId: subChatId, error: err.message, }) } @@ -215,11 +311,12 @@ if (message.type === "subscribe" && message.chatId) { complete: () => { console.log(`[WS] Chat subscription complete for ${subChatId}`) if (sharedSub) { - for (const [clientWs, subId] of sharedSub.clients) { + for (const [clientWs] of sharedSub.clients) { send(clientWs, { - id: subId, + id: null, type: "data", - chatId: subChatId, + resource: "chat", + resourceId: subChatId, data: { completed: true }, }) } @@ -239,10 +336,20 @@ if (message.type === "subscribe" && message.chatId) { // Add client to shared subscription sharedSub.clients.set(ws, message.id) + // Store subscription in client + const subKey = `chat:${subChatId}` + client.subscriptions.set(subKey, { + resource: "chat", + resourceId: subChatId, + ws, + }) + + // Send subscribed confirmation (messages will come via stream) send(ws, { id: message.id, type: "subscribed", - chatId: subChatId, + resource: "chat", + resourceId: subChatId, }) // Clean up on disconnect @@ -256,6 +363,7 @@ if (message.type === "subscribe" && message.chatId) { } }) + console.log(`[WS] Subscribed to chat ${subChatId}`) return } ``` @@ -264,12 +372,75 @@ if (message.type === "subscribe" && message.chatId) { ```bash git add src/main/lib/remote-access/ws-server.ts -git commit -m "refactor: simplify subscribe handler" +git commit -m "feat: add chat subscription with message streaming" +``` + +--- + +## Task 5: Send Message Handler + +**Files:** +- Modify: `src/main/lib/remote-access/ws-server.ts` + +**Step 1: Add send message handler** + +```typescript +// Send chat message +if (message.type === "send" && message.resourceId && message.message) { + const subChatId = message.resourceId + const client = authenticatedClients.get(ws) + + if (!client) { + send(ws, { id: message.id, type: "error", error: "Not authenticated" }) + return + } + + try { + console.log(`[WS] Sending message to chat ${subChatId}`) + + // Forward to tRPC via desktop's router + const router = createAppRouter(() => BrowserWindow.getFocusedWindow()) + const caller = router.createCaller({ + getWindow: () => BrowserWindow.getFocusedWindow(), + }) + + // Call claude.sendMessage + await caller.claude.sendMessage({ + subChatId, + message: message.message, + }) + + // Send success (actual response will come through subscription) + send(ws, { + id: message.id, + type: "data", + resource: "chat", + resourceId: subChatId, + data: { sent: true }, + }) + + return + } catch (error) { + send(ws, { + id: message.id, + type: "error", + error: error instanceof Error ? error.message : "Failed to send message", + }) + return + } +} +``` + +**Step 2: Commit** + +```bash +git add src/main/lib/remote-access/ws-server.ts +git commit -m "feat: add send message handler" ``` --- -## Task 4: Add Unsubscribe Handler +## Task 6: Unsubscribe Handler **Files:** - Modify: `src/main/lib/remote-access/ws-server.ts` @@ -277,26 +448,41 @@ git commit -m "refactor: simplify subscribe handler" **Step 1: Add unsubscribe handler** ```typescript -// Unsubscribe from chat -if (message.type === "unsubscribe" && message.chatId) { - const subChatId = message.chatId - const sharedSub = sharedChatSubscriptions.get(subChatId) - - if (sharedSub) { - sharedSub.clients.delete(ws) - - // Clean up if no clients left - if (sharedSub.clients.size === 0 && sharedSub.listeners.size === 0) { - sharedSub.subscription.unsubscribe() - sharedChatSubscriptions.delete(subChatId) +// Unsubscribe from resource +if (message.type === "unsubscribe" && message.resource && message.resourceId) { + const { resource, resourceId } = message + const client = authenticatedClients.get(ws) + + if (!client) { + send(ws, { id: message.id, type: "error", error: "Not authenticated" }) + return + } + + const subKey = `${resource}:${resourceId}` + const subscription = client.subscriptions.get(subKey) + + if (subscription) { + client.subscriptions.delete(subKey) + + // If chat, remove from shared subscription + if (resource === "chat") { + const sharedSub = sharedChatSubscriptions.get(resourceId) + if (sharedSub) { + sharedSub.clients.delete(ws) + if (sharedSub.clients.size === 0 && sharedSub.listeners.size === 0) { + sharedSub.subscription.unsubscribe() + sharedChatSubscriptions.delete(resourceId) + } + } } send(ws, { id: message.id, type: "data", - chatId: subChatId, data: { unsubscribed: true }, }) + + console.log(`[WS] Unsubscribed from ${resource}:${resourceId}`) } return @@ -312,20 +498,23 @@ git commit -m "feat: add unsubscribe handler" --- -## Task 5: Update Client-Side ws-link +## Task 7: Update Client-Side ws-link **Files:** - Modify: `src/renderer/lib/remote-transport/ws-link.ts` -**Step 1: Simplify message types** +**Step 1: Update message types** ```typescript +type ResourceType = "project" | "chat" | "modelProfiles" + type WSMessageType = "auth" | "send" | "subscribe" | "unsubscribe" interface WSRequest { id: string type: WSMessageType - chatId?: string + resource?: ResourceType + resourceId?: string message?: any pin?: string } @@ -333,39 +522,67 @@ interface WSRequest { interface WSResponse { id: string | null type: "auth_required" | "auth_success" | "auth_failed" | "subscribed" | "error" | "data" - chatId?: string + resource?: ResourceType + resourceId?: string data?: unknown error?: string } ``` -**Step 2: Add send message function** +**Step 2: Add auth data handler** + +```typescript +// Store initial data from auth +let initialData: { + projects: any[] + modelProfiles: any[] +} | null = null + +// In ws.onmessage, after auth success: +if (message.type === "auth_success") { + console.log("[ws-link] Authentication successful") + isAuthenticated = true + initialData = message.data as { projects: any[], modelProfiles: any[] } + connectionPromise = null + resolve() + return +} +``` + +**Step 3: Add subscribe to project function** ```typescript /** - * Send a chat message via WebSocket + * Subscribe to a project (receives chat list) */ -export async function sendChatMessage(chatId: string, message: any): Promise { +export async function subscribeToProject(projectId: string): Promise { await connect() return new Promise((resolve, reject) => { - const id = `send-${++requestId}-${Date.now()}` - const request: WSRequest = { id, type: "send", chatId, message } + const id = `sub-project-${Date.now()}` + const request: WSRequest = { id, type: "subscribe", resource: "project", resourceId: projectId } const timeout = setTimeout(() => { - pendingRequests.delete(id) - reject(new Error("Send timeout")) - }, 30000) + subscriptionHandlers.delete(id) + reject(new Error("Subscribe timeout")) + }, 10000) - pendingRequests.set(id, { - resolve: (data) => { + // One-time handler for initial data + subscriptionHandlers.set(id, { + onData: (data: any) => { clearTimeout(timeout) - resolve(data) + subscriptionHandlers.delete(id) + resolve(data.chats || []) }, - reject: (error) => { + onError: (error) => { clearTimeout(timeout) + subscriptionHandlers.delete(id) reject(error) }, + onComplete: () => { + clearTimeout(timeout) + subscriptionHandlers.delete(id) + }, }) ws!.send(JSON.stringify(request)) @@ -373,20 +590,18 @@ export async function sendChatMessage(chatId: string, message: any): Promise void): () => void { - const id = `sub-${++requestId}-${Date.now()}` + const id = `sub-chat-${Date.now()}` const handler: SubscriptionHandler = { onData: (data) => { - console.log("[ws-link] Chat data received:", chatId, data) + console.log("[ws-link] Chat data received:", chatId, data.type) onData(data) }, onError: (error) => { @@ -399,250 +614,351 @@ export function subscribeToChat(chatId: string, onData: (data: any) => void): () subscriptionHandlers.set(id, handler) - // Wait for connection, then send subscribe request connect().then(() => { - const request: WSRequest = { id, type: "subscribe", chatId } + const request: WSRequest = { id, type: "subscribe", resource: "chat", resourceId: chatId } ws!.send(JSON.stringify(request)) - console.log("[ws-link] Subscribe request sent:", id, chatId) + console.log("[ws-link] Subscribe chat request sent:", chatId) }).catch((error) => { - console.error("[ws-link] Failed to connect for subscription:", error) + console.error("[ws-link] Failed to connect:", error) subscriptionHandlers.delete(id) }) - // Return unsubscribe function return () => { - console.log("[ws-link] Unsubscribing:", chatId, id) + console.log("[ws-link] Unsubscribing from chat:", chatId) subscriptionHandlers.delete(id) if (ws?.readyState === WebSocket.OPEN) { - const unsubscribeRequest: WSRequest = { id: `unsub-${Date.now()}`, type: "unsubscribe", chatId } + const unsubscribeRequest: WSRequest = { + id: `unsub-${Date.now()}`, + type: "unsubscribe", + resource: "chat", + resourceId: chatId, + } ws.send(JSON.stringify(unsubscribeRequest)) } } } ``` -**Step 4: Remove old tRPC link code** +**Step 5: Add send message function** -Delete the entire `wsLink()` function and related `sendTRPCRequest()`. The new API is just `sendChatMessage()` and `subscribeToChat()`. - -**Step 5: Commit** +```typescript +/** + * Send a message to a chat + */ +export async function sendChatMessage(chatId: string, message: any): Promise { + await connect() -```bash -git add src/renderer/lib/remote-transport/ws-link.ts -git commit -m "refactor: simplify ws-link for send/subscribe API" -``` + return new Promise((resolve, reject) => { + const id = `send-${Date.now()}` + const request: WSRequest = { id, type: "send", resourceId: chatId, message } ---- + const timeout = setTimeout(() => { + pendingRequests.delete(id) + reject(new Error("Send timeout")) + }, 30000) -## Task 6: Create Chat Transport Hook + pendingRequests.set(id, { + resolve: (data) => { + clearTimeout(timeout) + resolve(data) + }, + reject: (error) => { + clearTimeout(timeout) + reject(error) + }, + }) -**Files:** -- Create: `src/renderer/lib/hooks/use-chat-transport.ts` + ws!.send(JSON.stringify(request)) + }) +} +``` -**Step 1: Write the hook** +**Step 6: Add helper to get initial data** ```typescript -import { useEffect, useRef } from "react" -import { subscribeToChat, sendChatMessage } from "../remote-transport/ws-link" - /** - * Hook for chat communication via WebSocket - * @param chatId - The sub-chat ID to communicate with - * @returns Object with sendMessage function + * Get initial data from auth (projects, model profiles) */ -export function useChatTransport(chatId: string | null) { - const unsubscribeRef = useRef<(() => void) | null>(null) +export function getInitialData() { + return initialData +} +``` - useEffect(() => { - if (!chatId) return +**Step 7: Update onmessage to handle resource-based messages** - // Subscribe to chat updates - unsubscribeRef.current = subscribeToChat(chatId, (data) => { - console.log("[useChatTransport] Received data:", chatId, data) - // Data will be handled by the existing chat subscription system - }) +```typescript +ws.onmessage = (event) => { + const message: WSResponse = JSON.parse(event.data) + + // Handle auth response + if (message.type === "auth_success") { + console.log("[ws-link] Authentication successful") + isAuthenticated = true + initialData = message.data as { projects: any[], modelProfiles: any[] } + connectionPromise = null + resolve() + return + } - return () => { - if (unsubscribeRef.current) { - unsubscribeRef.current() - unsubscribeRef.current = null + // ... rest of handlers (auth_failed, auth_required) ... + + // Handle subscription data (streaming) + if (!message.id && message.type === "data" && message.resource) { + // This is streaming data from active subscription + const subKey = `${message.resource}:${message.resourceId}` + // Find all handlers for this resource and notify them + for (const [subId, handler] of subscriptionHandlers) { + if (subId.includes(message.resource!)) { + handler.onData(message.data) } } - }, [chatId]) + return + } - const sendMessage = async (message: any) => { - if (!chatId) { - throw new Error("No active chat") + // Handle response to specific request + if (message.id) { + const subHandler = subscriptionHandlers.get(message.id) + if (subHandler) { + if (message.type === "subscribed" || (message.type === "data" && message.data !== undefined)) { + subHandler.onData(message.data) + } else if (message.type === "error") { + subHandler.onError(new Error(message.error || "Error")) + } else if (message.type === "data") { + subHandler.onComplete() + subscriptionHandlers.delete(message.id) + } + return + } + + // Handle pending requests + const pending = pendingRequests.get(message.id) + if (pending) { + pendingRequests.delete(message.id) + if (message.type === "error") { + pending.reject(new Error(message.error || "Unknown error")) + } else { + pending.resolve(message.data) + } } - return sendChatMessage(chatId, message) } +} +``` + +**Step 8: Commit** + +```bash +git add src/renderer/lib/remote-transport/ws-link.ts +git commit -m "refactor: update ws-link for push-based protocol" +``` + +--- + +## Task 8: Create Resource Data Store + +**Files:** +- Create: `src/renderer/lib/stores/remote-data-store.ts` + +**Step 1: Create Zustand store for remote data** - return { sendMessage } +```typescript +import { create } from 'zustand' +import { subscribeToProject, subscribeToChat, sendChatMessage, getInitialData } from '../remote-transport/ws-link' + +interface RemoteDataState { + // Data + projects: any[] + modelProfiles: any[] + projectChats: Map // projectId -> chats + + // Actions + loadInitialData: () => void + subscribeToProject: (projectId: string) => Promise + subscribeToChat: (chatId: string, onData: (data: any) => void) => () => void + sendMessage: (chatId: string, message: any) => Promise } + +export const useRemoteDataStore = create((set, get) => ({ + projects: [], + modelProfiles: [], + projectChats: new Map(), + + loadInitialData: () => { + const data = getInitialData() + if (data) { + set({ projects: data.projects, modelProfiles: data.modelProfiles }) + } + }, + + subscribeToProject: async (projectId: string) => { + const chats = await subscribeToProject(projectId) + set((state) => { + const newMap = new Map(state.projectChats) + newMap.set(projectId, chats) + return { projectChats: newMap } + }) + }, + + subscribeToChat: (chatId: string, onData: (data: any) => void) => { + return subscribeToChat(chatId, onData) + }, + + sendMessage: async (chatId: string, message: any) => { + return sendChatMessage(chatId, message) + }, +})) ``` **Step 2: Commit** ```bash -git add src/renderer/lib/hooks/use-chat-transport.ts -git commit -m "feat: add useChatTransport hook" +git add src/renderer/lib/stores/remote-data-store.ts +git commit -m "feat: create remote data store" ``` --- -## Task 7: Update Active Chat to Use New Transport +## Task 9: Update UI to Use New Store **Files:** +- Modify: `src/renderer/features/sidebar/agents-sidebar.tsx` - Modify: `src/renderer/features/agents/main/active-chat.tsx` -**Step 1: Find where messages are sent** +**Step 1: Update sidebar to use remote data store** + +```typescript +import { useRemoteDataStore } from "@/lib/stores/remote-data-store" + +export function AgentsSidebar() { + const { projects, loadInitialData } = useRemoteDataStore() + + useEffect(() => { + // Load initial data on mount + loadInitialData() + }, []) -Search for `trpc.claude.sendMessage` or similar mutation calls. + // Render projects from store instead of tRPC query + // ... +} +``` -**Step 2: Replace with new transport** +**Step 2: Update active chat to use new transport** ```typescript -import { useChatTransport } from "@/lib/hooks/use-chat-transport" -import { useAtomValue } from "jotai" -import { selectedSubChatIdAtom } from "@/lib/atoms" +import { useRemoteDataStore } from "@/lib/stores/remote-data-store" -// Inside component -const selectedSubChatId = useAtomValue(selectedSubChatIdAtom) -const { sendMessage } = useChatTransport(selectedSubChatId?.id) +export function ActiveChat() { + const { sendMessage, subscribeToChat } = useRemoteDataStore() -// Replace mutation call with: -const handleSend = async (message: string) => { - try { - await sendMessage({ message }) - } catch (error) { - console.error("Failed to send message:", error) + useEffect(() => { + if (chatId) { + const unsubscribe = subscribeToChat(chatId, (data) => { + // Handle incoming message data + console.log("Received chat data:", data) + }) + return unsubscribe + } + }, [chatId]) + + const handleSend = async (message: string) => { + await sendMessage(chatId, { message }) } + + // ... } ``` **Step 3: Commit** ```bash +git add src/renderer/features/sidebar/agents-sidebar.tsx git add src/renderer/features/agents/main/active-chat.tsx -git commit -m "refactor: use WebSocket transport for chat messages" +git commit -m "refactor: use remote data store in UI" ``` --- -## Task 8: Remove Old tRPC Remote Links +## Task 10: Remove Old tRPC Usage from Web **Files:** -- Modify: `src/renderer/lib/trpc.ts` or wherever tRPC client is configured +- Modify: `src/renderer/lib/trpc.ts` - Modify: `src/renderer/contexts/TRPCProvider.tsx` -**Step 1: Remove WebSocket link from tRPC client** +**Step 1: Remove wsLink from tRPC client for web** -If tRPC client has `wsLink` mixed in, remove it for remote mode. Desktop still uses IPC, web uses WebSocket transport directly. +Web mode should only use WebSocket transport, not tRPC. Desktop keeps tRPC for IPC. **Step 2: Commit** ```bash git add src/renderer/lib/trpc.ts -git commit -m "refactor: remove WebSocket from tRPC client" +git commit -m "refactor: remove tRPC WebSocket from web mode" ``` --- -## Task 9: Test Web Client Send Message +## Task 11: Test Complete Flow **Files:** -- Test: Manual testing via browser +- Test: Manual testing -**Step 1: Start desktop app** +**Step 1: Test auth + initial data** ```bash bun run dev ``` -**Step 2: Open web app** - -Navigate to `http://localhost:PORT/app` and enter PIN. - -**Step 3: Send message from web** - -Type a message in chat input and send. +Open web app, enter PIN. **Expected:** -- Message appears in web app chat -- Message appears in desktop app chat simultaneously -- No errors in console - -**Step 4: Verify logs** - -Desktop terminal should show: -``` -[WS] Received message for chat ml3xxx: { message: "hello" } -[WS] Broadcasting chat data to 1 WS clients, 1 IPC listeners -``` +- Auth success +- Projects list appears +- Model profiles loaded -**Step 5: Debug if fails** +**Step 2: Test project subscription** -Check: -- WebSocket connection established -- Subscription active before send -- tRPC mutation succeeds -- Broadcast reaches both clients - ---- - -## Task 10: Test Desktop Still Works - -**Files:** -- Test: Manual testing - -**Step 1: Send message from desktop** - -Type in desktop chat input and send. +Click on a project. **Expected:** -- Message appears in desktop app -- Message appears in web app simultaneously +- Chat list for project appears -**Step 2: Verify no regression** +**Step 3: Test chat subscription** -- Desktop chat still works via IPC -- Desktop and web stay in sync -- No performance issues +Click on a chat. ---- +**Expected:** +- Message history loads +- Real-time updates work -## Task 11: Cleanup Unused Code +**Step 4: Test send message** -**Files:** -- Modify: Multiple files +Send a message from web. -**Step 1: Remove unused imports and functions** +**Expected:** +- Message appears in web +- Message appears in desktop +- Response appears in both -After confirming everything works: -- Remove old `sendTRPCRequest` if fully replaced -- Remove complex tRPC subscription handling in ws-link -- Clean up any commented-out code +**Step 5: Test sync** -**Step 2: Final commit** +Send message from desktop. -```bash -git add -A -git commit -m "chore: cleanup unused WebSocket code" -``` +**Expected:** +- Message appears in web immediately --- ## Testing Checklist -- [ ] Web client can subscribe to chat -- [ ] Web client can send messages -- [ ] Messages appear in both web and desktop simultaneously -- [ ] Desktop can still send messages -- [ ] Desktop messages appear in web -- [ ] Unsubscribe works correctly -- [ ] Reconnection after disconnect works -- [ ] No memory leaks (subscriptions cleaned up) -- [ ] Console shows expected logs +- [ ] Auth sends projects + model profiles +- [ ] Project subscription sends chat list +- [ ] Chat subscription sends message history +- [ ] Chat subscription streams new messages +- [ ] Send message works from web +- [ ] Send message works from desktop +- [ ] Messages sync between web and desktop +- [ ] Unsubscribe works +- [ ] Reconnection works +- [ ] No memory leaks --- @@ -654,5 +970,3 @@ git revert HEAD # Undo last commit # Or git reset --hard ``` - -The old code is preserved in git history for easy rollback. From 953fc0a31e84244a26b2c5b942ec6d907d3b5db3 Mon Sep 17 00:00:00 2001 From: Yogi Date: Mon, 2 Feb 2026 18:34:04 +0700 Subject: [PATCH 16/16] docs: expand tRPC removal task with detailed steps - Add conditional wsLink only for desktop - Remove tRPC queries from web sidebar/content - Guard all tRPC usage with isDesktopApp() - Update TRPCProvider for web compatibility Co-Authored-By: Claude --- docs/plans/2025-02-02-websocket-chat-relay.md | 106 +++++++++++++++++- 1 file changed, 101 insertions(+), 5 deletions(-) diff --git a/docs/plans/2025-02-02-websocket-chat-relay.md b/docs/plans/2025-02-02-websocket-chat-relay.md index d9ad98a80..87878b4dc 100644 --- a/docs/plans/2025-02-02-websocket-chat-relay.md +++ b/docs/plans/2025-02-02-websocket-chat-relay.md @@ -877,21 +877,117 @@ git commit -m "refactor: use remote data store in UI" --- -## Task 10: Remove Old tRPC Usage from Web +## Task 10: Remove tRPC Functions from Web App **Files:** - Modify: `src/renderer/lib/trpc.ts` - Modify: `src/renderer/contexts/TRPCProvider.tsx` +- Modify: `src/renderer/features/sidebar/agents-sidebar.tsx` +- Modify: `src/renderer/features/agents/ui/agents-content.tsx` -**Step 1: Remove wsLink from tRPC client for web** +**Step 1: Remove wsLink from tRPC client configuration** -Web mode should only use WebSocket transport, not tRPC. Desktop keeps tRPC for IPC. +In `src/renderer/lib/trpc.ts`, conditionally add wsLink only for desktop: -**Step 2: Commit** +```typescript +import { wsLink } from "./remote-transport/ws-link" + +// In createTRPCClient(), only add wsLink for desktop +const links = [] + +// Always add HTTP link (for desktop IPC and web initial load) +links.push(httpBatchLink) + +// Only add WebSocket link for desktop (web uses WebSocket transport directly) +if (isDesktopApp) { + links.push(wsLink()) +} + +export const trpcClient = createTRPCClient({ + links, +}) +``` + +**Step 2: Remove tRPC queries from web sidebar** + +In `src/renderer/features/sidebar/agents-sidebar.tsx`, remove tRPC queries for remote mode: + +```typescript +import { useRemoteDataStore } from "@/lib/stores/remote-data-store" +import { isDesktopApp } from "@/lib/utils/platform" + +export function AgentsSidebar() { + const isRemoteMode = !isDesktopApp() + + // Desktop: use tRPC queries + // Web: use remote data store (populated via WebSocket) + const { projects, loadInitialData } = useRemoteDataStore() + + const { data: localProjects } = trpc.projects.list.useQuery( + undefined, + // Only enable tRPC query for desktop + { enabled: isDesktopApp } + ) + + // Use appropriate data source + const projects = isDesktopApp ? localProjects : remoteProjects + + useEffect(() => { + // Load initial data from WebSocket for web + if (isRemoteMode) { + loadInitialData() + } + }, [isRemoteMode, loadInitialData]) + + // ... rest of component +} +``` + +**Step 3: Remove tRPC queries from agents content** + +In `src/renderer/features/agents/ui/agents-content.tsx`: + +```typescript +// Only use tRPC for desktop +const { data: projects } = trpc.projects.list.useQuery( + undefined, + { enabled: isDesktopApp() } +) + +// For web, data comes from remoteDataStore +``` + +**Step 4: Update TRPCProvider to handle web mode** + +In `src/renderer/contexts/TRPCProvider.tsx`: + +```typescript +// Ensure tRPC provider still works for desktop IPC +// Web mode won't use tRPC queries, but provider should still be available +// for any shared types/utilities +``` + +**Step 5: Verify no remaining tRPC calls in web mode** + +Search for remaining tRPC usage: +```bash +grep -r "trpc\." src/renderer/features --include="*.tsx" --include="*.ts" +``` + +All usages should be guarded with `isDesktopApp()` check. + +**Step 6: Commit** ```bash git add src/renderer/lib/trpc.ts -git commit -m "refactor: remove tRPC WebSocket from web mode" +git add src/renderer/contexts/TRPCProvider.tsx +git add src/renderer/features/sidebar/agents-sidebar.tsx +git add src/renderer/features/agents/ui/agents-content.tsx +git commit -m "refactor: remove tRPC queries from web mode + +- WebSocket transport handles all data for web +- Desktop continues using tRPC for IPC +- All web data flows through push-based WebSocket protocol" ``` ---