diff --git a/.gitignore b/.gitignore
index 11a499a..06c378b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,14 @@
node_modules/
coverage/
dist/
+*.tsbuildinfo
ci-logs/
*.log
.DS_Store
.env
.env.*
!.env.example
+web/public/tdweb.js
+web/public/*.worker.js
+web/public/*.wasm
+web/public/*.mem
diff --git a/README.md b/README.md
index 6b4bef9..f294cf2 100644
--- a/README.md
+++ b/README.md
@@ -38,6 +38,7 @@ This repository is in the foundation phase. The current implementation establish
- Desktop wrapper contract for the Electron stack, runnable debug artifact metadata for Linux, macOS, and Windows, tray menu behavior, system notifications, shortcuts, autostart, protocol routing, and DMG/EXE/AppImage packaging targets.
- Responsive tablet layout contract for chats, settings, agent, and wallet views, including shared breakpoints, portrait and landscape navigation modes, split-pane frames, and narrow-tablet sheet fallback behavior.
- PWA wrapper contract for the web app manifest, service worker strategy, offline shell validation, update behavior, installability metadata, and browser fallbacks for unsupported native capabilities.
+- Alpha Web client in `web/` with React 18, Vite, TypeScript, TailwindCSS, TDWeb service bindings, encrypted opt-in settings/session storage, proxy configuration, Teleton Agent controls, and TON operation panels.
- Local Teleton Agent runtime supervisor contract with mock lifecycle tests for start, stop, health, resource monitoring, and logs.
- Teleton Agent action notification contract for proposals, starts, completions, approval-required states, and failures with settings-aware delivery and redacted lock-screen text.
- Teleton Agent action history contract for redacted action records, retention filtering, rollback eligibility, and irreversible action markers.
@@ -81,7 +82,7 @@ The project is intended to evolve through these layers:
4. TON blockchain integrations for wallet, transfers, swaps, NFTs, staking, and DNS.
5. Security and privacy controls for credentials, user consent, and auditability.
-See `SECURITY.md`, `docs/architecture.md`, `docs/backlog.md`, `docs/tdlib-adapter.md`, `docs/android-wrapper.md`, `docs/ios-wrapper.md`, `docs/desktop-wrapper.md`, `docs/tablet-layout.md`, `docs/web-pwa-wrapper.md`, `docs/security-audit.md`, `docs/license-matrix.md`, `docs/release-strategy.md`, `docs/release-packaging.md`, and `docs/release-readiness.md` for the current foundation plan. The agent settings, local runtime, input action map, Android wrapper, iOS wrapper, desktop wrapper, tablet layout, PWA, message database encryption, secure data deletion, security policy, security audit, license matrix, release packaging, and release readiness sections record the shared settings UI contract, supported runtime directions, platform execution boundaries, shortcut and gesture behavior, hardware security key capability checks, responsive tablet behavior, web installability behavior, vulnerability reporting expectations, credential rotation expectations, secure storage review requirements, upstream license obligations, source publication review, protected signing boundaries, human release approval, and remaining native packaging implementation gaps for Android, iOS, desktop, and web wrappers.
+See `SECURITY.md`, `docs/architecture.md`, `docs/backlog.md`, `docs/AUTH.md`, `docs/tdlib-adapter.md`, `docs/android-wrapper.md`, `docs/ios-wrapper.md`, `docs/desktop-wrapper.md`, `docs/tablet-layout.md`, `docs/web-pwa-wrapper.md`, `web/README.md`, `docs/security-audit.md`, `docs/license-matrix.md`, `docs/release-strategy.md`, `docs/release-packaging.md`, and `docs/release-readiness.md` for the current foundation plan and alpha web client. The agent settings, local runtime, input action map, Android wrapper, iOS wrapper, desktop wrapper, tablet layout, PWA, authentication flows, message database encryption, secure data deletion, security policy, security audit, license matrix, release packaging, and release readiness sections record the shared settings UI contract, supported runtime directions, platform execution boundaries, shortcut and gesture behavior, hardware security key capability checks, responsive tablet behavior, web installability behavior, vulnerability reporting expectations, credential rotation expectations, secure storage review requirements, upstream license obligations, source publication review, protected signing boundaries, human release approval, and remaining native packaging implementation gaps for Android, iOS, desktop, and web wrappers.
## Contribution Templates
diff --git a/docs/AUTH.md b/docs/AUTH.md
new file mode 100644
index 0000000..69a3229
--- /dev/null
+++ b/docs/AUTH.md
@@ -0,0 +1,36 @@
+# Authentication
+
+Teleton Client Web uses TDWeb/TDLib authorization. The alpha supports two interactive user flows:
+
+- Phone number, one-time code, and optional two-step password.
+- Telegram QR login through TDLib native QR authorization.
+
+## Telegram API Credentials
+
+The browser client still needs `VITE_TELEGRAM_API_ID` and `VITE_TELEGRAM_API_HASH` from `.env` before either sign-in method can start. These values are read by the TDLib service and are not hardcoded in source.
+
+## Phone Sign-In
+
+The default flow follows the TDLib authorization state machine:
+
+1. `authorizationStateWaitPhoneNumber`
+2. `setAuthenticationPhoneNumber`
+3. `authorizationStateWaitCode`
+4. `checkAuthenticationCode`
+5. `authorizationStateWaitPassword`, when the account requires two-step verification
+6. `authorizationStateReady`
+
+Telegram session data remains runtime-only by default. The UI can save an encrypted session marker only after explicit user consent.
+
+## QR Sign-In
+
+The QR option uses TDLib directly instead of a separate web backend. When TDLib is in `authorizationStateWaitPhoneNumber`, the client calls `requestQrCodeAuthentication` with an empty `other_user_ids` list. TDLib then emits `authorizationStateWaitOtherDeviceConfirmation` with a `link` field. The UI renders that `tg://` link as a QR code and refreshes it whenever TDLib sends a new link.
+
+The QR payload is never stored in localStorage. The rendered code is derived from the latest in-memory TDLib authorization update and is cleared when the user switches back to phone login, restarts auth, or reaches `authorizationStateReady`.
+
+## Security Notes
+
+- Do not add bot tokens, Telegram API hashes, or session strings to source control.
+- Do not place login tokens in URLs owned by the web app.
+- Keep QR payloads one-session only and in memory.
+- If a future backend flow is added, implement short TTLs, single-use server state, rate limiting, and audit logging before exposing it.
diff --git a/docs/license-matrix.md b/docs/license-matrix.md
index 5611604..e48223c 100644
--- a/docs/license-matrix.md
+++ b/docs/license-matrix.md
@@ -27,6 +27,7 @@ Verified on 2026-04-26 UTC from GitHub license metadata and upstream license or
| [`ton-org/ton-core`](https://github.com/ton-org/ton-core) | Low-level TypeScript TON cell, address, and serialization utilities for shared TON adapters. | MIT | Permissive license with notice preservation. No copyleft source publication obligation is expected from the top-level license. | Candidate dependency. Preserve MIT notice, pin package version or commit, and review transitive dependencies before release readiness. |
| [`toncenter/tonweb`](https://github.com/toncenter/tonweb) | Candidate fallback JavaScript SDK for TON wallet and provider interactions. | MIT | Permissive license with notice preservation. No copyleft source publication obligation is expected from the top-level license. | Candidate fallback only. Preserve MIT notice, compare maintenance/security posture with `ton-org/ton`, and review transitive dependencies before release readiness. |
| [`ton-connect/sdk`](https://github.com/ton-connect/sdk) | Candidate wallet-provider connection SDK for TON confirmation and signing handoff flows. | Apache-2.0 | Permissive license with NOTICE and patent-license conditions. No copyleft source publication obligation is expected, but NOTICE files and local modifications must be preserved. | Candidate dependency. Preserve Apache-2.0 license and NOTICE content, pin package version or commit, and include patent/notice review in release readiness. |
+| [`soldair/node-qrcode`](https://github.com/soldair/node-qrcode) | Web QR image generation for TDLib Telegram QR authorization in `web/`. | MIT | Permissive license with notice preservation. No copyleft source publication obligation is expected from the top-level license. | Web dependency. Preserve MIT notice, pin npm package version, and include transitive dependency review before release readiness. |
| [`ston-fi/sdk`](https://github.com/ston-fi/sdk) | Candidate STON.fi swap provider SDK for quote and unsigned swap transaction preparation. | MIT | Permissive license with notice preservation. No copyleft source publication obligation is expected from the top-level license. | Candidate dependency for swap work. Preserve MIT notice, pin package version or commit, and review transitive dependencies before release readiness. |
| [`dedust-io/sdk`](https://github.com/dedust-io/sdk) | Candidate DeDust swap provider SDK for quote and unsigned swap transaction preparation. | Apache-2.0 | Permissive license with NOTICE and patent-license conditions. No copyleft source publication obligation is expected, but NOTICE files and local modifications must be preserved. | Candidate dependency for swap work. Preserve Apache-2.0 license and NOTICE content, pin package version or commit, and include patent/notice review in release readiness. |
| [`xssnick/tonutils-go`](https://github.com/xssnick/tonutils-go) | Candidate native helper or service implementation for TON protocol operations where a Go bridge is preferred. | MIT | Permissive license with notice preservation. No copyleft source publication obligation is expected from the top-level license. | Optional native helper candidate. Preserve MIT notice, pin revision, audit transitive Go modules, and review binary distribution notices before release readiness. |
diff --git a/docs/screenshots/web-alpha-auth.png b/docs/screenshots/web-alpha-auth.png
new file mode 100644
index 0000000..aff58b4
Binary files /dev/null and b/docs/screenshots/web-alpha-auth.png differ
diff --git a/docs/screenshots/web-alpha-qr-auth-mobile.png b/docs/screenshots/web-alpha-qr-auth-mobile.png
new file mode 100644
index 0000000..8c81eba
Binary files /dev/null and b/docs/screenshots/web-alpha-qr-auth-mobile.png differ
diff --git a/docs/screenshots/web-alpha-qr-auth.png b/docs/screenshots/web-alpha-qr-auth.png
new file mode 100644
index 0000000..dad8d0b
Binary files /dev/null and b/docs/screenshots/web-alpha-qr-auth.png differ
diff --git a/docs/screenshots/web-alpha-settings-mobile.png b/docs/screenshots/web-alpha-settings-mobile.png
new file mode 100644
index 0000000..747d81b
Binary files /dev/null and b/docs/screenshots/web-alpha-settings-mobile.png differ
diff --git a/test/web-alpha-release.test.mjs b/test/web-alpha-release.test.mjs
new file mode 100644
index 0000000..1ec026d
--- /dev/null
+++ b/test/web-alpha-release.test.mjs
@@ -0,0 +1,106 @@
+import assert from 'node:assert/strict';
+import { existsSync } from 'node:fs';
+import { readFile } from 'node:fs/promises';
+import { test } from 'node:test';
+
+const root = new URL('../', import.meta.url);
+
+function pathFor(relativePath) {
+ return new URL(relativePath, root);
+}
+
+async function readJson(relativePath) {
+ return JSON.parse(await readFile(pathFor(relativePath), 'utf8'));
+}
+
+test('alpha web client release project is scaffolded with the required runtime contracts', async () => {
+ const requiredFiles = [
+ 'web/package.json',
+ 'web/index.html',
+ 'web/vite.config.ts',
+ 'web/.env.example',
+ 'web/README.md',
+ 'web/deploy.sh',
+ 'docs/AUTH.md',
+ 'web/public/manifest.webmanifest',
+ 'web/public/service-worker.js',
+ 'web/public/offline.html',
+ 'web/public/icons/icon-192.png',
+ 'web/public/icons/icon-512.png',
+ 'web/src/app/App.tsx',
+ 'web/src/services/tdlib.service.ts',
+ 'web/src/services/agent.service.ts',
+ 'web/src/services/proxy.service.ts',
+ 'web/src/services/crypto.service.ts',
+ 'web/src/shared/store/useTeletonStore.ts',
+ 'web/src/widgets/ChatList.tsx',
+ 'web/src/widgets/MessageWindow.tsx',
+ 'web/src/widgets/InputBar.tsx',
+ 'web/tests/services.test.ts'
+ ];
+
+ for (const requiredFile of requiredFiles) {
+ assert.equal(existsSync(pathFor(requiredFile)), true, `${requiredFile} is required for the alpha web release`);
+ }
+
+ const packageJson = await readJson('web/package.json');
+ assert.equal(packageJson.scripts.dev, 'vite --host 0.0.0.0');
+ assert.equal(packageJson.scripts.build, 'tsc -b && vite build');
+ assert.equal(packageJson.scripts.test, 'vitest run');
+
+ for (const dependency of ['@tonconnect/ui-react', 'qrcode', 'react', 'react-router-dom', 'tdweb', 'zustand']) {
+ assert.ok(packageJson.dependencies[dependency], `web/package.json must depend on ${dependency}`);
+ }
+
+ assert.ok(packageJson.devDependencies.tailwindcss, 'web/package.json must configure TailwindCSS');
+
+ const envExample = await readFile(pathFor('web/.env.example'), 'utf8');
+ assert.match(envExample, /VITE_TELEGRAM_API_ID=/);
+ assert.match(envExample, /VITE_TELEGRAM_API_HASH=/);
+ assert.match(envExample, /VITE_TELETON_AGENT_WS_URL=ws:\/\/localhost:8765/);
+ assert.match(envExample, /VITE_TELETON_AGENT_MANAGEMENT_URL=https:\/\/localhost:7778/);
+
+ const tdlibService = await readFile(pathFor('web/src/services/tdlib.service.ts'), 'utf8');
+ assert.match(tdlibService, /authPhone/);
+ assert.match(tdlibService, /requestQrCodeAuthentication/);
+ assert.match(tdlibService, /authCode/);
+ assert.match(tdlibService, /getChats/);
+ assert.match(tdlibService, /sendMessage/);
+ assert.match(tdlibService, /setProxy/);
+ assert.match(tdlibService, /tdweb/);
+ assert.match(tdlibService, /loadTdClientConstructor/);
+
+ const agentService = await readFile(pathFor('web/src/services/agent.service.ts'), 'utf8');
+ assert.match(agentService, /jsonrpc: '2\.0'/);
+ assert.match(agentService, /agent\.enable/);
+ assert.match(agentService, /agent\.disable/);
+ assert.match(agentService, /ton\.getBalance/);
+ assert.match(agentService, /ton\.sendTx/);
+ assert.match(agentService, /\/v1\/agent\/status/);
+
+ const cryptoService = await readFile(pathFor('web/src/services/crypto.service.ts'), 'utf8');
+ assert.match(cryptoService, /AES-GCM/);
+ assert.match(cryptoService, /indexedDB/);
+ assert.match(cryptoService, /persistEncryptedSession/);
+ assert.match(cryptoService, /consent !== true/);
+
+ const store = await readFile(pathFor('web/src/shared/store/useTeletonStore.ts'), 'utf8');
+ assert.doesNotMatch(store, /localStorage\.setItem\([^)]*session/i, 'Telegram sessions must not be persisted to localStorage');
+ assert.match(store, /authorizationStateWaitOtherDeviceConfirmation/);
+ assert.match(store, /qrLoginLink/);
+
+ const authScreen = await readFile(pathFor('web/src/features/auth/AuthScreen.tsx'), 'utf8');
+ assert.match(authScreen, /QRCode\.toDataURL/);
+ assert.match(authScreen, /requestQrLogin/);
+
+ const webReadme = await readFile(pathFor('web/README.md'), 'utf8');
+ assert.match(webReadme, /npm install/);
+ assert.match(webReadme, /npm run build/);
+ assert.match(webReadme, /teleton-agent/i);
+ assert.match(webReadme, /encrypted/i);
+
+ const authDocs = await readFile(pathFor('docs/AUTH.md'), 'utf8');
+ assert.match(authDocs, /QR/i);
+ assert.match(authDocs, /requestQrCodeAuthentication/);
+ assert.match(authDocs, /authorizationStateWaitOtherDeviceConfirmation/);
+});
diff --git a/web/.env.example b/web/.env.example
new file mode 100644
index 0000000..6a0e713
--- /dev/null
+++ b/web/.env.example
@@ -0,0 +1,6 @@
+VITE_TELEGRAM_API_ID=
+VITE_TELEGRAM_API_HASH=
+VITE_TDLIB_LOG_VERBOSITY=2
+VITE_TELETON_AGENT_WS_URL=ws://localhost:8765
+VITE_TELETON_AGENT_MANAGEMENT_URL=https://localhost:7778
+VITE_TONCONNECT_MANIFEST_URL=/tonconnect-manifest.json
diff --git a/web/README.md b/web/README.md
new file mode 100644
index 0000000..48dd6b8
--- /dev/null
+++ b/web/README.md
@@ -0,0 +1,69 @@
+# Teleton Client Web Alpha
+
+This directory contains the React 18 + Vite web client for issue `#135`.
+
+## Start
+
+```sh
+npm install
+cp .env.example .env
+npm run dev
+```
+
+Open `http://localhost:5173/app/`.
+
+## Required Environment
+
+Set Telegram API credentials before using live TDLib authentication:
+
+```sh
+VITE_TELEGRAM_API_ID=123456
+VITE_TELEGRAM_API_HASH=your_hash
+```
+
+The sign-in screen supports both phone/code authorization and Telegram QR login. QR login calls TDLib `requestQrCodeAuthentication` and renders the `authorizationStateWaitOtherDeviceConfirmation.link` payload locally; no QR token is stored in `localStorage`.
+
+The default Teleton Agent endpoints are:
+
+```sh
+VITE_TELETON_AGENT_WS_URL=ws://localhost:8765
+VITE_TELETON_AGENT_MANAGEMENT_URL=https://localhost:7778
+```
+
+The WebSocket endpoint implements the issue contract with JSON-RPC 2.0 methods:
+
+- `agent.enable({ session, userId })`
+- `agent.disable()`
+- `ton.getBalance({ address })`
+- `ton.sendTx({ to, amount, comment })`
+
+The Management API status button calls the current `teleton-agent` HTTPS API at `/v1/agent/status` with an optional `tltn_` Bearer key.
+
+## TDWeb Assets
+
+`npm install` runs `scripts/copy-tdweb-assets.mjs`, copying `node_modules/tdweb/dist/*` into `web/public/`.
+Those generated WASM, worker, and memory files are intentionally not committed because they are large upstream runtime artifacts.
+
+## Storage
+
+Telegram session data is runtime-only by default. The sign-in screen can persist an encrypted session marker only after explicit user consent.
+
+Proxy and agent settings are saved as AES-256-GCM envelopes in `localStorage`; the non-extractable browser key is stored in IndexedDB. Raw Telegram API hashes, proxy secrets, and agent API keys must stay out of source control.
+
+## Checks
+
+```sh
+npm run test
+npm run build
+npm run preview
+```
+
+`npm audit --omit=dev` currently reports a moderate `tdweb -> uuid` advisory with no upstream fix available in `tdweb@1.8.0`. Keep that dependency under release review before publishing the alpha.
+
+## Deploy
+
+```sh
+./deploy.sh
+```
+
+The script builds the app and uses `vercel` or `wrangler` when either CLI is installed.
diff --git a/web/deploy.sh b/web/deploy.sh
new file mode 100755
index 0000000..e081587
--- /dev/null
+++ b/web/deploy.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env sh
+set -eu
+
+npm install
+npm run build
+
+if command -v vercel >/dev/null 2>&1; then
+ vercel deploy --prod dist
+elif command -v wrangler >/dev/null 2>&1; then
+ wrangler pages deploy dist --project-name teleton-client-web
+else
+ printf '%s\n' "Build is ready in web/dist. Install vercel or wrangler to deploy from this script."
+fi
diff --git a/web/index.html b/web/index.html
new file mode 100644
index 0000000..fca5af5
--- /dev/null
+++ b/web/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+ Teleton Client
+
+
+
+
+
+
diff --git a/web/package-lock.json b/web/package-lock.json
new file mode 100644
index 0000000..be58a18
--- /dev/null
+++ b/web/package-lock.json
@@ -0,0 +1,3352 @@
+{
+ "name": "teleton-client-web",
+ "version": "0.1.0-alpha.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "teleton-client-web",
+ "version": "0.1.0-alpha.0",
+ "hasInstallScript": true,
+ "dependencies": {
+ "@tonconnect/ui-react": "^2.4.4",
+ "lucide-react": "^1.11.0",
+ "qrcode": "^1.5.4",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-router-dom": "^7.14.2",
+ "tdweb": "^1.8.0",
+ "zustand": "^5.0.12"
+ },
+ "devDependencies": {
+ "@types/qrcode": "^1.5.6",
+ "@types/react": "^18.3.27",
+ "@types/react-dom": "^18.3.7",
+ "@vitejs/plugin-react": "^6.0.1",
+ "autoprefixer": "^10.4.23",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^3.4.19",
+ "typescript": "^5.9.3",
+ "vite": "^8.0.10",
+ "vitest": "^4.1.5"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
+ "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
+ "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.1",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
+ "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
+ "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
+ "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.127.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz",
+ "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
+ "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz",
+ "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz",
+ "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz",
+ "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz",
+ "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz",
+ "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz",
+ "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz",
+ "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz",
+ "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz",
+ "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz",
+ "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz",
+ "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz",
+ "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.10.0",
+ "@emnapi/runtime": "1.10.0",
+ "@napi-rs/wasm-runtime": "^1.1.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz",
+ "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz",
+ "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.7",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
+ "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tonconnect/isomorphic-eventsource": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@tonconnect/isomorphic-eventsource/-/isomorphic-eventsource-0.0.2.tgz",
+ "integrity": "sha512-B4UoIjPi0QkvIzZH5fV3BQLWrqSYABdrzZQSI9sJA9aA+iC0ohOzFwVVGXanlxeDAy1bcvPbb29f6sVUk0UnnQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "eventsource": "^2.0.2"
+ }
+ },
+ "node_modules/@tonconnect/isomorphic-fetch": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@tonconnect/isomorphic-fetch/-/isomorphic-fetch-0.0.3.tgz",
+ "integrity": "sha512-jIg5nTrDwnite4fXao3dD83eCpTvInTjZon/rZZrIftIegh4XxyVb5G2mpMqXrVGk1e8SVXm3Kj5OtfMplQs0w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "node-fetch": "^2.6.9"
+ }
+ },
+ "node_modules/@tonconnect/protocol": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/@tonconnect/protocol/-/protocol-2.4.0.tgz",
+ "integrity": "sha512-3xg6sMWIrSgW7/f7iPgOb2BI3LaMScjDMqfu20fcEMbOVvNFk3TGAUKK5cJ3pfvUINsyLgPoylcIbPap37jXiA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tweetnacl": "^1.0.3",
+ "tweetnacl-util": "^0.15.1"
+ }
+ },
+ "node_modules/@tonconnect/sdk": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/@tonconnect/sdk/-/sdk-3.4.1.tgz",
+ "integrity": "sha512-eVH8erAFout89gXYHXua/es+mmLPiW1r7ng9hgHKYQv85HLq8/zzQEkbJpbTlnIuQ6CJ/QKchjMsIgYz3BYCUQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@tonconnect/isomorphic-eventsource": "0.0.2",
+ "@tonconnect/isomorphic-fetch": "0.0.3",
+ "@tonconnect/protocol": "2.4.0"
+ }
+ },
+ "node_modules/@tonconnect/ui": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/@tonconnect/ui/-/ui-2.4.4.tgz",
+ "integrity": "sha512-XgIHAkdimDk+YeRDPsQp7eOz2lM4aScYL3hiTP1Z9YgWRVROjEqTbw5ZP9pt6rgmYTzL26QpqhI6uNTFdYNFIA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@tonconnect/sdk": "3.4.1",
+ "classnames": "^2.5.1",
+ "csstype": "^3.1.3",
+ "deepmerge": "^4.3.1",
+ "ua-parser-js": "^1.0.35"
+ }
+ },
+ "node_modules/@tonconnect/ui-react": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/@tonconnect/ui-react/-/ui-react-2.4.4.tgz",
+ "integrity": "sha512-4qpAABZK+nsJSQPGAVsj6xPtQ6HkqGIn/M984dNgp5eqVoaVHti0rCMOqcTutb3BScAvWLJoo3hONqNIg2fuEQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@tonconnect/ui": "2.4.4"
+ },
+ "peerDependencies": {
+ "react": ">=17.0.0",
+ "react-dom": ">=17.0.0"
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "25.6.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz",
+ "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.19.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/qrcode": {
+ "version": "1.5.6",
+ "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz",
+ "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+ "devOptional": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
+ "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "1.0.0-rc.7"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rolldown/plugin-babel": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz",
+ "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.5",
+ "@vitest/utils": "4.1.5",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz",
+ "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.5",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz",
+ "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz",
+ "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.5",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz",
+ "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.5",
+ "@vitest/utils": "4.1.5",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz",
+ "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz",
+ "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.5",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz",
+ "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.2",
+ "caniuse-lite": "^1.0.30001787",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.23",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.23.tgz",
+ "integrity": "sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/big-integer": {
+ "version": "1.6.52",
+ "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
+ "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
+ "license": "Unlicense",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.14",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz",
+ "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/broadcast-channel": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-2.3.4.tgz",
+ "integrity": "sha512-cx1/dSb6KZ9HW1VtlqM/HLPjrdyzkKoteVmUpLXEpra00mDQW/F9ieDkoavuZMoh9/hC/6OplGzCERsZBfz/Wg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.6.2",
+ "detect-node": "^2.0.4",
+ "js-sha3": "0.8.0",
+ "microseconds": "0.1.0",
+ "nano-time": "1.0.0",
+ "rimraf": "2.6.3",
+ "unload": "2.2.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001791",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz",
+ "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
+ "license": "MIT"
+ },
+ "node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
+ "license": "MIT"
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dijkstrajs": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
+ "license": "MIT"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.344",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.344.tgz",
+ "integrity": "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
+ "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/eventsource": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz",
+ "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+ "license": "MIT"
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "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.",
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-sha3": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
+ "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==",
+ "license": "MIT"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/lie": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+ "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
+ "license": "MIT",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/localforage": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
+ "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "lie": "3.1.1"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.11.0.tgz",
+ "integrity": "sha512-UOhjdztXCgdBReRcIhsvz2siIBogfv/lhJEIViCpLt924dO+GDms9T7DNoucI23s6kEPpe988m5N0D2ajnzb2g==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/microseconds": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.1.0.tgz",
+ "integrity": "sha512-yF2K4aHXKxO4OGhW7Ek2KLgKEAFbSblBLKlF6KzwQUhjK7+uAzatRr6fZ82bftdnuDQrkBHAJp5s8quj1ME3wA==",
+ "license": "MIT"
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nano-time": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
+ "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
+ "license": "ISC",
+ "dependencies": {
+ "big-integer": "^1.6.16"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.38",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz",
+ "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.12",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz",
+ "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/qrcode": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
+ "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+ "license": "MIT",
+ "dependencies": {
+ "dijkstrajs": "^1.0.1",
+ "pngjs": "^5.0.0",
+ "yargs": "^15.3.1"
+ },
+ "bin": {
+ "qrcode": "bin/qrcode"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.14.2",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.2.tgz",
+ "integrity": "sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.14.2",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.2.tgz",
+ "integrity": "sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.14.2"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "license": "ISC"
+ },
+ "node_modules/resolve": {
+ "version": "1.22.12",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
+ "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+ "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
+ "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.127.0",
+ "@rolldown/pluginutils": "1.0.0-rc.17"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.0.0-rc.17",
+ "@rolldown/binding-darwin-arm64": "1.0.0-rc.17",
+ "@rolldown/binding-darwin-x64": "1.0.0-rc.17",
+ "@rolldown/binding-freebsd-x64": "1.0.0-rc.17",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17",
+ "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17",
+ "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17",
+ "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17",
+ "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17",
+ "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17",
+ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17"
+ }
+ },
+ "node_modules/rolldown/node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.17",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz",
+ "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "license": "ISC"
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
+ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.1",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "tinyglobby": "^0.2.11",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
+ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.7",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tdweb": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/tdweb/-/tdweb-1.8.0.tgz",
+ "integrity": "sha512-FIu48oWAUK2pAh/mp8q/faZMcik8qwzmdxl+p6gbNhEfjF/lYadDEgFBXOv/Xp5er19nFrZayIMYz3QxWBLaYw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.4.3",
+ "broadcast-channel": "^2.1.12",
+ "localforage": "^1.7.3",
+ "uuid": "^3.3.2"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz",
+ "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/tweetnacl": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
+ "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
+ "license": "Unlicense"
+ },
+ "node_modules/tweetnacl-util": {
+ "version": "0.15.1",
+ "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz",
+ "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==",
+ "license": "Unlicense"
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/ua-parser-js": {
+ "version": "1.0.41",
+ "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz",
+ "integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ua-parser-js"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/faisalman"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/faisalman"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "ua-parser-js": "script/cli.js"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.19.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
+ "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unload": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
+ "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/runtime": "^7.6.2",
+ "detect-node": "^2.0.4"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/uuid": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+ "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
+ "license": "MIT",
+ "bin": {
+ "uuid": "bin/uuid"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.0.10",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
+ "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.4",
+ "postcss": "^8.5.10",
+ "rolldown": "1.0.0-rc.17",
+ "tinyglobby": "^0.2.16"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.1.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.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
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz",
+ "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.5",
+ "@vitest/mocker": "4.1.5",
+ "@vitest/pretty-format": "4.1.5",
+ "@vitest/runner": "4.1.5",
+ "@vitest/snapshot": "4.1.5",
+ "@vitest/spy": "4.1.5",
+ "@vitest/utils": "4.1.5",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.1.0",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.5",
+ "@vitest/browser-preview": "4.1.5",
+ "@vitest/browser-webdriverio": "4.1.5",
+ "@vitest/coverage-istanbul": "4.1.5",
+ "@vitest/coverage-v8": "4.1.5",
+ "@vitest/ui": "4.1.5",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/coverage-istanbul": {
+ "optional": true
+ },
+ "@vitest/coverage-v8": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+ "license": "ISC"
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz",
+ "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/web/package.json b/web/package.json
new file mode 100644
index 0000000..ffaa27a
--- /dev/null
+++ b/web/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "teleton-client-web",
+ "version": "0.1.0-alpha.0",
+ "private": true,
+ "type": "module",
+ "description": "Alpha web client for Teleton Client with TDLib, proxy, Teleton Agent, and TON workflows.",
+ "scripts": {
+ "dev": "vite --host 0.0.0.0",
+ "build": "tsc -b && vite build",
+ "preview": "vite preview --host 0.0.0.0",
+ "test": "vitest run",
+ "postinstall": "node scripts/copy-tdweb-assets.mjs",
+ "generate:icons": "node scripts/generate-icons.mjs"
+ },
+ "dependencies": {
+ "@tonconnect/ui-react": "^2.4.4",
+ "lucide-react": "^1.11.0",
+ "qrcode": "^1.5.4",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-router-dom": "^7.14.2",
+ "tdweb": "^1.8.0",
+ "zustand": "^5.0.12"
+ },
+ "devDependencies": {
+ "@types/qrcode": "^1.5.6",
+ "@types/react": "^18.3.27",
+ "@types/react-dom": "^18.3.7",
+ "@vitejs/plugin-react": "^6.0.1",
+ "autoprefixer": "^10.4.23",
+ "postcss": "^8.5.6",
+ "tailwindcss": "^3.4.19",
+ "typescript": "^5.9.3",
+ "vite": "^8.0.10",
+ "vitest": "^4.1.5"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+}
diff --git a/web/postcss.config.js b/web/postcss.config.js
new file mode 100644
index 0000000..ba80730
--- /dev/null
+++ b/web/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {}
+ }
+};
diff --git a/web/public/icons/icon-192.png b/web/public/icons/icon-192.png
new file mode 100644
index 0000000..faa89dd
Binary files /dev/null and b/web/public/icons/icon-192.png differ
diff --git a/web/public/icons/icon-512.png b/web/public/icons/icon-512.png
new file mode 100644
index 0000000..34905a3
Binary files /dev/null and b/web/public/icons/icon-512.png differ
diff --git a/web/public/manifest.webmanifest b/web/public/manifest.webmanifest
new file mode 100644
index 0000000..704e28c
--- /dev/null
+++ b/web/public/manifest.webmanifest
@@ -0,0 +1,54 @@
+{
+ "id": "/app/",
+ "name": "Teleton Client",
+ "short_name": "Teleton",
+ "description": "Installable Teleton Client web shell for messaging, agent review, proxy settings, and TON workflows.",
+ "lang": "en",
+ "dir": "ltr",
+ "start_url": "/app/",
+ "scope": "/",
+ "display": "standalone",
+ "display_override": ["standalone", "minimal-ui", "browser"],
+ "orientation": "any",
+ "theme_color": "#1f7a8c",
+ "background_color": "#f5f7f6",
+ "categories": ["social", "productivity", "utilities"],
+ "prefer_related_applications": false,
+ "icons": [
+ {
+ "src": "/icons/icon-192.png",
+ "sizes": "192x192",
+ "type": "image/png",
+ "purpose": "any"
+ },
+ {
+ "src": "/icons/icon-512.png",
+ "sizes": "512x512",
+ "type": "image/png",
+ "purpose": "any maskable"
+ }
+ ],
+ "shortcuts": [
+ {
+ "name": "Chats",
+ "short_name": "Chats",
+ "description": "Open messaging",
+ "url": "/app/?view=chats",
+ "icons": [{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }]
+ },
+ {
+ "name": "Agent",
+ "short_name": "Agent",
+ "description": "Open Teleton Agent controls",
+ "url": "/app/?view=agent",
+ "icons": [{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }]
+ },
+ {
+ "name": "Wallet",
+ "short_name": "Wallet",
+ "description": "Open TON wallet",
+ "url": "/app/?view=ton",
+ "icons": [{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }]
+ }
+ ]
+}
diff --git a/web/public/offline.html b/web/public/offline.html
new file mode 100644
index 0000000..5ebc3fa
--- /dev/null
+++ b/web/public/offline.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+ Teleton Client Offline
+
+
+
+
+ Teleton Client is offline
+ Live Telegram, agent, proxy, and TON actions need network access or a configured local bridge.
+
+
+
diff --git a/web/public/service-worker.js b/web/public/service-worker.js
new file mode 100644
index 0000000..9a4be39
--- /dev/null
+++ b/web/public/service-worker.js
@@ -0,0 +1,45 @@
+const BUILD_ID = 'teleton-web-alpha-0.1.0';
+const SHELL_CACHE = `teleton-shell-${BUILD_ID}`;
+const PRECACHE_ASSETS = ['/app/', '/offline.html', '/manifest.webmanifest', '/icons/icon-192.png', '/icons/icon-512.png'];
+
+self.addEventListener('install', (event) => {
+ event.waitUntil(
+ caches
+ .open(SHELL_CACHE)
+ .then((cache) => cache.addAll(PRECACHE_ASSETS))
+ .then(() => self.skipWaiting())
+ );
+});
+
+self.addEventListener('activate', (event) => {
+ event.waitUntil(
+ caches
+ .keys()
+ .then((keys) => Promise.all(keys.filter((key) => key.startsWith('teleton-shell-') && key !== SHELL_CACHE).map((key) => caches.delete(key))))
+ .then(() => self.clients.claim())
+ );
+});
+
+self.addEventListener('fetch', (event) => {
+ const url = new URL(event.request.url);
+
+ if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/v1/') || url.pathname.startsWith('/media/')) {
+ event.respondWith(fetch(event.request));
+ return;
+ }
+
+ if (event.request.mode === 'navigate') {
+ event.respondWith(
+ fetch(event.request)
+ .then((response) => {
+ const clone = response.clone();
+ caches.open(SHELL_CACHE).then((cache) => cache.put('/app/', clone));
+ return response;
+ })
+ .catch(() => caches.match('/offline.html'))
+ );
+ return;
+ }
+
+ event.respondWith(caches.match(event.request).then((cached) => cached || fetch(event.request)));
+});
diff --git a/web/public/tonconnect-manifest.json b/web/public/tonconnect-manifest.json
new file mode 100644
index 0000000..9276cad
--- /dev/null
+++ b/web/public/tonconnect-manifest.json
@@ -0,0 +1,5 @@
+{
+ "url": "https://client.teleton.dev",
+ "name": "Teleton Client",
+ "iconUrl": "https://client.teleton.dev/icons/icon-512.png"
+}
diff --git a/web/scripts/copy-tdweb-assets.mjs b/web/scripts/copy-tdweb-assets.mjs
new file mode 100644
index 0000000..bb5d94c
--- /dev/null
+++ b/web/scripts/copy-tdweb-assets.mjs
@@ -0,0 +1,19 @@
+#!/usr/bin/env node
+import { cp, mkdir } from 'node:fs/promises';
+import { existsSync } from 'node:fs';
+import { dirname, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const here = dirname(fileURLToPath(import.meta.url));
+const webRoot = resolve(here, '..');
+const source = resolve(webRoot, 'node_modules/tdweb/dist');
+const target = resolve(webRoot, 'public');
+
+if (!existsSync(source)) {
+ console.warn('tdweb dist assets are not available yet; run npm install in web/ to copy them.');
+ process.exit(0);
+}
+
+await mkdir(target, { recursive: true });
+await cp(source, target, { recursive: true });
+console.log(`Copied tdweb runtime assets to ${target}`);
diff --git a/web/scripts/generate-icons.mjs b/web/scripts/generate-icons.mjs
new file mode 100644
index 0000000..2543640
--- /dev/null
+++ b/web/scripts/generate-icons.mjs
@@ -0,0 +1,137 @@
+#!/usr/bin/env node
+import { mkdir, writeFile } from 'node:fs/promises';
+import { dirname, resolve } from 'node:path';
+import { deflateSync } from 'node:zlib';
+
+const outputDir = resolve('public/icons');
+const sizes = [192, 512];
+
+const palette = {
+ ink: [16, 32, 39, 255],
+ teal: [31, 122, 140, 255],
+ mint: [45, 154, 115, 255],
+ paper: [245, 247, 246, 255],
+ saffron: [196, 124, 33, 255]
+};
+
+function crc32(buffer) {
+ let crc = 0xffffffff;
+
+ for (const byte of buffer) {
+ crc ^= byte;
+ for (let index = 0; index < 8; index += 1) {
+ crc = (crc >>> 1) ^ (0xedb88320 & -(crc & 1));
+ }
+ }
+
+ return (crc ^ 0xffffffff) >>> 0;
+}
+
+function chunk(type, data = Buffer.alloc(0)) {
+ const typeBuffer = Buffer.from(type);
+ const length = Buffer.alloc(4);
+ length.writeUInt32BE(data.length, 0);
+ const checksum = Buffer.alloc(4);
+ checksum.writeUInt32BE(crc32(Buffer.concat([typeBuffer, data])), 0);
+ return Buffer.concat([length, typeBuffer, data, checksum]);
+}
+
+function encodePng(width, height, rgba) {
+ const header = Buffer.alloc(13);
+ header.writeUInt32BE(width, 0);
+ header.writeUInt32BE(height, 4);
+ header[8] = 8;
+ header[9] = 6;
+ header[10] = 0;
+ header[11] = 0;
+ header[12] = 0;
+
+ const rows = Buffer.alloc((width * 4 + 1) * height);
+ for (let y = 0; y < height; y += 1) {
+ const rowOffset = y * (width * 4 + 1);
+ rows[rowOffset] = 0;
+ rgba.copy(rows, rowOffset + 1, y * width * 4, (y + 1) * width * 4);
+ }
+
+ return Buffer.concat([
+ Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
+ chunk('IHDR', header),
+ chunk('IDAT', deflateSync(rows, { level: 9 })),
+ chunk('IEND')
+ ]);
+}
+
+function blend(base, overlay, alpha) {
+ return base.map((value, index) => {
+ if (index === 3) return 255;
+ return Math.round(value * (1 - alpha) + overlay[index] * alpha);
+ });
+}
+
+function setPixel(buffer, width, x, y, color) {
+ if (x < 0 || y < 0 || x >= width || y >= width) return;
+ const offset = (Math.floor(y) * width + Math.floor(x)) * 4;
+ buffer[offset] = color[0];
+ buffer[offset + 1] = color[1];
+ buffer[offset + 2] = color[2];
+ buffer[offset + 3] = color[3];
+}
+
+function fillRoundedRect(buffer, width, x, y, w, h, radius, color) {
+ for (let py = y; py < y + h; py += 1) {
+ for (let px = x; px < x + w; px += 1) {
+ const left = px < x + radius;
+ const right = px >= x + w - radius;
+ const top = py < y + radius;
+ const bottom = py >= y + h - radius;
+
+ if ((left || right) && (top || bottom)) {
+ const cx = left ? x + radius : x + w - radius - 1;
+ const cy = top ? y + radius : y + h - radius - 1;
+ if ((px - cx) ** 2 + (py - cy) ** 2 > radius ** 2) continue;
+ }
+
+ setPixel(buffer, width, px, py, color);
+ }
+ }
+}
+
+function fillDiamond(buffer, width, centerX, centerY, radius, color) {
+ for (let py = centerY - radius; py <= centerY + radius; py += 1) {
+ for (let px = centerX - radius; px <= centerX + radius; px += 1) {
+ if (Math.abs(px - centerX) + Math.abs(py - centerY) <= radius) {
+ setPixel(buffer, width, px, py, color);
+ }
+ }
+ }
+}
+
+function generateIcon(size) {
+ const buffer = Buffer.alloc(size * size * 4);
+
+ for (let y = 0; y < size; y += 1) {
+ for (let x = 0; x < size; x += 1) {
+ const diagonal = (x + y) / (size * 2);
+ const color = blend(palette.teal, palette.ink, diagonal * 0.44);
+ setPixel(buffer, size, x, y, color);
+ }
+ }
+
+ fillRoundedRect(buffer, size, size * 0.16, size * 0.2, size * 0.68, size * 0.48, size * 0.08, palette.paper);
+ fillDiamond(buffer, size, size * 0.71, size * 0.72, size * 0.13, palette.mint);
+ fillDiamond(buffer, size, size * 0.71, size * 0.72, size * 0.07, palette.paper);
+ fillRoundedRect(buffer, size, size * 0.25, size * 0.32, size * 0.34, size * 0.045, size * 0.02, palette.teal);
+ fillRoundedRect(buffer, size, size * 0.25, size * 0.43, size * 0.43, size * 0.045, size * 0.02, palette.saffron);
+ fillRoundedRect(buffer, size, size * 0.25, size * 0.54, size * 0.25, size * 0.045, size * 0.02, palette.mint);
+
+ return encodePng(size, size, buffer);
+}
+
+await mkdir(outputDir, { recursive: true });
+
+for (const size of sizes) {
+ const target = resolve(outputDir, `icon-${size}.png`);
+ await mkdir(dirname(target), { recursive: true });
+ await writeFile(target, generateIcon(size));
+ console.log(`Wrote ${target}`);
+}
diff --git a/web/src/app/App.tsx b/web/src/app/App.tsx
new file mode 100644
index 0000000..9504d82
--- /dev/null
+++ b/web/src/app/App.tsx
@@ -0,0 +1,154 @@
+import { TonConnectUIProvider } from '@tonconnect/ui-react';
+import { Bot, MessageCircle, Settings, WalletCards, X } from 'lucide-react';
+import { useEffect } from 'react';
+import { useSearchParams } from 'react-router-dom';
+
+import { AgentPanel } from '../features/agent/AgentPanel';
+import { AuthScreen } from '../features/auth/AuthScreen';
+import { ChatScreen } from '../features/chat/ChatScreen';
+import { SettingsScreen } from '../features/settings/SettingsScreen';
+import { TonPanel } from '../features/ton/TonPanel';
+import { useTeletonStore } from '../shared/store/useTeletonStore';
+
+const views = [
+ { id: 'chats', label: 'Chats', icon: MessageCircle },
+ { id: 'agent', label: 'Agent', icon: Bot },
+ { id: 'ton', label: 'TON', icon: WalletCards },
+ { id: 'settings', label: 'Settings', icon: Settings }
+] as const;
+
+type ViewId = (typeof views)[number]['id'];
+
+function isView(value: string | null): value is ViewId {
+ return views.some((view) => view.id === value);
+}
+
+function NoticeStack() {
+ const notices = useTeletonStore((state) => state.notices);
+ const dismissNotice = useTeletonStore((state) => state.dismissNotice);
+
+ return (
+
+ {notices.map((entry) => (
+
+
+
{entry.message}
+
dismissNotice(entry.id)}
+ >
+
+
+
+ ))}
+
+ );
+}
+
+export function App() {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const bootstrap = useTeletonStore((state) => state.bootstrap);
+ const authStatus = useTeletonStore((state) => state.authStatus);
+ const view = isView(searchParams.get('view')) ? searchParams.get('view') : 'chats';
+ const showMobileNav = view !== 'chats' || authStatus === 'ready';
+
+ useEffect(() => {
+ void bootstrap();
+ }, [bootstrap]);
+
+ const setView = (next: ViewId) => {
+ setSearchParams({ view: next });
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
Teleton Client
+
Alpha Web
+
+
+
+ {views.map((item) => {
+ const Icon = item.icon;
+ const active = view === item.id;
+
+ return (
+ setView(item.id)}
+ >
+
+ {item.label}
+
+ );
+ })}
+
+
+
+
+ {view === 'chats' && (authStatus === 'ready' ? : )}
+ {view === 'agent' && }
+ {view === 'ton' && }
+ {view === 'settings' && }
+
+
+ {showMobileNav && (
+
+ {views.map((item) => {
+ const Icon = item.icon;
+ const active = view === item.id;
+
+ return (
+ setView(item.id)}
+ >
+
+ {item.label}
+
+ );
+ })}
+
+ )}
+
+
+
+ );
+}
diff --git a/web/src/features/agent/AgentPanel.tsx b/web/src/features/agent/AgentPanel.tsx
new file mode 100644
index 0000000..6cf3ad5
--- /dev/null
+++ b/web/src/features/agent/AgentPanel.tsx
@@ -0,0 +1,147 @@
+import { Activity, Bot, Cable, Power, ShieldCheck } from 'lucide-react';
+import { ChangeEvent } from 'react';
+
+import { useTeletonStore } from '../../shared/store/useTeletonStore';
+
+export function AgentPanel() {
+ const agent = useTeletonStore((state) => state.agent);
+ const agentStatus = useTeletonStore((state) => state.agentStatus);
+ const setAgentSettings = useTeletonStore((state) => state.setAgentSettings);
+ const saveSettings = useTeletonStore((state) => state.saveSettings);
+ const connectAgent = useTeletonStore((state) => state.connectAgent);
+ const disconnectAgent = useTeletonStore((state) => state.disconnectAgent);
+ const enableAgent = useTeletonStore((state) => state.enableAgent);
+ const disableAgent = useTeletonStore((state) => state.disableAgent);
+ const checkManagementStatus = useTeletonStore((state) => state.checkManagementStatus);
+
+ const update = (event: ChangeEvent) => {
+ setAgentSettings({
+ ...agent,
+ [event.target.name]: event.target.type === 'checkbox' ? event.target.checked : event.target.value
+ });
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
Teleton Agent
+
{agentStatus.connection}
+
+
+
+
+
+ WebSocket URL
+
+
+
+ Management API URL
+
+
+
+ Management API key
+
+
+
+
+ Agent enabled
+
+
+
+
+
void connectAgent()}
+ >
+
+ Connect
+
+
+
+ Disconnect
+
+
void checkManagementStatus()}
+ >
+
+ Status
+
+
void saveSettings()}
+ >
+
+ Save
+
+
+
+
+
+
Runtime
+
+
+
Connection
+ {agentStatus.connection}
+
+
+
Lifecycle
+ {agentStatus.lifecycle ?? 'unknown'}
+
+
+
Uptime
+ {agentStatus.uptime ? `${Math.floor(agentStatus.uptime)}s` : 'none'}
+
+
+ {agentStatus.error &&
{agentStatus.error}
}
+
+ void enableAgent()}
+ >
+ Enable
+
+ void disableAgent()}
+ >
+ Disable
+
+
+
+
+
+ );
+}
diff --git a/web/src/features/auth/AuthScreen.tsx b/web/src/features/auth/AuthScreen.tsx
new file mode 100644
index 0000000..bbdb3e6
--- /dev/null
+++ b/web/src/features/auth/AuthScreen.tsx
@@ -0,0 +1,280 @@
+import { KeyRound, Loader2, Phone, QrCode, RefreshCw, ShieldCheck } from 'lucide-react';
+import QRCode from 'qrcode';
+import { FormEvent, useEffect, useState } from 'react';
+
+import { persistEncryptedSession } from '../../services/crypto.service';
+import { useTeletonStore } from '../../shared/store/useTeletonStore';
+
+export function AuthScreen() {
+ const authStatus = useTeletonStore((state) => state.authStatus);
+ const authError = useTeletonStore((state) => state.authError);
+ const qrLoginLink = useTeletonStore((state) => state.qrLoginLink);
+ const qrLoginUpdatedAt = useTeletonStore((state) => state.qrLoginUpdatedAt);
+ const sessionId = useTeletonStore((state) => state.sessionId);
+ const initializeTelegram = useTeletonStore((state) => state.initializeTelegram);
+ const restartTelegramAuth = useTeletonStore((state) => state.restartTelegramAuth);
+ const requestQrLogin = useTeletonStore((state) => state.requestQrLogin);
+ const submitPhone = useTeletonStore((state) => state.submitPhone);
+ const submitCode = useTeletonStore((state) => state.submitCode);
+ const submitPassword = useTeletonStore((state) => state.submitPassword);
+ const [authMethod, setAuthMethod] = useState<'phone' | 'qr'>('phone');
+ const [phoneNumber, setPhoneNumber] = useState('');
+ const [code, setCode] = useState('');
+ const [password, setPassword] = useState('');
+ const [qrCodeDataUrl, setQrCodeDataUrl] = useState('');
+ const [rememberSession, setRememberSession] = useState(false);
+ const [persisted, setPersisted] = useState(false);
+
+ useEffect(() => {
+ if (authStatus === 'idle') void initializeTelegram();
+ }, [authStatus, initializeTelegram]);
+
+ useEffect(() => {
+ if (authStatus === 'ready' && rememberSession && !persisted) {
+ void persistEncryptedSession({ sessionId, savedAt: new Date().toISOString() }, { consent: true }).then(() =>
+ setPersisted(true)
+ );
+ }
+ }, [authStatus, persisted, rememberSession, sessionId]);
+
+ useEffect(() => {
+ let active = true;
+ setQrCodeDataUrl('');
+
+ if (!qrLoginLink) {
+ return () => {
+ active = false;
+ };
+ }
+
+ void QRCode.toDataURL(qrLoginLink, {
+ errorCorrectionLevel: 'M',
+ margin: 2,
+ width: 224,
+ color: {
+ dark: '#102027',
+ light: '#ffffff'
+ }
+ })
+ .then((dataUrl) => {
+ if (active) setQrCodeDataUrl(dataUrl);
+ })
+ .catch(() => {
+ if (active) setQrCodeDataUrl('');
+ });
+
+ return () => {
+ active = false;
+ };
+ }, [qrLoginLink]);
+
+ const submit = (event: FormEvent) => {
+ event.preventDefault();
+
+ if (authStatus === 'phone-required') void submitPhone(phoneNumber);
+ if (authStatus === 'code-required') void submitCode(code);
+ if (authStatus === 'password-required') void submitPassword(password);
+ };
+
+ const selectPhoneLogin = () => {
+ setAuthMethod('phone');
+ if (authStatus === 'qr-required') void restartTelegramAuth();
+ };
+
+ const selectQrLogin = () => {
+ setAuthMethod('qr');
+ if (authStatus === 'phone-required' || authStatus === 'qr-required') void requestQrLogin();
+ };
+
+ const icon =
+ authStatus === 'qr-required' ? (
+
+ ) : authStatus === 'phone-required' ? (
+
+ ) : authStatus === 'password-required' ? (
+
+ ) : (
+
+ );
+
+ return (
+
+
+
+
+
+
Teleton Client
+
Alpha Web
+
+
+
+
+ {[
+ ['TDLib', authStatus === 'error' ? 'blocked' : authStatus === 'initializing' ? 'starting' : 'ready'],
+ ['Agent', 'optional'],
+ ['TON', 'confirmable']
+ ].map(([label, value]) => (
+
+ {label}
+ {value}
+
+ ))}
+
+
+
+
+
+
+
+ );
+}
diff --git a/web/src/features/chat/ChatScreen.tsx b/web/src/features/chat/ChatScreen.tsx
new file mode 100644
index 0000000..4acf0a8
--- /dev/null
+++ b/web/src/features/chat/ChatScreen.tsx
@@ -0,0 +1,40 @@
+import { RefreshCw } from 'lucide-react';
+
+import { useTeletonStore } from '../../shared/store/useTeletonStore';
+import { ChatList } from '../../widgets/ChatList';
+import { InputBar } from '../../widgets/InputBar';
+import { MessageWindow } from '../../widgets/MessageWindow';
+
+export function ChatScreen() {
+ const chats = useTeletonStore((state) => state.chats);
+ const selectedChatId = useTeletonStore((state) => state.selectedChatId);
+ const messages = useTeletonStore((state) => (selectedChatId ? state.messagesByChat[selectedChatId] ?? [] : []));
+ const selectChat = useTeletonStore((state) => state.selectChat);
+ const loadChats = useTeletonStore((state) => state.loadChats);
+ const sendMessage = useTeletonStore((state) => state.sendMessage);
+ const selectedChat = chats.find((chat) => chat.id === selectedChatId);
+
+ return (
+
+
+
+
Chats
+ void loadChats()}
+ >
+
+
+
+
void selectChat(chatId)} />
+
+
+
+ void sendMessage(text)} />
+
+
+ );
+}
diff --git a/web/src/features/settings/SettingsScreen.tsx b/web/src/features/settings/SettingsScreen.tsx
new file mode 100644
index 0000000..cb99c11
--- /dev/null
+++ b/web/src/features/settings/SettingsScreen.tsx
@@ -0,0 +1,130 @@
+import { Eraser, Save, SlidersHorizontal } from 'lucide-react';
+import { ChangeEvent } from 'react';
+
+import { useTeletonStore } from '../../shared/store/useTeletonStore';
+import type { ProxySettings } from '../../shared/types';
+
+export function SettingsScreen() {
+ const proxy = useTeletonStore((state) => state.proxy);
+ const setProxyDraft = useTeletonStore((state) => state.setProxyDraft);
+ const applyProxy = useTeletonStore((state) => state.applyProxy);
+ const saveSettings = useTeletonStore((state) => state.saveSettings);
+ const clearSettings = useTeletonStore((state) => state.clearSettings);
+
+ const updateProxy = (event: ChangeEvent) => {
+ const next: ProxySettings = {
+ ...proxy,
+ [event.target.name]:
+ event.target instanceof HTMLInputElement && event.target.type === 'checkbox'
+ ? event.target.checked
+ : event.target.name === 'port'
+ ? Number(event.target.value)
+ : event.target.value
+ };
+ setProxyDraft(next);
+ };
+
+ return (
+
+ );
+}
diff --git a/web/src/features/ton/TonPanel.tsx b/web/src/features/ton/TonPanel.tsx
new file mode 100644
index 0000000..f3ea3ac
--- /dev/null
+++ b/web/src/features/ton/TonPanel.tsx
@@ -0,0 +1,118 @@
+import { TonConnectButton } from '@tonconnect/ui-react';
+import { Send, WalletCards } from 'lucide-react';
+import { FormEvent, useState } from 'react';
+
+import { useTeletonStore } from '../../shared/store/useTeletonStore';
+
+export function TonPanel() {
+ const tonBalance = useTeletonStore((state) => state.tonBalance);
+ const getTonBalance = useTeletonStore((state) => state.getTonBalance);
+ const sendTonTx = useTeletonStore((state) => state.sendTonTx);
+ const [address, setAddress] = useState('');
+ const [to, setTo] = useState('');
+ const [amount, setAmount] = useState('');
+ const [comment, setComment] = useState('');
+ const [confirmation, setConfirmation] = useState(false);
+ const [txHash, setTxHash] = useState('');
+
+ const requestBalance = (event: FormEvent) => {
+ event.preventDefault();
+ if (address.trim()) void getTonBalance(address.trim());
+ };
+
+ const submitTx = (event: FormEvent) => {
+ event.preventDefault();
+ if (!confirmation || !to.trim() || !amount.trim()) return;
+
+ void sendTonTx({ to: to.trim(), amount: amount.trim(), comment: comment.trim() || undefined }).then(setTxHash);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
TON
+
Agent-backed operations
+
+
+
+
+
+
+
+
+
+
+
+
Wallet State
+
+
+
Balance
+
+ {tonBalance ? `${tonBalance.balance} ${tonBalance.currency}` : 'none'}
+
+
+
+
Address
+ {tonBalance?.address ?? 'none'}
+
+
+
Transaction
+ {txHash || 'none'}
+
+
+
+
+
+ );
+}
diff --git a/web/src/index.css b/web/src/index.css
new file mode 100644
index 0000000..77d20c7
--- /dev/null
+++ b/web/src/index.css
@@ -0,0 +1,45 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ color: #102027;
+ background: #f5f7f6;
+ font-family:
+ Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ min-width: 320px;
+ min-height: 100vh;
+ margin: 0;
+}
+
+button,
+input,
+select,
+textarea {
+ font: inherit;
+}
+
+button:focus-visible,
+a:focus-visible,
+input:focus-visible,
+select:focus-visible,
+textarea:focus-visible {
+ outline: 3px solid rgb(31 122 140 / 0.36);
+ outline-offset: 2px;
+}
+
+.scrollbar-thin {
+ scrollbar-width: thin;
+ scrollbar-color: #9ab1ad transparent;
+}
diff --git a/web/src/main.tsx b/web/src/main.tsx
new file mode 100644
index 0000000..cd9f476
--- /dev/null
+++ b/web/src/main.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { BrowserRouter } from 'react-router-dom';
+
+import { App } from './app/App';
+import './index.css';
+import { registerServiceWorker } from './shared/pwa/registerServiceWorker';
+
+if (window.location.pathname === '/') {
+ window.history.replaceState(null, '', '/app/');
+}
+
+ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+
+
+
+
+
+);
+
+registerServiceWorker();
diff --git a/web/src/services/agent.service.ts b/web/src/services/agent.service.ts
new file mode 100644
index 0000000..8304acc
--- /dev/null
+++ b/web/src/services/agent.service.ts
@@ -0,0 +1,237 @@
+import type { AgentStatus, TonBalance, TonTransactionDraft } from '../shared/types';
+
+export interface JsonRpcRequest {
+ jsonrpc: '2.0';
+ id: string;
+ method: string;
+ params?: TParams;
+}
+
+interface JsonRpcSuccess {
+ jsonrpc: '2.0';
+ id: string;
+ result: TResult;
+}
+
+interface JsonRpcFailure {
+ jsonrpc: '2.0';
+ id: string;
+ error: {
+ code: number;
+ message: string;
+ data?: unknown;
+ };
+}
+
+type JsonRpcResponse = JsonRpcSuccess | JsonRpcFailure;
+
+interface PendingRequest {
+ resolve: (value: unknown) => void;
+ reject: (error: Error) => void;
+ timer: number;
+}
+
+export interface AgentEnableParams {
+ session: string;
+ userId?: string | number;
+}
+
+export interface AgentManagementOptions {
+ baseUrl: string;
+ apiKey?: string;
+}
+
+const DEFAULT_RPC_TIMEOUT_MS = 10_000;
+
+function randomId() {
+ if ('crypto' in globalThis && 'randomUUID' in globalThis.crypto) {
+ return globalThis.crypto.randomUUID();
+ }
+
+ return `rpc_${Date.now()}_${Math.random().toString(16).slice(2)}`;
+}
+
+function normalizeBaseUrl(value: string) {
+ return value.trim().replace(/\/+$/, '');
+}
+
+function problemMessage(payload: unknown, fallback: string) {
+ if (payload && typeof payload === 'object') {
+ const detail = 'detail' in payload ? String(payload.detail) : '';
+ const title = 'title' in payload ? String(payload.title) : '';
+ return detail || title || fallback;
+ }
+
+ return fallback;
+}
+
+export function createJsonRpcRequest(method: string, params?: TParams, id = randomId()): JsonRpcRequest {
+ return params === undefined ? { jsonrpc: '2.0', id, method } : { jsonrpc: '2.0', id, method, params };
+}
+
+export function parseJsonRpcResponse(payload: string): JsonRpcResponse {
+ const parsed = JSON.parse(payload) as JsonRpcResponse;
+
+ if (!parsed || parsed.jsonrpc !== '2.0' || typeof parsed.id !== 'string') {
+ throw new Error('Invalid JSON-RPC 2.0 response.');
+ }
+
+ return parsed;
+}
+
+export class TeletonAgentService {
+ private socket: WebSocket | null = null;
+ private pending = new Map();
+
+ get connected() {
+ return this.socket?.readyState === WebSocket.OPEN;
+ }
+
+ async connect(url: string) {
+ this.disconnect();
+
+ await new Promise((resolve, reject) => {
+ const socket = new WebSocket(url);
+ this.socket = socket;
+
+ socket.addEventListener('open', () => resolve(), { once: true });
+ socket.addEventListener(
+ 'error',
+ () => {
+ reject(new Error('Unable to connect to Teleton Agent WebSocket.'));
+ },
+ { once: true }
+ );
+ socket.addEventListener('message', (event) => this.handleMessage(event.data));
+ socket.addEventListener('close', () => this.rejectPending('Teleton Agent WebSocket closed.'));
+ });
+ }
+
+ disconnect() {
+ if (this.socket) {
+ this.socket.close();
+ this.socket = null;
+ }
+
+ this.rejectPending('Teleton Agent WebSocket disconnected.');
+ }
+
+ enable(params: AgentEnableParams) {
+ return this.call<{ success: boolean }>('agent.enable', params);
+ }
+
+ disable() {
+ return this.call<{ success: boolean }>('agent.disable');
+ }
+
+ getTonBalance(address: string) {
+ return this.call('ton.getBalance', { address });
+ }
+
+ sendTx(draft: TonTransactionDraft) {
+ return this.call<{ txHash: string }>('ton.sendTx', draft);
+ }
+
+ private call(method: string, params?: unknown, timeoutMs = DEFAULT_RPC_TIMEOUT_MS) {
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
+ return Promise.reject(new Error('Teleton Agent is not connected.'));
+ }
+
+ const request = createJsonRpcRequest(method, params);
+
+ return new Promise((resolve, reject) => {
+ const timer = window.setTimeout(() => {
+ this.pending.delete(request.id);
+ reject(new Error(`${method} timed out after ${timeoutMs}ms.`));
+ }, timeoutMs);
+
+ this.pending.set(request.id, {
+ resolve: (value) => resolve(value as TResult),
+ reject,
+ timer
+ });
+
+ this.socket?.send(JSON.stringify(request));
+ });
+ }
+
+ private handleMessage(data: unknown) {
+ if (typeof data !== 'string') {
+ return;
+ }
+
+ let response: JsonRpcResponse;
+
+ try {
+ response = parseJsonRpcResponse(data);
+ } catch {
+ return;
+ }
+
+ const pending = this.pending.get(response.id);
+ if (!pending) return;
+
+ window.clearTimeout(pending.timer);
+ this.pending.delete(response.id);
+
+ if ('error' in response) {
+ pending.reject(new Error(response.error.message));
+ return;
+ }
+
+ pending.resolve(response.result);
+ }
+
+ private rejectPending(message: string) {
+ for (const request of this.pending.values()) {
+ window.clearTimeout(request.timer);
+ request.reject(new Error(message));
+ }
+
+ this.pending.clear();
+ }
+}
+
+export async function getManagementAgentStatus(options: AgentManagementOptions): Promise {
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
+ const response = await fetch(`${baseUrl}/v1/agent/status`, {
+ headers: options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : undefined
+ });
+
+ if (!response.ok) {
+ let body: unknown = null;
+ try {
+ body = await response.json();
+ } catch {
+ body = null;
+ }
+
+ throw new Error(problemMessage(body, `Management API returned ${response.status}.`));
+ }
+
+ const payload = (await response.json()) as { state?: AgentStatus['lifecycle']; uptime?: number; error?: string | null };
+ return {
+ connection: 'connected',
+ lifecycle: payload.state,
+ uptime: payload.uptime,
+ error: payload.error ?? undefined
+ };
+}
+
+export async function validateManagementApi(options: AgentManagementOptions) {
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
+ const response = await fetch(`${baseUrl}/v1/auth/validate`, {
+ method: 'POST',
+ headers: options.apiKey ? { Authorization: `Bearer ${options.apiKey}` } : undefined
+ });
+
+ if (!response.ok) {
+ return { valid: false, message: `Management API returned ${response.status}.` };
+ }
+
+ const payload = (await response.json()) as { valid?: boolean; keyPrefix?: string };
+ return {
+ valid: payload.valid === true,
+ keyPrefix: payload.keyPrefix
+ };
+}
diff --git a/web/src/services/crypto.service.ts b/web/src/services/crypto.service.ts
new file mode 100644
index 0000000..00b5fd9
--- /dev/null
+++ b/web/src/services/crypto.service.ts
@@ -0,0 +1,164 @@
+import type { PersistedSettings } from '../shared/types';
+
+interface EncryptedEnvelope {
+ version: 1;
+ algorithm: 'AES-GCM';
+ keyRef: string;
+ iv: string;
+ ciphertext: string;
+ createdAt: string;
+}
+
+const KEY_DB = 'teleton.web.crypto.v1';
+const KEY_STORE = 'keys';
+const RECORD_STORE = 'records';
+const SETTINGS_KEY_REF = 'teleton.web.settings.v1';
+const SESSION_RECORD_ID = 'teleton.web.session.v1';
+const SETTINGS_STORAGE_KEY = 'teleton.web.settings.encrypted';
+
+function requireBrowserStorage() {
+ if (!('indexedDB' in globalThis) || !('crypto' in globalThis) || !globalThis.crypto.subtle) {
+ throw new Error('Encrypted browser storage is unavailable.');
+ }
+}
+
+function toBase64Url(bytes: Uint8Array) {
+ const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
+}
+
+function fromBase64Url(value: string) {
+ const padded = value.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(value.length / 4) * 4, '=');
+ const binary = atob(padded);
+ const bytes = new Uint8Array(binary.length);
+
+ for (let index = 0; index < binary.length; index += 1) {
+ bytes[index] = binary.charCodeAt(index);
+ }
+
+ return bytes;
+}
+
+function openDb() {
+ requireBrowserStorage();
+
+ return new Promise((resolve, reject) => {
+ const request = indexedDB.open(KEY_DB, 1);
+
+ request.onupgradeneeded = () => {
+ const db = request.result;
+ if (!db.objectStoreNames.contains(KEY_STORE)) db.createObjectStore(KEY_STORE);
+ if (!db.objectStoreNames.contains(RECORD_STORE)) db.createObjectStore(RECORD_STORE);
+ };
+
+ request.onsuccess = () => resolve(request.result);
+ request.onerror = () => reject(request.error ?? new Error('Unable to open encrypted storage.'));
+ });
+}
+
+async function idbGet(storeName: string, key: string) {
+ const db = await openDb();
+
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction(storeName, 'readonly');
+ const request = tx.objectStore(storeName).get(key);
+ request.onsuccess = () => resolve(request.result as T | undefined);
+ request.onerror = () => reject(request.error ?? new Error(`Unable to read ${storeName}.`));
+ tx.oncomplete = () => db.close();
+ });
+}
+
+async function idbSet(storeName: string, key: string, value: T) {
+ const db = await openDb();
+
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction(storeName, 'readwrite');
+ const request = tx.objectStore(storeName).put(value, key);
+ request.onsuccess = () => resolve();
+ request.onerror = () => reject(request.error ?? new Error(`Unable to write ${storeName}.`));
+ tx.oncomplete = () => db.close();
+ });
+}
+
+async function idbDelete(storeName: string, key: string) {
+ const db = await openDb();
+
+ return new Promise((resolve, reject) => {
+ const tx = db.transaction(storeName, 'readwrite');
+ const request = tx.objectStore(storeName).delete(key);
+ request.onsuccess = () => resolve();
+ request.onerror = () => reject(request.error ?? new Error(`Unable to delete ${storeName}.`));
+ tx.oncomplete = () => db.close();
+ });
+}
+
+async function getOrCreateKey(keyRef = SETTINGS_KEY_REF) {
+ const existing = await idbGet(KEY_STORE, keyRef);
+ if (existing) return existing;
+
+ const key = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
+ await idbSet(KEY_STORE, keyRef, key);
+ return key;
+}
+
+export async function encryptJson(value: unknown, keyRef = SETTINGS_KEY_REF): Promise {
+ const key = await getOrCreateKey(keyRef);
+ const iv = crypto.getRandomValues(new Uint8Array(12));
+ const encoded = new TextEncoder().encode(JSON.stringify(value));
+ const ciphertext = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded));
+
+ return {
+ version: 1,
+ algorithm: 'AES-GCM',
+ keyRef,
+ iv: toBase64Url(iv),
+ ciphertext: toBase64Url(ciphertext),
+ createdAt: new Date().toISOString()
+ };
+}
+
+export async function decryptJson(envelope: EncryptedEnvelope): Promise {
+ if (envelope.algorithm !== 'AES-GCM' || envelope.version !== 1) {
+ throw new Error('Unsupported encrypted envelope.');
+ }
+
+ const key = await getOrCreateKey(envelope.keyRef);
+ const iv = fromBase64Url(envelope.iv);
+ const ciphertext = fromBase64Url(envelope.ciphertext);
+ const plaintext = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext);
+ return JSON.parse(new TextDecoder().decode(plaintext)) as T;
+}
+
+export async function saveEncryptedSettings(settings: PersistedSettings) {
+ const envelope = await encryptJson(settings);
+ localStorage.setItem(SETTINGS_STORAGE_KEY, JSON.stringify(envelope));
+}
+
+export async function loadEncryptedSettings() {
+ const raw = localStorage.getItem(SETTINGS_STORAGE_KEY);
+ if (!raw) return null;
+
+ return decryptJson(JSON.parse(raw) as EncryptedEnvelope);
+}
+
+export async function clearEncryptedSettings() {
+ localStorage.removeItem(SETTINGS_STORAGE_KEY);
+}
+
+export async function persistEncryptedSession(session: unknown, options: { consent: boolean }) {
+ if (options.consent !== true) {
+ throw new Error('Explicit consent is required before persisting a Telegram session.');
+ }
+
+ const envelope = await encryptJson(session, 'teleton.web.session.key.v1');
+ await idbSet(RECORD_STORE, SESSION_RECORD_ID, envelope);
+}
+
+export async function loadEncryptedSession() {
+ const envelope = await idbGet(RECORD_STORE, SESSION_RECORD_ID);
+ return envelope ? decryptJson(envelope) : null;
+}
+
+export async function clearEncryptedSession() {
+ await idbDelete(RECORD_STORE, SESSION_RECORD_ID);
+}
diff --git a/web/src/services/proxy.service.ts b/web/src/services/proxy.service.ts
new file mode 100644
index 0000000..190bfd7
--- /dev/null
+++ b/web/src/services/proxy.service.ts
@@ -0,0 +1,96 @@
+import type { ProxySettings } from '../shared/types';
+
+export interface ProxyValidationResult {
+ valid: boolean;
+ errors: string[];
+ normalized?: ProxySettings;
+}
+
+const DEFAULT_PROXY: ProxySettings = {
+ enabled: false,
+ type: 'none',
+ host: '',
+ port: 0
+};
+
+function normalizeHost(value: string) {
+ return value.trim().replace(/^https?:\/\//i, '').replace(/\/+$/, '');
+}
+
+function isValidHost(value: string) {
+ if (!value) return false;
+ if (value.includes('/') || value.includes('@')) return false;
+ return /^[A-Za-z0-9.-]+$/.test(value) || /^\[[0-9A-Fa-f:.]+\]$/.test(value);
+}
+
+function isValidPort(value: number) {
+ return Number.isInteger(value) && value >= 1 && value <= 65535;
+}
+
+function isSafeSecretRef(value?: string) {
+ if (!value) return false;
+ return /^(env|keychain|keystore|secret):[A-Za-z0-9_.:/-]+$/.test(value);
+}
+
+export function defaultProxySettings(): ProxySettings {
+ return { ...DEFAULT_PROXY };
+}
+
+export function validateProxySettings(input: ProxySettings): ProxyValidationResult {
+ const normalized: ProxySettings = {
+ enabled: Boolean(input.enabled),
+ type: input.enabled ? input.type : 'none',
+ host: normalizeHost(input.host ?? ''),
+ port: Number(input.port ?? 0),
+ username: input.username?.trim() || undefined,
+ password: input.password || undefined,
+ secret: input.secret?.trim() || undefined
+ };
+ const errors: string[] = [];
+
+ if (!normalized.enabled || normalized.type === 'none') {
+ return {
+ valid: true,
+ errors: [],
+ normalized: { ...DEFAULT_PROXY }
+ };
+ }
+
+ if (!['socks5', 'mtproto'].includes(normalized.type)) {
+ errors.push('Select SOCKS5 or MTProto.');
+ }
+
+ if (!isValidHost(normalized.host)) {
+ errors.push('Enter a proxy host without a scheme or path.');
+ }
+
+ if (!isValidPort(normalized.port)) {
+ errors.push('Use a proxy port from 1 to 65535.');
+ }
+
+ if (normalized.type === 'mtproto' && !isSafeSecretRef(normalized.secret)) {
+ errors.push('Use a secure reference for the MTProto secret.');
+ }
+
+ if (normalized.type === 'socks5' && normalized.password && !normalized.username) {
+ errors.push('Set a SOCKS5 username when a password is provided.');
+ }
+
+ return {
+ valid: errors.length === 0,
+ errors,
+ normalized
+ };
+}
+
+export function serializeProxyForDiagnostics(proxy: ProxySettings) {
+ return {
+ enabled: proxy.enabled,
+ type: proxy.type,
+ host: proxy.host,
+ port: proxy.port,
+ hasUsername: Boolean(proxy.username),
+ hasPassword: Boolean(proxy.password),
+ hasSecret: Boolean(proxy.secret)
+ };
+}
diff --git a/web/src/services/tdlib.service.ts b/web/src/services/tdlib.service.ts
new file mode 100644
index 0000000..f0f4867
--- /dev/null
+++ b/web/src/services/tdlib.service.ts
@@ -0,0 +1,309 @@
+import type { ChatMessage, ChatSummary, ProxySettings } from '../shared/types';
+import { validateProxySettings } from './proxy.service';
+
+interface TdlibOptions {
+ apiId: number;
+ apiHash: string;
+ logVerbosityLevel?: number;
+ onUpdate?: (update: Record) => void;
+}
+
+interface TdClientConstructor {
+ new (options: Record): {
+ send(query: Record): Promise>;
+ close?: () => void;
+ };
+}
+
+declare global {
+ interface Window {
+ tdweb?: TdClientConstructor;
+ }
+}
+
+const TDWEB_SCRIPT = '/tdweb.js';
+
+function appendScript(src: string) {
+ return new Promise((resolve, reject) => {
+ const existing = document.querySelector(`script[src="${src}"]`);
+ if (existing) {
+ resolve();
+ return;
+ }
+
+ const script = document.createElement('script');
+ script.src = src;
+ script.async = true;
+ script.onload = () => resolve();
+ script.onerror = () => reject(new Error(`Unable to load ${src}. Run npm install in web/ to copy tdweb assets.`));
+ document.head.append(script);
+ });
+}
+
+async function loadTdClientConstructor(): Promise {
+ if (window.tdweb) return window.tdweb;
+
+ try {
+ await appendScript(TDWEB_SCRIPT);
+ if (window.tdweb) return window.tdweb;
+ } catch {
+ // Fall through to the npm module path for test/build environments that provide a bundler shim.
+ }
+
+ const tdwebModule = await import(/* @vite-ignore */ 'tdweb');
+ const candidate = tdwebModule.default as unknown;
+ if (typeof candidate === 'function') return candidate as TdClientConstructor;
+
+ throw new Error('tdweb did not expose a TdClient constructor.');
+}
+
+function textFromMessage(message: Record | undefined) {
+ const content = message?.content as Record | undefined;
+ const text = content?.text as Record | undefined;
+ const value = text?.text;
+ return typeof value === 'string' ? value : '';
+}
+
+function normalizeChat(chat: Record): ChatSummary {
+ const lastMessage = chat.last_message as Record | undefined;
+
+ return {
+ id: Number(chat.id),
+ title: String(chat.title ?? 'Untitled chat'),
+ unreadCount: Number(chat.unread_count ?? 0),
+ lastMessage: textFromMessage(lastMessage),
+ updatedAt: Number(lastMessage?.date ?? 0)
+ };
+}
+
+function normalizeMessage(chatId: number, message: Record, userId?: number): ChatMessage {
+ const senderId = message.sender_id as Record | undefined;
+ const senderUserId = Number(senderId?.user_id ?? 0);
+
+ return {
+ id: Number(message.id),
+ chatId,
+ sender: senderUserId && senderUserId === userId ? 'me' : 'them',
+ text: textFromMessage(message),
+ createdAt: Number(message.date ?? Date.now() / 1000) * 1000
+ };
+}
+
+export class TdlibService {
+ private client: InstanceType | null = null;
+ private apiId = 0;
+ private apiHash = '';
+ private currentUserId?: number;
+ private onUpdate?: (update: Record) => void;
+ private parametersSent = false;
+
+ get initialized() {
+ return Boolean(this.client);
+ }
+
+ async init(options: TdlibOptions) {
+ if (!Number.isInteger(options.apiId) || options.apiId <= 0 || !options.apiHash.trim()) {
+ throw new Error('Telegram API credentials are required.');
+ }
+
+ this.apiId = options.apiId;
+ this.apiHash = options.apiHash.trim();
+ this.onUpdate = options.onUpdate;
+
+ const TdClient = await loadTdClientConstructor();
+ this.client = new TdClient({
+ instanceName: 'teleton-web-alpha',
+ mode: 'wasm',
+ useDatabase: true,
+ readOnly: false,
+ jsLogVerbosityLevel: 'warning',
+ logVerbosityLevel: options.logVerbosityLevel ?? 2,
+ onUpdate: (update: Record) => {
+ void this.handleUpdate(update);
+ this.onUpdate?.(update);
+ }
+ });
+
+ await this.send({ '@type': 'getAuthorizationState' });
+ }
+
+ async authPhone(phoneNumber: string) {
+ await this.ensureParameters();
+ return this.send({
+ '@type': 'setAuthenticationPhoneNumber',
+ phone_number: phoneNumber.trim(),
+ settings: {
+ '@type': 'phoneNumberAuthenticationSettings',
+ allow_flash_call: false,
+ allow_missed_call: false,
+ is_current_phone_number: false,
+ allow_sms_retriever_api: false
+ }
+ });
+ }
+
+ async requestQrCodeAuthentication(otherUserIds: number[] = []) {
+ await this.ensureParameters();
+ return this.send({
+ '@type': 'requestQrCodeAuthentication',
+ other_user_ids: otherUserIds
+ });
+ }
+
+ authCode(code: string) {
+ return this.send({
+ '@type': 'checkAuthenticationCode',
+ code: code.trim()
+ });
+ }
+
+ authPassword(password: string) {
+ return this.send({
+ '@type': 'checkAuthenticationPassword',
+ password
+ });
+ }
+
+ async getChats(limit = 30): Promise {
+ const result = await this.send({
+ '@type': 'getChats',
+ chat_list: { '@type': 'chatListMain' },
+ limit
+ });
+ const chatIds = Array.isArray(result.chat_ids) ? result.chat_ids.slice(0, limit) : [];
+ const chats = await Promise.all(chatIds.map((id) => this.send({ '@type': 'getChat', chat_id: id })));
+ return chats.map(normalizeChat).sort((left, right) => (right.updatedAt ?? 0) - (left.updatedAt ?? 0));
+ }
+
+ async getMessages(chatId: number, limit = 40): Promise {
+ const result = await this.send({
+ '@type': 'getChatHistory',
+ chat_id: chatId,
+ from_message_id: 0,
+ offset: 0,
+ limit,
+ only_local: false
+ });
+ const messages = Array.isArray(result.messages) ? result.messages : [];
+ return messages.map((message) => normalizeMessage(chatId, message as Record, this.currentUserId)).reverse();
+ }
+
+ sendMessage(chatId: number, text: string) {
+ return this.send({
+ '@type': 'sendMessage',
+ chat_id: chatId,
+ input_message_content: {
+ '@type': 'inputMessageText',
+ text: {
+ '@type': 'formattedText',
+ text,
+ entities: []
+ },
+ disable_web_page_preview: true,
+ clear_draft: true
+ }
+ });
+ }
+
+ async setProxy(proxy: ProxySettings) {
+ const validation = validateProxySettings(proxy);
+ if (!validation.valid || !validation.normalized) {
+ throw new Error(validation.errors.join(' '));
+ }
+
+ if (!validation.normalized.enabled) {
+ return this.send({ '@type': 'disableProxy' });
+ }
+
+ const type =
+ validation.normalized.type === 'socks5'
+ ? {
+ '@type': 'proxyTypeSocks5',
+ username: validation.normalized.username ?? '',
+ password: validation.normalized.password ?? ''
+ }
+ : {
+ '@type': 'proxyTypeMtproto',
+ secret: validation.normalized.secret ?? ''
+ };
+
+ const added = await this.send({
+ '@type': 'addProxy',
+ server: validation.normalized.host,
+ port: validation.normalized.port,
+ enable: true,
+ type
+ });
+
+ const proxyId = Number(added.id ?? 0);
+ if (proxyId) {
+ await this.send({ '@type': 'enableProxy', proxy_id: proxyId });
+ }
+
+ return added;
+ }
+
+ close() {
+ this.client?.close?.();
+ this.client = null;
+ this.parametersSent = false;
+ }
+
+ private async handleUpdate(update: Record) {
+ const type = update['@type'];
+ const authorizationState = update.authorization_state as Record | undefined;
+
+ if (type === 'updateAuthorizationState' && authorizationState?.['@type'] === 'authorizationStateWaitTdlibParameters') {
+ await this.ensureParameters();
+ }
+
+ if (type === 'updateAuthorizationState' && authorizationState?.['@type'] === 'authorizationStateWaitEncryptionKey') {
+ await this.send({ '@type': 'checkDatabaseEncryptionKey', encryption_key: '' });
+ }
+
+ if (type === 'updateAuthorizationState' && authorizationState?.['@type'] === 'authorizationStateReady') {
+ const me = await this.send({ '@type': 'getMe' });
+ this.currentUserId = Number(me.id ?? 0);
+ }
+ }
+
+ private async ensureParameters() {
+ if (this.parametersSent) {
+ return { '@type': 'ok' };
+ }
+
+ this.parametersSent = true;
+
+ try {
+ return await this.send({
+ '@type': 'setTdlibParameters',
+ use_test_dc: false,
+ database_directory: 'teleton-web-db',
+ files_directory: 'teleton-web-files',
+ use_file_database: true,
+ use_chat_info_database: true,
+ use_message_database: true,
+ use_secret_chats: false,
+ api_id: this.apiId,
+ api_hash: this.apiHash,
+ system_language_code: navigator.language || 'en',
+ device_model: 'Teleton Web',
+ system_version: navigator.userAgent,
+ application_version: '0.1.0-alpha.0',
+ enable_storage_optimizer: true,
+ ignore_file_names: false
+ });
+ } catch (error) {
+ this.parametersSent = false;
+ throw error;
+ }
+ }
+
+ private send(query: Record) {
+ if (!this.client) {
+ throw new Error('TDLib is not initialized.');
+ }
+
+ return this.client.send(query);
+ }
+}
diff --git a/web/src/shared/pwa/registerServiceWorker.ts b/web/src/shared/pwa/registerServiceWorker.ts
new file mode 100644
index 0000000..20f5c04
--- /dev/null
+++ b/web/src/shared/pwa/registerServiceWorker.ts
@@ -0,0 +1,11 @@
+export function registerServiceWorker() {
+ if (!('serviceWorker' in navigator) || import.meta.env.DEV) {
+ return;
+ }
+
+ window.addEventListener('load', () => {
+ navigator.serviceWorker.register('/service-worker.js', { scope: '/' }).catch((error) => {
+ console.warn('Teleton service worker registration failed', error);
+ });
+ });
+}
diff --git a/web/src/shared/store/useTeletonStore.ts b/web/src/shared/store/useTeletonStore.ts
new file mode 100644
index 0000000..777598e
--- /dev/null
+++ b/web/src/shared/store/useTeletonStore.ts
@@ -0,0 +1,410 @@
+import { create } from 'zustand';
+
+import { TeletonAgentService, getManagementAgentStatus } from '../../services/agent.service';
+import { clearEncryptedSettings, loadEncryptedSettings, saveEncryptedSettings } from '../../services/crypto.service';
+import { defaultProxySettings, validateProxySettings } from '../../services/proxy.service';
+import { TdlibService } from '../../services/tdlib.service';
+import type {
+ AgentSettings,
+ AgentStatus,
+ AuthStatus,
+ ChatMessage,
+ ChatSummary,
+ PersistedSettings,
+ ProxySettings,
+ TonBalance,
+ TonTransactionDraft,
+ UiNotice
+} from '../types';
+
+interface TeletonState {
+ authStatus: AuthStatus;
+ authError?: string;
+ qrLoginLink?: string;
+ qrLoginUpdatedAt?: string;
+ sessionId: string;
+ chats: ChatSummary[];
+ messagesByChat: Record;
+ selectedChatId?: number;
+ proxy: ProxySettings;
+ agent: AgentSettings;
+ agentStatus: AgentStatus;
+ tonBalance?: TonBalance;
+ notices: UiNotice[];
+ bootstrapped: boolean;
+ bootstrap: () => Promise;
+ initializeTelegram: () => Promise;
+ restartTelegramAuth: () => Promise;
+ requestQrLogin: () => Promise;
+ submitPhone: (phoneNumber: string) => Promise;
+ submitCode: (code: string) => Promise;
+ submitPassword: (password: string) => Promise;
+ loadChats: () => Promise;
+ selectChat: (chatId: number) => Promise;
+ sendMessage: (text: string) => Promise;
+ setProxyDraft: (proxy: ProxySettings) => void;
+ applyProxy: () => Promise;
+ setAgentSettings: (settings: AgentSettings) => void;
+ saveSettings: () => Promise;
+ clearSettings: () => Promise;
+ connectAgent: () => Promise;
+ disconnectAgent: () => void;
+ enableAgent: () => Promise;
+ disableAgent: () => Promise;
+ checkManagementStatus: () => Promise;
+ getTonBalance: (address: string) => Promise;
+ sendTonTx: (draft: TonTransactionDraft) => Promise;
+ dismissNotice: (id: string) => void;
+}
+
+const tdlibService = new TdlibService();
+const agentService = new TeletonAgentService();
+
+function defaultAgentSettings(): AgentSettings {
+ return {
+ wsUrl: import.meta.env.VITE_TELETON_AGENT_WS_URL || 'ws://localhost:8765',
+ managementUrl: import.meta.env.VITE_TELETON_AGENT_MANAGEMENT_URL || 'https://localhost:7778',
+ enabled: false
+ };
+}
+
+function createSessionId() {
+ if ('crypto' in globalThis && 'randomUUID' in globalThis.crypto) {
+ return globalThis.crypto.randomUUID();
+ }
+
+ return `session_${Date.now()}_${Math.random().toString(16).slice(2)}`;
+}
+
+function notice(tone: UiNotice['tone'], message: string): UiNotice {
+ return {
+ id: `${Date.now()}_${Math.random().toString(16).slice(2)}`,
+ tone,
+ message
+ };
+}
+
+function authStatusFromTdlibState(stateType: unknown): AuthStatus | null {
+ switch (stateType) {
+ case 'authorizationStateWaitPhoneNumber':
+ return 'phone-required';
+ case 'authorizationStateWaitOtherDeviceConfirmation':
+ return 'qr-required';
+ case 'authorizationStateWaitCode':
+ return 'code-required';
+ case 'authorizationStateWaitPassword':
+ return 'password-required';
+ case 'authorizationStateReady':
+ return 'ready';
+ case 'authorizationStateClosed':
+ return 'idle';
+ default:
+ return null;
+ }
+}
+
+function addNotice(set: (partial: Partial) => void, get: () => TeletonState, entry: UiNotice) {
+ set({ notices: [entry, ...get().notices].slice(0, 5) });
+}
+
+export const useTeletonStore = create((set, get) => ({
+ authStatus: 'idle',
+ sessionId: createSessionId(),
+ chats: [],
+ messagesByChat: {},
+ proxy: defaultProxySettings(),
+ agent: defaultAgentSettings(),
+ agentStatus: { connection: 'disconnected' },
+ notices: [],
+ bootstrapped: false,
+
+ async bootstrap() {
+ try {
+ const stored = await loadEncryptedSettings();
+ if (stored) {
+ set({
+ proxy: stored.proxy,
+ agent: stored.agent,
+ bootstrapped: true
+ });
+ return;
+ }
+ } catch (error) {
+ addNotice(set, get, notice('warning', error instanceof Error ? error.message : 'Encrypted settings could not be loaded.'));
+ }
+
+ set({ bootstrapped: true });
+ },
+
+ async initializeTelegram() {
+ set({ authStatus: 'initializing', authError: undefined, qrLoginLink: undefined, qrLoginUpdatedAt: undefined });
+
+ const apiId = Number(import.meta.env.VITE_TELEGRAM_API_ID);
+ const apiHash = import.meta.env.VITE_TELEGRAM_API_HASH?.trim() ?? '';
+
+ try {
+ await tdlibService.init({
+ apiId,
+ apiHash,
+ logVerbosityLevel: Number(import.meta.env.VITE_TDLIB_LOG_VERBOSITY ?? 2),
+ onUpdate: (update) => {
+ const authorizationState = update.authorization_state as Record | undefined;
+ const nextAuthStatus = authStatusFromTdlibState(authorizationState?.['@type']);
+
+ if (nextAuthStatus) {
+ const nextAuthState: Partial = { authStatus: nextAuthStatus };
+ if (nextAuthStatus === 'qr-required') {
+ const qrLink = authorizationState?.link;
+ if (typeof qrLink === 'string') {
+ nextAuthState.qrLoginLink = qrLink;
+ nextAuthState.qrLoginUpdatedAt = new Date().toISOString();
+ }
+ } else {
+ nextAuthState.qrLoginLink = undefined;
+ nextAuthState.qrLoginUpdatedAt = undefined;
+ }
+
+ set(nextAuthState);
+ if (nextAuthStatus === 'ready') void get().loadChats();
+ }
+
+ if (update['@type'] === 'updateNewMessage') {
+ const message = update.message as ChatMessage | undefined;
+ if (message?.chatId) {
+ set({
+ messagesByChat: {
+ ...get().messagesByChat,
+ [message.chatId]: [...(get().messagesByChat[message.chatId] ?? []), message]
+ }
+ });
+ }
+ }
+ }
+ });
+
+ set({ authStatus: 'phone-required', qrLoginLink: undefined, qrLoginUpdatedAt: undefined });
+ } catch (error) {
+ const message = error instanceof Error ? error.message : 'TDLib initialization failed.';
+ set({ authStatus: 'error', authError: message, qrLoginLink: undefined, qrLoginUpdatedAt: undefined });
+ addNotice(set, get, notice('error', message));
+ }
+ },
+
+ async restartTelegramAuth() {
+ tdlibService.close();
+ set({ authStatus: 'initializing', authError: undefined, qrLoginLink: undefined, qrLoginUpdatedAt: undefined });
+ await get().initializeTelegram();
+ },
+
+ async requestQrLogin() {
+ try {
+ if (get().authStatus === 'qr-required') {
+ tdlibService.close();
+ await get().initializeTelegram();
+ }
+
+ set({ authStatus: 'qr-required', authError: undefined, qrLoginLink: undefined, qrLoginUpdatedAt: undefined });
+ await tdlibService.requestQrCodeAuthentication();
+ } catch (error) {
+ const message = error instanceof Error ? error.message : 'QR authentication failed.';
+ set({ authStatus: 'phone-required', authError: message, qrLoginLink: undefined, qrLoginUpdatedAt: undefined });
+ addNotice(set, get, notice('error', message));
+ }
+ },
+
+ async submitPhone(phoneNumber) {
+ try {
+ await tdlibService.authPhone(phoneNumber);
+ set({ authStatus: 'code-required', authError: undefined, qrLoginLink: undefined, qrLoginUpdatedAt: undefined });
+ } catch (error) {
+ const message = error instanceof Error ? error.message : 'Phone authentication failed.';
+ set({ authStatus: 'error', authError: message });
+ addNotice(set, get, notice('error', message));
+ }
+ },
+
+ async submitCode(code) {
+ try {
+ await tdlibService.authCode(code);
+ set({ authError: undefined });
+ } catch (error) {
+ const message = error instanceof Error ? error.message : 'Code authentication failed.';
+ set({ authStatus: 'code-required', authError: message });
+ addNotice(set, get, notice('error', message));
+ }
+ },
+
+ async submitPassword(password) {
+ try {
+ await tdlibService.authPassword(password);
+ set({ authError: undefined });
+ } catch (error) {
+ const message = error instanceof Error ? error.message : 'Password authentication failed.';
+ set({ authStatus: 'password-required', authError: message });
+ addNotice(set, get, notice('error', message));
+ }
+ },
+
+ async loadChats() {
+ try {
+ const chats = await tdlibService.getChats();
+ set({
+ chats,
+ selectedChatId: get().selectedChatId ?? chats[0]?.id
+ });
+ } catch (error) {
+ addNotice(set, get, notice('warning', error instanceof Error ? error.message : 'Chats could not be loaded.'));
+ }
+ },
+
+ async selectChat(chatId) {
+ set({ selectedChatId: chatId });
+
+ try {
+ const messages = await tdlibService.getMessages(chatId);
+ set({
+ messagesByChat: {
+ ...get().messagesByChat,
+ [chatId]: messages
+ }
+ });
+ } catch (error) {
+ addNotice(set, get, notice('warning', error instanceof Error ? error.message : 'Messages could not be loaded.'));
+ }
+ },
+
+ async sendMessage(text) {
+ const selectedChatId = get().selectedChatId;
+ if (!selectedChatId || !text.trim()) return;
+
+ const optimistic: ChatMessage = {
+ id: Date.now(),
+ chatId: selectedChatId,
+ sender: 'me',
+ text: text.trim(),
+ createdAt: Date.now(),
+ pending: true
+ };
+
+ set({
+ messagesByChat: {
+ ...get().messagesByChat,
+ [selectedChatId]: [...(get().messagesByChat[selectedChatId] ?? []), optimistic]
+ }
+ });
+
+ try {
+ await tdlibService.sendMessage(selectedChatId, text.trim());
+ } catch (error) {
+ addNotice(set, get, notice('error', error instanceof Error ? error.message : 'Message send failed.'));
+ }
+ },
+
+ setProxyDraft(proxy) {
+ set({ proxy });
+ },
+
+ async applyProxy() {
+ const validation = validateProxySettings(get().proxy);
+ if (!validation.valid || !validation.normalized) {
+ addNotice(set, get, notice('error', validation.errors.join(' ')));
+ return;
+ }
+
+ set({ proxy: validation.normalized });
+
+ try {
+ if (tdlibService.initialized) {
+ await tdlibService.setProxy(validation.normalized);
+ }
+ await get().saveSettings();
+ addNotice(set, get, notice('success', 'Proxy settings applied.'));
+ } catch (error) {
+ addNotice(set, get, notice('error', error instanceof Error ? error.message : 'Proxy settings failed.'));
+ }
+ },
+
+ setAgentSettings(settings) {
+ set({ agent: settings });
+ },
+
+ async saveSettings() {
+ const persisted: PersistedSettings = {
+ proxy: get().proxy,
+ agent: get().agent,
+ consentedAt: new Date().toISOString()
+ };
+ await saveEncryptedSettings(persisted);
+ },
+
+ async clearSettings() {
+ await clearEncryptedSettings();
+ set({
+ proxy: defaultProxySettings(),
+ agent: defaultAgentSettings()
+ });
+ addNotice(set, get, notice('success', 'Encrypted settings cleared.'));
+ },
+
+ async connectAgent() {
+ set({ agentStatus: { connection: 'connecting' } });
+
+ try {
+ await agentService.connect(get().agent.wsUrl);
+ set({ agentStatus: { connection: 'connected' } });
+ } catch (error) {
+ const message = error instanceof Error ? error.message : 'Agent connection failed.';
+ set({ agentStatus: { connection: 'error', error: message } });
+ addNotice(set, get, notice('error', message));
+ }
+ },
+
+ disconnectAgent() {
+ agentService.disconnect();
+ set({ agentStatus: { connection: 'disconnected' } });
+ },
+
+ async enableAgent() {
+ if (!agentService.connected) await get().connectAgent();
+ await agentService.enable({ session: get().sessionId });
+ set({ agent: { ...get().agent, enabled: true } });
+ await get().saveSettings();
+ },
+
+ async disableAgent() {
+ if (agentService.connected) await agentService.disable();
+ set({ agent: { ...get().agent, enabled: false } });
+ await get().saveSettings();
+ },
+
+ async checkManagementStatus() {
+ try {
+ const status = await getManagementAgentStatus({
+ baseUrl: get().agent.managementUrl,
+ apiKey: get().agent.managementApiKey
+ });
+ set({ agentStatus: status });
+ } catch (error) {
+ const message = error instanceof Error ? error.message : 'Management API status failed.';
+ set({ agentStatus: { connection: 'error', error: message } });
+ addNotice(set, get, notice('warning', message));
+ }
+ },
+
+ async getTonBalance(address) {
+ if (!agentService.connected) await get().connectAgent();
+ const balance = await agentService.getTonBalance(address);
+ set({ tonBalance: balance });
+ },
+
+ async sendTonTx(draft) {
+ if (!agentService.connected) await get().connectAgent();
+ const result = await agentService.sendTx(draft);
+ addNotice(set, get, notice('success', `Transaction submitted: ${result.txHash}`));
+ return result.txHash;
+ },
+
+ dismissNotice(id) {
+ set({ notices: get().notices.filter((entry) => entry.id !== id) });
+ }
+}));
diff --git a/web/src/shared/types.ts b/web/src/shared/types.ts
new file mode 100644
index 0000000..1f26185
--- /dev/null
+++ b/web/src/shared/types.ts
@@ -0,0 +1,78 @@
+export type AuthStatus =
+ | 'idle'
+ | 'initializing'
+ | 'phone-required'
+ | 'qr-required'
+ | 'code-required'
+ | 'password-required'
+ | 'ready'
+ | 'error';
+
+export interface ChatSummary {
+ id: number;
+ title: string;
+ unreadCount: number;
+ lastMessage?: string;
+ updatedAt?: number;
+}
+
+export interface ChatMessage {
+ id: number;
+ chatId: number;
+ sender: 'me' | 'them' | 'system';
+ text: string;
+ createdAt: number;
+ pending?: boolean;
+}
+
+export type ProxyType = 'none' | 'socks5' | 'mtproto';
+
+export interface ProxySettings {
+ enabled: boolean;
+ type: ProxyType;
+ host: string;
+ port: number;
+ username?: string;
+ password?: string;
+ secret?: string;
+}
+
+export interface AgentSettings {
+ wsUrl: string;
+ managementUrl: string;
+ managementApiKey?: string;
+ enabled: boolean;
+}
+
+export type AgentConnectionState = 'disconnected' | 'connecting' | 'connected' | 'error';
+
+export interface AgentStatus {
+ connection: AgentConnectionState;
+ lifecycle?: 'stopped' | 'starting' | 'running' | 'stopping';
+ error?: string;
+ uptime?: number;
+}
+
+export interface TonBalance {
+ address: string;
+ balance: string;
+ currency: 'TON';
+}
+
+export interface TonTransactionDraft {
+ to: string;
+ amount: string;
+ comment?: string;
+}
+
+export interface UiNotice {
+ id: string;
+ tone: 'info' | 'success' | 'warning' | 'error';
+ message: string;
+}
+
+export interface PersistedSettings {
+ proxy: ProxySettings;
+ agent: AgentSettings;
+ consentedAt: string;
+}
diff --git a/web/src/vite-env.d.ts b/web/src/vite-env.d.ts
new file mode 100644
index 0000000..2367739
--- /dev/null
+++ b/web/src/vite-env.d.ts
@@ -0,0 +1,22 @@
+///
+
+interface ImportMetaEnv {
+ readonly VITE_TELEGRAM_API_ID?: string;
+ readonly VITE_TELEGRAM_API_HASH?: string;
+ readonly VITE_TDLIB_LOG_VERBOSITY?: string;
+ readonly VITE_TELETON_AGENT_WS_URL?: string;
+ readonly VITE_TELETON_AGENT_MANAGEMENT_URL?: string;
+ readonly VITE_TONCONNECT_MANIFEST_URL?: string;
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv;
+}
+
+declare module 'tdweb' {
+ export default class TdClient {
+ constructor(options: Record);
+ send(query: Record): Promise>;
+ close(): void;
+ }
+}
diff --git a/web/src/widgets/ChatList.tsx b/web/src/widgets/ChatList.tsx
new file mode 100644
index 0000000..76f6607
--- /dev/null
+++ b/web/src/widgets/ChatList.tsx
@@ -0,0 +1,38 @@
+import type { ChatSummary } from '../shared/types';
+
+interface ChatListProps {
+ chats: ChatSummary[];
+ selectedChatId?: number;
+ onSelect: (chatId: number) => void;
+}
+
+export function ChatList({ chats, selectedChatId, onSelect }: ChatListProps) {
+ if (chats.length === 0) {
+ return No chats loaded.
;
+ }
+
+ return (
+
+ {chats.map((chat) => (
+ onSelect(chat.id)}
+ >
+
+ {chat.title}
+ {chat.lastMessage || 'No recent message'}
+
+ {chat.unreadCount > 0 && (
+
+ {chat.unreadCount}
+
+ )}
+
+ ))}
+
+ );
+}
diff --git a/web/src/widgets/InputBar.tsx b/web/src/widgets/InputBar.tsx
new file mode 100644
index 0000000..5493cf8
--- /dev/null
+++ b/web/src/widgets/InputBar.tsx
@@ -0,0 +1,41 @@
+import { Send } from 'lucide-react';
+import { FormEvent, useState } from 'react';
+
+interface InputBarProps {
+ disabled?: boolean;
+ onSend: (text: string) => void;
+}
+
+export function InputBar({ disabled, onSend }: InputBarProps) {
+ const [text, setText] = useState('');
+
+ const submit = (event: FormEvent) => {
+ event.preventDefault();
+ const value = text.trim();
+ if (!value) return;
+ onSend(value);
+ setText('');
+ };
+
+ return (
+
+ );
+}
diff --git a/web/src/widgets/MessageWindow.tsx b/web/src/widgets/MessageWindow.tsx
new file mode 100644
index 0000000..6b09f88
--- /dev/null
+++ b/web/src/widgets/MessageWindow.tsx
@@ -0,0 +1,40 @@
+import type { ChatMessage, ChatSummary } from '../shared/types';
+
+interface MessageWindowProps {
+ chat?: ChatSummary;
+ messages: ChatMessage[];
+}
+
+export function MessageWindow({ chat, messages }: MessageWindowProps) {
+ return (
+
+
+
+ {messages.length === 0 &&
No messages loaded.
}
+ {messages.map((message) => (
+
+
{message.text}
+
+ {new Date(message.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
+ {message.pending ? ' pending' : ''}
+
+
+ ))}
+
+
+ );
+}
diff --git a/web/tailwind.config.ts b/web/tailwind.config.ts
new file mode 100644
index 0000000..89c229f
--- /dev/null
+++ b/web/tailwind.config.ts
@@ -0,0 +1,22 @@
+import type { Config } from 'tailwindcss';
+
+export default {
+ content: ['./index.html', './src/**/*.{ts,tsx}'],
+ theme: {
+ extend: {
+ colors: {
+ ink: '#102027',
+ paper: '#f5f7f6',
+ mist: '#e6ecea',
+ teal: '#1f7a8c',
+ mint: '#2d9a73',
+ saffron: '#c47c21',
+ coral: '#d45b4d'
+ },
+ boxShadow: {
+ panel: '0 14px 38px rgb(16 32 39 / 0.08)'
+ }
+ }
+ },
+ plugins: []
+} satisfies Config;
diff --git a/web/tests/services.test.ts b/web/tests/services.test.ts
new file mode 100644
index 0000000..2f50a49
--- /dev/null
+++ b/web/tests/services.test.ts
@@ -0,0 +1,83 @@
+import { describe, expect, it } from 'vitest';
+
+import { createJsonRpcRequest, parseJsonRpcResponse } from '../src/services/agent.service';
+import { defaultProxySettings, serializeProxyForDiagnostics, validateProxySettings } from '../src/services/proxy.service';
+
+describe('proxy settings', () => {
+ it('normalizes a disabled proxy to a direct route', () => {
+ const result = validateProxySettings({
+ ...defaultProxySettings(),
+ enabled: false,
+ type: 'socks5',
+ host: 'proxy.local',
+ port: 1080
+ });
+
+ expect(result.valid).toBe(true);
+ expect(result.normalized).toEqual(defaultProxySettings());
+ });
+
+ it('requires a secure reference for MTProto secrets', () => {
+ const invalid = validateProxySettings({
+ enabled: true,
+ type: 'mtproto',
+ host: 'proxy.teleton.local',
+ port: 443,
+ secret: 'plaintext-secret'
+ });
+ const valid = validateProxySettings({
+ enabled: true,
+ type: 'mtproto',
+ host: 'proxy.teleton.local',
+ port: 443,
+ secret: 'env:TELETON_MTPROTO_SECRET'
+ });
+
+ expect(invalid.valid).toBe(false);
+ expect(invalid.errors.join(' ')).toMatch(/secure reference/i);
+ expect(valid.valid).toBe(true);
+ });
+
+ it('redacts proxy credential presence in diagnostics', () => {
+ expect(
+ serializeProxyForDiagnostics({
+ enabled: true,
+ type: 'socks5',
+ host: '127.0.0.1',
+ port: 9050,
+ username: 'user',
+ password: 'secret'
+ })
+ ).toEqual({
+ enabled: true,
+ type: 'socks5',
+ host: '127.0.0.1',
+ port: 9050,
+ hasUsername: true,
+ hasPassword: true,
+ hasSecret: false
+ });
+ });
+});
+
+describe('agent JSON-RPC helpers', () => {
+ it('builds the WebSocket JSON-RPC methods required by the client contract', () => {
+ expect(createJsonRpcRequest('agent.enable', { session: 'runtime-session' }, '1')).toEqual({
+ jsonrpc: '2.0',
+ id: '1',
+ method: 'agent.enable',
+ params: { session: 'runtime-session' }
+ });
+ expect(createJsonRpcRequest('ton.getBalance', { address: 'EQ...' }, '2').method).toBe('ton.getBalance');
+ expect(createJsonRpcRequest('ton.sendTx', { to: 'EQ...', amount: '1' }, '3').method).toBe('ton.sendTx');
+ });
+
+ it('parses valid JSON-RPC responses and rejects malformed payloads', () => {
+ expect(parseJsonRpcResponse('{"jsonrpc":"2.0","id":"1","result":{"success":true}}')).toEqual({
+ jsonrpc: '2.0',
+ id: '1',
+ result: { success: true }
+ });
+ expect(() => parseJsonRpcResponse('{"id":"1"}')).toThrow(/Invalid JSON-RPC/);
+ });
+});
diff --git a/web/tsconfig.json b/web/tsconfig.json
new file mode 100644
index 0000000..0c57c01
--- /dev/null
+++ b/web/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ES2020"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "types": ["vite/client", "vitest/globals"]
+ },
+ "include": ["src", "tests", "vite.config.ts", "tailwind.config.ts"]
+}
diff --git a/web/vite.config.ts b/web/vite.config.ts
new file mode 100644
index 0000000..9a89694
--- /dev/null
+++ b/web/vite.config.ts
@@ -0,0 +1,41 @@
+import react from '@vitejs/plugin-react';
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ host: '0.0.0.0',
+ port: 5173
+ },
+ preview: {
+ host: '0.0.0.0',
+ port: 4173
+ },
+ build: {
+ sourcemap: true,
+ chunkSizeWarningLimit: 5000,
+ rollupOptions: {
+ output: {
+ manualChunks(id) {
+ if (id.includes('node_modules/@tonconnect')) {
+ return 'wallet';
+ }
+
+ if (
+ id.includes('node_modules/react') ||
+ id.includes('node_modules/react-dom') ||
+ id.includes('node_modules/react-router-dom')
+ ) {
+ return 'react';
+ }
+
+ return undefined;
+ }
+ }
+ }
+ },
+ test: {
+ environment: 'node',
+ globals: true
+ }
+});