diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..d7ddf640 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + app: + runs-on: ubuntu-latest + defaults: + run: + working-directory: app + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 10 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: app/pnpm-lock.yaml + - run: pnpm install --frozen-lockfile + - run: pnpm typecheck + - run: pnpm lint + + server: + runs-on: ubuntu-latest + defaults: + run: + working-directory: server + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 10 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + cache-dependency-path: server/pnpm-lock.yaml + - run: pnpm install --frozen-lockfile + - run: pnpm typecheck + - run: pnpm test diff --git a/app/App.tsx b/app/App.tsx index 0375cfe4..85208f75 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -4,20 +4,21 @@ import { NavigationContainer } from '@react-navigation/native' import { Main } from './src/main' import { useFonts } from 'expo-font' import { ThemeContext, AppContext } from './src/context' -import * as themes from './src/theme' -import { IMAGE_MODELS, MODELS } from './constants' +import { getTheme } from './src/theme' +import { IMAGE_MODELS, MODELS, fetchModels } from './constants' import { GestureHandlerRootView } from 'react-native-gesture-handler' import { ChatModelModal } from './src/components/index' -import { Model } from './types' +import { Model, Theme } from './types' import { ActionSheetProvider } from '@expo/react-native-action-sheet' import AsyncStorage from '@react-native-async-storage/async-storage' +import { StatusBar } from 'expo-status-bar' import { BottomSheetBackdrop, BottomSheetModal, BottomSheetModalProvider, BottomSheetView, } from '@gorhom/bottom-sheet' -import { StyleSheet, LogBox } from 'react-native' +import { StyleSheet, LogBox, useColorScheme } from 'react-native' LogBox.ignoreLogs([ 'Key "cancelled" in the image picker result is deprecated and will be removed in SDK 48, use "canceled" instead', @@ -25,11 +26,14 @@ LogBox.ignoreLogs([ ]) export default function App() { + const colorScheme = useColorScheme() const [theme, setTheme] = useState('light') const [chatType, setChatType] = useState(MODELS.claudeOpus) const [imageModel, setImageModel] = useState(IMAGE_MODELS.nanoBanana.label) + const [models, setModels] = useState(Object.values(MODELS)) + const [systemPrompt, setSystemPrompt] = useState('') const [modalVisible, setModalVisible] = useState(false) - const [fontsLoaded] = useFonts({ + const [fontsLoaded] = useFonts({ 'Geist-Regular': require('./assets/fonts/Geist-Regular.otf'), 'Geist-Light': require('./assets/fonts/Geist-Light.otf'), 'Geist-Bold': require('./assets/fonts/Geist-Bold.otf'), @@ -43,6 +47,10 @@ export default function App() { useEffect(() => { configureStorage() + fetchModels().then(remoteModels => { + setModels(remoteModels) + setChatType(current => remoteModels.find(m => m.label === current.label) || current) + }) }, []) async function configureStorage() { @@ -50,9 +58,15 @@ export default function App() { const _theme = await AsyncStorage.getItem('rnai-theme') if (_theme) setTheme(_theme) const _chatType = await AsyncStorage.getItem('rnai-chatType') - if (_chatType) setChatType(JSON.parse(_chatType)) + if (_chatType) { + const parsed = JSON.parse(_chatType) + const model = MODELS[parsed.label] + if (model) setChatType(model) + } const _imageModel = await AsyncStorage.getItem('rnai-imageModel') if (_imageModel) setImageModel(_imageModel) + const _systemPrompt = await AsyncStorage.getItem('rnai-systemPrompt') + if (_systemPrompt) setSystemPrompt(_systemPrompt) } catch (err) { console.log('error configuring storage', err) } @@ -73,22 +87,28 @@ export default function App() { } } - function _setChatType(type) { + function _setChatType(type: Model) { setChatType(type) - AsyncStorage.setItem('rnai-chatType', JSON.stringify(type)) + AsyncStorage.setItem('rnai-chatType', JSON.stringify({ label: type.label })) } - function _setImageModel(model) { + function _setImageModel(model: string) { setImageModel(model) AsyncStorage.setItem('rnai-imageModel', model) } - function _setTheme(theme) { + function _setTheme(theme: string) { setTheme(theme) AsyncStorage.setItem('rnai-theme', theme) } - const bottomSheetStyles = getBottomsheetStyles(getTheme(theme)) + function _setSystemPrompt(prompt: string) { + setSystemPrompt(prompt) + AsyncStorage.setItem('rnai-systemPrompt', prompt) + } + + const currentTheme = getTheme(theme, colorScheme) + const bottomSheetStyles = getBottomsheetStyles(currentTheme) if (!fontsLoaded) return null return ( @@ -101,13 +121,17 @@ export default function App() { imageModel, setImageModel: _setImageModel, closeModal, + models, + systemPrompt, + setSystemPrompt: _setSystemPrompt, }} > +
@@ -138,7 +162,7 @@ export default function App() { ) } -const getBottomsheetStyles = theme => StyleSheet.create({ +const getBottomsheetStyles = (theme: Theme) => StyleSheet.create({ background: { paddingHorizontal: 24, backgroundColor: theme.backgroundColor @@ -150,16 +174,6 @@ const getBottomsheetStyles = theme => StyleSheet.create({ borderTopRightRadius: 24, }, handleIndicator: { - backgroundColor: 'rgba(255, 255, 255, .3)' + backgroundColor: theme.mutedForegroundColor } }) - -function getTheme(theme: any) { - let current - Object.keys(themes).forEach(_theme => { - if (_theme.includes(theme)) { - current = themes[_theme] - } - }) - return current -} diff --git a/app/constants.ts b/app/constants.ts index 49729771..b5755eb9 100644 --- a/app/constants.ts +++ b/app/constants.ts @@ -3,6 +3,7 @@ import { GeminiIcon } from './src/components/GeminiIcon' import { OpenAIIcon } from './src/components/OpenAIIcon' import { GLMIcon } from './src/components/GLMIcon' import { KimiIcon } from './src/components/KimiIcon' +import { Model, Provider } from './types' const normalizeDomain = (value?: string) => { if (!value) return '' @@ -19,50 +20,137 @@ const rawDomain = env === 'DEVELOPMENT' ? devUrl : prodUrl export const DOMAIN = normalizeDomain(rawDomain || devUrl || prodUrl || '') -export const MODELS = { +const PROVIDER_ICONS: Record = { + anthropic: AnthropicIcon, + openai: OpenAIIcon, + google: GeminiIcon, + zai: GLMIcon, + moonshot: KimiIcon +} + +export function getProviderIcon(provider: Provider): Model['icon'] { + return PROVIDER_ICONS[provider] || GeminiIcon +} + +export const MODELS: Record = { claudeFable5: { name: 'Claude Fable 5', label: 'claudeFable5', + provider: 'anthropic', + supportsVision: true, icon: AnthropicIcon }, claudeSonnet5: { name: 'Claude Sonnet 5', label: 'claudeSonnet5', + provider: 'anthropic', + supportsVision: true, icon: AnthropicIcon }, claudeOpus47: { name: 'Claude Opus 4.7', label: 'claudeOpus47', + provider: 'anthropic', + supportsVision: true, icon: AnthropicIcon }, claudeOpus: { name: 'Claude Opus', label: 'claudeOpus', + provider: 'anthropic', + supportsVision: true, icon: AnthropicIcon }, claudeSonnet: { name: 'Claude Sonnet', label: 'claudeSonnet', + provider: 'anthropic', + supportsVision: true, icon: AnthropicIcon }, claudeHaiku: { name: 'Claude Haiku', label: 'claudeHaiku', + provider: 'anthropic', + supportsVision: true, icon: AnthropicIcon }, claudeSonnet4: { name: 'Claude Sonnet 4', label: 'claudeSonnet4', + provider: 'anthropic', + supportsVision: true, icon: AnthropicIcon }, - gpt52: { name: 'GPT 5.2', label: 'gpt52', icon: OpenAIIcon }, - gpt5Mini: { name: 'GPT 5 Mini', label: 'gpt5Mini', icon: OpenAIIcon }, - gemini: { name: 'Gemini', label: 'gemini', icon: GeminiIcon }, - glm52: { name: 'GLM 5.2', label: 'glm52', icon: GLMIcon }, - kimiK27: { name: 'Kimi K2.7', label: 'kimiK27', icon: KimiIcon }, + gpt52: { + name: 'GPT 5.2', + label: 'gpt52', + provider: 'openai', + supportsVision: true, + icon: OpenAIIcon + }, + gpt5Mini: { + name: 'GPT 5 Mini', + label: 'gpt5Mini', + provider: 'openai', + supportsVision: true, + icon: OpenAIIcon + }, + gemini: { + name: 'Gemini', + label: 'gemini', + provider: 'google', + supportsVision: true, + icon: GeminiIcon + }, + glm52: { + name: 'GLM 5.2', + label: 'glm52', + provider: 'zai', + supportsVision: false, + icon: GLMIcon + }, + kimiK27: { + name: 'Kimi K2.7', + label: 'kimiK27', + provider: 'moonshot', + supportsVision: false, + icon: KimiIcon + } } export const IMAGE_MODELS = { nanoBanana: { name: 'Nano Banana (Gemini Flash Image)', label: 'nanoBanana' }, nanoBananaPro: { name: 'Nano Banana Pro (Gemini 3 Pro)', label: 'nanoBananaPro' }, } + +export const PROMPT_SUGGESTIONS = [ + 'Explain a concept like I am five', + 'Help me brainstorm project ideas', + 'Write a short story about space travel', + 'Summarize the plot of a classic novel' +] + +/** + * Fetches the model registry from the server so the server remains the + * single source of truth. Falls back to the local constants when offline. + */ +export async function fetchModels(): Promise { + const fallback = Object.values(MODELS) + if (!DOMAIN) return fallback + try { + const res = await fetch(`${DOMAIN}/models`) + if (!res.ok) return fallback + const data = await res.json() + if (!Array.isArray(data.chatModels) || !data.chatModels.length) return fallback + return data.chatModels.map((m: any) => ({ + name: m.name, + label: m.label, + provider: m.provider, + supportsVision: !!m.supportsVision, + icon: getProviderIcon(m.provider) + })) + } catch { + return fallback + } +} diff --git a/app/eslint.config.js b/app/eslint.config.js new file mode 100644 index 00000000..ece97001 --- /dev/null +++ b/app/eslint.config.js @@ -0,0 +1,10 @@ +// https://docs.expo.dev/guides/using-eslint/ +const { defineConfig } = require("eslint/config"); +const expoConfig = require("eslint-config-expo/flat"); + +module.exports = defineConfig([ + expoConfig, + { + ignores: ["dist/*"], + }, +]); diff --git a/app/package.json b/app/package.json index 0efc125f..9cc26fde 100644 --- a/app/package.json +++ b/app/package.json @@ -6,11 +6,14 @@ "start": "expo start", "android": "expo run:android", "ios": "expo run:ios", - "web": "expo start --web" + "web": "expo start --web", + "typecheck": "tsc --noEmit", + "lint": "expo lint" }, "dependencies": { "@expo/metro-runtime": "~6.1.2", "@expo/react-native-action-sheet": "^4.0.1", + "@expo/vector-icons": "^15.0.3", "@gorhom/bottom-sheet": "^5.2.14", "@react-native-async-storage/async-storage": "3.1.1", "@react-native-camera-roll/camera-roll": "^7.10.2", @@ -23,7 +26,11 @@ "expo-document-picker": "~14.0.8", "expo-file-system": "~19.0.23", "expo-font": "~14.0.12", + "expo-haptics": "~15.0.8", "expo-image-picker": "~17.0.11", + "expo-media-library": "~18.2.1", + "expo-sharing": "~14.0.8", + "expo-speech": "~14.0.8", "expo-splash-screen": "~31.0.13", "expo-status-bar": "~3.0.9", "punycode": "^2.3.1", @@ -43,6 +50,8 @@ "devDependencies": { "@babel/core": "^7.24.0", "@types/react": "~19.2.17", + "eslint": "^9.39.4", + "eslint-config-expo": "^57.0.0", "typescript": "~5.9.3" }, "private": true diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index c921efe2..d5fd574c 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@expo/react-native-action-sheet': specifier: ^4.0.1 version: 4.1.1(@types/react@19.2.17)(react@19.1.0) + '@expo/vector-icons': + specifier: ^15.0.3 + version: 15.0.3(expo-font@14.0.12(expo@54.0.35)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0) '@gorhom/bottom-sheet': specifier: ^5.2.14 version: 5.2.14(@types/react@19.2.17)(react-native-gesture-handler@3.0.2(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.2.3(react-native-worklets@0.7.2(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0) @@ -50,9 +53,21 @@ importers: expo-font: specifier: ~14.0.12 version: 14.0.12(expo@54.0.35)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0) + expo-haptics: + specifier: ~15.0.8 + version: 15.0.8(expo@54.0.35) expo-image-picker: specifier: ~17.0.11 version: 17.0.11(expo@54.0.35) + expo-media-library: + specifier: ~18.2.1 + version: 18.2.1(expo@54.0.35)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0)) + expo-sharing: + specifier: ~14.0.8 + version: 14.0.8(expo@54.0.35) + expo-speech: + specifier: ~14.0.8 + version: 14.0.8(expo@54.0.35) expo-splash-screen: specifier: ~31.0.13 version: 31.0.13(expo@54.0.35) @@ -105,6 +120,12 @@ importers: '@types/react': specifier: ~19.2.17 version: 19.2.17 + eslint: + specifier: ^9.39.4 + version: 9.39.4 + eslint-config-expo: + specifier: ^57.0.0 + version: 57.0.0(eslint@9.39.4)(typescript@5.9.3) typescript: specifier: ~5.9.3 version: 5.9.3 @@ -652,6 +673,53 @@ packages: resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} engines: {node: '>=6.9.0'} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@expo/cli@54.0.25': resolution: {integrity: sha512-WnUqIb8oMBhtwSfIqdCHCzcaDIpLNXItRVd5miuvWi4GO0SGo89PSsAkbVJ+LJgcaY+v5rbgMELJS9I/CqOulA==} hasBin: true @@ -803,6 +871,26 @@ packages: react: '*' react-native: '*' + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} @@ -862,6 +950,16 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + '@react-native-async-storage/async-storage@3.1.1': resolution: {integrity: sha512-z+PnLz1n6ECKhgoHZHkfc+dijXZEyZnNFSajbtE0NEbsJhmX8x9GlOeiMQIKX2E4DUqPSgfIh4FYBv1M49KgPQ==} peerDependencies: @@ -1018,6 +1116,9 @@ packages: react: '>=16.2.0' react-native: '>=0.50.4' + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -1027,6 +1128,9 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1039,6 +1143,9 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -1056,6 +1163,12 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/node@25.1.0': resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==} @@ -1080,10 +1193,189 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@typescript-eslint/eslint-plugin@8.63.0': + resolution: {integrity: sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.63.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.63.0': + resolution: {integrity: sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.63.0': + resolution: {integrity: sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.63.0': + resolution: {integrity: sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.63.0': + resolution: {integrity: sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.63.0': + resolution: {integrity: sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.63.0': + resolution: {integrity: sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.63.0': + resolution: {integrity: sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.63.0': + resolution: {integrity: sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.63.0': + resolution: {integrity: sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} deprecated: Potential CWE-502 - Update to 1.3.1 or higher + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.12.2': + resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.12.2': + resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} + cpu: [arm64] + os: [openharmony] + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==} + cpu: [x64] + os: [win32] + '@urql/core@5.2.0': resolution: {integrity: sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==} @@ -1105,8 +1397,13 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} engines: {node: '>=0.4.0'} hasBin: true @@ -1114,6 +1411,9 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + anser@1.4.10: resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} @@ -1157,12 +1457,52 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + async-limiter@1.0.1: resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1295,6 +1635,22 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + camelcase@5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -1447,6 +1803,18 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -1480,6 +1848,9 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -1487,10 +1858,18 @@ packages: defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + define-lazy-prop@2.0.0: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -1503,6 +1882,10 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -1524,6 +1907,10 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -1552,6 +1939,42 @@ packages: error-stack-parser@2.1.4: resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + es-abstract-get@1.0.0: + resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} + engines: {node: '>= 0.4'} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + 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-iterator-helpers@1.3.3: + resolution: {integrity: sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.4: + resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} + engines: {node: '>= 0.4'} + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1571,11 +1994,127 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-config-expo@57.0.0: + resolution: {integrity: sha512-T7OTN9xrSZYjLw4qTkL1Mn2WfAUVmMGY38+OYAcraI1uiTFVH6jfkSkv84WLTqnneblLcb6AsMDV+SHcUj3hGw==} + peerDependencies: + eslint: '>=8.10' + + eslint-import-resolver-node@0.3.10: + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} + + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.14.0: + resolution: {integrity: sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-expo@1.1.0: + resolution: {integrity: sha512-vPP0EPx7IA7ZfP49dY4rq9RV5jqkFWG+Pih3/oGjzIRjMI+ogcOE8i6isYkLXAdw/yvFV2BRZkTaQaiOGQqn6Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + eslint: '>=8.10' + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -1625,6 +2164,11 @@ packages: react: '*' react-native: '*' + expo-haptics@15.0.8: + resolution: {integrity: sha512-lftutojy8Qs8zaDzzjwM3gKHFZ8bOOEZDCkmh2Ddpe95Ra6kt2izeOfOfKuP/QEh0MZ1j9TfqippyHdRd1ZM9g==} + peerDependencies: + expo: '*' + expo-image-loader@6.0.0: resolution: {integrity: sha512-nKs/xnOGw6ACb4g26xceBD57FKLFkSwEUTDXEDF3Gtcu3MqF3ZIYd3YM+sSb1/z9AKV1dYT7rMSGVNgsveXLIQ==} peerDependencies: @@ -1641,6 +2185,12 @@ packages: expo: '*' react: '*' + expo-media-library@18.2.1: + resolution: {integrity: sha512-dV1acx6Aseu+I5hmF61wY8UkD4vdt8d7YXHDfgNp6ZSs06qxayUxgrBsiG2eigLe54VLm3ycbFBbWi31lhfsCA==} + peerDependencies: + expo: '*' + react-native: '*' + expo-modules-autolinking@3.0.26: resolution: {integrity: sha512-WOaud6UKg16ciCOj8raKcMOoKFMHLXKI29U29yhgu1lf+Y7VxJyCktUcYo6AM+ccZ7zLD1uWZdMtgnpf+95OXA==} hasBin: true @@ -1655,6 +2205,16 @@ packages: resolution: {integrity: sha512-mcmyML3oXcqFUXUxtdtCL1O00ztNI2v76d+MdniXRUgHNxIcHZ05zo+DqBaOOT6LQnPk4vA4YHqQl7iGUfRb3g==} engines: {node: '>=20.16.0'} + expo-sharing@14.0.8: + resolution: {integrity: sha512-A1pPr2iBrxypFDCWVAESk532HK+db7MFXbvO2sCV9ienaFXAk7lIBm6bkqgE6vzRd9O3RGdEGzYx80cYlc089Q==} + peerDependencies: + expo: '*' + + expo-speech@14.0.8: + resolution: {integrity: sha512-UjBFCFv58nutlLw92L7kUS0ZjbOOfaTdiEv/HbjvMrT6BfldoOLLBZbaEcEhDdZK36NY/kass0Kzxk+co6vxSQ==} + peerDependencies: + expo: '*' + expo-splash-screen@31.0.13: resolution: {integrity: sha512-1epJLC1cDlwwj089R2h8cxaU5uk4ONVAC+vzGiTZH4YARQhL4Stlz1MbR6yAS173GMosvkE6CAeihR7oIbCkDA==} peerDependencies: @@ -1695,6 +2255,9 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -1713,6 +2276,10 @@ packages: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1729,16 +2296,31 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + find-up@8.0.0: resolution: {integrity: sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==} engines: {node: '>=20'} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + flow-enums-runtime@0.0.6: resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} fontfaceobserver@2.3.0: resolution: {integrity: sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==} + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + freeport-async@2.0.0: resolution: {integrity: sha512-K7od3Uw45AJg00XUmy15+Hae2hOcgKcmN3/EF6Y7i01O0gaqiRx8sUSpsb9+BRNL8RPBrhzPsVfy8q9ADlJuWQ==} engines: {node: '>=8'} @@ -1758,6 +2340,17 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function.prototype.name@1.2.0: + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1766,14 +2359,33 @@ packages: 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-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} - getenv@2.0.0: - resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + getenv@2.0.0: + resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} engines: {node: '>=6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@13.0.0: resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} engines: {node: 20 || >=22} @@ -1786,9 +2398,29 @@ packages: resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} engines: {node: '>=4'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + 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'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -1797,16 +2429,41 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + 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'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + hermes-estree@0.29.1: resolution: {integrity: sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==} hermes-estree@0.32.0: resolution: {integrity: sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==} + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + hermes-parser@0.29.1: resolution: {integrity: sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA==} @@ -1841,11 +2498,19 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + image-size@1.2.1: resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} engines: {node: '>=16.x'} hasBin: true + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} @@ -1863,33 +2528,143 @@ packages: inline-style-prefixer@7.0.1: resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==} + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + is-arrayish@0.3.4: resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + is-docker@2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} hasBin: true + is-document.all@1.0.0: + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1901,6 +2676,10 @@ packages: resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} engines: {node: '>=8'} + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + jest-environment-node@29.7.0: resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1959,11 +2738,31 @@ packages: 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-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -1976,6 +2775,10 @@ packages: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + lighthouse-logger@1.4.2: resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} @@ -2063,6 +2866,10 @@ packages: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + locate-path@8.0.0: resolution: {integrity: sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==} engines: {node: '>=20'} @@ -2070,6 +2877,9 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} @@ -2104,6 +2914,10 @@ packages: marky@1.3.0: resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdn-data@2.0.14: resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} @@ -2209,6 +3023,9 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -2243,6 +3060,14 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -2254,6 +3079,10 @@ packages: nested-error-stacks@2.0.1: resolution: {integrity: sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==} + node-exports-info@1.6.2: + resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==} + engines: {node: '>= 0.4'} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -2295,6 +3124,34 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + 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'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -2322,10 +3179,18 @@ packages: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + ora@3.4.0: resolution: {integrity: sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==} engines: {node: '>=6'} + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -2342,6 +3207,10 @@ packages: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + p-locate@6.0.0: resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -2350,6 +3219,10 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parse-png@2.1.0: resolution: {integrity: sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==} engines: {node: '>=10'} @@ -2400,6 +3273,10 @@ packages: resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} engines: {node: '>=4.0.0'} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -2407,6 +3284,10 @@ packages: resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + pretty-bytes@5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} @@ -2577,6 +3458,10 @@ packages: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + regenerate-unicode-properties@10.2.2: resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} engines: {node: '>=4'} @@ -2587,6 +3472,10 @@ packages: regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + regexpu-core@6.4.0: resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} engines: {node: '>=4'} @@ -2610,6 +3499,10 @@ packages: resolution: {integrity: sha512-nYzyjnFcPNGR3lx9lwPPPnuQxv6JWEZd2Ci0u9opN7N5zUEPIhY/GbL3vMGOr2UXwEg9WwSyV9X9Y/kLFgPsOg==} engines: {node: '>= 4.0.0'} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -2618,6 +3511,9 @@ packages: resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve-workspace-root@2.0.1: resolution: {integrity: sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==} @@ -2633,6 +3529,11 @@ packages: resolve@1.7.1: resolution: {integrity: sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==} + resolve@2.0.0-next.7: + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} + engines: {node: '>= 0.4'} + hasBin: true + restore-cursor@2.0.0: resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} engines: {node: '>=4'} @@ -2642,9 +3543,21 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + sax@1.4.4: resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==} engines: {node: '>=11.0.0'} @@ -2673,6 +3586,18 @@ packages: resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} engines: {node: '>= 0.8.0'} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} @@ -2695,6 +3620,22 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + 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.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -2737,6 +3678,9 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -2759,6 +3703,10 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + stream-buffers@2.2.0: resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} engines: {node: '>= 0.10.0'} @@ -2771,6 +3719,25 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.11: + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.10: + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + strip-ansi@5.2.0: resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} engines: {node: '>=6'} @@ -2779,10 +3746,18 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + structured-headers@0.4.1: resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} @@ -2864,9 +3839,25 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} @@ -2879,6 +3870,22 @@ packages: resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} engines: {node: '>=8'} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + engines: {node: '>= 0.4'} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -2891,6 +3898,10 @@ packages: uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -2926,12 +3937,18 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unrs-resolver@1.12.2: + resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} + 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-latest-callback@0.2.6: resolution: {integrity: sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==} peerDependencies: @@ -2992,6 +4009,22 @@ packages: whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.22: + resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} + engines: {node: '>= 0.4'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -3000,6 +4033,10 @@ packages: wonka@6.3.5: resolution: {integrity: sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==} + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -3094,6 +4131,15 @@ packages: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + snapshots: '@0no-co/graphql.web@1.2.0': {} @@ -3752,6 +4798,68 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': + dependencies: + eslint: 9.39.4 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + '@expo/cli@54.0.25(expo@54.0.35)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))': dependencies: '@0no-co/graphql.web': 1.2.0 @@ -4091,6 +5199,22 @@ snapshots: react: 19.1.0 react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0) + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@isaacs/fs-minipass@4.0.1': dependencies: minipass: 7.1.2 @@ -4184,6 +5308,15 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@nolyfill/is-core-module@1.0.39': {} + '@react-native-async-storage/async-storage@3.1.1(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0)': dependencies: idb: 8.0.3 @@ -4418,6 +5551,8 @@ snapshots: - '@react-native/assets-registry' - expo-font + '@rtsao/scc@1.1.0': {} + '@sinclair/typebox@0.27.8': {} '@sinonjs/commons@3.0.1': @@ -4428,6 +5563,11 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.28.6 @@ -4449,6 +5589,8 @@ snapshots: dependencies: '@babel/types': 7.28.6 + '@types/estree@1.0.9': {} + '@types/graceful-fs@4.1.9': dependencies: '@types/node': 25.1.0 @@ -4468,6 +5610,10 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + '@types/node@25.1.0': dependencies: undici-types: 7.16.0 @@ -4494,8 +5640,169 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 + '@typescript-eslint/eslint-plugin@8.63.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.63.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.63.0 + '@typescript-eslint/type-utils': 8.63.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/utils': 8.63.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.63.0 + eslint: 9.39.4 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.63.0 + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/typescript-estree': 8.63.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.63.0 + debug: 4.4.3 + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.63.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.63.0(typescript@5.9.3) + '@typescript-eslint/types': 8.63.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.63.0': + dependencies: + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/visitor-keys': 8.63.0 + + '@typescript-eslint/tsconfig-utils@8.63.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.63.0(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/typescript-estree': 8.63.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.63.0(eslint@9.39.4)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.63.0': {} + + '@typescript-eslint/typescript-estree@8.63.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.63.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.63.0(typescript@5.9.3) + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/visitor-keys': 8.63.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.63.0(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@typescript-eslint/scope-manager': 8.63.0 + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/typescript-estree': 8.63.0(typescript@5.9.3) + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.63.0': + dependencies: + '@typescript-eslint/types': 8.63.0 + eslint-visitor-keys: 5.0.1 + '@ungap/structured-clone@1.3.0': {} + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + optional: true + + '@unrs/resolver-binding-android-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + optional: true + '@urql/core@5.2.0': dependencies: '@0no-co/graphql.web': 1.2.0 @@ -4519,10 +5826,21 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - acorn@8.15.0: {} + acorn-jsx@5.3.2(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} agent-base@7.1.4: {} + ajv@6.15.0: + 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 + anser@1.4.10: {} ansi-escapes@4.3.2: @@ -4558,10 +5876,83 @@ snapshots: argparse@2.0.1: {} + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + asap@2.0.6: {} + async-function@1.0.0: {} + async-limiter@1.0.1: {} + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + babel-jest@29.7.0(@babel/core@7.28.6): dependencies: '@babel/core': 7.28.6 @@ -4755,6 +6146,25 @@ snapshots: bytes@3.1.2: {} + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + camelcase@5.3.1: {} camelcase@6.3.0: {} @@ -4922,6 +6332,24 @@ snapshots: csstype@3.2.3: {} + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + debug@2.6.9: dependencies: ms: 2.0.0 @@ -4938,20 +6366,38 @@ snapshots: deep-extend@0.6.0: {} + deep-is@0.1.4: {} + deepmerge@4.3.1: {} defaults@1.0.4: dependencies: clone: 1.0.4 + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + define-lazy-prop@2.0.0: {} + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + depd@2.0.0: {} destroy@1.2.0: {} detect-libc@2.1.2: {} + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -4976,6 +6422,12 @@ snapshots: dotenv@16.4.7: {} + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + ee-first@1.1.1: {} electron-to-chromium@1.5.283: {} @@ -4994,6 +6446,117 @@ snapshots: dependencies: stackframe: 1.3.4 + es-abstract-get@1.0.0: + dependencies: + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + is-callable: 1.2.7 + object-inspect: 1.13.4 + + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.4 + function.prototype.name: 1.2.0 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.4 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.11 + string.prototype.trimend: 1.0.10 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.8 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.22 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.3.3: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + math-intrinsics: 1.1.0 + + es-object-atoms@1.1.2: + 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 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.4: + dependencies: + es-abstract-get: 1.0.0 + es-define-property: 1.0.1 + es-errors: 1.3.0 + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -5004,8 +6567,198 @@ snapshots: escape-string-regexp@4.0.0: {} + eslint-config-expo@57.0.0(eslint@9.39.4)(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.63.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 8.63.0(eslint@9.39.4)(typescript@5.9.3) + eslint: 9.39.4 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4) + eslint-plugin-expo: 1.1.0(eslint@9.39.4)(typescript@5.9.3) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4))(eslint@9.39.4) + eslint-plugin-react: 7.37.5(eslint@9.39.4) + eslint-plugin-react-hooks: 7.1.1(eslint@9.39.4) + globals: 16.5.0 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + - typescript + + eslint-import-resolver-node@0.3.10: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 2.0.0-next.7 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 9.39.4 + get-tsconfig: 4.14.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.15 + unrs-resolver: 1.12.2 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4))(eslint@9.39.4) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4))(eslint@9.39.4): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.63.0(eslint@9.39.4)(typescript@5.9.3) + eslint: 9.39.4 + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4) + transitivePeerDependencies: + - supports-color + + eslint-plugin-expo@1.1.0(eslint@9.39.4)(typescript@5.9.3): + dependencies: + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/utils': 8.63.0(eslint@9.39.4)(typescript@5.9.3) + eslint: 9.39.4 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4))(eslint@9.39.4): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.4 + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4))(eslint@9.39.4))(eslint@9.39.4) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.10 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.63.0(eslint@9.39.4)(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-react-hooks@7.1.1(eslint@9.39.4): + dependencies: + '@babel/core': 7.28.6 + '@babel/parser': 7.28.6 + eslint: 9.39.4 + hermes-parser: 0.25.1 + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react@7.37.5(eslint@9.39.4): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.3.3 + eslint: 9.39.4 + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.7 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + eslint-visitor-keys: 4.2.1 + esprima@4.0.1: {} + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + etag@1.8.1: {} event-target-shim@5.0.1: {} @@ -5053,6 +6806,10 @@ snapshots: react: 19.1.0 react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0) + expo-haptics@15.0.8(expo@54.0.35): + dependencies: + expo: 54.0.35(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0) + expo-image-loader@6.0.0(expo@54.0.35): dependencies: expo: 54.0.35(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0) @@ -5067,6 +6824,11 @@ snapshots: expo: 54.0.35(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0) react: 19.1.0 + expo-media-library@18.2.1(expo@54.0.35)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0)): + dependencies: + expo: 54.0.35(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0) + react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0) + expo-modules-autolinking@3.0.26: dependencies: '@expo/spawn-async': 1.7.2 @@ -5083,6 +6845,14 @@ snapshots: expo-server@1.0.7: {} + expo-sharing@14.0.8(expo@54.0.35): + dependencies: + expo: 54.0.35(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0) + + expo-speech@14.0.8(expo@54.0.35): + dependencies: + expo: 54.0.35(@babel/core@7.28.6)(@expo/metro-runtime@6.1.2)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.2.17)(react@19.1.0))(react@19.1.0) + expo-splash-screen@31.0.13(expo@54.0.35): dependencies: '@expo/prebuild-config': 54.0.8(expo@54.0.35) @@ -5139,6 +6909,8 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-levenshtein@2.0.6: {} + fb-watchman@2.0.2: dependencies: bser: 2.1.1 @@ -5161,6 +6933,10 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -5184,15 +6960,31 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + find-up@8.0.0: dependencies: locate-path: 8.0.0 unicorn-magic: 0.3.0 + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + flow-enums-runtime@0.0.6: {} fontfaceobserver@2.3.0: {} + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + freeport-async@2.0.0: {} fresh@0.5.2: {} @@ -5204,14 +6996,62 @@ snapshots: function-bind@1.1.2: {} + function.prototype.name@1.2.0: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + es-define-property: 1.0.1 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + hasown: 2.0.4 + is-callable: 1.2.7 + is-document.all: 1.0.0 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + 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.2 + 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-package-type@0.1.0: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + getenv@2.0.0: {} + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@13.0.0: dependencies: minimatch: 10.2.5 @@ -5231,20 +7071,57 @@ snapshots: dependencies: ini: 1.3.8 + globals@14.0.0: {} + + globals@16.5.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + graceful-fs@4.2.11: {} + has-bigints@1.1.0: {} + has-flag@3.0.0: {} has-flag@4.0.0: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hasown@2.0.2: dependencies: function-bind: 1.1.2 + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hermes-estree@0.25.1: {} + hermes-estree@0.29.1: {} hermes-estree@0.32.0: {} + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + hermes-parser@0.29.1: dependencies: hermes-estree: 0.29.1 @@ -5284,10 +7161,17 @@ snapshots: ignore@5.3.2: {} + ignore@7.0.5: {} + image-size@1.2.1: dependencies: queue: 6.0.2 + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + imurmurhash@0.1.4: {} inflight@1.0.6: @@ -5303,26 +7187,148 @@ snapshots: dependencies: css-in-js-utils: 3.1.0 + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.1 + invariant@2.2.4: dependencies: loose-envify: 1.4.0 + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-arrayish@0.3.4: {} + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.7.3 + + is-callable@1.2.7: {} + is-core-module@2.16.1: dependencies: hasown: 2.0.2 + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-docker@2.2.1: {} + is-document.all@1.0.0: + dependencies: + call-bound: 1.0.4 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-number@7.0.0: {} + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.22 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-wsl@2.2.0: dependencies: is-docker: 2.2.1 + isarray@2.0.5: {} + isexe@2.0.0: {} istanbul-lib-coverage@3.2.2: {} @@ -5337,6 +7343,15 @@ snapshots: transitivePeerDependencies: - supports-color + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + jest-environment-node@29.7.0: dependencies: '@jest/environment': 29.7.0 @@ -5426,14 +7441,40 @@ snapshots: jsesc@3.1.0: {} + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + json5@2.2.3: {} + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + kleur@3.0.3: {} lan-network@0.2.1: {} leven@3.1.0: {} + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + lighthouse-logger@1.4.2: dependencies: debug: 2.6.9 @@ -5500,12 +7541,18 @@ snapshots: dependencies: p-locate: 4.1.0 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + locate-path@8.0.0: dependencies: p-locate: 6.0.0 lodash.debounce@4.0.8: {} + lodash.merge@4.6.2: {} + lodash.throttle@4.1.1: {} log-symbols@2.2.0: @@ -5541,6 +7588,8 @@ snapshots: marky@1.3.0: {} + math-intrinsics@1.1.0: {} + mdn-data@2.0.14: {} mdurl@2.0.0: {} @@ -5751,6 +7800,10 @@ snapshots: dependencies: brace-expansion: 1.1.12 + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -5777,12 +7830,23 @@ snapshots: nanoid@3.3.11: {} + napi-postinstall@0.3.4: {} + + natural-compare@1.4.0: {} + negotiator@0.6.3: {} negotiator@0.6.4: {} nested-error-stacks@2.0.1: {} + node-exports-info@1.6.2: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 @@ -5814,6 +7878,46 @@ snapshots: object-assign@4.1.1: {} + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -5843,6 +7947,15 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + ora@3.4.0: dependencies: chalk: 2.4.2 @@ -5852,6 +7965,12 @@ snapshots: strip-ansi: 5.2.0 wcwidth: 1.0.1 + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -5868,12 +7987,20 @@ snapshots: dependencies: p-limit: 2.3.0 + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + p-locate@6.0.0: dependencies: p-limit: 4.0.0 p-try@2.2.0: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parse-png@2.1.0: dependencies: pngjs: 3.4.0 @@ -5909,6 +8036,8 @@ snapshots: pngjs@3.4.0: {} + possible-typed-array-names@1.1.0: {} + postcss-value-parser@4.2.0: {} postcss@8.4.49: @@ -5917,6 +8046,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prelude-ls@1.2.1: {} + pretty-bytes@5.6.0: {} pretty-format@29.7.0: @@ -6143,6 +8274,17 @@ snapshots: react@19.1.0: {} + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + regenerate-unicode-properties@10.2.2: dependencies: regenerate: 1.4.2 @@ -6151,6 +8293,15 @@ snapshots: regenerator-runtime@0.13.11: {} + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + regexpu-core@6.4.0: dependencies: regenerate: 1.4.2 @@ -6176,12 +8327,16 @@ snapshots: rc: 1.2.8 resolve: 1.7.1 + resolve-from@4.0.0: {} + resolve-from@5.0.0: {} resolve-global@1.0.0: dependencies: global-dirs: 0.1.1 + resolve-pkg-maps@1.0.0: {} + resolve-workspace-root@2.0.1: {} resolve.exports@2.0.3: {} @@ -6196,6 +8351,15 @@ snapshots: dependencies: path-parse: 1.0.7 + resolve@2.0.0-next.7: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + node-exports-info: 1.6.2 + object-keys: 1.1.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@2.0.0: dependencies: onetime: 2.0.1 @@ -6205,8 +8369,27 @@ snapshots: dependencies: glob: 7.2.3 + safe-array-concat@1.1.4: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + sax@1.4.4: {} scheduler@0.26.0: {} @@ -6244,6 +8427,28 @@ snapshots: transitivePeerDependencies: - supports-color + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + setimmediate@1.0.5: {} setprototypeof@1.2.0: {} @@ -6258,6 +8463,34 @@ snapshots: shell-quote@1.8.3: {} + side-channel-list@1.0.1: + 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.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + signal-exit@3.0.7: {} simple-plist@1.3.1: @@ -6291,6 +8524,8 @@ snapshots: sprintf-js@1.0.3: {} + stable-hash@0.0.5: {} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -6307,6 +8542,11 @@ snapshots: statuses@2.0.2: {} + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + stream-buffers@2.2.0: {} strict-uri-encode@2.0.0: {} @@ -6317,6 +8557,51 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.1 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.trim@1.2.11: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + has-property-descriptors: 1.0.2 + safe-regex-test: 1.1.0 + + string.prototype.trimend@1.0.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + strip-ansi@5.2.0: dependencies: ansi-regex: 4.1.1 @@ -6325,8 +8610,12 @@ snapshots: dependencies: ansi-regex: 5.0.1 + strip-bom@3.0.0: {} + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} + structured-headers@0.4.1: {} styleq@0.1.3: {} @@ -6378,7 +8667,7 @@ snapshots: terser@5.46.0: dependencies: '@jridgewell/source-map': 0.3.11 - acorn: 8.15.0 + acorn: 8.17.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -6413,20 +8702,78 @@ snapshots: tr46@0.0.3: {} + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-interface-checker@0.1.13: {} + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: + optional: true + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-detect@4.0.8: {} type-fest@0.21.3: {} type-fest@0.7.1: {} + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.8: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + typescript@5.9.3: {} ua-parser-js@1.0.41: {} uc.micro@2.1.0: {} + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + undici-types@7.16.0: {} undici@6.23.0: {} @@ -6450,12 +8797,43 @@ snapshots: unpipe@1.0.0: {} + unrs-resolver@1.12.2: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.12.2 + '@unrs/resolver-binding-android-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-x64': 1.12.2 + '@unrs/resolver-binding-freebsd-x64': 1.12.2 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-arm64-musl': 1.12.2 + '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-loong64-musl': 1.12.2 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2 + '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-musl': 1.12.2 + '@unrs/resolver-binding-openharmony-arm64': 1.12.2 + '@unrs/resolver-binding-wasm32-wasi': 1.12.2 + '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2 + '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 + '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 + 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-latest-callback@0.2.6(react@19.1.0): dependencies: react: 19.1.0 @@ -6503,12 +8881,55 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.2.0 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.22 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.22: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@2.0.2: dependencies: isexe: 2.0.0 wonka@6.3.5: {} + word-wrap@1.2.5: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -6567,3 +8988,9 @@ snapshots: yocto-queue@0.1.0: {} yocto-queue@1.2.2: {} + + zod-validation-error@4.0.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@4.4.3: {} diff --git a/app/src/components/AnthropicIcon.tsx b/app/src/components/AnthropicIcon.tsx index e9b4bdcc..e120f1c9 100644 --- a/app/src/components/AnthropicIcon.tsx +++ b/app/src/components/AnthropicIcon.tsx @@ -1,9 +1,9 @@ import Svg, { Path } from 'react-native-svg'; interface IAnthropicIcon { - size: number + size?: number theme: any - selected: boolean + selected?: boolean } export function AnthropicIcon({ diff --git a/app/src/components/ChatModelModal.tsx b/app/src/components/ChatModelModal.tsx index 68991f5a..094a66f0 100644 --- a/app/src/components/ChatModelModal.tsx +++ b/app/src/components/ChatModelModal.tsx @@ -1,15 +1,15 @@ import { useContext } from 'react' import { ThemeContext, AppContext } from '../context' -import { MODELS } from '../../constants' import { View, Text, StyleSheet, TouchableHighlight } from 'react-native' +import { Model, Theme } from '../../types' -export function ChatModelModal({ handlePresentModalPress }) { +export function ChatModelModal({ handlePresentModalPress }: { handlePresentModalPress: () => void }) { const { theme } = useContext(ThemeContext) - const { setChatType, chatType } = useContext(AppContext) + const { setChatType, chatType, models } = useContext(AppContext) const styles = getStyles(theme) - const options = Object.values(MODELS) + const options = models - function _setChatType(v) { + function _setChatType(v: Model) { setChatType(v) handlePresentModalPress() } @@ -27,6 +27,8 @@ export function ChatModelModal({ handlePresentModalPress }) { _setChatType(option)} + accessibilityRole="button" + accessibilityLabel={`Select model ${option.name}`} key={index}> + + + + {chatType.name} + + ({ - theme: {}, + theme: lightTheme, setTheme: () => null, themeName: '' }) @@ -15,8 +16,11 @@ const AppContext = createContext({ handlePresentModalPress: () => null, setImageModel: () => null, closeModal: () => null, + models: Object.values(MODELS), + systemPrompt: '', + setSystemPrompt: () => null, }) export { ThemeContext, AppContext -} \ No newline at end of file +} diff --git a/app/src/hooks/useChatStream.ts b/app/src/hooks/useChatStream.ts new file mode 100644 index 00000000..a2ce3510 --- /dev/null +++ b/app/src/hooks/useChatStream.ts @@ -0,0 +1,131 @@ +import { useRef, useState, useCallback, useEffect } from 'react' +import { getEventSource, getChatRoute } from '../utils' +import { ChatMessage, Model } from '../../types' + +const FLUSH_INTERVAL_MS = 50 + +export interface StreamCallbacks { + onToken: (fullResponse: string) => void + onDone: (fullResponse: string) => void + onError: (message: string) => void +} + +interface WireMessage { + role: 'system' | 'user' | 'assistant' + content: string + image?: { mimeType: string, data: string } +} + +function toWireMessages(messages: ChatMessage[], systemPrompt: string): WireMessage[] { + const wire: WireMessage[] = [] + if (systemPrompt.trim()) { + wire.push({ role: 'system', content: systemPrompt.trim() }) + } + for (const message of messages) { + wire.push({ + role: message.role, + content: message.content, + ...(message.image?.base64 + ? { image: { mimeType: message.image.mimeType, data: message.image.base64 } } + : {}) + }) + } + return wire +} + +/** + * Streams a chat completion for any provider through the server proxy. + * Batches token updates to avoid re-rendering on every SSE event and + * supports cancelling an in-flight response. + */ +export function useChatStream() { + const [streaming, setStreaming] = useState(false) + const esRef = useRef | null>(null) + const flushTimerRef = useRef | null>(null) + + const stop = useCallback(() => { + if (flushTimerRef.current) { + clearTimeout(flushTimerRef.current) + flushTimerRef.current = null + } + esRef.current?.close() + esRef.current = null + setStreaming(false) + }, []) + + useEffect(() => stop, [stop]) + + const send = useCallback(( + model: Model, + messages: ChatMessage[], + systemPrompt: string, + callbacks: StreamCallbacks + ) => { + stop() + setStreaming(true) + + let response = '' + let pendingFlush = false + let finished = false + + const flush = () => { + flushTimerRef.current = null + pendingFlush = false + callbacks.onToken(response) + } + + const scheduleFlush = () => { + if (pendingFlush) return + pendingFlush = true + flushTimerRef.current = setTimeout(flush, FLUSH_INTERVAL_MS) + } + + const finish = (handler: () => void) => { + if (finished) return + finished = true + if (flushTimerRef.current) { + clearTimeout(flushTimerRef.current) + flushTimerRef.current = null + } + esRef.current?.close() + esRef.current = null + setStreaming(false) + handler() + } + + const es = getEventSource({ + body: { + model: model.label, + messages: toWireMessages(messages, systemPrompt) + }, + type: getChatRoute(model) + }) + esRef.current = es + + es.addEventListener('message', (event: any) => { + if (event.data === '[DONE]') { + finish(() => callbacks.onDone(response)) + return + } + try { + const data = JSON.parse(event.data) + if (data?.error) { + finish(() => callbacks.onError(data.error)) + return + } + if (typeof data?.content === 'string') { + response += data.content + scheduleFlush() + } + } catch {} + }) + + es.addEventListener('error', (event: any) => { + finish(() => callbacks.onError( + event?.message || 'Unable to reach the server. Check your connection and try again.' + )) + }) + }, [stop]) + + return { streaming, send, stop } +} diff --git a/app/src/main.tsx b/app/src/main.tsx index 78f382d3..13201a2a 100644 --- a/app/src/main.tsx +++ b/app/src/main.tsx @@ -1,9 +1,9 @@ -import { useContext, useRef, useCallback } from 'react'; -import { StyleSheet, Text, View, Button } from 'react-native'; +import { useContext } from 'react'; +import { StyleSheet, View } from 'react-native'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { Chat, Images, Settings } from './screens' import { Header } from './components' -import FeatherIcon from '@expo/vector-icons/Feather' +import { Feather as FeatherIcon } from '@expo/vector-icons' import { SafeAreaProvider, useSafeAreaInsets, diff --git a/app/src/screens/assistant.tsx b/app/src/screens/assistant.tsx deleted file mode 100644 index acac94eb..00000000 --- a/app/src/screens/assistant.tsx +++ /dev/null @@ -1,791 +0,0 @@ -import { useState, useContext, useRef } from 'react' -import { - View, - Text, - KeyboardAvoidingView, - StyleSheet, - TouchableHighlight, - TextInput, - FlatList, - ScrollView, - Dimensions, - Keyboard, - ActivityIndicator -} from 'react-native' -import { ThemeContext } from '../context' -import { DOMAIN } from '../../constants' -import { useActionSheet } from '@expo/react-native-action-sheet' -import Ionicons from '@expo/vector-icons/Ionicons' -import MaterialCommunityIcons from '@expo/vector-icons/MaterialCommunityIcons' -import Markdown from '@ronradtke/react-native-markdown-display' -import * as DocumentPicker from 'expo-document-picker' -import * as Clipboard from 'expo-clipboard' - -const { height } = Dimensions.get('window') - -export function Assistant() { - const [loading, setLoading] = useState(false) - const scrollViewRef = useRef(null) - const [input, setInput] = useState("") - const [instructions, setInstructions] = useState("") - const [file, setFile] = useState(null) - const [assistantId, setAssistantId] = useState('') - const [threadId, setThreadId] = useState('') - const [openaiResponse, setOpenaiResponse] = useState([]) - - const { theme } = useContext(ThemeContext) - const { showActionSheetWithOptions } = useActionSheet() - - function onChangeInputText(v) { - setInput(v) - } - - function onChangeInstructionsText(v) { - setInstructions(v) - } - - async function clearChat() { - if (loading) return - setFile(null) - setOpenaiResponse([]) - setInput('') - setInstructions('') - setAssistantId('') - setThreadId('') - } - - async function copyToClipboard(text) { - await Clipboard.setStringAsync(text) - } - - async function showClipboardActionsheet(text) { - const cancelButtonIndex = 2 - showActionSheetWithOptions({ - options: ['Copy to clipboard', 'Clear chat', 'cancel'], - cancelButtonIndex - }, selectedIndex => { - if (selectedIndex === Number(0)) { - copyToClipboard(text) - } - if (selectedIndex === 1) { - clearChat() - } - }) - } - - async function createThread() { - if (!input) return - generateAssistantResponse() - } - - async function generateAssistantResponse() { - try { - Keyboard.dismiss() - const fileCopy = file - let localInput = { - type: 'user', - value: input - } as any - - if (instructions) { - localInput.instructions = instructions - } - setOpenaiResponse([localInput]) - - setInput('') - setInstructions('') - setLoading(true) - setFile(null) - - const body: { - input: string, - instructions?: string - } = { - input - } - if (instructions) { - body.instructions = instructions - } - let response - if (fileCopy) { - const formData = new FormData() - // @ts-ignore - formData.append('file', { - uri: fileCopy.uri.replace('file://', ''), - name: fileCopy.name, - type: fileCopy.mimeType - }) - for (const key in body) { - formData.append(key, body[key]) - } - response = await fetch(`${DOMAIN}/chat/create-assistant`, { - method: 'POST', - body: formData, - headers: { - 'Content-Type': 'multipart/form-data' - } - }).then(res => res.json()) - - } else { - response = await fetch(`${DOMAIN}/chat/create-assistant`, { - method: 'POST', - body: JSON.stringify(body), - headers: { - 'Content-Type': 'application/json' - } - }).then(res => res.json()) - } - - const { - assistantId: _assistantId, threadId: _threadId, runId: _runId - } = await response - - setThreadId(_threadId) - setAssistantId(_assistantId) - checkThread(_runId, _threadId) - } catch (err) { - console.log('error calling assistant API:', err) - setLoading(false) - } - } - - async function checkThread(_runId, _threadId) { - try { - let finished = false - while (!finished) { - const response = await fetch(`${DOMAIN}/chat/run-status`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - thread_id: _threadId, - run_id: _runId - }) - }).then(res => res.json()) - if (response.status === 'completed') { - finished = true - break - } else { - await new Promise(resolve => setTimeout(resolve, 250)) - } - } - - const thread = await fetch(`${DOMAIN}/chat/get-thread-messages`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - thread_id: _threadId - }) - }).then(res => res.json()) - - const { data } = thread - let localResponse:any = [] - - data.data.forEach((data) => { - data.content.forEach(({ text }) => { - if (data.role === 'assistant') { - localResponse.push({ - type: 'assistant', - value: text.value - }) - } else { - localResponse.push({ - type: 'user', - value: text.value - }) - } - }) - }) - setOpenaiResponse(localResponse.reverse()) - setLoading(false) - } catch (err) { - console.log('error: ', err) - setLoading(false) - } - } - - - - async function addMessageToThread() { - try { - if (!input) return - Keyboard.dismiss() - const fileCopy = file - - let localInput = { - type: 'user', - value: input - } as any - setInput('') - setOpenaiResponse([...openaiResponse, localInput]) - setInput('') - setLoading(true) - setFile(null) - - const body: { - input: string, - thread_id: string, - assistant_id: string - } = { - input, - thread_id: threadId, - assistant_id: assistantId - } - let response - if (fileCopy) { - const formData = new FormData() - // @ts-ignore - formData.append('file', { - uri: fileCopy.uri.replace('file://', ''), - name: fileCopy.name, - type: fileCopy.mimeType - }) - for (const key in body) { - formData.append(key, body[key]) - } - response = await fetch(`${DOMAIN}/chat/add-message-to-thread`, { - method: 'POST', - body: formData, - headers: { - 'Content-Type': 'multipart/form-data' - } - }).then(res => res.json()) - } else { - response = await fetch(`${DOMAIN}/chat/add-message-to-thread`, { - method: 'POST', - body: JSON.stringify(body), - headers: { - 'Content-Type': 'application/json' - } - }).then(res => res.json()) - } - - const { - runId: _runId - } = response - - checkThread(_runId, threadId) - } catch (err) { - console.log('error: ', err) - setLoading(false) - } - } - - async function chooseDocument() { - try { - const res = await DocumentPicker.getDocumentAsync() - console.log('res: ', res) - if (!res || !res.assets) return - setFile(res.assets[0]) - } catch (err) { - console.log('error:', err) - } - } - - const styles = getStyleSheet(theme) - const isStarted = openaiResponse.length - - function renderItem({ item, index }) { - return ( - - { - item.type === 'user' && ( - - - - {item.value} - - - - ) - } - { - item.type === 'assistant' && ( - - {item.value} - showClipboardActionsheet(item.assistant)} - underlayColor={'transparent'} - > - - - - - - ) - } - - ) - } - - - return ( - - - { - (!isStarted) && ( - - - - - - - - - - - - Chat - - - - { - file && ( - - - {file.name || 'File from device'} - - setFile(null)} - style={styles.closeIconContainer} - underlayColor={'transparent'} - > - - - - ) - } - - Chat with an assistant (with optional instructions and file interpreter) - - - - ) - } - { - Boolean(isStarted) && ( - - ) - } - { - loading && ( - - - - ) - } - - { - Boolean(isStarted) && file && ( - - - {file.name || 'File from device'} - - setFile(null)} - style={styles.closeIconContainer} - underlayColor={'transparent'} - > - - - - ) - } - { - (Boolean(isStarted)) && ( - - - - - - - - - - - - ) - } - - ) -} - -const getStyleSheet = theme => StyleSheet.create({ - chatDescription: { - color: theme.textColor, - textAlign: 'center', - marginTop: 15, - fontSize: 13, - opacity: .8, - paddingHorizontal: 30, - fontFamily: theme.regularFont - }, - image: { - width: 250, - height: 250, - borderRadius: 8, - marginBottom: 5, - marginTop: 10, - borderWidth:1, - borderColor: theme.borderColor - }, - optionsIconWrapper: { - padding: 10, - paddingTop: 9, - alignItems: 'flex-end' - }, - loadingContainer: { - marginVertical: 25, - justifyContent: 'center', - flexDirection: 'row', - alignItems: 'center' - }, - iconContainer: { - justifyContent: 'center', - alignItems: 'center', - }, - midInput: { - marginBottom: 8, - borderWidth: 1, - paddingHorizontal: 25, - paddingRight: 48, - marginHorizontal: 10, - paddingVertical: 15, - borderRadius: 99, - color: theme.textColor, - borderColor: theme.borderColor, - fontFamily: theme.semiBoldFont, - }, - midChatInputWrapper: { - flex: 1, - justifyContent: 'center', - alignItems: 'center' - }, - midChatInputContainer: { - width: '100%', - paddingVertical: 5 - }, - midButtonStyle: { - flexDirection: 'row', - marginHorizontal: 14, - paddingHorizontal: 15, - paddingVertical: 12, - borderRadius: 99, - backgroundColor: theme.tintColor, - justifyContent: 'center', - alignItems: 'center' - }, - midButtonText: { - color: theme.tintTextColor, - marginLeft: 10, - fontFamily: theme.boldFont, - fontSize: 16 - }, - closeIconContainer: { - position: 'absolute', - right: -15, - top: -17, - padding: 10, - backgroundColor: 'transparent', - borderRadius: 25, - }, - closeIcon: { - borderWidth: 1, - padding: 4, - backgroundColor: theme.backgroundColor, - borderColor: theme.borderColor, - borderRadius: 15 - }, - fileNameContainer: { - marginHorizontal: 10, - marginRight: 20, - paddingHorizontal: 10, - paddingVertical: 10, - borderWidth: 1, - borderColor: theme.borderColor, - borderRadius: 7, - }, - midFileNameContainer: { - marginTop: 20, - marginHorizontal: 10, - marginRight: 20, - paddingHorizontal: 10, - paddingVertical: 10, - borderWidth: 1, - borderColor: theme.borderColor, - borderRadius: 7, - }, - fileName: { - color: theme.textColor, - }, - chatTypeContainer: { - flexDirection: 'row', - justifyContent: 'center', - alignItems: 'center' - }, - heroContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - height: height - 300 - }, - promptResponse: { - marginTop: 10, - }, - textStyleContainer: { - borderWidth: 1, - marginRight: 25, - borderColor: theme.borderColor, - padding: 15, - paddingBottom: 6, - paddingTop: 5, - margin: 10, - marginTop: 0, - borderRadius: 13 - }, - textStyle: { - body: { - color: theme.textColor, - fontFamily: theme.regularFont - }, - paragraph: { - color: theme.textColor, - fontSize: 16, - fontFamily: theme.regularFont - }, - heading1: { - color: theme.textColor, - fontFamily: theme.semiBoldFont, - marginVertical: 5 - }, - heading2: { - marginTop: 20, - color: theme.textColor, - fontFamily: theme.semiBoldFont, - marginBottom: 5 - }, - heading3: { - marginTop: 20, - color: theme.textColor, - fontFamily: theme.mediumFont, - marginBottom: 5 - }, - heading4: { - marginTop: 10, - color: theme.textColor, - fontFamily: theme.mediumFont, - marginBottom: 5 - }, - heading5: { - marginTop: 10, - color: theme.textColor, - fontFamily: theme.mediumFont, - marginBottom: 5 - }, - heading6: { - color: theme.textColor, - fontFamily: theme.mediumFont, - marginVertical: 5 - }, - list_item: { - marginTop: 7, - fontFamily: theme.regularFont, - fontSize: 16 - }, - ordered_list_icon: { - color: theme.textColor, - fontSize: 16, - fontFamily: theme.regularFont - }, - bullet_list: { - marginTop: 10 - }, - ordered_list: { - marginTop: 7 - }, - bullet_list_icon: { - color: theme.textColor, - fontSize: 16, - fontFamily: theme.regularFont - }, - code_inline: { - color: theme.secondaryTextColor, - backgroundColor: theme.secondaryBackgroundColor, - borderWidth: 1, - borderColor: 'rgba(255, 255, 255, .1)', - fontFamily: theme.lightFont - }, - hr: { - backgroundColor: 'rgba(255, 255, 255, .1)', - height: 1, - }, - fence: { - marginVertical: 5, - padding: 10, - color: theme.secondaryTextColor, - backgroundColor: theme.secondaryBackgroundColor, - borderColor: 'rgba(255, 255, 255, .1)', - fontFamily: theme.regularFont - }, - tr: { - borderBottomWidth: 1, - borderColor: 'rgba(255, 255, 255, .2)', - flexDirection: 'row', - }, - table: { - marginTop: 7, - borderWidth: 1, - borderColor: 'rgba(255, 255, 255, .2)', - borderRadius: 3, - }, - blockquote: { - backgroundColor: '#312e2e', - borderColor: '#CCC', - borderLeftWidth: 4, - marginLeft: 5, - paddingHorizontal: 5, - marginVertical: 5, - }, - } as any, - promptTextContainer: { - flex: 1, - alignItems: 'flex-end', - marginRight: 15, - marginLeft: 24 - }, - promptTextWrapper: { - borderRadius: 8, - borderTopRightRadius: 0, - backgroundColor: theme.tintColor - }, - promptText: { - color: theme.tintTextColor, - fontFamily: theme.regularFont, - paddingVertical: 5, - paddingHorizontal: 9, - fontSize: 16 - }, - chatButton: { - marginRight: 14, - padding: 5, - borderRadius: 99, - backgroundColor: theme.tintColor - }, - buttonText: { - color: theme.textColor, - fontFamily: theme.mediumFont, - }, - chatInputContainer: { - paddingTop: 5, - borderColor: theme.borderColor, - width: '100%', - flexDirection: 'row', - alignItems: 'center', - paddingBottom: 5 - }, - container: { - flex: 1, - backgroundColor: theme.backgroundColor - }, - input: { - flex: 1, - borderWidth: 1, - borderRadius: 99, - color: theme.textColor, - marginHorizontal: 10, - paddingVertical: 10, - paddingHorizontal: 21, - paddingRight: 39, - borderColor: theme.borderColor, - fontFamily: theme.semiBoldFont, - } -}) \ No newline at end of file diff --git a/app/src/screens/chat.tsx b/app/src/screens/chat.tsx index b48d1217..40ff0a8e 100644 --- a/app/src/screens/chat.tsx +++ b/app/src/screens/chat.tsx @@ -4,478 +4,595 @@ import { KeyboardAvoidingView, StyleSheet, TouchableHighlight, + TouchableOpacity, TextInput, - ScrollView, ActivityIndicator, FlatList, - Keyboard + Keyboard, + Share, + Platform, + Image } from 'react-native' import 'react-native-get-random-values' -import { useContext, useState, useRef } from 'react' +import { useContext, useState, useRef, useEffect, useCallback, memo } from 'react' import { ThemeContext, AppContext } from '../context' -import { getEventSource, getFirstNCharsOrLess, getChatType } from '../utils' -import { v4 as uuid } from 'uuid' -import Ionicons from '@expo/vector-icons/Ionicons' +import { useChatStream } from '../hooks/useChatStream' +import { PROMPT_SUGGESTIONS } from '../../constants' +import { Ionicons } from '@expo/vector-icons' import * as Clipboard from 'expo-clipboard' +import * as Haptics from 'expo-haptics' +import * as ImagePicker from 'expo-image-picker' +import * as Speech from 'expo-speech' +import AsyncStorage from '@react-native-async-storage/async-storage' import { useActionSheet } from '@expo/react-native-action-sheet' import Markdown from '@ronradtke/react-native-markdown-display' +import { ChatMessage, ImageAttachment, Theme } from '../../types' -type ChatState = { - messages: Array<{user: string, assistant?: string}>, - index: string, - apiMessages: string +const STORAGE_KEY = 'rnai-chats-v1' + +type ChatStates = Record + +function stripMarkdown(text: string) { + return text + .replace(/```[\s\S]*?```/g, ' code block ') + .replace(/[*_~`#>]/g, '') } -const createEmptyChatState = (): ChatState => ({ - messages: [], - index: uuid(), - apiMessages: '' +const MessageBubble = memo(function MessageBubble({ + item, + theme, + streaming, + isLast, + onOptions, + onRetry +}: { + item: ChatMessage, + theme: Theme, + streaming: boolean, + isLast: boolean, + onOptions: (message: ChatMessage) => void, + onRetry: () => void +}) { + const styles = getStyles(theme) + if (item.role === 'user') { + return ( + + + + { + item.image && ( + + ) + } + { + !!item.content && ( + + {item.content} + + ) + } + + + + ) + } + if (item.error) { + return ( + + {item.error} + + + Retry + + + ) + } + return ( + + { + streaming && isLast && !item.content ? ( + + ) : ( + {item.content} + ) + } + { + !(streaming && isLast) && !!item.content && ( + onOptions(item)} + underlayColor={'transparent'} + accessibilityRole="button" + accessibilityLabel="Message options" + > + + + + + ) + } + + ) }) export function Chat() { - const [loading, setLoading] = useState(false) const [input, setInput] = useState('') - const scrollViewRef = useRef(null) + const [attachment, setAttachment] = useState(null) + const [chatStates, setChatStates] = useState({}) + const [hydrated, setHydrated] = useState(false) + const flatListRef = useRef(null) + const autoScrollRef = useRef(true) const { showActionSheetWithOptions } = useActionSheet() - - // Per-model chat state - each model has its own conversation history - const [chatStates, setChatStates] = useState>({}) - - // Helper to get or create chat state for current model - const getChatState = (modelLabel: string): ChatState => { - return chatStates[modelLabel] || createEmptyChatState() - } - - // Helper to update chat state for a specific model - const updateChatState = (modelLabel: string, updater: (prev: ChatState) => ChatState) => { - setChatStates(prev => ({ - ...prev, - [modelLabel]: updater(prev[modelLabel] || createEmptyChatState()) - })) - } + const { streaming, send, stop } = useChatStream() const { theme } = useContext(ThemeContext) - const { chatType } = useContext(AppContext) + const { chatType, systemPrompt } = useContext(AppContext) const styles = getStyles(theme) - async function chat() { - if (!input) return - Keyboard.dismiss() - if (chatType.label.includes('claude')) { - generateClaudeResponse() - } else if (chatType.label.includes('gpt')) { - generateGptResponse() - } else if (chatType.label.includes('gemini')) { - generateGeminiResponse() - } else if (chatType.label.includes('glm') || chatType.label.includes('kimi')) { - generateGptResponse() - } - } - async function generateGptResponse() { - if (!input) return - Keyboard.dismiss() - let localResponse = '' - const modelLabel = chatType.label - const currentState = getChatState(modelLabel) + const messages = chatStates[chatType.label] || [] - let messageArray = [ - ...currentState.messages, { - user: input, + useEffect(() => { + AsyncStorage.getItem(STORAGE_KEY) + .then(value => { + if (value) setChatStates(JSON.parse(value)) + }) + .catch(err => console.log('error restoring chats', err)) + .finally(() => setHydrated(true)) + }, []) + + useEffect(() => { + if (!hydrated) return + const timer = setTimeout(() => { + const sanitized: ChatStates = {} + for (const [label, msgs] of Object.entries(chatStates)) { + sanitized[label] = msgs + .filter(m => !m.error) + .map(m => m.image ? { ...m, image: { ...m.image, base64: undefined } } : m) } - ] as [{user: string, assistant?: string}] + AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(sanitized)) + .catch(err => console.log('error persisting chats', err)) + }, 400) + return () => clearTimeout(timer) + }, [chatStates, hydrated]) - updateChatState(modelLabel, prev => ({ + const updateMessages = useCallback((modelLabel: string, updater: (prev: ChatMessage[]) => ChatMessage[]) => { + setChatStates(prev => ({ ...prev, - messages: JSON.parse(JSON.stringify(messageArray)) + [modelLabel]: updater(prev[modelLabel] || []) })) - - setLoading(true) - setTimeout(() => { - scrollViewRef.current?.scrollToEnd({ - animated: true - }) - }, 1) - setInput('') - - const messages = messageArray.reduce((acc: any[], message) => { - acc.push({ role: 'user', content: message.user }) - if (message.assistant) { - acc.push({ role: 'assistant', content: message.assistant }) - } - return acc - }, []) - - const eventSourceArgs = { - body: { - messages, - model: chatType.label + }, []) + + function streamResponse(modelLabel: string, history: ChatMessage[]) { + autoScrollRef.current = true + updateMessages(modelLabel, () => [ + ...history, + { role: 'assistant', content: '', model: modelLabel } + ]) + send(chatType, history, systemPrompt, { + onToken: response => { + updateMessages(modelLabel, prev => [ + ...prev.slice(0, -1), + { role: 'assistant', content: response, model: modelLabel } + ]) }, - type: getChatType(chatType) - } - - const es = await getEventSource(eventSourceArgs) - - const listener = (event) => { - if (event.type === "open") { - console.log("Open SSE connection.") - setLoading(false) - } else if (event.type === "message") { - if (event.data !== "[DONE]") { - if (localResponse.length < 850) { - scrollViewRef.current?.scrollToEnd({ - animated: true - }) - } - const data = JSON.parse(event.data) - if (typeof data === 'string') { - localResponse = localResponse + data - } else if (data?.content) { - localResponse = localResponse + data.content - } - messageArray[messageArray.length - 1].assistant = localResponse - updateChatState(modelLabel, prev => ({ - ...prev, - messages: JSON.parse(JSON.stringify(messageArray)) - })) - } else { - setLoading(false) - es.close() - } - } else if (event.type === "error") { - console.error("Connection error:", event.message) - setLoading(false) - } else if (event.type === "exception") { - console.error("Error:", event.message, event.error) - setLoading(false) + onDone: response => { + updateMessages(modelLabel, prev => [ + ...prev.slice(0, -1), + { role: 'assistant', content: response, model: modelLabel } + ]) + }, + onError: message => { + updateMessages(modelLabel, prev => [ + ...prev.slice(0, -1), + { role: 'assistant', content: '', error: message, model: modelLabel } + ]) } - } - - es.addEventListener("open", listener) - es.addEventListener("message", listener) - es.addEventListener("error", listener) + }) } - async function generateGeminiResponse() { - if (!input) return - Keyboard.dismiss() - let localResponse = '' - const modelLabel = chatType.label - const currentState = getChatState(modelLabel) - const geminiInput = `${input}` - - let messageArray = [ - ...currentState.messages, { - user: input, - } - ] as [{user: string, assistant?: string}] - - updateChatState(modelLabel, prev => ({ - ...prev, - messages: JSON.parse(JSON.stringify(messageArray)) - })) - - setLoading(true) - setTimeout(() => { - scrollViewRef.current?.scrollToEnd({ - animated: true - }) - }, 1) - setInput('') - - const eventSourceArgs = { - body: { - prompt: geminiInput, - model: chatType.label - }, - type: getChatType(chatType) - } - const es = await getEventSource(eventSourceArgs) + function chat(text?: string) { + const value = (text ?? input).trim() + if (!value && !attachment) return + if (streaming) return + Keyboard.dismiss() + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => null) - - const listener = (event) => { - if (event.type === "open") { - console.log("Open SSE connection.") - setLoading(false) - } else if (event.type === "message") { - if (event.data !== "[DONE]") { - if (localResponse.length < 850) { - scrollViewRef.current?.scrollToEnd({ - animated: true - }) - } - - const data = event.data - localResponse = localResponse + JSON.parse(data) - messageArray[messageArray.length - 1].assistant = localResponse - updateChatState(modelLabel, prev => ({ - ...prev, - messages: JSON.parse(JSON.stringify(messageArray)) - })) - } else { - setLoading(false) - updateChatState(modelLabel, prev => ({ - ...prev, - apiMessages: `${prev.apiMessages}\n\nPrompt: ${input}\n\nResponse:${localResponse}` - })) - es.close() - } - } else if (event.type === "error") { - console.error("Connection error:", event.message) - setLoading(false) - } else if (event.type === "exception") { - console.error("Error:", event.message, event.error) - setLoading(false) - } + const userMessage: ChatMessage = { + role: 'user', + content: value, + ...(attachment ? { image: attachment } : {}) } - - es.addEventListener("open", listener); - es.addEventListener("message", listener); - es.addEventListener("error", listener); + const history = [...messages.filter(m => !m.error), userMessage] + setInput('') + setAttachment(null) + streamResponse(chatType.label, history) } - async function generateClaudeResponse() { - if (!input) return - Keyboard.dismiss() - let localResponse = '' - const modelLabel = chatType.label - const currentState = getChatState(modelLabel) - const claudeInput = `${currentState.apiMessages}\n\nHuman: ${input}\n\nAssistant:` - - let messageArray = [ - ...currentState.messages, { - user: input, - } - ] as [{user: string, assistant?: string}] + function retryLast() { + if (streaming) return + const history = messages.filter(m => !m.error) + const lastUserIndex = history.map(m => m.role).lastIndexOf('user') + if (lastUserIndex === -1) return + streamResponse(chatType.label, history.slice(0, lastUserIndex + 1)) + } - updateChatState(modelLabel, prev => ({ - ...prev, - messages: JSON.parse(JSON.stringify(messageArray)) - })) + function regenerate() { + retryLast() + } - setLoading(true) - setTimeout(() => { - scrollViewRef.current?.scrollToEnd({ - animated: true + async function attachImage() { + try { + const res = await ImagePicker.launchImageLibraryAsync({ + mediaTypes: ImagePicker.MediaTypeOptions.Images, + allowsEditing: true, + quality: 0.8, + base64: true }) - }, 1) - setInput('') - - const eventSourceArgs = { - body: { - prompt: claudeInput, - model: chatType.label - }, - type: getChatType(chatType), + if (res.canceled || !res.assets?.length) return + const asset = res.assets[0] + setAttachment({ + uri: asset.uri, + mimeType: asset.mimeType || 'image/jpeg', + base64: asset.base64 || undefined + }) + } catch (err) { + console.log('error picking image:', err) } + } - const es = await getEventSource(eventSourceArgs) + async function copyToClipboard(text: string) { + await Clipboard.setStringAsync(text) + Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => null) + } - const listener = (event) => { - if (event.type === "open") { - console.log("Open SSE connection.") - setLoading(false) - } else if (event.type === "message") { - if (event.data !== "[DONE]") { - if (localResponse.length < 850) { - scrollViewRef.current?.scrollToEnd({ - animated: true - }) - } - const data = event.data - localResponse = localResponse + JSON.parse(data).text - messageArray[messageArray.length - 1].assistant = localResponse - updateChatState(modelLabel, prev => ({ - ...prev, - messages: JSON.parse(JSON.stringify(messageArray)) - })) - } else { - setLoading(false) - updateChatState(modelLabel, prev => ({ - ...prev, - apiMessages: `${prev.apiMessages}\n\nHuman: ${input}\n\nAssistant:${getFirstNCharsOrLess(localResponse, 2000)}` - })) - es.close() - } - } else if (event.type === "error") { - console.error("Connection error:", event.message) - setLoading(false) - } else if (event.type === "exception") { - console.error("Error:", event.message, event.error) - setLoading(false) - } - } - es.addEventListener("open", listener) - es.addEventListener("message", listener) - es.addEventListener("error", listener) + async function shareConversation() { + const transcript = messages + .filter(m => !m.error) + .map(m => `**${m.role === 'user' ? 'You' : chatType.name}**: ${m.content}`) + .join('\n\n') + try { + await Share.share({ message: transcript }) + } catch {} } - async function copyToClipboard(text) { - await Clipboard.setStringAsync(text) + function clearChat() { + if (streaming) return + Speech.stop() + updateMessages(chatType.label, () => []) } - async function showClipboardActionsheet(text) { - const cancelButtonIndex = 2 + function showMessageOptions(message: ChatMessage) { + const options = [ + 'Copy to clipboard', + 'Speak aloud', + 'Regenerate response', + 'Share conversation', + 'Clear chat', + 'Cancel' + ] showActionSheetWithOptions({ - options: ['Copy to clipboard', 'Clear chat', 'cancel'], - cancelButtonIndex - }, selectedIndex => { - if (selectedIndex === Number(0)) { - copyToClipboard(text) - } - if (selectedIndex === 1) { - clearChat() + options, + cancelButtonIndex: options.length - 1, + destructiveButtonIndex: 4 + }, index => { + switch (index) { + case 0: copyToClipboard(message.content); break + case 1: Speech.speak(stripMarkdown(message.content)); break + case 2: regenerate(); break + case 3: shareConversation(); break + case 4: clearChat(); break } }) } - async function clearChat() { - if (loading) return - const modelLabel = chatType.label - updateChatState(modelLabel, () => createEmptyChatState()) + const callMade = messages.length > 0 + + function renderItem({ item, index }: { item: ChatMessage, index: number }) { + return ( + + ) } - function renderItem({ - item, index - } : { - item: any, index: number - }) { + function renderAttachmentPreview() { + if (!attachment) return null return ( - - - - - {item.user} - - - - { - item.assistant && ( - - {item.assistant} - showClipboardActionsheet(item.assistant)} - underlayColor={'transparent'} - > - - - - - - ) - } + + + Image attached + setAttachment(null)} + accessibilityRole="button" + accessibilityLabel="Remove attached image" + style={styles.attachmentRemove} + > + + ) } - const currentChatState = getChatState(chatType.label) - const callMade = currentChatState.messages.length > 0 + function renderInputRow(midScreen: boolean) { + return ( + + { + chatType.supportsVision && !midScreen && ( + + + + ) + } + + { + !midScreen && ( + streaming ? ( + + + + + + ) : ( + chat()} + accessibilityRole="button" + accessibilityLabel="Send message" + > + + + + + ) + ) + } + + ) + } return ( - - { - !callMade && ( - - - - setInput(v)} - style={styles.midInput} - placeholder='Message' - placeholderTextColor={theme.placeholderTextColor} - autoCorrect={true} - /> - - - - - Start chat - - - - - Chat with a variety of different language models. - + { + !callMade ? ( + + + {renderInputRow(true)} + chat()} + underlayColor={'transparent'} + accessibilityRole="button" + accessibilityLabel="Start chat" + > + + + + Start chat + + + + {renderAttachmentPreview()} + { + chatType.supportsVision && !attachment && ( + + + Attach an image + + ) + } + + Chat with a variety of different language models. + + + { + PROMPT_SUGGESTIONS.map(suggestion => ( + chat(suggestion)} + accessibilityRole="button" + style={styles.suggestionChip} + > + {suggestion} + + )) + } - ) - } - { - callMade && ( + + ) : ( + <> - ) - } - { - loading && ( - - ) - } - - { - callMade && ( - - setInput(v)} - placeholder='Message' - placeholderTextColor={theme.placeholderTextColor} - value={input} + keyExtractor={(_, index) => `${chatType.label}-${index}`} + keyboardShouldPersistTaps='handled' + contentContainerStyle={styles.listContent} + onScrollBeginDrag={() => { autoScrollRef.current = false }} + onScroll={event => { + const { layoutMeasurement, contentOffset, contentSize } = event.nativeEvent + const distanceFromBottom = contentSize.height - layoutMeasurement.height - contentOffset.y + if (distanceFromBottom < 40) autoScrollRef.current = true + }} + scrollEventThrottle={100} + onContentSizeChange={() => { + if (autoScrollRef.current) { + flatListRef.current?.scrollToEnd({ animated: true }) + } + }} /> - - - - - - + {renderAttachmentPreview()} + {renderInputRow(false)} + ) } ) } -const getStyles = (theme: any) => StyleSheet.create({ +const getStyles = (theme: Theme) => StyleSheet.create({ optionsIconWrapper: { padding: 10, paddingTop: 9, alignItems: 'flex-end' }, - scrollContentContainer: { + listContent: { + paddingBottom: 10 + }, + typingIndicator: { + marginVertical: 10, + alignSelf: 'flex-start', + marginLeft: 5 + }, + errorContainer: { + marginHorizontal: 10, + marginTop: 10, + padding: 13, + borderRadius: 13, + borderWidth: 1, + borderColor: theme.borderColor, + }, + errorText: { + color: theme.textColor, + fontFamily: theme.regularFont, + marginBottom: 10 + }, + retryButton: { + flexDirection: 'row', + alignSelf: 'flex-start', + alignItems: 'center', + backgroundColor: theme.tintColor, + borderRadius: 99, + paddingVertical: 6, + paddingHorizontal: 14 + }, + retryButtonText: { + color: theme.tintTextColor, + fontFamily: theme.mediumFont, + marginLeft: 6 + }, + suggestionsContainer: { + flexDirection: 'row', + flexWrap: 'wrap', + justifyContent: 'center', + marginTop: 18, + paddingHorizontal: 14, + gap: 8 + }, + suggestionChip: { + borderWidth: 1, + borderColor: theme.borderColor, + borderRadius: 99, + paddingVertical: 8, + paddingHorizontal: 14 + }, + suggestionText: { + color: theme.textColor, + fontFamily: theme.regularFont, + fontSize: 13 + }, + attachmentPreview: { + flexDirection: 'row', + alignItems: 'center', + marginHorizontal: 10, + marginTop: 8, + padding: 8, + borderWidth: 1, + borderColor: theme.borderColor, + borderRadius: 10 + }, + attachmentThumbnail: { + width: 36, + height: 36, + borderRadius: 6, + marginRight: 10 + }, + attachmentText: { flex: 1, + color: theme.textColor, + fontFamily: theme.regularFont, + fontSize: 13 + }, + attachmentRemove: { + padding: 6 + }, + attachIconButton: { + marginLeft: 10, + padding: 8, + borderRadius: 99, + borderWidth: 1, + borderColor: theme.borderColor + }, + midAttachButton: { + flexDirection: 'row', + alignItems: 'center', + alignSelf: 'center', + marginTop: 12, + borderWidth: 1, + borderColor: theme.borderColor, + borderRadius: 99, + paddingVertical: 7, + paddingHorizontal: 14 + }, + midAttachButtonText: { + color: theme.textColor, + fontFamily: theme.mediumFont, + fontSize: 13, + marginLeft: 7 }, chatDescription: { color: theme.textColor, @@ -492,7 +609,7 @@ const getStyles = (theme: any) => StyleSheet.create({ paddingHorizontal: 25, marginHorizontal: 10, paddingVertical: 15, - borderRadius: 99, + borderRadius: 24, color: theme.textColor, borderColor: theme.borderColor, fontFamily: theme.mediumFont, @@ -523,9 +640,6 @@ const getStyles = (theme: any) => StyleSheet.create({ paddingTop: 5, paddingBottom: 5 }, - loadingContainer: { - marginTop: 25 - }, promptResponse: { marginTop: 10, }, @@ -549,6 +663,13 @@ const getStyles = (theme: any) => StyleSheet.create({ borderRadius: 8, borderTopRightRadius: 0, backgroundColor: theme.tintColor, + overflow: 'hidden' + }, + promptImage: { + width: 180, + height: 180, + borderRadius: 4, + margin: 5 }, promptText: { color: theme.tintTextColor, @@ -574,12 +695,13 @@ const getStyles = (theme: any) => StyleSheet.create({ input: { flex: 1, borderWidth: 1, - borderRadius: 99, + borderRadius: 24, color: theme.textColor, marginHorizontal: 10, paddingVertical: 10, paddingHorizontal: 21, paddingRight: 39, + maxHeight: 110, borderColor: theme.borderColor, fontFamily: theme.semiBoldFont, }, @@ -657,11 +779,11 @@ const getStyles = (theme: any) => StyleSheet.create({ color: theme.secondaryTextColor, backgroundColor: theme.secondaryBackgroundColor, borderWidth: 1, - borderColor: 'rgba(255, 255, 255, .1)', + borderColor: theme.codeBorderColor, fontFamily: theme.lightFont }, hr: { - backgroundColor: 'rgba(255, 255, 255, .1)', + backgroundColor: theme.codeBorderColor, height: 1, }, fence: { @@ -669,23 +791,23 @@ const getStyles = (theme: any) => StyleSheet.create({ padding: 10, color: theme.secondaryTextColor, backgroundColor: theme.secondaryBackgroundColor, - borderColor: 'rgba(255, 255, 255, .1)', + borderColor: theme.codeBorderColor, fontFamily: theme.regularFont }, tr: { borderBottomWidth: 1, - borderColor: 'rgba(255, 255, 255, .2)', + borderColor: theme.borderColor, flexDirection: 'row', }, table: { marginTop: 7, borderWidth: 1, - borderColor: 'rgba(255, 255, 255, .2)', + borderColor: theme.borderColor, borderRadius: 3, }, blockquote: { - backgroundColor: '#312e2e', - borderColor: '#CCC', + backgroundColor: theme.quoteBackgroundColor, + borderColor: theme.tintColor, borderLeftWidth: 4, marginLeft: 5, paddingHorizontal: 5, diff --git a/app/src/screens/images.tsx b/app/src/screens/images.tsx index cb2abce2..c0541b3c 100644 --- a/app/src/screens/images.tsx +++ b/app/src/screens/images.tsx @@ -9,18 +9,20 @@ import { TextInput, Dimensions, Keyboard, - Image + Image, + Alert } from 'react-native' import { useState, useRef, useContext } from 'react' import { DOMAIN, IMAGE_MODELS } from '../../constants' import { v4 as uuid } from 'uuid' import { ThemeContext, AppContext } from '../context' -import Ionicons from '@expo/vector-icons/Ionicons' -import MaterialIcons from '@expo/vector-icons/MaterialCommunityIcons' +import { Theme } from '../../types' +import { Ionicons, MaterialCommunityIcons as MaterialIcons } from '@expo/vector-icons' import { useActionSheet } from '@expo/react-native-action-sheet' -import * as FileSystem from 'expo-file-system' +import * as FileSystem from 'expo-file-system/legacy' import * as ImagePicker from 'expo-image-picker' -import * as Clipboard from 'expo-clipboard' +import * as MediaLibrary from 'expo-media-library' +import * as Sharing from 'expo-sharing' const { width } = Dimensions.get('window') @@ -66,7 +68,7 @@ export function Images() { } Keyboard.dismiss() const imageCopy = image - const currentModel = IMAGE_MODELS[imageModel].name + const currentModel = IMAGE_MODELS[imageModel as keyof typeof IMAGE_MODELS].name const providerLabel = 'Gemini' try { setTimeout(() => { @@ -140,10 +142,11 @@ export function Images() { }, 50) } else { setLoading(false) - console.log('error generating image ...', response) + Alert.alert('Image generation failed', response?.error || 'The model did not return an image. Please try again.') } } catch (err) { setLoading(false) + Alert.alert('Image generation failed', 'Unable to reach the server. Check your connection and try again.') console.log('error generating image ...', err) } } @@ -171,10 +174,6 @@ export function Images() { ) } - async function copyToClipboard(text:string) { - await Clipboard.setStringAsync(text) - } - function clearPrompts() { setCallMade(false) setImages({ @@ -183,36 +182,61 @@ export function Images() { }) } - async function showClipboardActionsheet(d) { + async function showClipboardActionsheet(d: { image: string }) { closeModal() - const cancelButtonIndex = 2 + const cancelButtonIndex = 3 showActionSheetWithOptions({ - options: ['Save image', 'Clear prompts', 'cancel'], + options: ['Save to camera roll', 'Share image', 'Clear prompts', 'Cancel'], cancelButtonIndex }, selectedIndex => { - if (selectedIndex === Number(0)) { - console.log('saving image ...') - downloadImageToDevice(d.image) + if (selectedIndex === 0) { + saveImageToCameraRoll(d.image) + } + if (selectedIndex === 1) { + shareImage(d.image) } - if (selectedIndex === Number(1)) { + if (selectedIndex === 2) { clearPrompts() } }) } - async function downloadImageToDevice(url: string) { + async function imageToLocalFile(image: string): Promise { + if (!image.startsWith('data:')) return image + const base64 = image.split(',')[1] + const fileUri = FileSystem.cacheDirectory + uuid() + '.png' + await FileSystem.writeAsStringAsync(fileUri, base64, { + encoding: FileSystem.EncodingType.Base64 + }) + return fileUri + } + + async function saveImageToCameraRoll(image: string) { try { - const downloadResumable = FileSystem.createDownloadResumable( - url, - FileSystem.documentDirectory + uuid() + '.png', - ) - try { - await downloadResumable.downloadAsync() - } catch (e) { - console.error(e) + const { status } = await MediaLibrary.requestPermissionsAsync() + if (status !== 'granted') { + Alert.alert('Permission needed', 'Allow photo library access to save images.') + return } + const fileUri = await imageToLocalFile(image) + await MediaLibrary.saveToLibraryAsync(fileUri) + Alert.alert('Saved', 'Image saved to your camera roll.') } catch (err) { console.log('error saving image ...', err) + Alert.alert('Save failed', 'Unable to save the image. Please try again.') + } + } + + async function shareImage(image: string) { + try { + if (!(await Sharing.isAvailableAsync())) { + Alert.alert('Sharing unavailable', 'Sharing is not available on this device.') + return + } + const fileUri = await imageToLocalFile(image) + await Sharing.shareAsync(fileUri, { mimeType: 'image/png' }) + } catch (err) { + console.log('error sharing image ...', err) } } @@ -470,7 +494,7 @@ export function Images() { ) } -const getStyles = theme => StyleSheet.create({ +const getStyles = (theme: Theme) => StyleSheet.create({ midButtonRow: { flexDirection: 'row', alignItems: 'center', diff --git a/app/src/screens/settings.tsx b/app/src/screens/settings.tsx index fae1e910..bd253d2c 100644 --- a/app/src/screens/settings.tsx +++ b/app/src/screens/settings.tsx @@ -4,30 +4,26 @@ import { StyleSheet, TouchableHighlight, ScrollView, - Dimensions, - Image + TextInput } from 'react-native' import { useContext } from 'react' import { AppContext, ThemeContext } from '../context' -import { - AnthropicIcon, - GeminiIcon, - OpenAIIcon, - GLMIcon, - KimiIcon - } from '../components/index' -import FontAwesome from '@expo/vector-icons/FontAwesome5' -import { IIconProps } from '../../types' -import { MODELS, IMAGE_MODELS } from '../../constants' -import * as themes from '../theme' +import { GeminiIcon } from '../components/index' +import { Ionicons } from '@expo/vector-icons' +import { IMAGE_MODELS } from '../../constants' +import { THEMES } from '../theme' +import { Theme } from '../../types' -const { width } = Dimensions.get('window') -const models = Object.values(MODELS) const imageModels = Object.values(IMAGE_MODELS) -const _themes = Object.values(themes).map(v => ({ - name: v.name, - label: v.label -})) + +const themeOptions: { name: string, label: string, swatches?: string[] }[] = [ + { name: 'System', label: 'system' }, + ...Object.values(THEMES).map(v => ({ + name: v.name, + label: v.label, + swatches: [v.backgroundColor, v.tintColor, v.textColor] + })) +] export function Settings() { const { theme, setTheme, themeName } = useContext(ThemeContext) @@ -36,87 +32,96 @@ export function Settings() { setChatType, setImageModel, imageModel, + models, + systemPrompt, + setSystemPrompt, } = useContext(AppContext) const styles = getStyles(theme) - function renderIcon({ - type, props - }: IIconProps) { - if (type.includes('claude')) { - return - } - if (type.includes('gpt')) { - return - } - if (type.includes('gemini')) { - return - } - if (type.includes('glm')) { - return - } - if (type.includes('kimi')) { - return - } - if (type.includes('nanoBanana')) { - return - } - return - } - return ( - - Theme + + Theme { - _themes.map((value, index) => ( - { - setTheme(value.label) - }} - > - - { + const selected = themeName === value.label + return ( + { + setTheme(value.label) }} > - {value.name} - - - - )) + + { + value.swatches ? ( + + {value.swatches.map((color, i) => ( + + ))} + + ) : ( + + ) + } + + {value.name} + + { + selected && ( + + ) + } + + + ) + }) } - - Chat Model + + Chat Model { models.map((model, index) => { + const ModelIcon = model.icon + const selected = chatType.label === model.label return ( { setChatType(model) }} @@ -124,42 +129,46 @@ export function Settings() { - { - renderIcon({ - type: model.label, - props: { - theme, - size: 18, - style: {marginRight: 8}, - selected: chatType.label === model.label - } - }) - } - - { model.name } - - - + + + { model.name } + + { + selected && ( + + ) + } + + ) }) } - - Image Model + + Image Model { imageModels.map((model, index) => { + const selected = imageModel === model.label return ( { setImageModel(model.label) }} @@ -167,33 +176,53 @@ export function Settings() { - { - renderIcon({ - type: model.label, - props: { - theme, - size: 18, - style: {marginRight: 8}, - color: imageModel === model.label ? theme.tintTextColor : theme.textColor - } - }) - } - - { model.name } - - - + + + { model.name } + + { + selected && ( + + ) + } + + ) }) } + + Custom Instructions + + Sent to the model with every chat as a system prompt. + + + ) } -function getDynamicTextStyle(baseType:string, type:string, theme:any) { +function getDynamicTextStyle(baseType: string, type: string, theme: Theme) { if (type === baseType) { return { color: theme.tintTextColor, @@ -201,8 +230,7 @@ function getDynamicTextStyle(baseType:string, type:string, theme:any) { } else return {} } - -function getDynamicViewStyle(baseType:string, type:string, theme:any) { +function getDynamicViewStyle(baseType: string, type: string, theme: Theme) { if (type === baseType) { return { backgroundColor: theme.tintColor @@ -210,7 +238,7 @@ function getDynamicViewStyle(baseType:string, type:string, theme:any) { } else return {} } -const getStyles = (theme:any) => StyleSheet.create({ +const getStyles = (theme: Theme) => StyleSheet.create({ buttonContainer: { marginBottom: 20 }, @@ -228,10 +256,28 @@ const getStyles = (theme:any) => StyleSheet.create({ paddingHorizontal: 15, marginTop: 10 }, + swatchRow: { + flexDirection: 'row', + alignItems: 'center', + marginRight: 10 + }, + swatch: { + width: 14, + height: 14, + borderRadius: 7, + marginRight: 4, + borderWidth: 1, + borderColor: theme.borderColor + }, + checkmark: { + marginLeft: 'auto' + }, chatChoiceButton: { padding: 15, borderRadius: 8, - flexDirection: 'row' + flexDirection: 'row', + alignItems: 'center', + minHeight: 44 }, chatTypeText: { fontFamily: theme.semiBoldFont, @@ -242,4 +288,22 @@ const getStyles = (theme:any) => StyleSheet.create({ fontSize: 18, color: theme.textColor }, -}) \ No newline at end of file + subText: { + fontFamily: theme.regularFont, + fontSize: 13, + marginTop: 4, + color: theme.mutedForegroundColor + }, + systemPromptInput: { + borderWidth: 1, + borderColor: theme.borderColor, + borderRadius: 12, + marginHorizontal: 15, + marginBottom: 24, + padding: 12, + minHeight: 90, + textAlignVertical: 'top', + color: theme.textColor, + fontFamily: theme.regularFont + }, +}) diff --git a/app/src/theme.ts b/app/src/theme.ts index 4bf4b103..fe79563c 100644 --- a/app/src/theme.ts +++ b/app/src/theme.ts @@ -1,3 +1,5 @@ +import { Theme } from '../types' + const colors = { white: '#fff', black: '#000', @@ -28,10 +30,13 @@ const fonts = { ultraBlackFont: 'Geist-Ultrablack', } -const lightTheme = { +const lightTheme: Theme = { ...fonts, name: 'Light', label: 'light', + quoteBackgroundColor: 'rgba(0, 0, 0, .05)', + codeBorderColor: 'rgba(0, 0, 0, .15)', + statusBarStyle: 'dark', textColor: colors.black, secondaryTextColor: colors.white, mutedForegroundColor: colors.gray, @@ -45,10 +50,13 @@ const lightTheme = { tabBarInactiveTintColor: colors.gray, } -const darkTheme = { +const darkTheme: Theme = { ...fonts, name: 'Dark', label: 'dark', + quoteBackgroundColor: 'rgba(255, 255, 255, .06)', + codeBorderColor: 'rgba(255, 255, 255, .2)', + statusBarStyle: 'light', textColor: colors.white, secondaryTextColor: colors.black, mutedForegroundColor: colors.lightWhite, @@ -62,7 +70,7 @@ const darkTheme = { tabBarInactiveTintColor: colors.lightWhite, } -const hackerNews = { +const hackerNews: Theme = { ...lightTheme, name: 'Hacker News', label: 'hackerNews', @@ -70,7 +78,7 @@ const hackerNews = { tintColor: '#ed702d', } -const miami = { +const miami: Theme = { ...darkTheme, name: 'Miami', label: 'miami', @@ -80,7 +88,7 @@ const miami = { tabBarActiveTintColor: colors.lightPink } -const vercel = { +const vercel: Theme = { ...darkTheme, name: 'Vercel', label: 'vercel', @@ -91,7 +99,7 @@ const vercel = { secondaryTextColor: colors.white, } -const cyberpunk = { +const cyberpunk: Theme = { ...darkTheme, name: 'Cyberpunk', label: 'cyberpunk', @@ -103,7 +111,7 @@ const cyberpunk = { borderColor: 'rgba(0, 240, 255, .3)', } -const matrix = { +const matrix: Theme = { ...darkTheme, name: 'Matrix', label: 'matrix', @@ -115,7 +123,7 @@ const matrix = { borderColor: 'rgba(0, 255, 65, .3)', } -const pink = { +const pink: Theme = { ...lightTheme, name: 'Pink', label: 'pink', @@ -127,6 +135,24 @@ const pink = { borderColor: 'rgba(233, 30, 140, .2)', } +const THEMES: Record = { + light: lightTheme, + dark: darkTheme, + hackerNews, + miami, + vercel, + cyberpunk, + matrix, + pink +} + +function getTheme(label: string, systemColorScheme?: 'light' | 'dark' | null): Theme { + if (label === 'system') { + return systemColorScheme === 'dark' ? darkTheme : lightTheme + } + return THEMES[label] || lightTheme +} + export { - lightTheme, darkTheme, hackerNews, miami, vercel, cyberpunk, matrix, pink + lightTheme, darkTheme, hackerNews, miami, vercel, cyberpunk, matrix, pink, THEMES, getTheme } diff --git a/app/src/utils.ts b/app/src/utils.ts index c4479242..5f9166f5 100644 --- a/app/src/utils.ts +++ b/app/src/utils.ts @@ -1,14 +1,26 @@ import { DOMAIN } from '../constants' import EventSource from 'react-native-sse' -import { Model } from '../types' +import { Model, Provider } from '../types' + +const PROVIDER_ROUTES: Record = { + anthropic: 'claude', + openai: 'gpt', + google: 'gemini', + zai: 'glm', + moonshot: 'kimi' +} + +export function getChatRoute(model: Model): string { + return PROVIDER_ROUTES[model.provider] || 'claude' +} export function getEventSource({ headers, body, type } : { - headers?: any, - body: any, + headers?: Record, + body: object, type: string }) { const es = new EventSource(`${DOMAIN}/chat/${type}`, { @@ -22,38 +34,3 @@ export function getEventSource({ return es; } - -export function getFirstNCharsOrLess(text:string, numChars:number = 1000) { - if (text.length <= numChars) { - return text; - } - return text.substring(0, numChars); -} - -export function getFirstN({ messages, size = 10 } : { size?: number, messages: any[] }) { - if (messages.length > size) { - const firstN = new Array() - for(let i = 0; i < size; i++) { - firstN.push(messages[i]) - } - return firstN - } else { - return messages - } -} - -export function getChatType(type: Model) { - if (type.label.includes('gpt')) { - return 'gpt' - } - if (type.label.includes('gemini')) { - return 'gemini' - } - if (type.label.includes('glm')) { - return 'glm' - } - if (type.label.includes('kimi')) { - return 'kimi' - } - else return 'claude' -} diff --git a/app/tsconfig.json b/app/tsconfig.json index 0b9a390a..a0863d0f 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -2,7 +2,6 @@ "extends": "expo/tsconfig.base", "compilerOptions": { "strict": true, - "noImplicitAny": false, "jsx": "react-jsx" } } diff --git a/app/types.ts b/app/types.ts index 104ff22c..5d4425c3 100644 --- a/app/types.ts +++ b/app/types.ts @@ -1,27 +1,85 @@ -import { SetStateAction, Dispatch } from 'react' +import { ComponentType } from 'react' + +export type Provider = 'anthropic' | 'openai' | 'google' | 'zai' | 'moonshot' + +export interface Theme { + name: string + label: string + textColor: string + secondaryTextColor: string + mutedForegroundColor: string + backgroundColor: string + placeholderTextColor: string + secondaryBackgroundColor: string + borderColor: string + tintColor: string + tintTextColor: string + tabBarActiveTintColor: string + tabBarInactiveTintColor: string + quoteBackgroundColor: string + codeBorderColor: string + statusBarStyle: 'light' | 'dark' + regularFont: string + lightFont: string + mediumFont: string + semiBoldFont: string + boldFont: string + blackFont: string + thinFont: string + ultraLightFont: string + ultraBlackFont: string +} + +export interface IconProps { + size?: number + theme: Theme + selected?: boolean + color?: string + style?: object + fill?: string +} export interface IIconProps { type: string props: any } -export interface IThemeContext { - theme: any - setTheme: Dispatch> - themeName: string +export interface Model { + name: string + label: string + provider: Provider + supportsVision: boolean + icon: ComponentType } -export interface Model { - name: string; - label: string; - icon: any +export interface ImageAttachment { + uri: string + mimeType: string + base64?: string +} + +export interface ChatMessage { + role: 'user' | 'assistant' + content: string + image?: ImageAttachment + error?: string + model?: string +} + +export interface IThemeContext { + theme: Theme + setTheme: (theme: string) => void + themeName: string } export interface IAppContext { chatType: Model - setChatType: Dispatch> + setChatType: (model: Model) => void handlePresentModalPress: () => void - setImageModel: Dispatch> - imageModel: string, - closeModal: () => void, -} \ No newline at end of file + setImageModel: (model: string) => void + imageModel: string + closeModal: () => void + models: Model[] + systemPrompt: string + setSystemPrompt: (prompt: string) => void +} diff --git a/server/.env.example b/server/.env.example index 079fa5e9..c3bf8129 100644 --- a/server/.env.example +++ b/server/.env.example @@ -3,3 +3,10 @@ OPENAI_API_KEY="" GEMINI_API_KEY="" ZAI_API_KEY="" MOONSHOT_API_KEY="" + +# Optional server configuration +# PORT=3050 +# CORS_ORIGIN="*" +# RATE_LIMIT_PER_MINUTE=60 +# Require clients to send "Authorization: Bearer " when set +# API_AUTH_TOKEN="" diff --git a/server/package.json b/server/package.json index 13960817..fc0c5085 100644 --- a/server/package.json +++ b/server/package.json @@ -9,34 +9,31 @@ "ngrok": "ngrok http 3050", "dev": "concurrently \"npx tsc --watch\" \"nodemon --exitcrash dist/index.js\"", "ngrokfixed": "ngrok http --domain=f82f850f17ee-443370837071186036.ngrok-free.app 3050", - "test": "echo \"Error: no test specified\" && exit 1" + "typecheck": "tsc --noEmit", + "test": "vitest run" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { - "@google/generative-ai": "^0.24.1", - "@types/body-parser": "^1.19.6", "@types/express": "^5.0.6", "@types/node": "^26.1.0", - "body-parser": "^2.3.0", "cors": "^2.8.6", "dotenv": "^17.4.2", - "eventsource": "^4.1.0", "express": "^5.2.1", "express-async-handler": "^1.2.0", + "express-rate-limit": "^8.2.1", "multer": "^2.2.0", - "node-fetch": "2.7.0", - "stream": "^0.0.3", - "ts-node-dev": "^2.0.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "zod": "^3.25.76" }, "devDependencies": { "@types/cors": "^2.8.19", - "@types/eventsource": "^3.0.0", "@types/multer": "^2.2.0", - "@types/node-fetch": "^2.6.13", + "@types/supertest": "^6.0.3", "concurrently": "^10.0.3", - "nodemon": "^3.1.14" + "nodemon": "^3.1.14", + "supertest": "^7.1.4", + "vitest": "^3.2.4" } } diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 68deac8c..07479a14 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -8,115 +8,385 @@ importers: .: dependencies: - '@google/generative-ai': - specifier: ^0.24.1 - version: 0.24.1 - '@types/body-parser': - specifier: ^1.19.6 - version: 1.19.6 '@types/express': specifier: ^5.0.6 version: 5.0.6 '@types/node': specifier: ^26.1.0 version: 26.1.0 - body-parser: - specifier: ^2.3.0 - version: 2.3.0 cors: specifier: ^2.8.6 version: 2.8.6 dotenv: specifier: ^17.4.2 version: 17.4.2 - eventsource: - specifier: ^4.1.0 - version: 4.1.0 express: specifier: ^5.2.1 version: 5.2.1 express-async-handler: specifier: ^1.2.0 version: 1.2.0 + express-rate-limit: + specifier: ^8.2.1 + version: 8.5.2(express@5.2.1) multer: specifier: ^2.2.0 version: 2.2.0 - node-fetch: - specifier: 2.7.0 - version: 2.7.0 - stream: - specifier: ^0.0.3 - version: 0.0.3 - ts-node-dev: - specifier: ^2.0.0 - version: 2.0.0(@types/node@26.1.0)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 + zod: + specifier: ^3.25.76 + version: 3.25.76 devDependencies: '@types/cors': specifier: ^2.8.19 version: 2.8.19 - '@types/eventsource': - specifier: ^3.0.0 - version: 3.0.0 '@types/multer': specifier: ^2.2.0 version: 2.2.0 - '@types/node-fetch': - specifier: ^2.6.13 - version: 2.6.13 + '@types/supertest': + specifier: ^6.0.3 + version: 6.0.3 concurrently: specifier: ^10.0.3 version: 10.0.3 nodemon: specifier: ^3.1.14 version: 3.1.14 + supertest: + specifier: ^7.1.4 + version: 7.2.2 + vitest: + specifier: ^3.2.4 + version: 3.2.7(@types/node@26.1.0) packages: - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] - '@google/generative-ai@0.24.1': - resolution: {integrity: sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==} - engines: {node: '>=18.0.0'} + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} - '@tsconfig/node10@1.0.12': - resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} + '@paralleldrive/cuid2@2.3.1': + resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/cookiejar@2.1.5': + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} - '@types/eventsource@3.0.0': - resolution: {integrity: sha512-yEhFj31FTD29DtNeqePu+A+lD6loRef6YOM5XfN1kUwBHyy2DySGlA3jJU+FbQSkrfmlBVluf2Dub/OyReFGKA==} - deprecated: This is a stub types definition. eventsource provides its own type definitions, so you do not need this installed. + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} '@types/express-serve-static-core@5.1.1': resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} @@ -127,12 +397,12 @@ packages: '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/methods@1.1.4': + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + '@types/multer@2.2.0': resolution: {integrity: sha512-3U1troeqGV8Ntp7Q3klwf4zr23VEoqYVocYXaswm9+8z3O9UHDYAqLxjJ/h550iRADTjKdOdhhasXw6gD6kYtg==} - '@types/node-fetch@2.6.13': - resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} - '@types/node@26.1.0': resolution: {integrity: sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==} @@ -148,25 +418,45 @@ packages: '@types/serve-static@2.2.0': resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} - '@types/strip-bom@3.0.0': - resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==} + '@types/superagent@8.1.10': + resolution: {integrity: sha512-nbt4IWXABhW0jGmmpRzCFNlbmwCTzZ2gTUsNIr+X+ItdqPms+PAJZbWsNzpS2USqXjcoNLQcO6nXo60zcPQiIg==} + + '@types/supertest@6.0.3': + resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==} + + '@vitest/expect@3.2.7': + resolution: {integrity: sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==} + + '@vitest/mocker@3.2.7': + resolution: {integrity: sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.7': + resolution: {integrity: sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==} + + '@vitest/runner@3.2.7': + resolution: {integrity: sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==} + + '@vitest/snapshot@3.2.7': + resolution: {integrity: sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==} + + '@vitest/spy@3.2.7': + resolution: {integrity: sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==} - '@types/strip-json-comments@0.0.30': - resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} + '@vitest/utils@3.2.7': + resolution: {integrity: sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==} accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} - engines: {node: '>=0.4.0'} - hasBin: true - ansi-regex@6.2.2: resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} engines: {node: '>=12'} @@ -182,15 +472,16 @@ packages: append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -203,9 +494,6 @@ packages: resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@5.0.7: resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} engines: {node: 18 || 20 || >=22} @@ -225,6 +513,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -233,10 +525,18 @@ packages: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -249,12 +549,8 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} - component-emitter@2.0.0: - resolution: {integrity: sha512-4m5s3Me2xxlVKG9PkZpQqHQR7bgpnN7joDMJ4yvVkVXngjoITG76IaZmzmywSeRTeTpc6N6r3H3+KyUurV8OYw==} - engines: {node: '>=18'} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} concat-stream@2.0.0: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} @@ -285,13 +581,13 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + cors@2.8.6: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -301,6 +597,10 @@ packages: supports-color: optional: true + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -309,9 +609,8 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - diff@4.0.4: - resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} - engines: {node: '>=0.3.1'} + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} dotenv@17.4.2: resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} @@ -321,9 +620,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - dynamic-dedupe@0.3.0: - resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==} - ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -342,6 +638,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -350,6 +649,11 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -357,25 +661,42 @@ packages: escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - eventsource-parser@3.1.0: - resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==} - engines: {node: '>=18.0.0'} - - eventsource@4.1.0: - resolution: {integrity: sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ==} - engines: {node: '>=20.0.0'} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} express-async-handler@1.2.0: resolution: {integrity: sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==} + express-rate-limit@8.5.2: + resolution: {integrity: sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + express@5.2.1: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -388,6 +709,10 @@ packages: resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} + formidable@3.5.4: + resolution: {integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==} + engines: {node: '>=14.0.0'} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -396,9 +721,6 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.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} @@ -427,10 +749,6 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -462,13 +780,13 @@ packages: ignore-by-default@1.0.1: resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} - 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==} + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -477,10 +795,6 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -496,8 +810,14 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} @@ -515,6 +835,10 @@ packages: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -531,21 +855,15 @@ packages: 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 + minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -553,19 +871,15 @@ packages: resolution: {integrity: sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==} engines: {node: '>= 10.16.0'} + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + negotiator@1.0.0: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - nodemon@3.1.14: resolution: {integrity: sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==} engines: {node: '>=10'} @@ -594,20 +908,31 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + 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.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} + engines: {node: ^10 || ^12 || >=14} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -639,14 +964,9 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} - engines: {node: '>= 0.4'} - hasBin: true - - rimraf@2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - deprecated: Rimraf versions prior to v4 are no longer supported + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true router@2.2.0: @@ -682,10 +1002,6 @@ packages: resolution: {integrity: sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==} engines: {node: '>= 0.4'} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - side-channel-list@1.0.1: resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} @@ -706,23 +1022,26 @@ packages: resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - stream@0.0.3: - resolution: {integrity: sha512-aMsbn7VKrl4A2T7QAQQbzgN7NVc70vgF5INQrBXqn4dCXN1zy3L9HGgLO5s7PExmdrzTJ8uR/27aviW8or8/+A==} + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} @@ -739,13 +1058,16 @@ packages: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} + superagent@10.3.0: + resolution: {integrity: sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==} + engines: {node: '>=14.18.0'} + + supertest@7.2.2: + resolution: {integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==} + engines: {node: '>=14.18.0'} supports-color@10.2.2: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} @@ -755,9 +1077,27 @@ packages: resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} engines: {node: '>=4'} - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} @@ -771,41 +1111,10 @@ packages: resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} hasBin: true - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true - ts-node-dev@2.0.0: - resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==} - engines: {node: '>=0.8.0'} - hasBin: true - peerDependencies: - node-notifier: '*' - typescript: '*' - peerDependenciesMeta: - node-notifier: - optional: true - - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - - tsconfig@7.0.0: - resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -838,18 +1147,87 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + vite@7.3.6: + resolution: {integrity: sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + 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 + + vitest@3.2.7: + resolution: {integrity: sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.7 + '@vitest/ui': 3.2.7 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} @@ -858,10 +1236,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -874,51 +1248,195 @@ packages: resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} engines: {node: ^20.19.0 || ^22.12.0 || >=23} - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} snapshots: - '@cspotcode/source-map-support@0.8.1': - dependencies: - '@jridgewell/trace-mapping': 0.3.9 + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true - '@google/generative-ai@0.24.1': {} + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true - '@jridgewell/resolve-uri@3.1.2': {} + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.9': + '@noble/hashes@1.8.0': {} + + '@paralleldrive/cuid2@2.3.1': dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 + '@noble/hashes': 1.8.0 - '@tsconfig/node10@1.0.12': {} + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true - '@tsconfig/node12@1.0.11': {} + '@rollup/rollup-android-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true - '@tsconfig/node14@1.0.3': {} + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true - '@tsconfig/node16@1.0.4': {} + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 '@types/node': 26.1.0 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + '@types/connect@3.4.38': dependencies: '@types/node': 26.1.0 + '@types/cookiejar@2.1.5': {} + '@types/cors@2.8.19': dependencies: '@types/node': 26.1.0 - '@types/eventsource@3.0.0': - dependencies: - eventsource: 4.1.0 + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} '@types/express-serve-static-core@5.1.1': dependencies: @@ -935,15 +1453,12 @@ snapshots: '@types/http-errors@2.0.5': {} + '@types/methods@1.1.4': {} + '@types/multer@2.2.0': dependencies: '@types/express': 5.0.6 - '@types/node-fetch@2.6.13': - dependencies: - '@types/node': 26.1.0 - form-data: 4.0.5 - '@types/node@26.1.0': dependencies: undici-types: 8.3.0 @@ -961,20 +1476,64 @@ snapshots: '@types/http-errors': 2.0.5 '@types/node': 26.1.0 - '@types/strip-bom@3.0.0': {} + '@types/superagent@8.1.10': + dependencies: + '@types/cookiejar': 2.1.5 + '@types/methods': 1.1.4 + '@types/node': 26.1.0 + form-data: 4.0.5 + + '@types/supertest@6.0.3': + dependencies: + '@types/methods': 1.1.4 + '@types/superagent': 8.1.10 - '@types/strip-json-comments@0.0.30': {} + '@vitest/expect@3.2.7': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.7 + '@vitest/utils': 3.2.7 + chai: 5.3.3 + tinyrainbow: 2.0.0 - accepts@2.0.0: + '@vitest/mocker@3.2.7(vite@7.3.6(@types/node@26.1.0))': dependencies: - mime-types: 3.0.2 - negotiator: 1.0.0 + '@vitest/spy': 3.2.7 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.6(@types/node@26.1.0) + + '@vitest/pretty-format@3.2.7': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.7': + dependencies: + '@vitest/utils': 3.2.7 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.7': + dependencies: + '@vitest/pretty-format': 3.2.7 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.7': + dependencies: + tinyspy: 4.0.4 - acorn-walk@8.3.4: + '@vitest/utils@3.2.7': dependencies: - acorn: 8.15.0 + '@vitest/pretty-format': 3.2.7 + loupe: 3.2.1 + tinyrainbow: 2.0.0 - acorn@8.15.0: {} + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 ansi-regex@6.2.2: {} @@ -987,11 +1546,11 @@ snapshots: append-field@1.0.0: {} - arg@4.1.3: {} + asap@2.0.6: {} - asynckit@0.4.0: {} + assertion-error@2.0.1: {} - balanced-match@1.0.2: {} + asynckit@0.4.0: {} balanced-match@4.0.4: {} @@ -1011,11 +1570,6 @@ snapshots: transitivePeerDependencies: - supports-color - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - brace-expansion@5.0.7: dependencies: balanced-match: 4.0.4 @@ -1032,6 +1586,8 @@ snapshots: bytes@3.1.2: {} + cac@6.7.14: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -1042,8 +1598,18 @@ snapshots: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chalk@5.6.2: {} + check-error@2.1.3: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -1066,9 +1632,7 @@ snapshots: dependencies: delayed-stream: 1.0.0 - component-emitter@2.0.0: {} - - concat-map@0.0.1: {} + component-emitter@1.3.1: {} concat-stream@2.0.0: dependencies: @@ -1096,24 +1660,29 @@ snapshots: cookie@0.7.2: {} + cookiejar@2.1.4: {} + cors@2.8.6: dependencies: object-assign: 4.1.1 vary: 1.1.2 - create-require@1.1.1: {} - debug@4.4.3(supports-color@5.5.0): dependencies: ms: 2.1.3 optionalDependencies: supports-color: 5.5.0 + deep-eql@5.0.2: {} + delayed-stream@1.0.0: {} depd@2.0.0: {} - diff@4.0.4: {} + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 dotenv@17.4.2: {} @@ -1123,10 +1692,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - dynamic-dedupe@0.3.0: - dependencies: - xtend: 4.0.2 - ee-first@1.1.1: {} emoji-regex@10.6.0: {} @@ -1137,6 +1702,8 @@ snapshots: es-errors@1.3.0: {} + es-module-lexer@1.7.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -1148,20 +1715,54 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + escalade@3.2.0: {} escape-html@1.0.3: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + etag@1.8.1: {} - eventsource-parser@3.1.0: {} - - eventsource@4.1.0: - dependencies: - eventsource-parser: 3.1.0 + expect-type@1.4.0: {} express-async-handler@1.2.0: {} + express-rate-limit@8.5.2(express@5.2.1): + dependencies: + express: 5.2.1 + ip-address: 10.2.0 + express@5.2.1: dependencies: accepts: 2.0.0 @@ -1195,6 +1796,12 @@ snapshots: transitivePeerDependencies: - supports-color + fast-safe-stringify@2.1.1: {} + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -1218,12 +1825,16 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.3.1 + dezalgo: 1.0.4 + once: 1.4.0 + forwarded@0.2.0: {} fresh@2.0.0: {} - fs.realpath@1.0.0: {} - fsevents@2.3.3: optional: true @@ -1255,15 +1866,6 @@ snapshots: dependencies: is-glob: 4.0.3 - 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 - gopd@1.2.0: {} has-flag@3.0.0: {} @@ -1292,23 +1894,16 @@ snapshots: ignore-by-default@1.0.1: {} - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - inherits@2.0.4: {} + ip-address@10.2.0: {} + ipaddr.js@1.9.1: {} is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - is-extglob@2.1.1: {} is-glob@4.0.3: @@ -1319,7 +1914,13 @@ snapshots: is-promise@4.0.0: {} - make-error@1.3.6: {} + js-tokens@9.0.1: {} + + loupe@3.2.1: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 math-intrinsics@1.1.0: {} @@ -1329,6 +1930,8 @@ snapshots: merge-descriptors@2.0.0: {} + methods@1.1.2: {} + mime-db@1.52.0: {} mime-db@1.54.0: {} @@ -1341,18 +1944,12 @@ snapshots: dependencies: mime-db: 1.54.0 + mime@2.6.0: {} + minimatch@10.2.5: dependencies: brace-expansion: 5.0.7 - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.12 - - minimist@1.2.8: {} - - mkdirp@1.0.4: {} - ms@2.1.3: {} multer@2.2.0: @@ -1362,11 +1959,9 @@ snapshots: concat-stream: 2.0.0 type-is: 1.6.18 - negotiator@1.0.0: {} + nanoid@3.3.15: {} - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 + negotiator@1.0.0: {} nodemon@3.1.14: dependencies: @@ -1397,14 +1992,24 @@ snapshots: parseurl@1.3.3: {} - path-is-absolute@1.0.1: {} + path-to-regexp@8.4.2: {} - path-parse@1.0.7: {} + pathe@2.0.3: {} - path-to-regexp@8.4.2: {} + pathval@2.0.1: {} + + picocolors@1.1.1: {} picomatch@2.3.1: {} + picomatch@4.0.5: {} + + postcss@8.5.16: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -1440,15 +2045,36 @@ snapshots: dependencies: picomatch: 2.3.1 - resolve@1.22.11: + rollup@4.62.2: dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - rimraf@2.7.1: - dependencies: - glob: 7.2.3 + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 router@2.2.0: dependencies: @@ -1499,11 +2125,6 @@ snapshots: shell-quote@1.8.4: {} - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 @@ -1528,7 +2149,7 @@ snapshots: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.0 + side-channel-list: 1.0.1 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 @@ -1540,22 +2161,19 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + simple-update-notifier@2.0.0: dependencies: semver: 7.7.3 - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 + source-map-js@1.2.1: {} - source-map@0.6.1: {} + stackback@0.0.2: {} statuses@2.0.2: {} - stream@0.0.3: - dependencies: - component-emitter: 2.0.0 + std-env@3.10.0: {} streamsearch@1.1.0: {} @@ -1573,9 +2191,31 @@ snapshots: dependencies: ansi-regex: 6.2.2 - strip-bom@3.0.0: {} + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + superagent@10.3.0: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.4.3(supports-color@5.5.0) + fast-safe-stringify: 2.1.1 + form-data: 4.0.5 + formidable: 3.5.4 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.15.3 + transitivePeerDependencies: + - supports-color - strip-json-comments@2.0.1: {} + supertest@7.2.2: + dependencies: + cookie-signature: 1.2.2 + methods: 1.1.2 + superagent: 10.3.0 + transitivePeerDependencies: + - supports-color supports-color@10.2.2: {} @@ -1583,7 +2223,20 @@ snapshots: dependencies: has-flag: 3.0.0 - supports-preserve-symlinks-flag@1.0.0: {} + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} to-regex-range@5.0.1: dependencies: @@ -1593,53 +2246,8 @@ snapshots: touch@3.1.1: {} - tr46@0.0.3: {} - tree-kill@1.2.2: {} - ts-node-dev@2.0.0(@types/node@26.1.0)(typescript@5.9.3): - dependencies: - chokidar: 3.6.0 - dynamic-dedupe: 0.3.0 - minimist: 1.2.8 - mkdirp: 1.0.4 - resolve: 1.22.11 - rimraf: 2.7.1 - source-map-support: 0.5.21 - tree-kill: 1.2.2 - ts-node: 10.9.2(@types/node@26.1.0)(typescript@5.9.3) - tsconfig: 7.0.0 - typescript: 5.9.3 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' - - '@types/node' - - ts-node@10.9.2(@types/node@26.1.0)(typescript@5.9.3): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.12 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 26.1.0 - acorn: 8.15.0 - acorn-walk: 8.3.4 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.4 - make-error: 1.3.6 - typescript: 5.9.3 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - - tsconfig@7.0.0: - dependencies: - '@types/strip-bom': 3.0.0 - '@types/strip-json-comments': 0.0.30 - strip-bom: 3.0.0 - strip-json-comments: 2.0.1 - tslib@2.8.1: {} type-is@1.6.18: @@ -1665,16 +2273,86 @@ snapshots: util-deprecate@1.0.2: {} - v8-compile-cache-lib@3.0.1: {} - vary@1.1.2: {} - webidl-conversions@3.0.1: {} + vite-node@3.2.4(@types/node@26.1.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3(supports-color@5.5.0) + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.6(@types/node@26.1.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.3.6(@types/node@26.1.0): + dependencies: + esbuild: 0.28.1 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + postcss: 8.5.16 + rollup: 4.62.2 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 26.1.0 + fsevents: 2.3.3 + + vitest@3.2.7(@types/node@26.1.0): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.7 + '@vitest/mocker': 3.2.7(vite@7.3.6(@types/node@26.1.0)) + '@vitest/pretty-format': 3.2.7 + '@vitest/runner': 3.2.7 + '@vitest/snapshot': 3.2.7 + '@vitest/spy': 3.2.7 + '@vitest/utils': 3.2.7 + chai: 5.3.3 + debug: 4.4.3(supports-color@5.5.0) + expect-type: 1.4.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.17 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.3.6(@types/node@26.1.0) + vite-node: 3.2.4(@types/node@26.1.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 26.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml - whatwg-url@5.0.0: + why-is-node-running@2.3.0: dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 + siginfo: 2.0.0 + stackback: 0.0.2 wrap-ansi@9.0.2: dependencies: @@ -1684,8 +2362,6 @@ snapshots: wrappy@1.0.2: {} - xtend@4.0.2: {} - y18n@5.0.8: {} yargs-parser@22.0.0: {} @@ -1699,4 +2375,4 @@ snapshots: y18n: 5.0.8 yargs-parser: 22.0.0 - yn@3.1.1: {} + zod@3.25.76: {} diff --git a/server/src/__tests__/api.test.ts b/server/src/__tests__/api.test.ts new file mode 100644 index 00000000..95ed8f36 --- /dev/null +++ b/server/src/__tests__/api.test.ts @@ -0,0 +1,113 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import request from 'supertest' +import { createApp } from '../index' + +function sseResponse(events: string[]) { + const encoder = new TextEncoder() + const stream = new ReadableStream({ + start(controller) { + for (const event of events) { + controller.enqueue(encoder.encode(event)) + } + controller.close() + } + }) + return new Response(stream, { + status: 200, + headers: { 'Content-Type': 'text/event-stream' } + }) +} + +describe('server', () => { + beforeEach(() => { + delete process.env.API_AUTH_TOKEN + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('GET /health returns ok', async () => { + const res = await request(createApp()).get('/health') + expect(res.status).toBe(200) + expect(res.body).toEqual({ status: 'ok' }) + }) + + it('GET /models returns chat and image models', async () => { + const res = await request(createApp()).get('/models') + expect(res.status).toBe(200) + expect(res.body.chatModels.length).toBeGreaterThan(0) + expect(res.body.imageModels.length).toBeGreaterThan(0) + const claude = res.body.chatModels.find((m: any) => m.label === 'claudeOpus') + expect(claude.provider).toBe('anthropic') + expect(res.body.chatModels.every((m: any) => !m.modelId)).toBe(true) + }) + + it('rejects invalid chat request bodies', async () => { + const res = await request(createApp()) + .post('/chat/claude') + .send({ model: 'claudeOpus' }) + expect(res.status).toBe(400) + expect(res.body.error).toBe('invalid request body') + }) + + it('requires bearer token when API_AUTH_TOKEN is set', async () => { + process.env.API_AUTH_TOKEN = 'secret' + const app = createApp() + const unauthorized = await request(app) + .post('/chat/claude') + .send({ model: 'claudeOpus', messages: [{ role: 'user', content: 'hi' }] }) + expect(unauthorized.status).toBe(401) + }) + + it('streams claude tokens as normalized SSE events', async () => { + vi.spyOn(global, 'fetch').mockResolvedValue( + sseResponse([ + 'data: {"type":"content_block_delta","delta":{"text":"Hel"}}\n\n', + 'data: {"type":"content_block_delta","del', + 'ta":{"text":"lo"}}\n\n' + ]) as any + ) + const res = await request(createApp()) + .post('/chat/claude') + .send({ model: 'claudeOpus', messages: [{ role: 'user', content: 'hi' }] }) + expect(res.status).toBe(200) + expect(res.text).toContain('data: {"content":"Hel"}') + expect(res.text).toContain('data: {"content":"lo"}') + expect(res.text).toContain('data: [DONE]') + }) + + it('streams openai-compatible tokens as normalized SSE events', async () => { + vi.spyOn(global, 'fetch').mockResolvedValue( + sseResponse([ + 'data: {"choices":[{"delta":{"content":"Hi"}}]}\n\n', + 'data: [DONE]\n\n' + ]) as any + ) + const res = await request(createApp()) + .post('/chat/gpt') + .send({ model: 'gpt52', messages: [{ role: 'user', content: 'hi' }] }) + expect(res.status).toBe(200) + expect(res.text).toContain('data: {"content":"Hi"}') + expect(res.text).toContain('data: [DONE]') + }) + + it('emits an error event for unsupported models', async () => { + const res = await request(createApp()) + .post('/chat/gpt') + .send({ model: 'not-a-model', messages: [{ role: 'user', content: 'hi' }] }) + expect(res.text).toContain('unsupported model') + expect(res.text).toContain('data: [DONE]') + }) + + it('emits an error event when the provider fails', async () => { + vi.spyOn(global, 'fetch').mockResolvedValue( + new Response('rate limited', { status: 429 }) as any + ) + const res = await request(createApp()) + .post('/chat/claude') + .send({ model: 'claudeOpus', messages: [{ role: 'user', content: 'hi' }] }) + expect(res.text).toContain('provider error (429)') + expect(res.text).toContain('data: [DONE]') + }) +}) diff --git a/server/src/chat/addMessageToThread.ts b/server/src/chat/addMessageToThread.ts deleted file mode 100644 index be697a84..00000000 --- a/server/src/chat/addMessageToThread.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Request, Response } from "express" -import { saveFileToOpenai } from '../helpers/saveFileToOpenai' - -interface Body { - role: string - content: string - file_ids?: [string] - tools?: [{ type: string }] -} - -export async function addMessageToThread(req: Request, res: Response) { - try { - const { thread_id, input, assistant_id } = req.body - const file = req.file - - const body:Body = { - role: 'user', - content: input - } - - if (file) { - const response = await saveFileToOpenai(file) - console.log('response: ', response) - body.file_ids = [response.id] - } - console.log('body:', body) - - const headers = { - 'Content-Type': 'application/json', - 'OpenAI-Beta': 'assistants=v1', - 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` - } - - await fetch(`https://api.openai.com/v1/threads/${thread_id}/messages`, { - method: 'POST', - body: JSON.stringify(body), - headers - }).then(res => res.json()) - - const run = await fetch(`https://api.openai.com/v1/threads/${thread_id}/runs`, { - method: 'POST', - headers, - body: JSON.stringify({ - assistant_id, - tools: [{ type: "code_interpreter" }, { type: "retrieval" }] - }) - }).then(res => res.json()) - - return res.json({ - runId: run.id - }) - } catch (err) { - console.log('error in assistant chat: ', err) - return res.json({ - error: err - }) - } -} diff --git a/server/src/chat/chatRouter.ts b/server/src/chat/chatRouter.ts index 64c851b2..50faaeb1 100644 --- a/server/src/chat/chatRouter.ts +++ b/server/src/chat/chatRouter.ts @@ -4,9 +4,13 @@ import { gpt } from './gpt' import { gemini } from './gemini' import { glm } from './glm' import { kimi } from './kimi' +import { validateBody } from '../middleware' +import { chatRequestSchema } from '../types' const router = express.Router() +router.use(validateBody(chatRequestSchema)) + router.post('/claude', claude) router.post('/gpt', gpt) router.post('/gemini', gemini) diff --git a/server/src/chat/claude.ts b/server/src/chat/claude.ts index 602f9d15..54fe0f59 100644 --- a/server/src/chat/claude.ts +++ b/server/src/chat/claude.ts @@ -1,106 +1,85 @@ -import { Request, Response, NextFunction } from "express" +import { Request, Response } from 'express' import asyncHandler from 'express-async-handler' +import { getChatModel } from '../models' +import { ChatMessage, ChatRequest } from '../types' +import { initSSE, sendToken, sendError, sendDone, createSSEParser, pumpStream } from '../sse' -type ModelLabel = 'claudeOpus' | 'claudeOpus47' | 'claudeSonnet' | 'claudeHaiku' | 'claudeSonnet4' | 'claudeFable5' | 'claudeSonnet5' -type ModelName = - | 'claude-opus-4-5-20251101' - | 'claude-opus-4-7' - | 'claude-sonnet-4-5-20250929' - | 'claude-haiku-4-5-20251001' - | 'claude-sonnet-4-6-20260201' - | 'claude-fable-5' - | 'claude-sonnet-5'; - -const models: Record = { - claudeFable5: 'claude-fable-5', - claudeSonnet5: 'claude-sonnet-5', - claudeOpus: 'claude-opus-4-5-20251101', - claudeOpus47: 'claude-opus-4-7', - claudeSonnet: 'claude-sonnet-4-5-20250929', - claudeHaiku: 'claude-haiku-4-5-20251001', - claudeSonnet4: 'claude-sonnet-4-6-20260201' -} - -interface RequestBody { - prompt: any; - model: ModelLabel; +function toAnthropicMessages(messages: ChatMessage[]) { + return messages + .filter(m => m.role !== 'system') + .map(m => { + if (m.image) { + return { + role: m.role, + content: [ + { + type: 'image', + source: { + type: 'base64', + media_type: m.image.mimeType, + data: m.image.data + } + }, + ...(m.content ? [{ type: 'text', text: m.content }] : []) + ] + } + } + return { role: m.role, content: m.content } + }) } export const claude = asyncHandler(async (req: Request, res: Response) => { + initSSE(res) try { - res.writeHead(200, { - 'Content-Type': 'text/event-stream', - 'Connection': 'keep-alive', - 'Cache-Control': 'no-cache' - }) + const { messages, model }: ChatRequest = req.body + const chatModel = getChatModel(model) - const { prompt, model }: RequestBody = req.body - const selectedModel = models[model] - - if (!selectedModel) { - res.write('data: [DONE]\n\n') - res.end() + if (!chatModel || chatModel.provider !== 'anthropic') { + sendError(res, `unsupported model: ${model}`) + sendDone(res) return } - const decoder = new TextDecoder() + const system = messages.filter(m => m.role === 'system').map(m => m.content).join('\n') + const response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01', - 'anthropic-beta': 'messages-2023-12-15', 'x-api-key': process.env.ANTHROPIC_API_KEY || '' }, body: JSON.stringify({ - model: selectedModel, - "messages": [{"role": "user", "content": prompt }], - "max_tokens": 4096, + model: chatModel.modelId, + messages: toAnthropicMessages(messages), + ...(system ? { system } : {}), + max_tokens: 4096, stream: true }) }) - const reader = response.body?.getReader() - if (reader) { - let index = 0 - - while (true) { - const { done, value } = await reader.read() + if (!response.ok) { + const detail = await response.text() + console.error('anthropic error:', response.status, detail) + sendError(res, `provider error (${response.status})`) + sendDone(res) + return + } - if (done) { - break + const parse = createSSEParser(data => { + try { + const parsed = JSON.parse(data) + if (parsed.type === 'content_block_delta' && parsed.delta?.text) { + sendToken(res, parsed.delta.text) } - - let chunk = decoder.decode(value) - - const lines = chunk.split("\n") - - const parsedLines = lines - .filter(line => line.startsWith('data: ')) - .map(line => { - try { - return JSON.parse(line.replace('data: ', '')) - } catch { - return null - } - }) - .filter(Boolean) + } catch {} + }) - for (const parsedLine of parsedLines) { - if (parsedLine) { - if (parsedLine.delta && parsedLine.delta.text) { - res.write(`data: ${JSON.stringify(parsedLine.delta)}\n\n`) - } - } - } - } - - res.write('data: [DONE]\n\n') - res.end() - } + await pumpStream(response.body, parse) + sendDone(res) } catch (err) { - console.log('error in claude chat: ', err) - res.write('data: [DONE]\n\n') - res.end() + console.error('error in claude chat:', err) + sendError(res, 'unexpected server error') + sendDone(res) } }) diff --git a/server/src/chat/cohere.ts b/server/src/chat/cohere.ts deleted file mode 100644 index 120cd45a..00000000 --- a/server/src/chat/cohere.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* -* stream help from https://www.builder.io/blog/stream-ai-javascript -*/ -import { Request, Response } from "express" - -type ModelName = 'cohere' | 'cohereWeb'; - -interface RequestBody { - prompt: any; - model: ModelName; - conversation_id: string; -} - -export async function cohere(req: Request, res: Response) { - try { - res.writeHead(200, { - 'Content-Type': 'text/event-stream', - 'Connection': 'keep-alive', - 'Cache-Control': 'no-cache' - }) - let { prompt, conversation_id, model }: RequestBody = req.body - - if (!prompt) { - return res.json({ - error: 'no prompt' - }) - } - - const body = { - message: prompt, - stream: true, - conversation_id, - } as { message: string, stream: boolean, conversation_id: string, connectors?: any[]} - if (model.includes('web')) { - body.connectors = [{"id": "web-search"}] - } - - const decoder = new TextDecoder() - const response = await fetch('https://api.cohere.ai/v1/chat', { - method: 'POST', - headers: { - 'accept': 'application/json', - 'content-type': 'application/json', - 'authorization': `Bearer ${process.env.COHERE_API_KEY}` - }, - body: JSON.stringify(body) - }) - - if (response) { - const reader = response.body?.getReader() - if (!reader) return res.end - - while (true) { - const { done, value } = await reader.read() - if (done) { - break - } - let chunk = decoder.decode(value) - res.write(`data: ${chunk}\n\n`) - } - - res.write('data: [DONE]\n\n') - res.end() - } else { - res.end() - } - - } catch (err) { - console.log('error in cohere chat: ', err) - res.write('data: [DONE]\n\n') - res.end() - } -} \ No newline at end of file diff --git a/server/src/chat/createAssistant.ts b/server/src/chat/createAssistant.ts deleted file mode 100644 index 7a8e690e..00000000 --- a/server/src/chat/createAssistant.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Request, Response } from "express" -import { saveFileToOpenai } from '../helpers/saveFileToOpenai' - -interface Body { - model: string - name: string - instructions?: string - file_ids?: [string] - tools?: any[] -} - -export async function createAssistant(req: Request, res: Response) { - try { - const { instructions, input } = req.body - const file = req.file - let assistantId - let threadId - let runId - - const body: Body = { - model: 'gpt-4-1106-preview', - name: 'RN AI Assistant' - } - - const headers = { - 'Content-Type': 'application/json', - 'OpenAI-Beta': 'assistants=v1', - 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` - } - - if (instructions) { - body.instructions = instructions - } - - if (file) { - const response = await saveFileToOpenai(file) - body.file_ids = [response.id] - body.tools = [{ type: "code_interpreter" }, { type: "retrieval" }] - } - - const assistant = await fetch('https://api.openai.com/v1/assistants', { - method: 'POST', - body: JSON.stringify(body), - headers - }).then(res => res.json()) - assistantId = assistant.id - - const thread = await fetch('https://api.openai.com/v1/threads', { - method: 'POST', - headers - }).then(res => res.json()) - threadId = thread.id - - console.log('threadId: ', threadId) - - await fetch(`https://api.openai.com/v1/threads/${threadId}/messages`, { - method: 'POST', - body: JSON.stringify({ - role: 'user', - content: input - }), - headers - }).then(res => res.json()) - - const run = await fetch(`https://api.openai.com/v1/threads/${threadId}/runs`, { - method: 'POST', - headers, - body: JSON.stringify({ - assistant_id: assistantId - }) - }).then(res => res.json()) - runId = run.id - - return res.json({ - assistantId, - threadId, - runId - }) - - } catch (err) { - console.log('error in assistant chat: ', err) - return res.json({ error: err }) - } -} \ No newline at end of file diff --git a/server/src/chat/gemini.ts b/server/src/chat/gemini.ts index 1ff3030c..16519b16 100644 --- a/server/src/chat/gemini.ts +++ b/server/src/chat/gemini.ts @@ -1,47 +1,82 @@ -import { GoogleGenerativeAI } from "@google/generative-ai" +import { Request, Response } from 'express' +import asyncHandler from 'express-async-handler' +import { getChatModel } from '../models' +import { ChatMessage, ChatRequest } from '../types' +import { initSSE, sendToken, sendError, sendDone, createSSEParser, pumpStream } from '../sse' -import { Request, Response } from "express" +function toGeminiContents(messages: ChatMessage[]) { + return messages + .filter(m => m.role !== 'system') + .map(m => { + const parts: any[] = [] + if (m.content) parts.push({ text: m.content }) + if (m.image) { + parts.push({ + inline_data: { + mime_type: m.image.mimeType, + data: m.image.data + } + }) + } + return { + role: m.role === 'assistant' ? 'model' : 'user', + parts + } + }) +} -export async function gemini(req: Request, res: Response) { +export const gemini = asyncHandler(async (req: Request, res: Response) => { + initSSE(res) try { - res.writeHead(200, { - 'Content-Type': 'text/event-stream', - 'Connection': 'keep-alive', - 'Cache-Control': 'no-cache' - }) - const { prompt } = req.body - if (!prompt) { - return res.json({ - error: 'no prompt' - }) - } + const { messages, model }: ChatRequest = req.body + const chatModel = getChatModel(model) - const genAIInit = new GoogleGenerativeAI(`${process.env.GEMINI_API_KEY}`) + if (!chatModel || chatModel.provider !== 'google') { + sendError(res, `unsupported model: ${model}`) + sendDone(res) + return + } - const model = genAIInit.getGenerativeModel({ - model: "gemini-3-pro-preview", - }) - - const geminiResult = await model.generateContentStream(prompt) + const system = messages.filter(m => m.role === 'system').map(m => m.content).join('\n') - if (geminiResult && geminiResult.stream) { - await streamToStdout(geminiResult.stream, res) - } else { - res.end() + const response = await fetch( + `https://generativelanguage.googleapis.com/v1beta/models/${chatModel.modelId}:streamGenerateContent?alt=sse`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-goog-api-key': process.env.GEMINI_API_KEY || '' + }, + body: JSON.stringify({ + contents: toGeminiContents(messages), + ...(system ? { systemInstruction: { parts: [{ text: system }] } } : {}) + }) } - - } catch (err) { - console.log('error in Gemini chat: ', err) - res.write('data: [DONE]\n\n') - res.end() + ) + + if (!response.ok) { + const detail = await response.text() + console.error('gemini error:', response.status, detail) + sendError(res, `provider error (${response.status})`) + sendDone(res) + return } -} -export async function streamToStdout(stream :any, res: Response) { - for await (const chunk of stream) { - const chunkText = chunk.text() - res.write(`data: ${JSON.stringify(chunkText)}\n\n`) + const parse = createSSEParser(data => { + try { + const parsed = JSON.parse(data) + const text = parsed.candidates?.[0]?.content?.parts + ?.map((p: any) => p.text || '') + .join('') + if (text) sendToken(res, text) + } catch {} + }) + + await pumpStream(response.body, parse) + sendDone(res) + } catch (err) { + console.error('error in gemini chat:', err) + sendError(res, 'unexpected server error') + sendDone(res) } - res.write('data: [DONE]\n\n') - res.end() -} \ No newline at end of file +}) diff --git a/server/src/chat/getThreadMessages.ts b/server/src/chat/getThreadMessages.ts deleted file mode 100644 index d12718ee..00000000 --- a/server/src/chat/getThreadMessages.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Request, Response } from "express" - -export async function getThreadMessages(req: Request, res: Response) { - try { - const { thread_id } = req.body - const headers = { - 'OpenAI-Beta': 'assistants=v1', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` - } - const response = await fetch(`https://api.openai.com/v1/threads/${thread_id}/messages`, { - headers - }).then(res => res.json()) - - return res.json({ - success: true, - data: response - }) - } catch (err) { - console.log('error in assistant chat: ', err) - } -} diff --git a/server/src/chat/glm.ts b/server/src/chat/glm.ts index 426ead7c..bfe0c81e 100644 --- a/server/src/chat/glm.ts +++ b/server/src/chat/glm.ts @@ -1,15 +1,12 @@ -import { Request, Response } from "express" +import { Request, Response } from 'express' import asyncHandler from 'express-async-handler' import { streamOpenAICompatible } from './openaiCompatible' export const glm = asyncHandler(async (req: Request, res: Response) => { - const models: any = { - glm52: 'glm-5.2' - } await streamOpenAICompatible({ req, res, - models, + provider: 'zai', apiUrl: 'https://api.z.ai/api/paas/v4/chat/completions', apiKey: process.env.ZAI_API_KEY || '' }) diff --git a/server/src/chat/gpt.ts b/server/src/chat/gpt.ts index 83a37ce6..c8860e1c 100644 --- a/server/src/chat/gpt.ts +++ b/server/src/chat/gpt.ts @@ -1,91 +1,13 @@ -import { Request, Response } from "express" +import { Request, Response } from 'express' import asyncHandler from 'express-async-handler' +import { streamOpenAICompatible } from './openaiCompatible' export const gpt = asyncHandler(async (req: Request, res: Response) => { - const models:any = { - gpt52: 'gpt-5.2-2025-12-11', - gpt5Mini: 'gpt-5-mini-2025-08-07' - } - try { - res.writeHead(200, { - 'Content-Type': 'text/event-stream', - 'Connection': 'keep-alive', - 'Cache-Control': 'no-cache' - }) - const { model, messages } = req.body - const selectedModel = models[model] - - if (!selectedModel) { - res.write('data: [DONE]\n\n') - res.end() - return - } - - const response = await fetch('https://api.openai.com/v1/chat/completions', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` - }, - body: JSON.stringify({ - model: selectedModel, - messages, - stream: true - }) - }) - const reader = response.body?.getReader() - const decoder = new TextDecoder() - let brokenLine = '' - if (reader) { - while (true) { - const { done, value } = await reader.read() - if (done) { - break - } - - let chunk = decoder.decode(value) - if (brokenLine) { - try { - const { choices } = JSON.parse(brokenLine) - const { delta } = choices[0] - const { content } = delta - if (content) { - res.write(`data: ${JSON.stringify(content)}\n\n`) - } - brokenLine = '' - } catch (err) {} - } - - const lines = chunk.split("data: ") - const parsedLines = lines - .filter(line => line !== "" && line !== "[DONE]") - .filter(l => { - try { - JSON.parse(l) - return true - } catch (err) { - console.log('line thats not json:', l) - if (!l.includes('[DONE]')) { - brokenLine = brokenLine + l - } - return false - } - }) - .map(l => JSON.parse(l)) - - for (const parsedLine of parsedLines) { - const { choices } = parsedLine - const { delta } = choices[0] - const { content } = delta - if (content) { - res.write(`data: ${JSON.stringify(delta)}\n\n`) - } - } - } - } - - res.write('data: [DONE]\n\n') - } catch (err) { - console.log('error: ', err) - } -}) \ No newline at end of file + await streamOpenAICompatible({ + req, + res, + provider: 'openai', + apiUrl: 'https://api.openai.com/v1/chat/completions', + apiKey: process.env.OPENAI_API_KEY || '' + }) +}) diff --git a/server/src/chat/kimi.ts b/server/src/chat/kimi.ts index 6a8eddca..6970b874 100644 --- a/server/src/chat/kimi.ts +++ b/server/src/chat/kimi.ts @@ -1,15 +1,12 @@ -import { Request, Response } from "express" +import { Request, Response } from 'express' import asyncHandler from 'express-async-handler' import { streamOpenAICompatible } from './openaiCompatible' export const kimi = asyncHandler(async (req: Request, res: Response) => { - const models: any = { - kimiK27: 'kimi-k2.7' - } await streamOpenAICompatible({ req, res, - models, + provider: 'moonshot', apiUrl: 'https://api.moonshot.ai/v1/chat/completions', apiKey: process.env.MOONSHOT_API_KEY || '' }) diff --git a/server/src/chat/openaiCompatible.ts b/server/src/chat/openaiCompatible.ts index f0dbcd0c..4a0eaa05 100644 --- a/server/src/chat/openaiCompatible.ts +++ b/server/src/chat/openaiCompatible.ts @@ -1,26 +1,43 @@ -import { Request, Response } from "express" +import { Request, Response } from 'express' +import { Provider, getChatModel } from '../models' +import { ChatMessage, ChatRequest } from '../types' +import { initSSE, sendToken, sendError, sendDone, createSSEParser, pumpStream } from '../sse' interface StreamArgs { - req: Request; - res: Response; - models: Record; - apiUrl: string; - apiKey: string; + req: Request + res: Response + provider: Provider + apiUrl: string + apiKey: string } -export async function streamOpenAICompatible({ req, res, models, apiUrl, apiKey }: StreamArgs) { +function toOpenAIMessages(messages: ChatMessage[], supportsVision: boolean) { + return messages.map(m => { + if (m.image && supportsVision) { + return { + role: m.role, + content: [ + ...(m.content ? [{ type: 'text', text: m.content }] : []), + { + type: 'image_url', + image_url: { url: `data:${m.image.mimeType};base64,${m.image.data}` } + } + ] + } + } + return { role: m.role, content: m.content } + }) +} + +export async function streamOpenAICompatible({ req, res, provider, apiUrl, apiKey }: StreamArgs) { + initSSE(res) try { - res.writeHead(200, { - 'Content-Type': 'text/event-stream', - 'Connection': 'keep-alive', - 'Cache-Control': 'no-cache' - }) - const { model, messages } = req.body - const selectedModel = models[model] + const { model, messages }: ChatRequest = req.body + const chatModel = getChatModel(model) - if (!selectedModel) { - res.write('data: [DONE]\n\n') - res.end() + if (!chatModel || chatModel.provider !== provider) { + sendError(res, `unsupported model: ${model}`) + sendDone(res) return } @@ -31,66 +48,34 @@ export async function streamOpenAICompatible({ req, res, models, apiUrl, apiKey 'Authorization': `Bearer ${apiKey}` }, body: JSON.stringify({ - model: selectedModel, - messages, + model: chatModel.modelId, + messages: toOpenAIMessages(messages, chatModel.supportsVision), stream: true }) }) - const reader = response.body?.getReader() - const decoder = new TextDecoder() - let brokenLine = '' - if (reader) { - while (true) { - const { done, value } = await reader.read() - if (done) { - break - } - - let chunk = decoder.decode(value) - if (brokenLine) { - try { - const { choices } = JSON.parse(brokenLine) - const { delta } = choices[0] - const { content } = delta - if (content) { - res.write(`data: ${JSON.stringify(content)}\n\n`) - } - brokenLine = '' - } catch (err) {} - } - const lines = chunk.split("data: ") - const parsedLines = lines - .filter(line => line !== "" && line !== "[DONE]") - .filter(l => { - try { - JSON.parse(l) - return true - } catch (err) { - if (!l.includes('[DONE]')) { - brokenLine = brokenLine + l - } - return false - } - }) - .map(l => JSON.parse(l)) - - for (const parsedLine of parsedLines) { - const { choices } = parsedLine - const { delta } = choices[0] - const { content } = delta - if (content) { - res.write(`data: ${JSON.stringify(delta)}\n\n`) - } - } - } + if (!response.ok) { + const detail = await response.text() + console.error(`${provider} error:`, response.status, detail) + sendError(res, `provider error (${response.status})`) + sendDone(res) + return } - res.write('data: [DONE]\n\n') - res.end() + const parse = createSSEParser(data => { + if (data === '[DONE]') return + try { + const parsed = JSON.parse(data) + const content = parsed.choices?.[0]?.delta?.content + if (content) sendToken(res, content) + } catch {} + }) + + await pumpStream(response.body, parse) + sendDone(res) } catch (err) { - console.log('error in openai-compatible chat: ', err) - res.write('data: [DONE]\n\n') - res.end() + console.error(`error in ${provider} chat:`, err) + sendError(res, 'unexpected server error') + sendDone(res) } } diff --git a/server/src/chat/runResponse.ts b/server/src/chat/runResponse.ts deleted file mode 100644 index 9fe283b9..00000000 --- a/server/src/chat/runResponse.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Request, Response } from "express" - -// not in use yet, not complete - -export async function runResponse(req: Request, res: Response) { - try { - const { thread_id } = req.body - const headers = { - 'OpenAI-Beta': 'assistants=v1', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` - } - const response = await fetch(`https://api.openai.com/v1/threads/${thread_id}/runs`, { - headers - }) - const json = await response.json() - console.log('json: ', json) - return res.json({ - success: true - }) - } catch (err) { - console.log('error in assistant chat: ', err) - } -} diff --git a/server/src/chat/runStatus.ts b/server/src/chat/runStatus.ts deleted file mode 100644 index 565ad547..00000000 --- a/server/src/chat/runStatus.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Request, Response } from "express" - -export async function runStatus(req: Request, res: Response) { - try { - const { thread_id, run_id } = req.body - - console.log('thread_id: ', thread_id) - console.log('run_id: ', run_id) - - const headers = { - 'OpenAI-Beta': 'assistants=v1', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` - } - const response = await fetch(`https://api.openai.com/v1/threads/${thread_id}/runs/${run_id}`, { - headers - }) - const json = await response.json() - - if (json.status === 'completed') { - return res.json(json) - } else { - return res.json({ - status: 'running' - }) - } - } catch (err) { - console.log('error in assistant chat: ', err) - } -} \ No newline at end of file diff --git a/server/src/files/fileRouter.ts b/server/src/files/fileRouter.ts deleted file mode 100644 index e7855717..00000000 --- a/server/src/files/fileRouter.ts +++ /dev/null @@ -1,8 +0,0 @@ -import express from 'express' -import { uploadFile } from './upload-file' - -const router = express.Router() - -router.post('/upload-file', uploadFile) - -export default router diff --git a/server/src/files/upload-file.ts b/server/src/files/upload-file.ts deleted file mode 100644 index b492e693..00000000 --- a/server/src/files/upload-file.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Request, Response } from "express" - -const endpoint = 'https://api.openai.com/v1/files' - -export async function uploadFile(req: Request, res: Response) { - try { - const { prompt, codeInterpreter } = req.body - } catch (err) { - console.log('error in assistant chat: ', err) - } -} \ No newline at end of file diff --git a/server/src/helpers/saveFileToOpenai.ts b/server/src/helpers/saveFileToOpenai.ts deleted file mode 100644 index f363e498..00000000 --- a/server/src/helpers/saveFileToOpenai.ts +++ /dev/null @@ -1,40 +0,0 @@ -import fs from 'fs' -import path from 'path' -import { baseHeaders } from '../utils' -import { Readable } from 'stream' - -export async function saveFileToOpenai(file: any) { - const uploadsDir = path.join(process.cwd(), 'uploads') - if (!fs.existsSync(uploadsDir)){ - fs.mkdirSync(uploadsDir); - } - const filePath = path.join(uploadsDir, file.originalname); - try { - const writeToFile = new Promise((resolve, reject) => { - const readableStream = new Readable({ - read() { - this.push(file.buffer); - this.push(null); - } - }) - const writeStream = fs.createWriteStream(filePath) - readableStream.pipe(writeStream) - writeStream.on('finish', resolve) - writeStream.on('error', reject) - }) - await writeToFile - const formData = new FormData() - formData.append('purpose', 'assistants') - formData.append('file', new Blob([fs.readFileSync(filePath)], { type: 'application/json' }), file.originalname) - const response = await fetch('https://api.openai.com/v1/files', { - method: 'POST', - headers: { - ...baseHeaders - }, - body: formData - }).then(res => res.json()) - return response - } catch (err) { - console.log('error saving file to openai: ', err) - } -} \ No newline at end of file diff --git a/server/src/images/gemini.ts b/server/src/images/gemini.ts index 8b7b1b2b..ebaae0ff 100644 --- a/server/src/images/gemini.ts +++ b/server/src/images/gemini.ts @@ -1,13 +1,7 @@ -import { Request, Response } from "express" +import { Request, Response } from 'express' +import { getImageModel } from '../models' -type GeminiImageModel = "nanoBanana" | "nanoBananaPro" - -const geminiModels: Record = { - nanoBanana: "gemini-2.5-flash-image", - nanoBananaPro: "gemini-3-pro-image-preview" -} - -const geminiApiBase = "https://generativelanguage.googleapis.com/v1beta/models" +const geminiApiBase = 'https://generativelanguage.googleapis.com/v1beta/models' function getInlineData(part: any) { return part?.inlineData || part?.inline_data @@ -16,18 +10,14 @@ function getInlineData(part: any) { export async function geminiImage(req: Request, res: Response) { try { const { prompt, model } = req.body - const selectedModel = geminiModels[model as GeminiImageModel] + const imageModel = getImageModel(model) - if (!selectedModel) { - return res.json({ - error: "unsupported model" - }) + if (!imageModel) { + return res.status(400).json({ error: `unsupported model: ${model}` }) } if (!prompt && !req.file) { - return res.json({ - error: "no prompt" - }) + return res.status(400).json({ error: 'a prompt or image is required' }) } const parts: any[] = [] @@ -39,51 +29,50 @@ export async function geminiImage(req: Request, res: Response) { parts.push({ inline_data: { mime_type: req.file.mimetype, - data: req.file.buffer.toString("base64") + data: req.file.buffer.toString('base64') } }) } - const response = await fetch(`${geminiApiBase}/${selectedModel}:generateContent`, { - method: "POST", + const response = await fetch(`${geminiApiBase}/${imageModel.modelId}:generateContent`, { + method: 'POST', headers: { - "Content-Type": "application/json", - "x-goog-api-key": process.env.GEMINI_API_KEY || "" + 'Content-Type': 'application/json', + 'x-goog-api-key': process.env.GEMINI_API_KEY || '' }, body: JSON.stringify({ - contents: [{ - parts - }], + contents: [{ parts }], generationConfig: { - responseModalities: ["TEXT", "IMAGE"] + responseModalities: ['TEXT', 'IMAGE'] } }) }) + if (!response.ok) { + const detail = await response.text() + console.error('gemini image error:', response.status, detail) + return res.status(502).json({ error: `provider error (${response.status})` }) + } + const data = await response.json() const responseParts = data?.candidates?.[0]?.content?.parts || [] - const imagePart = responseParts.find((part: any) => { - const inlineData = getInlineData(part) - return inlineData?.data - }) + const imagePart = responseParts.find((part: any) => getInlineData(part)?.data) const inlineData = getInlineData(imagePart) if (!inlineData?.data) { - return res.json({ - error: "error generating image", + return res.status(502).json({ + error: 'the model did not return an image', details: data }) } - const mimeType = inlineData.mimeType || inlineData.mime_type || "image/png" + const mimeType = inlineData.mimeType || inlineData.mime_type || 'image/png' return res.json({ image: `data:${mimeType};base64,${inlineData.data}` }) } catch (err) { - console.log("error generating Gemini image: ", err) - return res.json({ - error: "error generating image" - }) + console.error('error generating Gemini image:', err) + return res.status(500).json({ error: 'error generating image' }) } } diff --git a/server/src/index.ts b/server/src/index.ts index 044ca985..6d6c62d5 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,22 +1,50 @@ import express from 'express' +import cors from 'cors' +import rateLimit from 'express-rate-limit' import chatRouter from './chat/chatRouter' import imagesRouter from './images/imagesRouter' -import bodyParser from 'body-parser' +import { auth } from './middleware' +import { CHAT_MODELS, IMAGE_MODELS } from './models' import 'dotenv/config' -const app = express() +export function createApp() { + const app = express() -app.use(bodyParser.urlencoded({ extended: true })) -app.use(bodyParser.json()) -app.use(express.json({limit: '50mb'})) + app.use(cors({ origin: process.env.CORS_ORIGIN || '*' })) + app.use(express.json({ limit: '50mb' })) + app.use(express.urlencoded({ extended: true })) -app.get('/', (req, res) => { - res.send('Hello World!') -}) + app.get('/health', (req, res) => { + res.json({ status: 'ok' }) + }) -app.use('/chat', chatRouter) -app.use('/images', imagesRouter) + app.get('/models', (req, res) => { + res.json({ + chatModels: Object.values(CHAT_MODELS).map(({ name, label, provider, supportsVision }) => ({ + name, label, provider, supportsVision + })), + imageModels: Object.values(IMAGE_MODELS).map(({ name, label, provider }) => ({ + name, label, provider + })) + }) + }) -app.listen(3050, () => { - console.log('Server started on port 3050') -}) + const limiter = rateLimit({ + windowMs: 60 * 1000, + limit: Number(process.env.RATE_LIMIT_PER_MINUTE || 60), + standardHeaders: true, + legacyHeaders: false + }) + + app.use('/chat', auth, limiter, chatRouter) + app.use('/images', auth, limiter, imagesRouter) + + return app +} + +if (require.main === module) { + const port = Number(process.env.PORT || 3050) + createApp().listen(port, () => { + console.log(`Server started on port ${port}`) + }) +} diff --git a/server/src/middleware.ts b/server/src/middleware.ts new file mode 100644 index 00000000..4b7f136d --- /dev/null +++ b/server/src/middleware.ts @@ -0,0 +1,35 @@ +import { Request, Response, NextFunction } from 'express' +import { timingSafeEqual } from 'crypto' +import { ZodSchema } from 'zod' + +/** + * Optional bearer-token auth. Enabled by setting API_AUTH_TOKEN in the + * server environment; clients must then send `Authorization: Bearer `. + */ +export function auth(req: Request, res: Response, next: NextFunction) { + const token = process.env.API_AUTH_TOKEN + if (!token) return next() + const header = req.headers.authorization || '' + const expected = Buffer.from(`Bearer ${token}`) + const provided = Buffer.from(header) + if ( + expected.length === provided.length && + timingSafeEqual(expected, provided) + ) return next() + res.status(401).json({ error: 'unauthorized' }) +} + +export function validateBody(schema: ZodSchema) { + return (req: Request, res: Response, next: NextFunction) => { + const result = schema.safeParse(req.body) + if (!result.success) { + res.status(400).json({ + error: 'invalid request body', + details: result.error.issues.map(i => `${i.path.join('.')}: ${i.message}`) + }) + return + } + req.body = result.data + next() + } +} diff --git a/server/src/models.ts b/server/src/models.ts new file mode 100644 index 00000000..e554895a --- /dev/null +++ b/server/src/models.ts @@ -0,0 +1,126 @@ +export type Provider = 'anthropic' | 'openai' | 'google' | 'zai' | 'moonshot' + +export interface ChatModel { + name: string + label: string + provider: Provider + modelId: string + supportsVision: boolean +} + +export interface ImageModel { + name: string + label: string + provider: Provider + modelId: string +} + +export const CHAT_MODELS: Record = { + claudeFable5: { + name: 'Claude Fable 5', + label: 'claudeFable5', + provider: 'anthropic', + modelId: 'claude-fable-5', + supportsVision: true + }, + claudeSonnet5: { + name: 'Claude Sonnet 5', + label: 'claudeSonnet5', + provider: 'anthropic', + modelId: 'claude-sonnet-5', + supportsVision: true + }, + claudeOpus47: { + name: 'Claude Opus 4.7', + label: 'claudeOpus47', + provider: 'anthropic', + modelId: 'claude-opus-4-7', + supportsVision: true + }, + claudeOpus: { + name: 'Claude Opus', + label: 'claudeOpus', + provider: 'anthropic', + modelId: 'claude-opus-4-5-20251101', + supportsVision: true + }, + claudeSonnet: { + name: 'Claude Sonnet', + label: 'claudeSonnet', + provider: 'anthropic', + modelId: 'claude-sonnet-4-5-20250929', + supportsVision: true + }, + claudeHaiku: { + name: 'Claude Haiku', + label: 'claudeHaiku', + provider: 'anthropic', + modelId: 'claude-haiku-4-5-20251001', + supportsVision: true + }, + claudeSonnet4: { + name: 'Claude Sonnet 4', + label: 'claudeSonnet4', + provider: 'anthropic', + modelId: 'claude-sonnet-4-6-20260201', + supportsVision: true + }, + gpt52: { + name: 'GPT 5.2', + label: 'gpt52', + provider: 'openai', + modelId: 'gpt-5.2-2025-12-11', + supportsVision: true + }, + gpt5Mini: { + name: 'GPT 5 Mini', + label: 'gpt5Mini', + provider: 'openai', + modelId: 'gpt-5-mini-2025-08-07', + supportsVision: true + }, + gemini: { + name: 'Gemini', + label: 'gemini', + provider: 'google', + modelId: 'gemini-3-pro-preview', + supportsVision: true + }, + glm52: { + name: 'GLM 5.2', + label: 'glm52', + provider: 'zai', + modelId: 'glm-5.2', + supportsVision: false + }, + kimiK27: { + name: 'Kimi K2.7', + label: 'kimiK27', + provider: 'moonshot', + modelId: 'kimi-k2.7', + supportsVision: false + } +} + +export const IMAGE_MODELS: Record = { + nanoBanana: { + name: 'Nano Banana (Gemini Flash Image)', + label: 'nanoBanana', + provider: 'google', + modelId: 'gemini-2.5-flash-image' + }, + nanoBananaPro: { + name: 'Nano Banana Pro (Gemini 3 Pro)', + label: 'nanoBananaPro', + provider: 'google', + modelId: 'gemini-3-pro-image-preview' + } +} + +export function getChatModel(label: string): ChatModel | undefined { + return CHAT_MODELS[label] +} + +export function getImageModel(label: string): ImageModel | undefined { + return IMAGE_MODELS[label] +} diff --git a/server/src/sse.ts b/server/src/sse.ts new file mode 100644 index 00000000..b2fc5f77 --- /dev/null +++ b/server/src/sse.ts @@ -0,0 +1,55 @@ +import { Response } from 'express' + +export function initSSE(res: Response) { + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Connection': 'keep-alive', + 'Cache-Control': 'no-cache' + }) +} + +export function sendToken(res: Response, content: string) { + res.write(`data: ${JSON.stringify({ content })}\n\n`) +} + +export function sendError(res: Response, message: string) { + res.write(`data: ${JSON.stringify({ error: message })}\n\n`) +} + +export function sendDone(res: Response) { + res.write('data: [DONE]\n\n') + res.end() +} + +/** + * Incrementally parses an SSE byte stream, buffering partial lines across + * chunk boundaries. Calls onData for each complete `data:` payload. + */ +export function createSSEParser(onData: (data: string) => void) { + let buffer = '' + return (chunk: string) => { + buffer += chunk + const lines = buffer.split('\n') + buffer = lines.pop() || '' + for (const line of lines) { + const trimmed = line.trim() + if (!trimmed.startsWith('data:')) continue + const data = trimmed.slice(5).trim() + if (data) onData(data) + } + } +} + +export async function pumpStream( + body: ReadableStream | null, + onChunk: (chunk: string) => void +) { + if (!body) return + const reader = body.getReader() + const decoder = new TextDecoder() + while (true) { + const { done, value } = await reader.read() + if (done) break + onChunk(decoder.decode(value, { stream: true })) + } +} diff --git a/server/src/types.ts b/server/src/types.ts new file mode 100644 index 00000000..29cb3a7a --- /dev/null +++ b/server/src/types.ts @@ -0,0 +1,26 @@ +import { z } from 'zod' + +export const imageAttachmentSchema = z.object({ + mimeType: z.string().min(1), + data: z.string().min(1) +}) + +export const chatMessageSchema = z.object({ + role: z.enum(['system', 'user', 'assistant']), + content: z.string(), + image: imageAttachmentSchema.optional() +}) + +export const chatRequestSchema = z.object({ + model: z.string().min(1), + messages: z.array(chatMessageSchema).min(1) +}) + +export const imageRequestSchema = z.object({ + model: z.string().min(1), + prompt: z.string().optional() +}) + +export type ImageAttachment = z.infer +export type ChatMessage = z.infer +export type ChatRequest = z.infer diff --git a/server/src/utils.ts b/server/src/utils.ts deleted file mode 100644 index 9842a98f..00000000 --- a/server/src/utils.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const baseHeaders = { - 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` -} \ No newline at end of file diff --git a/server/uploads/Dabit_chapter_10_April_10_2017.docx b/server/uploads/Dabit_chapter_10_April_10_2017.docx deleted file mode 100644 index ec9c4aec..00000000 Binary files a/server/uploads/Dabit_chapter_10_April_10_2017.docx and /dev/null differ diff --git a/server/uploads/migration_plan_v2_upgrade.pdf b/server/uploads/migration_plan_v2_upgrade.pdf deleted file mode 100644 index 22f7ebdf..00000000 Binary files a/server/uploads/migration_plan_v2_upgrade.pdf and /dev/null differ