diff --git a/README.md b/README.md index f9f0bd68..d04854a4 100644 --- a/README.md +++ b/README.md @@ -112,9 +112,22 @@ Place your cursor in any text field — ChatGPT, Claude, Cursor, Notion, an emai Unlike dictation tools that simply emit a word-for-word transcript, OpenLess's headline capability is its **AI-prompt mode**: you speak freely, and it adds structure, captures your constraints, and produces a context-rich prompt that you can paste directly into ChatGPT, Claude, or Cursor. +## From tool to infrastructure + +Advanced systems all do the same thing: **they turn today's communication into tomorrow's default rules.** The Internet won not because HTTP was faster, but because no one negotiates how data is transmitted anymore. Git won not because the code was better, but because teams stopped coordinating versions every day. Stripe won not because payments were faster, but because developers stopped coordinating with banks. + +What OpenLess does is not "faster dictation" — it **eliminates every act of coordination between a thought and clean text.** Turning what's in your head into usable text used to mean deciding, over and over: what tone for this one? how is that name spelled? which input field, copy or paste? where does the model's key live, and who has access? OpenLess **sediments** those repeated negotiations, layer by layer, into defaults: + +- **Tone** sediments into a **style pack** — named, switchable, live at a keystroke, never re-decided. +- **Proper nouns** sediment into **dictionary hotwords** — recognized by the ASR, hinted to the polish model, never re-corrected. +- **Where text lands** sediments into a **single insertion protocol** — hold → speak → it appears at the cursor, with an automatic clipboard fallback. +- **Model and credentials** sediment into the **OS vault** — authorized once at launch, never renegotiated. + +That is what **authorizing the infrastructure once, at launch** means: on first run you grant the microphone, accessibility, and cloud credentials exactly once; from that moment, "speak → clean text at the cursor" is no longer a negotiation you run each time, but a default protocol you can forget is even there. Judge OpenLess not by how much faster it is, but by how much **coordination it removes.** + ## ✨ What's new -Two features define how OpenLess feels in daily use: +Two capabilities that sediment yet more of the coordination you used to repeat every day into defaults: - 🎨 **Style Pack Marketplace.** OpenLess no longer ships a single fixed "polish" voice. Build your own **style packs** with custom system prompts, switch between them with a hotkey, and **install community packs in one click** — or publish your own to share. When a style is tuned to your exact task (cold emails, commit messages, 小红书 posts, formal reports, your team's tone), the output is not merely cleaner — it is *noticeably better*, because the model is finally writing the way you intend. - ⚡ **Streaming insertion.** Text now flows to your cursor **character by character** as it is polished, rather than making you wait for the complete result. Perceived latency drops sharply, so dictation feels nearly as fast as thinking — and it automatically falls back to a one-shot paste when an application cannot accept streamed keystrokes. @@ -187,6 +200,8 @@ OpenLess does one thing: it **turns speech into usable written text — AI promp ## Status (v1.3.6) +Every item below is one more layer sedimented into a default — a capability you authorize once and then never manage again. This is the infrastructure you stand on after launch: + - Tauri 2 backend in Rust with a React/TypeScript frontend. macOS 12+, Windows 10+. - 🎨 **Style Pack Marketplace** — browse, install, and like community **style packs** from the in-app Marketplace, and publish your own (custom system prompt per pack, switchable by hotkey). Backed by a moderated marketplace backend; uploads are reviewed before they go public. - ⚡ **Streaming insertion** — polished text is written to the cursor character by character to reduce perceived latency, with an automatic one-shot-paste fallback. Toggle in Settings → Recording. diff --git a/README.zh.md b/README.zh.md index 9f836fb8..6cc44672 100644 --- a/README.zh.md +++ b/README.zh.md @@ -112,9 +112,22 @@ OpenLess 是一款跨平台(macOS 与 Windows)语音输入应用,是 [Typeless]( 与只产出逐字转写稿的听写工具不同,OpenLess 的核心能力是 **AI Prompt 模式**:你自由地说,它来补充结构、整理你的约束条件,生成一段包含上下文的提示词,可直接粘贴进 ChatGPT、Claude 或 Cursor。 +## 从工具到基础设施 + +真正高级的系统都在做同一件事:**把今天的沟通,变成明天的默认规则。** 互联网赢,不是因为 HTTP 更快,而是因为没人再讨论数据怎么传;Git 赢,不是因为代码更优,而是因为团队不用每天协调版本;Stripe 赢,不是因为支付更快,而是因为开发者不用再协调银行。 + +OpenLess 做的不是“更快的听写”,而是**消灭“想法 → 干净文字”之间的每一次协调**。过去,把脑子里的话落成能用的文字,你要一遍遍临时决定:这段用什么语气?这个名字怎么拼?落到哪个输入框、复制还是粘贴?模型的钥匙放哪、谁有权限?OpenLess 把这些反复的临时协商,逐层**沉降**成默认规则: + +- **语气** 沉降成**风格包**——命名、可切换,热键一按即生效,不必每次重新决定。 +- **专有名词** 沉降成**词典热词**——ASR 端识别、润色端语义提示,不必逐次纠正。 +- **文字落点** 沉降成**唯一插入协议**——按住 → 说 → 落在光标处,失败自动回退剪贴板。 +- **模型与凭据** 沉降进**操作系统保管库**——开屏授权一次,此后永不重谈。 + +这就是**开屏即授权完基础设施**:首次启动,你只授权一次麦克风、辅助功能与云端凭据;从那一刻起,“开口说话 → 光标处出现干净文字”不再是一次次的操作协商,而是一条你可以忘记它存在的默认协议。衡量 OpenLess 的价值,不在于它快了多少,而在于它替你**消灭了多少协调**。 + ## ✨ 更新亮点 -有两项功能,显著改变了 OpenLess 的日常使用体验: +下面两项能力,把过去每天都要重复的协调,进一步沉降成了默认规则: - 🎨 **风格包市场(Style Pack Marketplace)。** OpenLess 不再只内置一种固定的“润色”语气。你可以用自定义系统提示词构建自己的**风格包**,用快捷键在它们之间切换,并**一键安装社区分享的风格包**——也可以发布自己的与他人分享。当风格与你的具体任务高度契合(冷启动邮件、commit message、小红书文案、正式报告、团队语气)时,产出的文本不只是更干净,而是*明显更好*,因为模型终于在按你真正想要的方式写作。 - ⚡ **流式插入。** 文本现在会随润色**逐字符**写入光标,而不必等待完整结果生成。感知延迟大幅下降,听写几乎和思考一样快——当某个应用无法接受流式按键时,它会自动回退为一次性粘贴。 @@ -187,6 +200,8 @@ OpenLess 只做一件事:**把语音变成可用的书面文字(尤其是 AI 提 ## 当前状态(v1.3.6) +下面每一项,都是一层已经沉降为默认、你授权一次之后就不必再操心的能力——这就是开屏之后你所站立的基础设施: + - Tauri 2 后端(Rust)+ React/TypeScript 前端。macOS 12+、Windows 10+。 - 🎨 **风格包市场**——在应用内的 Marketplace 浏览、安装、点赞社区**风格包**,并发布自己的(每个包一套自定义系统提示词,可用快捷键切换)。由经过审核的市场后端支撑;上传内容公开前会经过审核。 - ⚡ **流式插入**——润色后的文本逐字符写入光标以降低感知延迟,并带有自动的一次性粘贴回退。可在 设置 → 录音 中切换。 diff --git a/docs/CODE_QUALITY_CLEANUP.md b/docs/CODE_QUALITY_CLEANUP.md new file mode 100644 index 00000000..3f7533df --- /dev/null +++ b/docs/CODE_QUALITY_CLEANUP.md @@ -0,0 +1,98 @@ +# Code Quality Cleanup Roadmap + +> From: 1-app audit (2026-06-17) +> Issue: #699 +> Author: zsxh1990 + +## Overview + +This document tracks the code quality cleanup items identified in the security audit. Each item has a specific action, risk level, and estimated effort. + +## Items + +### 1. Remove blanket `#![allow(dead_code, unused_imports, unused_variables)]` (#27) + +**Files affected:** +- `src/lib.rs` (line 1-4) +- `src/coordinator.rs` (line 1-4) + +**Current state:** +```rust +#![cfg_attr( + target_os = "linux", + allow(dead_code, unused_imports, unused_variables) +)] +``` + +**Action:** Remove the blanket allow and clean dead code incrementally. + +**Approach:** +1. Remove the blanket allow from both files +2. Run `cargo check` to identify all warnings +3. Fix warnings in priority order: + - Remove unused imports + - Remove unused variables (prefix with `_`) + - Remove dead code or mark with targeted `#[allow(dead_code)]` + +**Risk:** Low (no behavior change, only compiler warnings) +**Effort:** 2-4 hours +**Dependencies:** None + +### 2. Extract shared request builder in polish.rs (#24) + +**File affected:** `src/polish.rs` + +**Current state:** +- `OpenAICompatibleLLMProvider` (line 278) implements `polish()`, `translate_to()`, `answer_chat_streaming()` +- `CodexOAuthLLMProvider` (line 912) implements the same functions +- Both share ~10-arg function signatures and similar logic + +**Action:** Extract a shared `RequestBuilder` trait or struct. + +**Approach:** +1. Identify common parameters across providers +2. Create `RequestBuilder` struct with shared fields +3. Implement builder pattern for provider-specific options +4. Refactor providers to use shared builder + +**Risk:** Medium (refactor, but no behavior change) +**Effort:** 4-6 hours +**Dependencies:** None + +### 3. Rename misleading `mobile_stubs/selection.rs` (#32) + +**File affected:** `src/mobile_stubs/selection.rs` + +**Current state:** +- File is labeled as "stubs" but contains real implementation +- 54 lines of actual code + +**Action:** Rename to `src/selection_impl.rs` or move to `src/selection/mobile.rs` + +**Approach:** +1. Check if file is actually used (grep for imports) +2. Rename file and update imports +3. Update any documentation references + +**Risk:** Low (rename only) +**Effort:** 30 minutes +**Dependencies:** None + +## Execution Order + +1. **#27** (Remove blanket allow) — Lowest risk, immediate value +2. **#32** (Rename stub) — Quick win, improves code clarity +3. **#24** (Extract shared builder) — Most complex, do last + +## Verification + +After each change: +1. `cargo check` — No new warnings +2. `cargo test` — All tests pass +3. `cargo clippy` — No new lint warnings + +## Notes + +- The blanket allow is Linux-specific (`cfg_attr(target_os = "linux")`) +- Dead code warnings (~87) are likely Linux-only platform abstractions +- Consider adding targeted `#[cfg(target_os = "linux")]` instead of blanket allow diff --git a/openless-all/app/package-lock.json b/openless-all/app/package-lock.json index 2a43a412..0f244e8d 100644 --- a/openless-all/app/package-lock.json +++ b/openless-all/app/package-lock.json @@ -1,45 +1,55 @@ { "name": "openless-app", - "version": "1.3.13-Beta.1", + "version": "1.3.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openless-app", - "version": "1.3.13-Beta.1", + "version": "1.3.14", "dependencies": { + "@base-ui/react": "^1.6.0", "@formkit/auto-animate": "^0.9.0", + "@shadcn/react": "^0.2.0", + "@tailwindcss/vite": "^4.3.2", "@tauri-apps/api": "^2.1.1", "@tauri-apps/plugin-autostart": "^2.5.1", "@tauri-apps/plugin-dialog": "^2.7.1", - "@tauri-apps/plugin-shell": "^2.0.1", + "@tauri-apps/plugin-shell": "^2.3.5", "@tauri-apps/plugin-updater": "^2.10.1", "@types/dompurify": "^3.0.5", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "dompurify": "^3.4.8", "framer-motion": "^12.39.0", "i18next": "^26.0.8", + "lucide-react": "^1.23.0", "marked": "^11.2.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-i18next": "^17.0.6" + "react-i18next": "^17.0.6", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.3.2" }, "devDependencies": { "@tauri-apps/cli": "^2.1.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.3", + "shadcn": "^4.13.0", + "tw-animate-css": "^1.4.0", "typescript": "^5.6.3", - "vite": "^5.4.10" + "vite": "^6.4.3" } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -48,9 +58,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -58,21 +68,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -89,14 +99,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -105,15 +115,28 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", + "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -122,40 +145,76 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", + "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/helper-replace-supers": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/traverse": "^7.29.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", + "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -164,20 +223,65 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", + "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", + "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.29.7", + "@babel/helper-optimise-call-expression": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", + "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -185,9 +289,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -195,9 +299,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -205,27 +309,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -234,6 +338,55 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", + "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", @@ -266,6 +419,46 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", + "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.29.7", + "@babel/helper-create-class-features-plugin": "^7.29.7", + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", + "@babel/plugin-syntax-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", + "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "@babel/plugin-syntax-jsx": "^7.29.7", + "@babel/plugin-transform-modules-commonjs": "^7.29.7", + "@babel/plugin-transform-typescript": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", @@ -276,33 +469,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -310,421 +503,751 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@base-ui/react": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@base-ui/react/-/react-1.6.0.tgz", + "integrity": "sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@base-ui/utils": "0.3.1", + "@floating-ui/react-dom": "^2.1.8", + "@floating-ui/utils": "^0.2.11", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@date-fns/tz": "^1.2.0", + "@types/react": "^17 || ^18 || ^19", + "date-fns": "^4.0.0", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@date-fns/tz": { + "optional": true + }, + "@types/react": { + "optional": true + }, + "date-fns": { + "optional": true + } + } + }, + "node_modules/@base-ui/utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@base-ui/utils/-/utils-0.3.1.tgz", + "integrity": "sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@floating-ui/utils": "^0.2.11", + "reselect": "^5.2.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "@types/react": "^17 || ^18 || ^19", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@dotenvx/dotenvx": { + "version": "1.75.1", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.75.1.tgz", + "integrity": "sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@dotenvx/primitives": "^0.8.0", + "commander": "^11.1.0", + "conf": "^10.2.0", + "dotenv": "^17.2.1", + "enquirer": "^2.4.1", + "env-paths": "^2.2.1", + "execa": "^5.1.1", + "fdir": "^6.2.0", + "ignore": "^5.3.0", + "object-treeify": "1.1.33", + "open": "^8.4.2", + "picomatch": "^4.0.4", + "systeminformation": "^5.22.11", + "undici": "^7.11.0", + "which": "^4.0.0", + "yocto-spinner": "^1.1.0" + }, + "bin": { + "dotenvx": "src/cli/dotenvx.js" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@dotenvx/dotenvx/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@dotenvx/primitives": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@dotenvx/primitives/-/primitives-0.8.0.tgz", + "integrity": "sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, "node_modules/@formkit/auto-animate": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-0.9.0.tgz", "integrity": "sha512-VhP4zEAacXS3dfTpJpJ88QdLqMTcabMg0jwpOSxZ/VzfQVfl3GkZSCZThhGC5uhq/TxPHPzW0dzr4H9Bb1OgKA==", "license": "MIT" }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "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", @@ -735,7 +1258,6 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -746,7 +1268,6 @@ "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" @@ -756,20 +1277,97 @@ "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/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "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/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -784,7 +1382,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -798,7 +1395,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -812,7 +1408,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -826,7 +1421,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -840,7 +1434,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -854,7 +1447,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -868,7 +1460,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -882,7 +1473,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -896,7 +1486,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -910,7 +1499,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -924,7 +1512,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -938,7 +1525,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -952,7 +1538,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -966,7 +1551,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -980,7 +1564,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -994,7 +1577,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1008,7 +1590,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1022,7 +1603,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1036,7 +1616,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1050,7 +1629,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1064,7 +1642,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1078,7 +1655,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1092,7 +1668,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1106,7 +1681,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1120,123 +1694,417 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, - "node_modules/@tauri-apps/api": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.0.tgz", - "integrity": "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==", - "license": "Apache-2.0 OR MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@shadcn/react": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@shadcn/react/-/react-0.2.0.tgz", + "integrity": "sha512-g/LMtyL0eiHCHkOCelhYf32RC5nTMdtUNag1ZQRhSDCW+jnHxDY1Dajk7P6zJosOg8z2N4wzfKOY5sh54QTDYA==", + "license": "MIT", + "peerDependencies": { + "@types/react": ">=19", + "react": ">=19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } } }, - "node_modules/@tauri-apps/cli": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.1.tgz", - "integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==", + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, - "license": "Apache-2.0 OR MIT", - "bin": { - "tauri": "tauri.js" - }, + "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", + "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", + "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "license": "MIT", + "engines": { + "node": ">= 20" }, "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.10.1", - "@tauri-apps/cli-darwin-x64": "2.10.1", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", - "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", - "@tauri-apps/cli-linux-arm64-musl": "2.10.1", - "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", - "@tauri-apps/cli-linux-x64-gnu": "2.10.1", - "@tauri-apps/cli-linux-x64-musl": "2.10.1", - "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", - "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", - "@tauri-apps/cli-win32-x64-msvc": "2.10.1" + "@tailwindcss/oxide-android-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-x64": "4.3.2", + "@tailwindcss/oxide-freebsd-x64": "4.3.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-x64-musl": "4.3.2", + "@tailwindcss/oxide-wasm32-wasi": "4.3.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" } }, - "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz", - "integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==", + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", + "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", "cpu": [ "arm64" ], - "dev": true, - "license": "Apache-2.0 OR MIT", + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", + "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "cpu": [ + "arm64" + ], + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, - "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz", - "integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==", + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", + "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", "cpu": [ "x64" ], - "dev": true, - "license": "Apache-2.0 OR MIT", + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, - "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz", - "integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==", + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", + "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", "cpu": [ - "arm" + "x64" ], - "dev": true, - "license": "Apache-2.0 OR MIT", + "license": "MIT", "optional": true, "os": [ - "linux" + "freebsd" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, - "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz", - "integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==", + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", + "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", "cpu": [ - "arm64" + "arm" ], - "dev": true, - "license": "Apache-2.0 OR MIT", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": ">= 10" + "node": ">= 20" } }, - "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.10.1", + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", + "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", + "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", + "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", + "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", + "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", + "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", + "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.2.tgz", + "integrity": "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.2", + "@tailwindcss/oxide": "4.3.2", + "tailwindcss": "4.3.2" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tauri-apps/api": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.0.tgz", + "integrity": "sha512-7CinYODhky9lmO23xHnUFv0Xt43fbtWMyxZcLcRBlFkcgXKuEirBvHpmtJ89YMhyeGcq20Wuc47Fa4XjyniywA==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.1.tgz", + "integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.10.1", + "@tauri-apps/cli-darwin-x64": "2.10.1", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", + "@tauri-apps/cli-linux-arm64-musl": "2.10.1", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-musl": "2.10.1", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", + "@tauri-apps/cli-win32-x64-msvc": "2.10.1" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz", + "integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz", + "integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz", + "integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz", + "integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.10.1", "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz", "integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==", "cpu": [ @@ -1390,6 +2258,18 @@ "@tauri-apps/api": "^2.10.1" } }, + "node_modules/@ts-morph/common": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.27.0.tgz", + "integrity": "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.3", + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1448,21 +2328,20 @@ "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/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "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==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1485,6 +2364,13 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, + "node_modules/@types/validate-npm-package-name": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/validate-npm-package-name/-/validate-npm-package-name-4.0.2.tgz", + "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1506,544 +2392,3816 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.24", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.24.tgz", - "integrity": "sha512-I2NkZOOrj2XuguvWCK6OVh9GavsNjZjK908Rq3mIBK25+GD8vPX5w2WdxVqnQ7xx3SrZJiCiZFu+/Oz50oSYSA==", + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">= 0.6" } }, - "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==", + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "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" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "bin": { - "browserslist": "cli.js" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "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==", + "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==", "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/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" + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "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==", + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, - "license": "MIT" + "license": "Python-2.0" }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "tslib": "^2.0.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dompurify": { - "version": "3.4.8", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.8.tgz", - "integrity": "sha512-yb1cEmaOum7wFvOCSQxyfgVlv5D47Rc30iZWoMpbDIWTnJ6grDDQyu2KFJzB2k7u0pMuJcQ1zphH//fFnw2tjQ==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" + "node": ">=4" } }, - "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==", + "node_modules/atomically": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz", + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", "dev": true, - "license": "ISC" + "license": "MIT", + "engines": { + "node": ">=10.12.0" + } }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "node": "18 || 20 || >=22" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/baseline-browser-mapping": { + "version": "2.10.40", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", + "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, "engines": { - "node": ">=6" + "node": ">=6.0.0" } }, - "node_modules/framer-motion": { - "version": "12.39.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.39.0.tgz", - "integrity": "sha512-+vnLfzrv0MzjLzNl+nvNvR7jdg3q4cxxjz/YvzfifHl0TREtL00cs1RoMTxs+1PzLiEqZGV6gYsBY0oEAYZ24w==", + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "dev": true, "license": "MIT", "dependencies": { - "motion-dom": "^12.39.0", - "motion-utils": "^12.39.0", - "tslib": "^2.4.0" + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, - "peerDependencies": { - "@emotion/is-prop-valid": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "@emotion/is-prop-valid": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, "engines": { - "node": ">=6.9.0" + "node": "18 || 20 || >=22" } }, - "node_modules/html-parse-stringify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", - "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "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": { - "void-elements": "3.1.0" + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/i18next": { - "version": "26.0.8", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.8.tgz", - "integrity": "sha512-BRzLom0mhDhV9v0QhgUUHWQJuwFmnr1194xEcNLYD6ym8y8s542n4jXUvRLnhNTbh9PmpU6kGZamyuGHQMsGjw==", + "node_modules/browserslist": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "dev": true, "funding": [ { - "type": "individual", - "url": "https://www.locize.com/i18next" + "type": "opencollective", + "url": "https://opencollective.com/browserslist" }, { - "type": "individual", - "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" }, { - "type": "individual", - "url": "https://www.locize.com" + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "license": "MIT", - "peerDependencies": { - "typescript": "^5 || ^6" + "dependencies": { + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "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/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "dev": true, "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "run-applescript": "^7.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, "engines": { - "node": ">=6" + "node": ">= 0.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==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, "license": "MIT", "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, - "bin": { - "loose-envify": "cli.js" + "engines": { + "node": ">= 0.4" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/marked": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-11.2.0.tgz", - "integrity": "sha512-HR0m3bvu0jAPYiIvLUUQtdg1g6D247//lvcekpHO1WMvbwDlwSkZAX9Lw4F4YHE1T0HaaNve0tuAWuV1UJ6vtw==", "license": "MIT", - "bin": { - "marked": "bin/marked.js" + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": ">= 18" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/motion-dom": { - "version": "12.39.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.39.0.tgz", - "integrity": "sha512-Xn7aAcGDhco/JZTXOub64UmaYn73C6J1Po7Fk+8EvkJsNGTqfhon6UJY53vJKXW5v5Zl8HrYsVxv6oPXeGoGLQ==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", - "dependencies": { - "motion-utils": "^12.39.0" + "engines": { + "node": ">=6" } }, - "node_modules/motion-utils": { - "version": "12.39.0", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", - "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", "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/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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==", + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/code-block-writer": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/conf": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz", + "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.6.3", + "ajv-formats": "^2.1.1", + "atomically": "^1.7.0", + "debounce-fn": "^4.0.0", + "dot-prop": "^6.0.1", + "env-paths": "^2.2.1", + "json-schema-typed": "^7.0.3", + "onetime": "^5.1.2", + "pkg-up": "^3.1.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/conf/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/conf/node_modules/json-schema-typed": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/conf/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", + "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dompurify": { + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", + "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.380", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.380.tgz", + "integrity": "sha512-W6d5AbuEoRayO447cqrg6lKJIlscgRnnxOZl/08kfV71BQDoEBC7Wwis68z87LjyK6f4kWyTaubuDbhHKrZkbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "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/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "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-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "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/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/framer-motion": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.39.0.tgz", + "integrity": "sha512-+vnLfzrv0MzjLzNl+nvNvR7jdg3q4cxxjz/YvzfifHl0TREtL00cs1RoMTxs+1PzLiEqZGV6gYsBY0oEAYZ24w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.39.0", + "motion-utils": "^12.39.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.6.tgz", + "integrity": "sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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/fuzzysort": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", + "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", + "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.27", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz", + "integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/i18next": { + "version": "26.0.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-26.0.8.tgz", + "integrity": "sha512-BRzLom0mhDhV9v0QhgUUHWQJuwFmnr1194xEcNLYD6ym8y8s542n4jXUvRLnhNTbh9PmpU6kGZamyuGHQMsGjw==", + "funding": [ + { + "type": "individual", + "url": "https://www.locize.com/i18next" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + }, + { + "type": "individual", + "url": "https://www.locize.com" + } + ], + "license": "MIT", + "peerDependencies": { + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-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-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regexp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", + "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/jose": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "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/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "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" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "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/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.23.0.tgz", + "integrity": "sha512-38BpJcD0JhFosxHApP/BYsBetLpQFRoTRzEzstM/XCc3jsAG7wqaY1lgVwxiUe3xqYE+lNxo2PkCmYwXWrwwIw==", + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/marked": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-11.2.0.tgz", + "integrity": "sha512-HR0m3bvu0jAPYiIvLUUQtdg1g6D247//lvcekpHO1WMvbwDlwSkZAX9Lw4F4YHE1T0HaaNve0tuAWuV1UJ6vtw==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "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/micromatch/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/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/motion-dom": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.39.0.tgz", + "integrity": "sha512-Xn7aAcGDhco/JZTXOub64UmaYn73C6J1Po7Fk+8EvkJsNGTqfhon6UJY53vJKXW5v5Zl8HrYsVxv6oPXeGoGLQ==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.39.0" + } + }, + "node_modules/motion-utils": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", + "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-treeify": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/object-treeify/-/object-treeify-1.1.33.tgz", + "integrity": "sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/open": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.4.0", + "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", + "is-inside-container": "^1.0.0", + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=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==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", + "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "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-selector-parser": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.4.tgz", + "integrity": "sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prompts/node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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-i18next": { + "version": "17.0.6", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.6.tgz", + "integrity": "sha512-WzJ6SMKF+GTD7JZZqxSR1AKKmXjaSu39sClUrNlwxS4Tl7a99O+ltFy6yhPMO+wgZuxpQjJ2PZkfrQKmAqrLhw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "html-parse-stringify": "^3.0.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "i18next": ">= 26.0.1", + "react": ">= 16.8.0", + "typescript": "^5 || ^6" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/recast": { + "version": "0.23.12", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.12.tgz", + "integrity": "sha512-dEWRjcINDu/F4l2dYx57ugBtD7HV9KXESyxhzw/MqWLeglJrsjJKqACPyUPg+6AF8mIgm+Zi0dZ3ACoIg+QtpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reselect": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.2.0.tgz", + "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", "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==", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } }, - "node_modules/postcss": { - "version": "8.5.12", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", - "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "opencollective", - "url": "https://opencollective.com/postcss/" + "type": "github", + "url": "https://github.com/sponsors/feross" }, { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" + "type": "patreon", + "url": "https://www.patreon.com/feross" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "consulting", + "url": "https://feross.org/support" } ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "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/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "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==", + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shadcn": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.13.0.tgz", + "integrity": "sha512-5fuJ4jI/GcPeA/iTL4cJivCZuYQGXz/N3bIzyd+Gd/FM6xUCy2MxGG+LaDQuw2cjNy9zGPSFPTEmI048UwPTZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/plugin-transform-typescript": "^7.28.0", + "@babel/preset-typescript": "^7.27.1", + "@dotenvx/dotenvx": "^1.48.4", + "@modelcontextprotocol/sdk": "^1.26.0", + "@types/validate-npm-package-name": "^4.0.2", + "browserslist": "^4.26.2", + "commander": "^14.0.0", + "cosmiconfig": "^9.0.0", + "dedent": "^1.6.0", + "deepmerge": "^4.3.1", + "diff": "^8.0.2", + "execa": "^9.6.0", + "fast-glob": "^3.3.3", + "fs-extra": "^11.3.1", + "fuzzysort": "^3.1.0", + "kleur": "^4.1.5", + "open": "^11.0.0", + "ora": "^8.2.0", + "postcss": "^8.5.6", + "postcss-selector-parser": "^7.1.0", + "prompts": "^2.4.2", + "recast": "^0.23.11", + "stringify-object": "^5.0.0", + "tailwind-merge": "^3.0.1", + "ts-morph": "^26.0.0", + "tsconfig-paths": "^4.2.0", + "undici": "^7.27.2", + "validate-npm-package-name": "^7.0.1", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "shadcn": "dist/index.js" + }, + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stringify-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-5.0.0.tgz", + "integrity": "sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-keys": "^1.0.0", + "is-obj": "^3.0.0", + "is-regexp": "^3.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/stringify-object?sponsor=1" + } + }, + "node_modules/stringify-object/node_modules/is-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", + "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "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==", + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" }, - "peerDependencies": { - "react": "^18.3.1" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/react-i18next": { - "version": "17.0.6", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-17.0.6.tgz", - "integrity": "sha512-WzJ6SMKF+GTD7JZZqxSR1AKKmXjaSu39sClUrNlwxS4Tl7a99O+ltFy6yhPMO+wgZuxpQjJ2PZkfrQKmAqrLhw==", + "node_modules/systeminformation": { + "version": "5.31.13", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.13.tgz", + "integrity": "sha512-iUJXJoKzm4vtLSeT3nwe2s9QjoJAxHg7wYJ0KaQ54Xy2u9jsTq0ULWQQ0+T72FXjX2XnGqubazNx9lUfng7ELw==", + "dev": true, "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.29.2", - "html-parse-stringify": "^3.0.1", - "use-sync-external-store": "^1.6.0" + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" }, - "peerDependencies": { - "i18next": ">= 26.0.1", - "react": ">= 16.8.0", - "typescript": "^5 || ^6" + "engines": { + "node": ">=8.0.0" }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - }, - "typescript": { - "optional": true - } + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" } }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", + "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/rollup": { - "version": "4.60.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", - "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": ">=12.0.0" }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.2", - "@rollup/rollup-android-arm64": "4.60.2", - "@rollup/rollup-darwin-arm64": "4.60.2", - "@rollup/rollup-darwin-x64": "4.60.2", - "@rollup/rollup-freebsd-arm64": "4.60.2", - "@rollup/rollup-freebsd-x64": "4.60.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", - "@rollup/rollup-linux-arm-musleabihf": "4.60.2", - "@rollup/rollup-linux-arm64-gnu": "4.60.2", - "@rollup/rollup-linux-arm64-musl": "4.60.2", - "@rollup/rollup-linux-loong64-gnu": "4.60.2", - "@rollup/rollup-linux-loong64-musl": "4.60.2", - "@rollup/rollup-linux-ppc64-gnu": "4.60.2", - "@rollup/rollup-linux-ppc64-musl": "4.60.2", - "@rollup/rollup-linux-riscv64-gnu": "4.60.2", - "@rollup/rollup-linux-riscv64-musl": "4.60.2", - "@rollup/rollup-linux-s390x-gnu": "4.60.2", - "@rollup/rollup-linux-x64-gnu": "4.60.2", - "@rollup/rollup-linux-x64-musl": "4.60.2", - "@rollup/rollup-openbsd-x64": "4.60.2", - "@rollup/rollup-openharmony-arm64": "4.60.2", - "@rollup/rollup-win32-arm64-msvc": "4.60.2", - "@rollup/rollup-win32-ia32-msvc": "4.60.2", - "@rollup/rollup-win32-x64-gnu": "4.60.2", - "@rollup/rollup-win32-x64-msvc": "4.60.2", - "fsevents": "~2.3.2" + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "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": { - "loose-envify": "^1.1.0" + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "license": "MIT", + "engines": { + "node": ">=0.6" } }, - "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==", + "node_modules/ts-morph": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", + "integrity": "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.27.0", + "code-block-writer": "^13.0.3" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, "node_modules/tslib": { @@ -2052,6 +6210,49 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -2066,6 +6267,49 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -2106,22 +6350,51 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "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/validate-npm-package-name": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", + "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -2130,19 +6403,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -2163,6 +6442,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -2175,12 +6460,101 @@ "node": ">=0.10.0" } }, + "node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/wsl-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" + }, + "node_modules/yocto-spinner": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/yocto-spinner/-/yocto-spinner-1.2.0.tgz", + "integrity": "sha512-Yw0hUB6UA3o4YUgKy3oSe9a4cxoaZ9sBfYDw+JSxo6Id0KoJGoxzPA24qqUXYKBWABs/zDSGTz9kww7t3F0XGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18.19" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } } } } diff --git a/openless-all/app/package.json b/openless-all/app/package.json index 45d7fdd1..65be9c15 100644 --- a/openless-all/app/package.json +++ b/openless-all/app/package.json @@ -1,7 +1,7 @@ { "name": "openless-app", "private": true, - "version": "1.3.13-Beta.1", + "version": "1.3.14", "type": "module", "scripts": { "dev": "vite", @@ -23,27 +23,43 @@ "check:android-updater-pubkey": "node scripts/check-android-updater-pubkey.mjs" }, "dependencies": { + "@base-ui/react": "^1.6.0", "@formkit/auto-animate": "^0.9.0", + "@shadcn/react": "^0.2.0", + "@tailwindcss/vite": "^4.3.2", "@tauri-apps/api": "^2.1.1", "@tauri-apps/plugin-autostart": "^2.5.1", "@tauri-apps/plugin-dialog": "^2.7.1", - "@tauri-apps/plugin-shell": "^2.0.1", + "@tauri-apps/plugin-shell": "^2.3.5", "@tauri-apps/plugin-updater": "^2.10.1", "@types/dompurify": "^3.0.5", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", "dompurify": "^3.4.8", "framer-motion": "^12.39.0", "i18next": "^26.0.8", + "lucide-react": "^1.23.0", "marked": "^11.2.0", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-i18next": "^17.0.6" + "react-i18next": "^17.0.6", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.3.2" }, "devDependencies": { "@tauri-apps/cli": "^2.1.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", "@vitejs/plugin-react": "^4.3.3", + "shadcn": "^4.13.0", + "tw-animate-css": "^1.4.0", "typescript": "^5.6.3", - "vite": "^5.4.10" + "vite": "^6.4.3" + }, + "overrides": { + "@shadcn/react": { + "react": "$react", + "@types/react": "$@types/react" + } } } diff --git a/openless-all/app/src-tauri/Cargo.lock b/openless-all/app/src-tauri/Cargo.lock index bae2f703..e621b2ec 100644 --- a/openless-all/app/src-tauri/Cargo.lock +++ b/openless-all/app/src-tauri/Cargo.lock @@ -3528,6 +3528,9 @@ name = "objc-sys" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" +dependencies = [ + "cc", +] [[package]] name = "objc2" @@ -3883,7 +3886,7 @@ dependencies = [ [[package]] name = "openless" -version = "1.3.13-Beta.1" +version = "1.3.14" dependencies = [ "anyhow", "arboard", diff --git a/openless-all/app/src-tauri/Cargo.toml b/openless-all/app/src-tauri/Cargo.toml index ff16aed3..830a690d 100644 --- a/openless-all/app/src-tauri/Cargo.toml +++ b/openless-all/app/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openless" -version = "1.3.13-Beta.1" +version = "1.3.14" description = "OpenLess — local voice input that types where your cursor is" authors = ["OpenLess"] edition = "2021" @@ -21,7 +21,8 @@ cc = "1.1" # macos-private-api must live in [dependencies] so tauri_build can match tauri.conf.json # macOSPrivateApi; tray-icon stays desktop-only in the target table below. tauri = { version = "~2.11", features = ["macos-private-api"] } -tauri-plugin-shell = "2" +# 锁 >=2.3.5 修复 GHSA-c9pr-q8gx-3mgp:tauri-plugin-shell `open` 端点作用域校验绕过(CRITICAL,issue #668)。 +tauri-plugin-shell = "2.3.5" tauri-plugin-dialog = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" @@ -108,7 +109,7 @@ core-graphics = "0.24" # (替代 10s 轮询,空闲零唤醒)。符号本就在依赖树(cpal → coreaudio-sys 0.2),提升为 # 直接依赖零额外编译成本。 coreaudio-sys = "0.2" -objc2 = "0.5" +objc2 = { version = "0.5", features = ["exception"] } objc2-foundation = "0.2" objc2-app-kit = "0.2" # qwen-asr 用 malloc 分配返回字符串,必须用 C `free()` 释放。 diff --git a/openless-all/app/src-tauri/src/asr/bailian.rs b/openless-all/app/src-tauri/src/asr/bailian.rs index 2aac2fa9..ce225581 100644 --- a/openless-all/app/src-tauri/src/asr/bailian.rs +++ b/openless-all/app/src-tauri/src/asr/bailian.rs @@ -62,6 +62,16 @@ impl BailianCredentials { } } +/// Bailian 实时 ASR 走 DashScope WebSocket 网关,接口地址只接受 ws:// 或 +/// wss://。用户容易把百炼控制台的 `https://` 兼容模式 / 专属域名地址粘进来 +/// ——那是另一套 HTTP 协议,WebSocket 握手必然失败且底层报错 +/// ("URL scheme not supported")对用户不可读。在验证入口先拦下, +/// 前端据 `bailianEndpointSchemeInvalid` 错误码给出可操作的提示。 +pub fn endpoint_scheme_is_websocket(endpoint: &str) -> bool { + let lower = endpoint.trim().to_ascii_lowercase(); + lower.starts_with("wss://") || lower.starts_with("ws://") +} + #[derive(Debug, thiserror::Error)] pub enum BailianASRError { #[error("credentials missing")] @@ -880,6 +890,23 @@ mod tests { // ---- existing tests kept ---- + #[test] + fn endpoint_scheme_accepts_websocket_only() { + assert!(endpoint_scheme_is_websocket(DEFAULT_ENDPOINT)); + assert!(endpoint_scheme_is_websocket("ws://localhost:9000/api-ws")); + assert!(endpoint_scheme_is_websocket( + " WSS://dashscope.aliyuncs.com/api-ws/v1/inference/ " + )); + // 百炼控制台的 https 兼容模式 / 专属域名地址不是 WebSocket 网关 + assert!(!endpoint_scheme_is_websocket( + "https://llm-xxx.cn-beijing.maas.aliyuncs.com/compatible-mode/v1" + )); + assert!(!endpoint_scheme_is_websocket( + "http://dashscope.aliyuncs.com/api-ws/v1/inference/" + )); + assert!(!endpoint_scheme_is_websocket("")); + } + #[test] fn credentials_apply_default_endpoint_and_model() { let creds = BailianCredentials { diff --git a/openless-all/app/src-tauri/src/asr/local/apple_speech_provider.rs b/openless-all/app/src-tauri/src/asr/local/apple_speech_provider.rs index 91628f68..7c64ae3d 100644 --- a/openless-all/app/src-tauri/src/asr/local/apple_speech_provider.rs +++ b/openless-all/app/src-tauri/src/asr/local/apple_speech_provider.rs @@ -17,7 +17,9 @@ #![cfg(target_os = "macos")] +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc; +use std::sync::Arc; use std::time::Duration; use anyhow::{anyhow, bail, Context, Result}; @@ -38,17 +40,56 @@ const SF_AUTH_AUTHORIZED: i64 = 3; /// 等待识别 / 授权回调的兜底超时。识别本身另有 coordinator 侧动态超时; /// 这里只防 block 永不回调导致线程永久阻塞。 const RECOGNITION_WAIT: Duration = Duration::from_secs(60); +/// 识别等待的轮询步长。把 `recv_timeout(RECOGNITION_WAIT)` 的一次性阻塞拆成每 +/// `RECOGNITION_POLL` 一轮:每轮之间检查 `cancel_flag`,取消 / 上层超时后阻塞线程 +/// 最多再等这一步长(~100ms)就退出,而不是傻等满 `RECOGNITION_WAIT`(60s)。 +const RECOGNITION_POLL: Duration = Duration::from_millis(100); const AUTHORIZATION_WAIT: Duration = Duration::from_secs(30); +/// 识别引擎就绪(isAvailable)的轮询等待:刚 init 的 recognizer 常瞬时不可用(异步 +/// 加载语言资源),稍等即就绪。等满仍不可用才报错——修「有时用不了」的竞态。 +const AVAILABILITY_WAIT: Duration = Duration::from_secs(3); +const AVAILABILITY_POLL: Duration = Duration::from_millis(100); + +/// `SFSpeechRecognitionTask` 的裸指针包装,仅为把 task 句柄从 spawn_blocking 线程 +/// 存进 `AppleSpeechAsr::active_task`,供任意线程(含 tokio 上取消的线程)调用 +/// `-[SFSpeechRecognitionTask cancel]` 终止识别。 +/// +/// SAFETY: `SFSpeechRecognitionTask` 是标准的 objc/ARC 对象,其 `cancel` 属于 +/// Speech.framework 文档承诺可从任意线程安全调用的操作(内部转派到自身队列); +/// 我们对该指针只做两件事——存入 `active_task`、以及调用 `cancel`——不做解引用、 +/// 不改内部状态。指针仅在对应识别请求存活期间被持有:`recognize_file` 返回前会把 +/// `active_task` 置回 `None`,此时 recognizer / request 仍在同一栈帧强引用存活, +/// task 不会被提前释放。因此跨线程传递该裸指针并调用 `cancel` 不违反内存/线程安全。 +/// 不实现 `Sync`——它只在 `Mutex` 保护下被取出后使用,无需并发共享引用。 +struct SendableTask(*mut AnyObject); + +// SAFETY: 见 `SendableTask` 文档注释——底层 SFSpeechRecognitionTask 线程安全, +// `cancel` 可跨线程调用,包装体只承载指针用于「存」与「取消」。 +unsafe impl Send for SendableTask {} pub struct AppleSpeechAsr { /// 16-bit LE PCM 字节缓冲(recorder 推什么我们存什么)。与 LocalQwenAsr 同形。 buffer: Mutex>, + /// 识别 locale(Apple 标识符,如 "zh-CN")。None = 用系统默认。由用户工作语言映射 + /// 而来 —— SFSpeechRecognizer 一个实例只认一种语言,不显式指定就落到系统首选语言 + /// (常是英文),中文语音会被英文引擎识别成英文且理解错误(用户报告的根因)。 + locale: Option, + /// 取消标志。`cancel()` 置位;`recognize_file` 的等待轮询每轮检查,置位即放弃 + /// 等待并真正 `cancel` 底层识别任务 —— 让被上层动态超时抛弃 / 被 `cancel()` 的 + /// spawn_blocking 阻塞线程在 ~100ms 内退出,而不是傻等满 `RECOGNITION_WAIT`。 + cancel_flag: Arc, + /// 当前在飞的识别任务句柄。`recognize_file` 拿到 task 即存入,返回前清空; + /// `cancel()` 从这里取出并调用 `-[SFSpeechRecognitionTask cancel]` 终止识别。 + active_task: Arc>>, } impl AppleSpeechAsr { - pub fn new() -> Self { + pub fn new(locale: Option) -> Self { Self { buffer: Mutex::new(Vec::new()), + locale, + cancel_flag: Arc::new(AtomicBool::new(false)), + active_task: Arc::new(Mutex::new(None)), } } @@ -73,12 +114,24 @@ impl AppleSpeechAsr { }); } let duration_ms = (pcm.len() as u64 / 2) * 1000 / 16_000; + let locale = self.locale.clone(); + + // 本次识别开始前复位取消标志:上一会话若以取消收尾,标志可能仍为 true。 + self.cancel_flag.store(false, Ordering::SeqCst); + let cancel_flag = Arc::clone(&self.cancel_flag); + let active_task = Arc::clone(&self.active_task); // SFSpeechRecognizer 是阻塞且基于 objc runloop 的同步桥接;放到 // spawn_blocking 不占 tokio runtime。与 LocalQwenAsr 走同一个 Tauri // 持有的 runtime handle。 let result = tauri::async_runtime::spawn_blocking(move || { - transcribe_pcm_blocking(&pcm, duration_ms) + transcribe_pcm_blocking( + &pcm, + duration_ms, + locale.as_deref(), + &cancel_flag, + &active_task, + ) }) .await .context("apple-speech transcribe spawn_blocking join 失败")?; @@ -90,13 +143,24 @@ impl AppleSpeechAsr { } pub fn cancel(&self) { + // 先置位取消标志:等待轮询下一轮(~100ms 内)看到即放弃等待并退出阻塞线程。 + self.cancel_flag.store(true, Ordering::SeqCst); + // 再真正终止在飞的识别任务(若有)。取出句柄后立即调用 cancel。 + if let Some(task) = self.active_task.lock().take() { + // SAFETY: `task.0` 是 `recognitionTaskWithRequest:` 返回的 + // SFSpeechRecognitionTask 指针。`-[SFSpeechRecognitionTask cancel]` 无参、 + // 无返回值,是 Speech.framework 承诺可从任意线程调用的操作。此处仅调用 + // cancel、不解引用指针;调用后不再使用该句柄(已 take 出 Option)。 + let _: () = unsafe { msg_send![task.0, cancel] }; + log::info!("[apple-speech] recognition task cancelled"); + } self.buffer.lock().clear(); } } impl Default for AppleSpeechAsr { fn default() -> Self { - Self::new() + Self::new(None) } } @@ -108,7 +172,13 @@ impl crate::recorder::AudioConsumer for AppleSpeechAsr { /// 把 PCM 写成临时 wav,确保授权,跑批处理识别,删临时文件,返回结果。 /// 在 spawn_blocking 线程内同步执行。 -fn transcribe_pcm_blocking(pcm: &[u8], duration_ms: u64) -> Result { +fn transcribe_pcm_blocking( + pcm: &[u8], + duration_ms: u64, + locale: Option<&str>, + cancel_flag: &AtomicBool, + active_task: &Mutex>, +) -> Result { ensure_authorized()?; let samples: Vec = pcm @@ -129,7 +199,7 @@ fn transcribe_pcm_blocking(pcm: &[u8], duration_ms: u64) -> Result Result<()> { /// 用 `SFSpeechURLRecognitionRequest` 对给定 wav 文件做一次批处理识别, /// 把 `recognitionTaskWithRequest:resultHandler:` 的异步回调同步化。 -fn recognize_file(wav_path: &str) -> Result { - let recognizer = create_recognizer()?; - - // recognizer.isAvailable —— 识别引擎当前是否可用(首次可能在下载语言资源)。 - // SAFETY: `recognizer` 是有效的 `SFSpeechRecognizer` 实例;`isAvailable` 无参,返回 BOOL。 - let available: Bool = unsafe { msg_send![recognizer, isAvailable] }; - if !available.as_bool() { - bail!("当前语言的语音识别暂不可用(系统可能正在准备识别资源,请稍后重试)"); - } +/// +/// 等待不再是一次性 `recv_timeout(RECOGNITION_WAIT)`,而是每 `RECOGNITION_POLL` +/// 一轮的轮询:每轮检查 `cancel_flag`,置位则 `cancel` 底层任务并返回「已取消」错误, +/// 让上层动态超时抛弃 / `cancel()` 触发时阻塞线程在 ~100ms 内退出。返回前无论成败 +/// 都清空 `active_task`(RAII guard 兜底 `?` 早退)。 +fn recognize_file( + wav_path: &str, + locale: Option<&str>, + cancel_flag: &AtomicBool, + active_task: &Mutex>, +) -> Result { + let recognizer = create_recognizer(locale)?; + + // 识别引擎就绪等待(isAvailable 竞态):SFSpeechRecognizer 刚 init 时引擎往往还没 + // 就绪(异步加载语言资源),isAvailable 瞬时为 false、稍等即 true。之前一见 false + // 就 bail —— 这正是「有时用不了」的主因。改为轮询等待最多几秒再判定。 + wait_until_available(recognizer)?; let url = file_url(wav_path)?; let request = create_url_request(url)?; + // on-device 优先:设备支持当前语言的设备端识别就强制 on-device —— 音频不出本机 + // (隐私)、离线可用、不受网络波动/限流影响(消除「有时连不上服务器」)。不支持的 + // 语言回退系统默认(可能走网络)以保底能用。 + configure_on_device(recognizer, request); + let (tx, rx) = mpsc::channel::(); // resultHandler: void(^)(SFSpeechRecognitionResult *result, NSError *error) let block = RcBlock::new(move |result: *mut AnyObject, error: *mut AnyObject| { @@ -209,8 +292,9 @@ fn recognize_file(wav_path: &str) -> Result { log::info!("[apple-speech] starting recognitionTaskWithRequest"); // SAFETY: `recognizer` 有效;`request` 是有效的 `SFSpeechURLRecognitionRequest`; // `&*block` 是稳定 block 指针,block 本体被 `block` 持有至本作用域结束。 - // 返回的 `SFSpeechRecognitionTask` 我们不持有(自身被 recognizer 强引用直到完成)。 - let _task: *mut AnyObject = unsafe { + // 返回的 `SFSpeechRecognitionTask` 自身被 recognizer 强引用直到完成;我们额外把 + // 句柄存进 `active_task` 供 `cancel()` 从别的线程终止它(见 SendableTask 文档)。 + let task: *mut AnyObject = unsafe { msg_send![ recognizer, recognitionTaskWithRequest: request, @@ -218,11 +302,85 @@ fn recognize_file(wav_path: &str) -> Result { ] }; - match rx.recv_timeout(RECOGNITION_WAIT) { - Ok(RecognitionOutcome::Final(text)) => Ok(text), - Ok(RecognitionOutcome::Failed(message)) => bail!("语音识别失败: {message}"), - Ok(RecognitionOutcome::Pending) => unreachable!("Pending 不会被发送"), - Err(err) => bail!("等待语音识别结果超时或失败: {err}"), + // 存句柄供 cancel();guard 保证本函数任意退出路径都把它清回 None,避免悬挂。 + *active_task.lock() = Some(SendableTask(task)); + let _task_guard = ActiveTaskGuard(active_task); + + // 轮询等待:每轮 recv 最多 RECOGNITION_POLL,累计超过 RECOGNITION_WAIT 则超时。 + // 每轮先查 cancel_flag —— 置位即真正 cancel 任务并返回「已取消」错误、放弃等待。 + let deadline = std::time::Instant::now() + RECOGNITION_WAIT; + loop { + if cancel_flag.load(Ordering::SeqCst) { + // 若 cancel() 尚未取走句柄(例如超时路径只置了 flag 没调 cancel),这里补发 + // 一次 cancel,确保底层识别任务被真正终止,而不是留它在后台跑满。 + if let Some(t) = active_task.lock().take() { + // SAFETY: 见 SendableTask 文档 —— `cancel` 无参、可跨线程调用,仅调用不解引用。 + let _: () = unsafe { msg_send![t.0, cancel] }; + } + bail!("语音识别已取消"); + } + match rx.recv_timeout(RECOGNITION_POLL) { + Ok(RecognitionOutcome::Final(text)) => return Ok(text), + Ok(RecognitionOutcome::Failed(message)) => bail!("语音识别失败: {message}"), + Ok(RecognitionOutcome::Pending) => unreachable!("Pending 不会被发送"), + Err(mpsc::RecvTimeoutError::Timeout) => { + if std::time::Instant::now() >= deadline { + bail!("等待语音识别结果超时"); + } + // 未到总时限:继续下一轮(回到循环顶部再查 cancel_flag)。 + } + Err(mpsc::RecvTimeoutError::Disconnected) => { + bail!("等待语音识别结果失败:回调通道已断开"); + } + } + } +} + +/// 保证 `recognize_file` 任意退出路径(含 `?` 早退、正常返回、取消/超时)都把 +/// `active_task` 清回 `None`,避免悬挂的 task 句柄被后续 `cancel()` 误用。 +struct ActiveTaskGuard<'a>(&'a Mutex>); + +impl Drop for ActiveTaskGuard<'_> { + fn drop(&mut self) { + *self.0.lock() = None; + } +} + +/// 轮询等待识别引擎就绪。init 后 isAvailable 可能瞬时 false(异步加载资源),稍等 +/// 即 true;等满 AVAILABILITY_WAIT 仍不可用才报错并引导。 +fn wait_until_available(recognizer: *mut AnyObject) -> Result<()> { + let deadline = std::time::Instant::now() + AVAILABILITY_WAIT; + loop { + // SAFETY: `recognizer` 有效;`isAvailable` 无参返回 BOOL。 + let available: Bool = unsafe { msg_send![recognizer, isAvailable] }; + if available.as_bool() { + return Ok(()); + } + if std::time::Instant::now() >= deadline { + bail!( + "当前语言的语音识别暂不可用:系统可能仍在准备识别资源,或需在 系统设置 → 键盘 → 听写 中下载对应语言。可稍后重试,或改用其它 ASR。" + ); + } + std::thread::sleep(AVAILABILITY_POLL); + } +} + +/// 支持设备端识别的语言就把请求设成强制 on-device(音频不出本机、离线可用);不支持 +/// 的语言不设,回退系统默认(可能走网络)以保底能用。 +fn configure_on_device(recognizer: *mut AnyObject, request: *mut AnyObject) { + // SFSpeechRecognizer.supportsOnDeviceRecognition(macOS 10.15+,BOOL 属性)。 + // SAFETY: `recognizer` 有效;无参返回 BOOL。 + let supports: Bool = unsafe { msg_send![recognizer, supportsOnDeviceRecognition] }; + if supports.as_bool() { + // SFSpeechRecognitionRequest.requiresOnDeviceRecognition = YES。 + // SAFETY: `request` 是 SFSpeechURLRecognitionRequest(父类 + // SFSpeechRecognitionRequest 提供该 setter);参数 BOOL。 + let _: () = unsafe { msg_send![request, setRequiresOnDeviceRecognition: Bool::new(true)] }; + log::info!("[apple-speech] on-device recognition enabled"); + } else { + log::info!( + "[apple-speech] on-device unsupported for current locale; using default (may use network)" + ); } } @@ -271,21 +429,74 @@ fn speech_recognizer_class() -> Result<&'static AnyClass> { }) } -/// `[[SFSpeechRecognizer alloc] init]` —— 用系统当前 locale。 -fn create_recognizer() -> Result<*mut AnyObject> { +/// 创建 recognizer。有指定 locale 就 `initWithLocale:`(关键 —— 否则落到系统首选语言, +/// 中文语音会被英文引擎误识别);无 locale 或 NSLocale 构造失败时回退 `init`(系统默认)。 +fn create_recognizer(locale: Option<&str>) -> Result<*mut AnyObject> { let cls = speech_recognizer_class()?; - // SAFETY: `cls` 是 `SFSpeechRecognizer` 类;`alloc` 返回未初始化实例, - // `init` 对其初始化,返回的实例由本函数所有权移交调用方(随后被 ARC 管理)。 - let recognizer: *mut AnyObject = unsafe { - let alloc: *mut AnyObject = msg_send![cls, alloc]; - msg_send![alloc, init] + let recognizer: *mut AnyObject = match locale.and_then(ns_locale) { + Some(ns_loc) => { + log::info!("[apple-speech] recognizer locale = {}", locale.unwrap_or("")); + // SAFETY: `cls` 是 SFSpeechRecognizer 类;`alloc` 得未初始化实例, + // `initWithLocale:` 用有效 NSLocale 初始化,返回实例移交调用方(ARC 管理)。 + unsafe { + let alloc: *mut AnyObject = msg_send![cls, alloc]; + msg_send![alloc, initWithLocale: ns_loc] + } + } + None => { + // SAFETY: 同上;`init` 用系统默认 locale。 + unsafe { + let alloc: *mut AnyObject = msg_send![cls, alloc]; + msg_send![alloc, init] + } + } }; if recognizer.is_null() { - bail!("无法创建 SFSpeechRecognizer(当前系统语言可能不支持语音识别)"); + bail!("无法创建 SFSpeechRecognizer(当前语言可能不支持语音识别)"); } Ok(recognizer) } +/// `[NSLocale localeWithLocaleIdentifier:]`。构造失败返回 None(调用方回退系统默认)。 +fn ns_locale(identifier: &str) -> Option<*mut AnyObject> { + let ns_id = ns_string_from_str(identifier).ok()?; + let cls = AnyClass::get("NSLocale")?; + // SAFETY: `cls` 是 NSLocale;`localeWithLocaleIdentifier:` 接收 NSString(`ns_id` 有效), + // 返回 autoreleased NSLocale(在 spawn_blocking 线程的 autorelease 池存活)。 + let loc: *mut AnyObject = unsafe { msg_send![cls, localeWithLocaleIdentifier: ns_id] }; + if loc.is_null() { + None + } else { + Some(loc) + } +} + +/// 用户工作语言(原生名,见前端 `SUPPORTED_LANGUAGES`)→ SFSpeechRecognizer 的 locale +/// 标识符。取 `working_languages` 主语言映射;未收录的语言返回 None(回退系统默认 locale)。 +/// SFSpeechRecognizer 一个实例只认一种语言,中英混说时以主语言为准 —— 这是 Apple 的固有 +/// 限制,云端 ASR 才能自由多语言混识。 +pub fn native_name_to_apple_locale(native_name: &str) -> Option { + let locale = match native_name.trim() { + "简体中文" => "zh-CN", + "繁体中文" | "繁體中文" => "zh-TW", + "English" => "en-US", + "日本語" => "ja-JP", + "한국어" => "ko-KR", + "Français" => "fr-FR", + "Deutsch" => "de-DE", + "Español" => "es-ES", + "Italiano" => "it-IT", + "Português" => "pt-BR", + "Русский" => "ru-RU", + "العربية" => "ar-SA", + "Tiếng Việt" => "vi-VN", + "ไทย" => "th-TH", + "हिन्दी" => "hi-IN", + _ => return None, + }; + Some(locale.to_string()) +} + /// `[NSURL fileURLWithPath:]`。 fn file_url(path: &str) -> Result<*mut AnyObject> { let ns_path = ns_string_from_str(path)?; @@ -387,7 +598,7 @@ mod tests { #[test] fn buffer_duration_tracks_consumed_pcm() { - let asr = AppleSpeechAsr::new(); + let asr = AppleSpeechAsr::new(None); assert_eq!(asr.buffer_duration_ms(), 0); // 16k * 2 bytes/sample * 1s = 32000 bytes。 asr.consume_pcm_chunk(&vec![0u8; 32_000]); @@ -398,7 +609,7 @@ mod tests { #[test] fn cancel_clears_buffer() { - let asr = AppleSpeechAsr::new(); + let asr = AppleSpeechAsr::new(None); asr.consume_pcm_chunk(&vec![0u8; 32_000]); asr.cancel(); assert_eq!(asr.buffer_duration_ms(), 0); @@ -406,7 +617,7 @@ mod tests { #[tokio::test] async fn transcribe_empty_buffer_returns_empty() { - let asr = AppleSpeechAsr::new(); + let asr = AppleSpeechAsr::new(None); let transcript = asr.transcribe().await.unwrap(); assert_eq!(transcript.text, ""); assert_eq!(transcript.duration_ms, 0); @@ -432,4 +643,72 @@ mod tests { let b = unique_suffix(); assert!(b > a); } + + #[test] + fn cancel_flag_defaults_false_and_set_by_cancel() { + let asr = AppleSpeechAsr::new(None); + assert!( + !asr.cancel_flag.load(Ordering::SeqCst), + "取消标志初值应为 false" + ); + asr.cancel(); + assert!( + asr.cancel_flag.load(Ordering::SeqCst), + "cancel() 应把取消标志置位,让等待轮询下一轮退出" + ); + } + + #[test] + fn active_task_defaults_none() { + let asr = AppleSpeechAsr::new(None); + assert!( + asr.active_task.lock().is_none(), + "尚未发起识别时 active_task 应为 None" + ); + } + + #[test] + fn active_task_guard_clears_handle_on_drop() { + // active_task 存了句柄后,ActiveTaskGuard 掉出作用域应把它清回 None。 + // 用 dangling 指针仅做占位:guard 的 Drop 只 take + 置 None,不触碰指针内容。 + let slot: Mutex> = Mutex::new(None); + *slot.lock() = Some(SendableTask(std::ptr::null_mut())); + assert!(slot.lock().is_some()); + { + let _guard = ActiveTaskGuard(&slot); + } + assert!( + slot.lock().is_none(), + "ActiveTaskGuard drop 后 active_task 必须清空,避免悬挂句柄" + ); + } + + #[test] + fn cancel_on_empty_active_task_is_noop_and_sets_flag() { + // active_task 为 None 时 cancel() 不应发起任何 objc 调用,只置标志 + 清缓冲。 + let asr = AppleSpeechAsr::new(None); + assert!(asr.active_task.lock().is_none()); + asr.cancel(); // 不得 panic + assert!(asr.cancel_flag.load(Ordering::SeqCst)); + assert!(asr.active_task.lock().is_none()); + } + + #[tokio::test] + async fn transcribe_empty_buffer_short_circuits_before_flag_reset() { + // 空缓冲在复位取消标志之前就提前 return,因此不进入识别逻辑,flag 维持原值。 + // 这条固定住短路顺序:只有真正要识别(缓冲非空)时才会复位并进入轮询。 + let asr = AppleSpeechAsr::new(None); + asr.cancel_flag.store(true, Ordering::SeqCst); + let out = asr.transcribe().await.unwrap(); + assert_eq!(out.text, ""); + assert!(asr.cancel_flag.load(Ordering::SeqCst)); + } + + #[test] + fn sendable_task_is_send() { + // 编译期断言:SendableTask 必须是 Send,才能被 spawn_blocking 捕获跨线程存取。 + fn assert_send() {} + assert_send::(); + assert_send::>>>(); + } } diff --git a/openless-all/app/src-tauri/src/asr/local/mod.rs b/openless-all/app/src-tauri/src/asr/local/mod.rs index 627f3ecf..cb60aec9 100644 --- a/openless-all/app/src-tauri/src/asr/local/mod.rs +++ b/openless-all/app/src-tauri/src/asr/local/mod.rs @@ -38,7 +38,7 @@ mod qwen_ffi; #[cfg(target_os = "macos")] #[allow(unused_imports)] -pub use apple_speech_provider::AppleSpeechAsr; +pub use apple_speech_provider::{native_name_to_apple_locale, AppleSpeechAsr}; #[cfg(target_os = "macos")] pub use local_provider::LocalQwenAsr; #[cfg(target_os = "macos")] diff --git a/openless-all/app/src-tauri/src/asr/volcengine.rs b/openless-all/app/src-tauri/src/asr/volcengine.rs index f356710c..80938774 100644 --- a/openless-all/app/src-tauri/src/asr/volcengine.rs +++ b/openless-all/app/src-tauri/src/asr/volcengine.rs @@ -32,6 +32,15 @@ const BYTES_PER_MS: f64 = 32.0; const HOTWORD_CAP: usize = 80; const FINAL_RESULT_TIMEOUT: Duration = Duration::from_secs(12); +/// 弱网下 TLS/WebSocket 握手可能一直挂到 OS 级 TCP 超时(几十秒),期间用户卡在 +/// 「Starting」无法语音输入。协调器的全局超时只覆盖 `await_final_result`,**不**覆盖 +/// `open_session`,所以这里必须自己给握手设上限:超时即快速失败并重试,而不是冻结。 +const CONNECT_TIMEOUT: Duration = Duration::from_secs(5); +/// 单次网络抖动(连接被重置 / 瞬时 DNS 失败)以前会直接让整次听写失败。重试几次让 +/// 抖动可恢复。`AuthRejected`(凭据被拒)不在重试之列——重试也不会变好,只会拖慢报错。 +const CONNECT_MAX_ATTEMPTS: usize = 3; +const CONNECT_RETRY_BACKOFF: Duration = Duration::from_millis(250); + #[derive(Clone, Debug)] pub struct VolcengineCredentials { pub app_id: String, @@ -57,6 +66,12 @@ pub enum VolcengineASRError { /// 文案简短,原因在文档里说明,capsule 不堆长引导。 #[error("凭据被拒({0})")] AuthRejected(u16), + /// WebSocket 握手阶段服务端返回 429:请求过多 / 账号被限流。 + /// 单独归类而非落入 `ConnectionFailed` —— 后者会被 `connect_with_retry` 当网络抖动 + /// 立即重试 3 次,反而加剧限流、且文案含糊指向「网络失败」误导用户。此类与 + /// `AuthRejected` 一样**短路不重试**:立即回带明确文案,让用户知道是限流不是断网。 + #[error("请求过多,账号被限流({0})")] + RateLimited(u16), #[error("no final result")] NoFinalResult, #[error("final result timed out")] @@ -131,34 +146,7 @@ impl VolcengineStreamingASR { } let connect_id = Uuid::new_v4().to_string(); - let mut request = ENDPOINT - .into_client_request() - .map_err(|e| VolcengineASRError::ConnectionFailed(e.to_string()))?; - let headers = request.headers_mut(); - headers.insert( - "X-Api-App-Key", - HeaderValue::from_str(&self.credentials.app_id) - .map_err(|e| VolcengineASRError::ConnectionFailed(e.to_string()))?, - ); - headers.insert( - "X-Api-Access-Key", - HeaderValue::from_str(&self.credentials.access_token) - .map_err(|e| VolcengineASRError::ConnectionFailed(e.to_string()))?, - ); - headers.insert( - "X-Api-Resource-Id", - HeaderValue::from_str(&self.credentials.resource_id) - .map_err(|e| VolcengineASRError::ConnectionFailed(e.to_string()))?, - ); - headers.insert( - "X-Api-Connect-Id", - HeaderValue::from_str(&connect_id) - .map_err(|e| VolcengineASRError::ConnectionFailed(e.to_string()))?, - ); - - let (ws, _resp) = connect_async(request) - .await - .map_err(classify_connect_error)?; + let ws = self.connect_with_retry(&connect_id).await?; let (write, read) = ws.split(); let (tx, rx) = oneshot::channel(); @@ -260,6 +248,78 @@ impl VolcengineStreamingASR { Ok(()) } + /// Build the WebSocket handshake request (endpoint + auth headers). Rebuilt + /// per connect attempt because `connect_async` consumes the request and + /// `http::Request` is not `Clone`. + fn build_connect_request( + &self, + connect_id: &str, + ) -> Result + { + let mut request = ENDPOINT + .into_client_request() + .map_err(|e| VolcengineASRError::ConnectionFailed(e.to_string()))?; + let headers = request.headers_mut(); + headers.insert( + "X-Api-App-Key", + HeaderValue::from_str(&self.credentials.app_id) + .map_err(|e| VolcengineASRError::ConnectionFailed(e.to_string()))?, + ); + headers.insert( + "X-Api-Access-Key", + HeaderValue::from_str(&self.credentials.access_token) + .map_err(|e| VolcengineASRError::ConnectionFailed(e.to_string()))?, + ); + headers.insert( + "X-Api-Resource-Id", + HeaderValue::from_str(&self.credentials.resource_id) + .map_err(|e| VolcengineASRError::ConnectionFailed(e.to_string()))?, + ); + headers.insert( + "X-Api-Connect-Id", + HeaderValue::from_str(connect_id) + .map_err(|e| VolcengineASRError::ConnectionFailed(e.to_string()))?, + ); + Ok(request) + } + + /// Connect with a per-attempt timeout and bounded retries so a poor network + /// (hung handshake or a transient blip) doesn't kill the whole dictation. + /// `AuthRejected` / `RateLimited` short-circuit — bad credentials never heal on + /// retry, and hammering a rate-limited account only makes the throttle worse. + async fn connect_with_retry(&self, connect_id: &str) -> Result { + let mut attempt = 0usize; + loop { + attempt += 1; + let request = self.build_connect_request(connect_id)?; + match tokio::time::timeout(CONNECT_TIMEOUT, connect_async(request)).await { + Ok(Ok((ws, _resp))) => return Ok(ws), + Ok(Err(e)) => { + let classified = classify_connect_error(e); + if is_non_retryable(&classified) || attempt >= CONNECT_MAX_ATTEMPTS { + return Err(classified); + } + log::warn!( + "[asr] 连接尝试 {attempt}/{CONNECT_MAX_ATTEMPTS} 失败: {classified};重试中" + ); + } + Err(_) => { + if attempt >= CONNECT_MAX_ATTEMPTS { + return Err(VolcengineASRError::ConnectionFailed(format!( + "连接超时({} ms)", + CONNECT_TIMEOUT.as_millis() + ))); + } + log::warn!( + "[asr] 连接尝试 {attempt}/{CONNECT_MAX_ATTEMPTS} 超时({} ms);重试中", + CONNECT_TIMEOUT.as_millis() + ); + } + } + tokio::time::sleep(CONNECT_RETRY_BACKOFF * attempt as u32).await; + } + } + pub async fn send_last_frame(&self) -> Result<(), VolcengineASRError> { // 等所有 fire-and-forget 发送完成。否则末帧(NegativeSequence)可能比尾部 // chunk 先到服务端,被识别为「流已结束」之后再到的 chunk 全部丢弃 = 尾句吞掉。 @@ -393,6 +453,7 @@ impl VolcengineStreamingASR { "enable_itn": true, "enable_punc": true, "show_utterances": true, + "enable_speaker_info": true, }); if let Some(context) = hotword_context(&self.hotwords) { request["context"] = Value::String(context); @@ -474,13 +535,59 @@ impl VolcengineStreamingASR { .to_string(); if let Some(utterances) = result.get("utterances").and_then(|v| v.as_array()) { - // 优先用 utterances 拼接的文本(包含全部分段,不论 definite 与否) - let pieces: Vec<&str> = utterances + // --- 声纹过滤:只保留主要说话人(说话时长最长的) --- + // 1. 统计每个 speaker 的说话时长 + let mut speaker_durations: std::collections::HashMap = + std::collections::HashMap::new(); + for u in utterances.iter() { + if let (Some(speaker), Some(start), Some(end)) = ( + u.get("speaker").and_then(|s| s.as_str()), + u.get("start_time").and_then(|t| t.as_u64()), + u.get("end_time").and_then(|t| t.as_u64()), + ) { + let dur = end.saturating_sub(start); + *speaker_durations.entry(speaker.to_string()).or_insert(0) += dur; + } + } + + // 2. 找到说话时长最长的 speaker(即"主要说话人") + let primary_speaker: Option = speaker_durations .iter() - .filter_map(|u| u.get("text").and_then(|t| t.as_str())) - .collect(); + .max_by_key(|(_, &dur)| dur) + .map(|(s, _)| s.clone()); + + // 3. 只拼接主要说话人的文本;如果没有任何 speaker 标签,回退到全量拼接 + let pieces: Vec<&str> = if let Some(ref primary) = primary_speaker { + utterances + .iter() + .filter(|u| { + u.get("speaker") + .and_then(|s| s.as_str()) + .map(|s| s == primary.as_str()) + .unwrap_or(true) // 无 speaker 字段的 utterance 保留 + }) + .filter_map(|u| u.get("text").and_then(|t| t.as_str())) + .collect() + } else { + utterances + .iter() + .filter_map(|u| u.get("text").and_then(|t| t.as_str())) + .collect() + }; + if !pieces.is_empty() { full_text = pieces.join(""); + if let Some(ref primary) = primary_speaker { + let filtered_count = utterances.len().saturating_sub(pieces.len()); + if filtered_count > 0 { + log::info!( + "[asr] speaker filter: primary={}, kept={}, filtered={}", + primary, + pieces.len(), + filtered_count + ); + } + } } } @@ -639,7 +746,8 @@ fn normalized_result(json: &Value) -> Option<&Value> { } /// 把 tokio-tungstenite 的 connect 错误分类:握手收到 HTTP 401 / 403 → `AuthRejected` -/// (凭据被拒,要 user 检查 App ID / Access Token / 账号资源开通状态);其它 → 通用 +/// (凭据被拒,要 user 检查 App ID / Access Token / 账号资源开通状态);429 → +/// `RateLimited`(请求过多 / 限流,重试只会火上浇油,短路报明确文案);其它 → 通用 /// `ConnectionFailed`(DNS / TLS / 网络层)。让 capsule 文案能跟泛泛 HTTP error 区分。 fn classify_connect_error(err: tokio_tungstenite::tungstenite::Error) -> VolcengineASRError { use tokio_tungstenite::tungstenite::Error as WsError; @@ -648,10 +756,23 @@ fn classify_connect_error(err: tokio_tungstenite::tungstenite::Error) -> Volceng if status == 401 || status == 403 { return VolcengineASRError::AuthRejected(status); } + if status == 429 { + return VolcengineASRError::RateLimited(status); + } } VolcengineASRError::ConnectionFailed(err.to_string()) } +/// 握手错误是否「重试也无益」,`connect_with_retry` 据此短路。凭据被拒(401/403) +/// 与限流(429)都属此类:前者重试不会变对,后者重试只会加剧限流。其余(网络层) +/// 才值得在抖动时重试。 +fn is_non_retryable(err: &VolcengineASRError) -> bool { + matches!( + err, + VolcengineASRError::AuthRejected(_) | VolcengineASRError::RateLimited(_) + ) +} + fn hotword_context(entries: &[DictionaryHotword]) -> Option { let mut seen: Vec = Vec::new(); for entry in entries { @@ -738,6 +859,67 @@ mod tests { ); } + /// 构造一个握手阶段返回给定 HTTP 状态码的 tungstenite 错误,用于分类测试。 + fn http_ws_error(status: u16) -> tokio_tungstenite::tungstenite::Error { + use tokio_tungstenite::tungstenite::http::Response; + let resp = Response::builder() + .status(status) + .body(None) + .expect("build test http response"); + tokio_tungstenite::tungstenite::Error::Http(resp) + } + + #[test] + fn classify_429_is_rate_limited_not_connection_failed() { + let classified = classify_connect_error(http_ws_error(429)); + assert!( + matches!(classified, VolcengineASRError::RateLimited(429)), + "429 应归类为 RateLimited 而非 ConnectionFailed,避免被当网络抖动重试" + ); + } + + #[test] + fn rate_limited_is_non_retryable_like_auth_rejected() { + assert!( + is_non_retryable(&VolcengineASRError::RateLimited(429)), + "限流不可重试:重试只会加剧限流" + ); + assert!( + is_non_retryable(&VolcengineASRError::AuthRejected(401)), + "凭据被拒不可重试" + ); + } + + #[test] + fn network_errors_stay_retryable() { + // 通用网络失败仍应重试(抖动可恢复)——不能被误判成短路。 + assert!(!is_non_retryable(&VolcengineASRError::ConnectionFailed( + "dns fail".into() + ))); + assert!(!is_non_retryable(&VolcengineASRError::FinalResultTimeout)); + } + + #[test] + fn classify_401_403_still_auth_rejected() { + // 回归:新增 429 分类不影响既有 401 / 403 → AuthRejected。 + assert!(matches!( + classify_connect_error(http_ws_error(401)), + VolcengineASRError::AuthRejected(401) + )); + assert!(matches!( + classify_connect_error(http_ws_error(403)), + VolcengineASRError::AuthRejected(403) + )); + } + + #[test] + fn rate_limited_message_mentions_throttling_not_network() { + // 文案必须明确指向「限流/请求过多」,不是含糊的「网络失败」。 + let msg = VolcengineASRError::RateLimited(429).to_string(); + assert!(msg.contains("限流") || msg.contains("请求过多"), "文案: {msg}"); + assert!(!msg.contains("网络"), "限流文案不应误导为网络失败: {msg}"); + } + #[tokio::test] async fn await_final_result_returns_error_when_final_frame_never_arrives() { let asr = VolcengineStreamingASR::new( diff --git a/openless-all/app/src-tauri/src/coding_agent/stream.rs b/openless-all/app/src-tauri/src/coding_agent/stream.rs index d1349eb0..78b8d722 100644 --- a/openless-all/app/src-tauri/src/coding_agent/stream.rs +++ b/openless-all/app/src-tauri/src/coding_agent/stream.rs @@ -15,6 +15,8 @@ pub enum CodingAgentEvent { Delta { session_id: String, text: String }, /// agent 触发了某个工具(如 Bash / Edit)。 ToolUse { session_id: String, name: String }, + /// 会话上下文被压缩(`system/compact_boundary`)。前端在消息流对应位置内嵌提示。 + Compaction { session_id: String }, /// 运行完成的最终结果。 Completed { session_id: String, @@ -65,6 +67,16 @@ pub fn parse_stream_json_line(session_id: &str, line: &str) -> Option { + // 上下文压缩:headless claude 压缩会话历史时发一行 system/compact_boundary。 + // 其余 system 行(init 等)继续忽略。 + if v.get("subtype")?.as_str()? != "compact_boundary" { + return None; + } + Some(CodingAgentEvent::Compaction { + session_id: session_id.to_string(), + }) + } "result" => { let is_error = v.get("is_error").and_then(|b| b.as_bool()).unwrap_or(false); let text = v @@ -154,6 +166,17 @@ mod tests { ); } + #[test] + fn parses_compact_boundary() { + let line = r#"{"type":"system","subtype":"compact_boundary","compact_metadata":{"trigger":"auto","pre_tokens":50000}}"#; + assert_eq!( + parse_stream_json_line("s1", line), + Some(CodingAgentEvent::Compaction { + session_id: "s1".into() + }) + ); + } + #[test] fn ignores_system_init_and_garbage() { assert_eq!( diff --git a/openless-all/app/src-tauri/src/commands/history.rs b/openless-all/app/src-tauri/src/commands/history.rs index e8fa29ff..1f6a84ab 100644 --- a/openless-all/app/src-tauri/src/commands/history.rs +++ b/openless-all/app/src-tauri/src/commands/history.rs @@ -15,6 +15,18 @@ pub fn clear_history(coord: CoordinatorState<'_>) -> Result<(), String> { coord.history().clear().map_err(|e| e.to_string()) } +/// 每日活动计数(日期升序),概览页年度热力图的数据源。与历史内容 / 保留策略解耦: +/// 清空历史不影响它,全年格子照亮。 +#[tauri::command] +pub fn get_activity_stats(coord: CoordinatorState<'_>) -> Vec { + coord + .activity() + .snapshot() + .into_iter() + .map(|(date, count)| ActivityDay { date, count }) + .collect() +} + /// 读取某次会话的原始麦克风 wav 字节流。文件存在的条件:debug 用户的任意会话,或任意 /// 「转录失败 / empty」会话(失败保留)——成功的非 debug 会话录音会在插入后删掉。 /// 文件名规约:`/recordings/.wav`,与 DictationSession.id 同名。 diff --git a/openless-all/app/src-tauri/src/commands/mod.rs b/openless-all/app/src-tauri/src/commands/mod.rs index cfb150be..bac691e0 100644 --- a/openless-all/app/src-tauri/src/commands/mod.rs +++ b/openless-all/app/src-tauri/src/commands/mod.rs @@ -56,7 +56,8 @@ pub(crate) use crate::recorder::{AudioConsumer, Recorder}; #[cfg(not(mobile))] pub(crate) use crate::types::WindowsImeStatus; pub(crate) use crate::types::{ - builtin_style_pack_id, default_active_style_pack_id, AndroidAccessibilityStatus, + builtin_style_pack_id, default_active_style_pack_id, ActivityDay, + AndroidAccessibilityStatus, AndroidOverlayStatus, ChineseScriptPreference, ComboBinding, CorrectionRule, CredentialsStatus, DictationSession, DictionaryEntry, HotkeyCapability, HotkeyStatus, OutputLanguagePreference, PolishMode, ShortcutBinding, StylePack, StylePackKind, StylePackRuntimeDiagnostics, diff --git a/openless-all/app/src-tauri/src/commands/providers.rs b/openless-all/app/src-tauri/src/commands/providers.rs index d6e29f37..9a376ffd 100644 --- a/openless-all/app/src-tauri/src/commands/providers.rs +++ b/openless-all/app/src-tauri/src/commands/providers.rs @@ -28,6 +28,11 @@ pub async fn validate_provider_credentials(kind: String) -> Result Result { if kind == "asr" && CredentialsVault::get_active_asr() == crate::asr::bailian::PROVIDER_ID { + // Bailian 实时 ASR 没有模型列表 HTTP 接口,列表只能是静态的。但直接返回 + // 硬编码列表会让「拉取模型」在 Key / endpoint 全错时也显示成功,给用户 + // "已连通"的错觉(其他厂商这颗按钮是真实带 Key GET /models)。先跑一次 + // 与「验证」相同的 WebSocket 连通性检查,语义对齐后再返回静态列表。 + validate_bailian_asr_provider().await?; return Ok(ProviderModelsResult { models: vec![crate::asr::bailian::DEFAULT_MODEL.to_string()], }); @@ -229,6 +234,12 @@ async fn validate_bailian_asr_provider() -> Result<(), String> { .map_err(|e| e.to_string())? .filter(|s| !s.trim().is_empty()) .unwrap_or_else(|| crate::asr::bailian::DEFAULT_ENDPOINT.to_string()); + // 协议头先行校验:填成 https://(百炼兼容模式 / 专属域名地址)时,WebSocket + // 握手报的 "URL scheme not supported" 会被前端兜底成笼统的「操作失败」, + // 用户无从定位。这里拦下并返回专用错误码,前端映射成可操作的提示。 + if !crate::asr::bailian::endpoint_scheme_is_websocket(&endpoint) { + return Err("bailianEndpointSchemeInvalid".to_string()); + } let model = CredentialsVault::get(CredentialAccount::AsrModel) .map_err(|e| e.to_string())? .filter(|s| !s.trim().is_empty()) @@ -245,9 +256,12 @@ async fn validate_bailian_asr_provider() -> Result<(), String> { }, )); asr.open_session().await.map_err(|e| e.to_string())?; + // 验证音频必须 ≥200ms:只发 1 个 100ms 静音块时 DashScope 必然返回 + // task-failed: EmptyAudio,导致有效凭据也永远验证失败(2026-07 实测边界: + // 100ms 拒、200ms 起收)。取 500ms 留余量,与 Mimo 验证的 250ms 同量级。 crate::asr::AudioConsumer::consume_pcm_chunk( &*asr, - &vec![0u8; crate::asr::bailian::TARGET_AUDIO_CHUNK_BYTES], + &vec![0u8; crate::asr::bailian::TARGET_AUDIO_CHUNK_BYTES * 5], ); asr.send_last_frame().await.map_err(|e| e.to_string())?; asr.await_final_result() diff --git a/openless-all/app/src-tauri/src/commands/qa.rs b/openless-all/app/src-tauri/src/commands/qa.rs index e4558b64..222413c6 100644 --- a/openless-all/app/src-tauri/src/commands/qa.rs +++ b/openless-all/app/src-tauri/src/commands/qa.rs @@ -46,12 +46,6 @@ pub fn qa_window_dismiss(coord: CoordinatorState<'_>) { coord.qa_window_dismiss(); } -/// 用户点 📌 / 取消 📌。pinned=true 时浮窗不会自动隐藏。 -#[tauri::command] -pub fn qa_window_pin(coord: CoordinatorState<'_>, pinned: bool) { - coord.qa_window_pin(pinned); -} - /// 移动端 QA 面板录音按钮:Idle -> begin_qa_session,Recording -> end_qa_session。 #[tauri::command] pub async fn qa_toggle_recording(coord: CoordinatorState<'_>) -> Result<(), String> { @@ -71,10 +65,80 @@ pub fn less_computer_window_dismiss(coord: CoordinatorState<'_>) { coord.less_computer_window_dismiss(); } -/// 前端按内容测高后回传高度,后端 clamp + bottom-anchored 重新摆放浮窗。 +/// 聊天面板(qa / less-computer)请求键盘焦点。 +/// +/// 两个浮窗都以「不抢前台」方式显示(macOS orderFrontRegardless,从不 makeKey), +/// 窗口不是 key window 时按键根本进不了 webview —— 「点了输入框却打不出字」的根因。 +/// +/// macOS:窗口已转「非激活 NSPanel」(make_chat_window_panel_macos),makeKeyAndOrderFront +/// 只给面板键盘焦点、**不激活 app**(Spotlight 同款)—— 之前用 window.set_focus() 会激活 +/// 整个 app,把主窗口(设置页)一起带到前台,且 frontmost 变成 OpenLess、AX 读不到原 app +/// 选区。其它平台仍走 set_focus。仅允许两个聊天面板窗口调用(与 less_computer_approve +/// 同款收紧)。 #[tauri::command] -pub fn less_computer_window_resize(coord: CoordinatorState<'_>, height: f64) { - coord.less_computer_window_resize(height); +pub fn chat_panel_focus_keyboard(window: Window) -> Result<(), String> { + let label = window.label(); + if label != "qa" && label != "less-computer" { + return Err("chat_panel_focus_keyboard is only available to chat panels".to_string()); + } + #[cfg(target_os = "macos")] + { + use tauri::Manager; + let label = label.to_string(); + let app = window.app_handle().clone(); + // NSWindow 操作必须在主线程(macOS 26 硬断言);异常兜底防 AppKit raise 穿透。 + let _ = window.app_handle().run_on_main_thread(move || { + use objc2::msg_send; + use objc2::runtime::AnyObject; + let Some(w) = app.get_webview_window(&label) else { + return; + }; + let Ok(handle) = w.ns_window() else { + log::warn!("[chat-panel] ns_window unavailable; focus skipped"); + return; + }; + let ns = handle as *mut AnyObject; + if ns.is_null() { + return; + } + // SAFETY: 闭包内只有一次无返回值的 ObjC 消息发送,无需运行 Rust 析构, + // 异常展开跳过闭包帧不破坏内存安全。 + let result = unsafe { + objc2::exception::catch(std::panic::AssertUnwindSafe(|| { + let nil: *mut AnyObject = std::ptr::null_mut(); + let _: () = msg_send![ns, makeKeyAndOrderFront: nil]; + })) + }; + if let Err(e) = result { + log::warn!("[chat-panel] makeKeyAndOrderFront raised (caught): {e:?}"); + } + }); + Ok(()) + } + #[cfg(not(target_os = "macos"))] + { + window.set_focus().map_err(|e| e.to_string()) + } +} + +/// 浮窗打字输入:文字指令直接进入 Less Computer 执行链(跳过录音与 ASR)。 +#[tauri::command] +pub fn less_computer_submit_text(coord: CoordinatorState<'_>, text: String) { + coord.less_computer_submit_text(text); +} + +/// 浮窗 mount 时拉取当前会话的事件缓冲(seq 升序)。 +/// +/// 浮窗首次创建时 webview 冷加载,后端事件(尤其第一条 `user` —— 用户说的话) +/// 先于前端 listener 注册被丢,表现为「AI 在干活但面板上没有我说的话」。前端 +/// mount 后先注册 listener 再调本命令重放积压,按 seq 去重衔接实时流。 +/// 会话内容敏感,仅允许 less-computer 窗口调用(与 less_computer_approve 同款收紧)。 +#[tauri::command] +pub fn less_computer_sync(window: Window) -> Result, String> { + if window.label() != "less-computer" { + return Err("sync can only be requested from the Less Computer window".to_string()); + } + Ok(crate::coordinator::less_computer_event_backlog()) } /// 内联审批卡的 Approve / Deny 回执。token 关联到等待中的拦截动作。 diff --git a/openless-all/app/src-tauri/src/commands/settings.rs b/openless-all/app/src-tauri/src/commands/settings.rs index 06e47433..ef5c306c 100644 --- a/openless-all/app/src-tauri/src/commands/settings.rs +++ b/openless-all/app/src-tauri/src/commands/settings.rs @@ -13,6 +13,14 @@ pub fn get_default_style_system_prompts() -> StyleSystemPrompts { pub(crate) trait SettingsWriter { fn read_settings(&self) -> UserPreferences; fn write_settings(&self, prefs: UserPreferences) -> Result<(), String>; + fn write_settings_preserving_current_style_preferences( + &self, + mut prefs: UserPreferences, + ) -> Result<(), String> { + let current = self.read_settings(); + prefs.preserve_style_preferences_from(¤t); + self.write_settings(prefs) + } fn sync_active_asr_provider(&self, provider: &str) -> Result<(), String>; fn refresh_dictation_hotkey(&self); fn refresh_qa_hotkey(&self); @@ -32,6 +40,15 @@ impl SettingsWriter for Coordinator { self.prefs().set(prefs).map_err(|e| e.to_string()) } + fn write_settings_preserving_current_style_preferences( + &self, + prefs: UserPreferences, + ) -> Result<(), String> { + self.prefs() + .set_preserving_current_style_preferences(prefs) + .map_err(|e| e.to_string()) + } + fn sync_active_asr_provider(&self, provider: &str) -> Result<(), String> { self.sync_active_asr_provider_to_vault(provider) } @@ -74,6 +91,13 @@ impl SettingsWriter for Arc { (**self).write_settings(prefs) } + fn write_settings_preserving_current_style_preferences( + &self, + prefs: UserPreferences, + ) -> Result<(), String> { + (**self).write_settings_preserving_current_style_preferences(prefs) + } + fn sync_active_asr_provider(&self, provider: &str) -> Result<(), String> { (**self).sync_active_asr_provider(provider) } @@ -162,7 +186,7 @@ pub(crate) fn persist_settings_with_keyboard_apply( } } - if let Err(error) = coord.write_settings(prefs.clone()) { + if let Err(error) = coord.write_settings_preserving_current_style_preferences(prefs.clone()) { if active_asr_provider_changed { match coord.sync_active_asr_provider(&previous.active_asr_provider) { Ok(()) => { @@ -180,11 +204,13 @@ pub(crate) fn persist_settings_with_keyboard_apply( } Err(rollback_error) => { // ASR vault 无法回滚时 roll-forward prefs;键盘列表保持新状态,避免三者分叉。 - coord.write_settings(prefs).map_err(|roll_forward_error| { - format!( - "{error}; additionally failed to restore active ASR provider: {rollback_error}; additionally failed to preserve active ASR provider consistency: {roll_forward_error}" - ) - })?; + coord + .write_settings_preserving_current_style_preferences(prefs) + .map_err(|roll_forward_error| { + format!( + "{error}; additionally failed to restore active ASR provider: {rollback_error}; additionally failed to preserve active ASR provider consistency: {roll_forward_error}" + ) + })?; } } } else if windows_keyboard_list_changed { @@ -241,7 +267,8 @@ pub fn set_settings( // 广播给所有 webview。issue #205:QaPanel 跑在独立 webview, // 没有 HotkeySettingsContext,必须靠事件感知录音键变化,否则面板可见时 // 用户改键会让浮窗里的 "{recordHotkey}" 文案一直停留在旧值。 - persist_settings(&*coord, prefs.clone())?; + persist_settings(&*coord, prefs)?; + let prefs = coord.prefs().get(); #[cfg(target_os = "android")] coord.apply_android_overlay_settings_change(&remote_prev, &prefs); // refresh_tray_microphone_menu 内部会调用 NSStatusItem.set_menu,必须在主线程上跑。 @@ -284,7 +311,8 @@ pub fn set_settings( let packs = coord.style_packs().list().map_err(|e| e.to_string())?; sync_style_pack_preferences(&mut prefs, &packs); prefs.android_overlay_trigger = prefs.android_overlay_trigger.normalized(); - persist_settings(&*coord, prefs.clone())?; + persist_settings(&*coord, prefs)?; + let prefs = coord.prefs().get(); #[cfg(target_os = "android")] coord.apply_android_overlay_settings_change(&previous, &prefs); let _ = app.emit("prefs:changed", &prefs); @@ -292,6 +320,109 @@ pub fn set_settings( Ok(()) } +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Mutex; + + #[derive(Default)] + struct RaceSettingsWriter { + reads: Mutex>, + saved: Mutex>, + } + + impl SettingsWriter for RaceSettingsWriter { + fn read_settings(&self) -> UserPreferences { + let mut reads = self.reads.lock().unwrap(); + if reads.is_empty() { + return self.saved.lock().unwrap().clone().unwrap_or_default(); + } + reads.remove(0) + } + + fn write_settings(&self, prefs: UserPreferences) -> Result<(), String> { + *self.saved.lock().unwrap() = Some(prefs); + Ok(()) + } + + fn sync_active_asr_provider(&self, _provider: &str) -> Result<(), String> { + Ok(()) + } + + fn refresh_dictation_hotkey(&self) {} + + fn refresh_qa_hotkey(&self) {} + + fn refresh_combo_hotkey(&self) {} + + fn refresh_translation_hotkey(&self) {} + + fn refresh_switch_style_hotkey(&self) {} + + fn refresh_open_app_hotkey(&self) {} + + fn refresh_coding_agent_hotkey(&self) {} + } + + #[test] + fn settings_save_preserves_current_style_preferences_before_write() { + let packs = crate::types::builtin_style_packs(); + let current = UserPreferences { + default_mode: PolishMode::Light, + active_style_pack_id: builtin_style_pack_id(PolishMode::Light).to_string(), + ..UserPreferences::default() + }; + let mut stale_settings_payload = UserPreferences { + default_mode: PolishMode::Formal, + active_style_pack_id: builtin_style_pack_id(PolishMode::Formal).to_string(), + ..UserPreferences::default() + }; + + stale_settings_payload.preserve_style_preferences_from(¤t); + sync_style_pack_preferences(&mut stale_settings_payload, &packs); + + assert_eq!( + stale_settings_payload.active_style_pack_id, + builtin_style_pack_id(PolishMode::Light) + ); + assert_eq!(stale_settings_payload.default_mode, PolishMode::Light); + } + + #[test] + fn persist_settings_keeps_style_change_that_lands_before_write() { + let active_before_request = UserPreferences { + default_mode: PolishMode::Formal, + active_style_pack_id: builtin_style_pack_id(PolishMode::Formal).to_string(), + ..UserPreferences::default() + }; + let active_before_write = UserPreferences { + default_mode: PolishMode::Light, + active_style_pack_id: builtin_style_pack_id(PolishMode::Light).to_string(), + ..UserPreferences::default() + }; + let stale_payload = UserPreferences { + default_mode: PolishMode::Formal, + active_style_pack_id: builtin_style_pack_id(PolishMode::Formal).to_string(), + microphone_device_name: "External Mic".to_string(), + ..UserPreferences::default() + }; + let writer = RaceSettingsWriter { + reads: Mutex::new(vec![active_before_request, active_before_write]), + saved: Mutex::new(None), + }; + + persist_settings(&writer, stale_payload).unwrap(); + + let saved = writer.saved.lock().unwrap().clone().expect("prefs saved"); + assert_eq!( + saved.active_style_pack_id, + builtin_style_pack_id(PolishMode::Light) + ); + assert_eq!(saved.default_mode, PolishMode::Light); + assert_eq!(saved.microphone_device_name, "External Mic"); + } +} + // ─────────────────────────── release channel (Beta opt-in) ─────────────────────────── // // 渠道偏好的写入路径跟 set_settings 复用 persist_settings:保持热键兜底归一化 @@ -321,8 +452,8 @@ pub fn set_update_channel( return Ok(()); } prefs.update_channel = channel; - persist_settings(&*coord, prefs.clone())?; - let _ = app.emit("prefs:changed", &prefs); + persist_settings(&*coord, prefs)?; + let _ = app.emit("prefs:changed", &coord.prefs().get()); Ok(()) } diff --git a/openless-all/app/src-tauri/src/coordinator.rs b/openless-all/app/src-tauri/src/coordinator.rs index 9acfd322..99b07d86 100644 --- a/openless-all/app/src-tauri/src/coordinator.rs +++ b/openless-all/app/src-tauri/src/coordinator.rs @@ -39,8 +39,8 @@ use crate::correction::apply_correction_rules; use crate::hotkey::{HotkeyEvent, HotkeyMonitor}; use crate::insertion::TextInserter; use crate::persistence::{ - sync_style_pack_preferences, CorrectionRuleStore, CredentialAccount, CredentialsVault, - DictionaryStore, HistoryStore, PreferencesStore, StylePackStore, + sync_style_pack_preferences, ActivityStore, CorrectionRuleStore, CredentialAccount, + CredentialsVault, DictionaryStore, HistoryStore, PreferencesStore, StylePackStore, }; use crate::llm_gemini::{GeminiConfig, GeminiProvider}; @@ -77,6 +77,9 @@ use hotkey_loops::*; use polish_flow::*; use qa_session::*; +// less_computer_sync 命令的数据源(浮窗 webview 冷加载竞态补偿,见 dictation.rs)。 +pub(crate) use dictation::less_computer_event_backlog; + pub(super) fn qa_event_target() -> &'static str { #[cfg(target_os = "android")] { @@ -238,6 +241,8 @@ pub struct Coordinator { struct Inner { app: Mutex>, history: HistoryStore, + /// 每日活动计数(热力图数据源),与 history 的保留策略解耦。 + activity: ActivityStore, prefs: PreferencesStore, style_packs: StylePackStore, vocab: DictionaryStore, @@ -305,6 +310,11 @@ struct Inner { /// 最近一次应用到 capsule 窗口的几何状态。避免录音 level tick 反复触发 /// resize / reposition。 capsule_layout: Mutex>, + /// 预备态标志:按下热键即"乐观显示"胶囊(带入场动画),此时麦克风还在 cpal + /// init 窗口内、没有第一帧 PCM。为 true 时 emit_capsule 把 Recording payload 的 + /// `warming` 打成 true(前端渲染"待命"光效);`level_handler` 首次触发(PCM 真的 + /// 流入)后置 false,光条"点亮"进入正式录音。begin_session 每次入场重置为 true。 + capsule_warming: AtomicBool, /// QA 用的 ASR 句柄。必须跟 active_asr_provider 保持一致,避免浮窗走不同入口。 qa_asr: Mutex>, /// QA 用的 Recorder 句柄。 @@ -383,10 +393,16 @@ impl Coordinator { CorrectionRuleStore::new_fallback() }); + let activity = ActivityStore::load().unwrap_or_else(|e| { + log::error!("[coord] ActivityStore init failed: {e}; 活动计数降级为内存态"); + ActivityStore::new_fallback() + }); + Self { inner: Arc::new(Inner { app: Mutex::new(None), history, + activity, prefs, style_packs, vocab, @@ -415,6 +431,7 @@ impl Coordinator { last_capsule_state: Mutex::new(None), qa_state: Mutex::new(QaSessionState::default()), capsule_layout: Mutex::new(None), + capsule_warming: AtomicBool::new(false), qa_asr: Mutex::new(None), qa_recorder: Mutex::new(None), qa_stream_cancelled: Arc::new(AtomicBool::new(false)), @@ -474,10 +491,16 @@ impl Coordinator { CorrectionRuleStore::new_fallback() }); + let activity = ActivityStore::load().unwrap_or_else(|e| { + log::error!("[coord] ActivityStore init failed: {e}; 活动计数降级为内存态"); + ActivityStore::new_fallback() + }); + Self { inner: Arc::new(Inner { app: Mutex::new(None), history, + activity, prefs, style_packs, vocab, @@ -508,6 +531,7 @@ impl Coordinator { last_capsule_state: Mutex::new(None), qa_state: Mutex::new(QaSessionState::default()), capsule_layout: Mutex::new(None), + capsule_warming: AtomicBool::new(false), qa_asr: Mutex::new(None), qa_recorder: Mutex::new(None), qa_stream_cancelled: Arc::new(AtomicBool::new(false)), @@ -1058,11 +1082,6 @@ impl Coordinator { close_qa_panel(&self.inner); } - /// 用户点 📌 切换 pinned 状态。pinned=true 时浮窗不自动隐藏。 - pub fn qa_window_pin(&self, pinned: bool) { - self.inner.qa_state.lock().pinned = pinned; - log::info!("[coord] QA window pinned={pinned}"); - } /// 用户点 ✕ / 按 Esc 关 Less Computer 浮窗:隐藏窗口 + 结束连续对话 /// (下次说话开新会话,不再 --continue 续旧上下文)。 @@ -1076,22 +1095,37 @@ impl Coordinator { } } - /// 前端按内容测高后回传,后端 clamp + bottom-anchored 重新摆放 Less Computer 浮窗。 - pub fn less_computer_window_resize(&self, height: f64) { - if let Some(app) = self.inner.app.lock().clone() { - crate::resize_less_computer_window(&app, height); - } - } - /// 内联审批卡的 Approve / Deny 回执:解析等待中的 token。 pub fn less_computer_approve(&self, token: &str, approved: bool) { dictation::resolve_less_computer_approval(token, approved); } + /// 浮窗打字输入:文字指令直接进入 Less Computer 执行链(与语音转写同一条 + /// 路径——同样的护栏钳制 / 审批循环 / 连续会话语义),跳过录音与 ASR。 + pub fn less_computer_submit_text(&self, text: String) { + let text = text.trim().to_string(); + if text.is_empty() { + return; + } + let inner = Arc::clone(&self.inner); + tokio::spawn(async move { + let session_id = crate::coordinator_state::new_session_id(); + if let Err(e) = + dictation::run_voice_agent_transcript(&inner, session_id, text, 0).await + { + log::warn!("[less-computer] text submit run failed: {e}"); + } + }); + } + pub fn history(&self) -> &HistoryStore { &self.inner.history } + pub fn activity(&self) -> &ActivityStore { + &self.inner.activity + } + pub fn prefs(&self) -> &PreferencesStore { &self.inner.prefs } @@ -1785,9 +1819,7 @@ pub(super) fn insert_via_non_tsf_fallback( _paste_shortcut: PasteShortcut, ) -> InsertStatus { let prefs = inner.prefs.get(); - let sendinput_options = crate::unicode_keystroke::WindowsSendInputOptions { - newline_mode: prefs.windows_sendinput_newline_mode, - }; + let sendinput_options = dictation::windows_sendinput_options_from_prefs(&prefs); let status = finish_non_tsf_insertion_fallback( || inner .inserter diff --git a/openless-all/app/src-tauri/src/coordinator/asr_wiring.rs b/openless-all/app/src-tauri/src/coordinator/asr_wiring.rs index 5d9353ba..8f949f40 100644 --- a/openless-all/app/src-tauri/src/coordinator/asr_wiring.rs +++ b/openless-all/app/src-tauri/src/coordinator/asr_wiring.rs @@ -299,8 +299,16 @@ pub(super) async fn build_local_qwen3( } #[cfg(target_os = "macos")] -pub(super) fn build_apple_speech() -> Arc { - Arc::new(crate::asr::local::AppleSpeechAsr::new()) +pub(super) fn build_apple_speech( + prefs: &crate::types::UserPreferences, +) -> Arc { + // Apple 识别 locale 跟随用户工作语言主语言 —— 不显式指定 SFSpeechRecognizer 就落到 + // 系统首选语言(常是英文),中文语音会被识别成英文且理解错误。未收录语言回退默认。 + let locale = prefs + .working_languages + .first() + .and_then(|name| crate::asr::local::native_name_to_apple_locale(name)); + Arc::new(crate::asr::local::AppleSpeechAsr::new(locale)) } /// `whisper` 是 OpenAI 原生;`siliconflow` / `zhipu` / `groq` 都暴露 @@ -496,7 +504,7 @@ pub(super) async fn build_qa_asr_start(inner: &Arc, active_asr: &str) -> #[cfg(target_os = "macos")] if crate::asr::local::is_apple_speech(active_asr) { - let local = build_apple_speech(); + let local = build_apple_speech(&inner.prefs.get()); let active = ActiveAsr::AppleSpeech(Arc::clone(&local)); let consumer: Arc = local; return Ok(QaAsrStart::Ready { active, consumer }); diff --git a/openless-all/app/src-tauri/src/coordinator/capsule_focus.rs b/openless-all/app/src-tauri/src/coordinator/capsule_focus.rs index 5b7c7ddc..2398433c 100644 --- a/openless-all/app/src-tauri/src/coordinator/capsule_focus.rs +++ b/openless-all/app/src-tauri/src/coordinator/capsule_focus.rs @@ -387,11 +387,16 @@ pub(super) fn emit_capsule( inserted_chars: Option, ) { // 在 app 句柄校验之前记录,便于无 GUI 的测试断言「按下热键 → 弹了哪种胶囊」。 - *inner.last_capsule_state.lock() = Some(state); + // replace 顺带取回上一帧 state,用于判断本次是不是「入场帧」(见下方 defer_capsule_emit)。 + let prev_state = inner.last_capsule_state.lock().replace(state); let app_opt = inner.app.lock().clone(); let Some(app) = app_opt else { return }; let translation = inner.translation_modifier_seen.load(Ordering::SeqCst); let operating = inner.state.lock().voice_agent; + // 预备态只对 Recording 有意义:麦克风还没吐第一帧 PCM 时(capsule_warming=true)把 + // warming 打成 true,前端渲染「待命」光效;level_handler 首触发后翻 false → 光条点亮。 + let warming = matches!(state, CapsuleState::Recording) + && inner.capsule_warming.load(Ordering::SeqCst); let payload = CapsulePayload { state, level, @@ -400,6 +405,7 @@ pub(super) fn emit_capsule( inserted_chars, translation, operating, + warming, }; #[cfg(target_os = "android")] @@ -409,6 +415,13 @@ pub(super) fn emit_capsule( // 必须在 call-site(即音频线程触发 emit_capsule 时)就算定,否则 main thread // 闭包里读到的将是「下一帧」的 state,跟实际下发给 JS 的 payload 不一致。 let visible = !matches!(state, CapsuleState::Idle); + // 入场帧:胶囊从不可见第一次变可见。按平时的「同步 emit + 异步 show」,前端会在窗口 + // 还隐藏时就起播 capsule-in,等窗口真 show 出来动画早已播完 → 用户看到胶囊「凭空出 + // 现」而非「滑入」。修法:入场帧把发给 capsule 窗口的事件推迟到主线程闭包里、 + // window.show 之后再 emit,保证前端起播入场动画时窗口已可见、动画完整可见。Linux 不 + // 走胶囊窗口(文字经 fcitx5 直接 commit),保持原同步 emit 不变。 + let was_visible = matches!(prev_state, Some(s) if !matches!(s, CapsuleState::Idle)); + let defer_capsule_emit = visible && !was_visible && cfg!(not(target_os = "linux")); // Linux: 通过 fcitx5 插件在候选词列表下方显示听写状态,不干扰输入法预编辑。 // 只在文本变化时调用 DBus,避免录音中 ~30Hz 的音频电平回调重复调用。 @@ -509,6 +522,13 @@ pub(super) fn emit_capsule( // 闪烁。pr_agent 关注点 — 见 audit follow-up。 let inner_for_main = Arc::clone(inner); let app_for_main = app.clone(); + // 入场帧要在 window.show 之后、闭包内部把 state 回发给前端,需要 payload 的独立副本 + // move 进闭包;非入场帧走闭包外的即时同步 emit(下方),这里就是 None。 + let payload_for_deferred_emit = if defer_capsule_emit { + Some(payload.clone()) + } else { + None + }; let _ = app.run_on_main_thread(move || { let Some(window) = app_for_main.get_webview_window("capsule") else { // #470 诊断 v2:比 A/B/C 更靠前的暗点 A0 —— capsule webview 句柄取不到 @@ -568,12 +588,22 @@ pub(super) fn emit_capsule( hide_capsule_window_if_present(); let _ = window.hide(); } + // 入场帧:窗口刚 show(或本次用户关了胶囊显示走了 hide 分支),此刻再把 state 发给 + // capsule 前端 —— 前端起播 capsule-in 时窗口已可见,入场动画从头完整播放。 + if let Some(payload) = payload_for_deferred_emit.as_ref() { + let _ = app_for_main.emit_to("capsule", "capsule:state", payload); + } } }); - let _ = app.emit_to("capsule", "capsule:state", &payload); + // 非入场帧(含 Linux、录音中的 level 更新、离场/终态)保持即时同步 emit,最低延迟; + // 入场帧已在上面的主线程闭包里、window.show 之后 emit 过,这里跳过避免重复下发。 + if !defer_capsule_emit { + let _ = app.emit_to("capsule", "capsule:state", &payload); + } // 主窗口也需要 capsule:state 事件:AudioCueListener 用它触发录音提示音。 - // Linux 上胶囊隐藏时提示音仍应工作,所以同时发给 main 窗口。 + // Linux 上胶囊隐藏时提示音仍应工作,所以同时发给 main 窗口。始终即时,与胶囊窗口 + // 显示时机解耦。 let _ = app.emit_to("main", "capsule:state", &payload); } diff --git a/openless-all/app/src-tauri/src/coordinator/dictation.rs b/openless-all/app/src-tauri/src/coordinator/dictation.rs index 922d6866..01384017 100644 --- a/openless-all/app/src-tauri/src/coordinator/dictation.rs +++ b/openless-all/app/src-tauri/src/coordinator/dictation.rs @@ -66,13 +66,114 @@ pub(super) fn resolve_less_computer_approval(token: &str, approved: bool) { } } +/// Less Computer 事件缓冲:浮窗首次创建时 webview 冷加载需要数百毫秒,此时后端 +/// emit 的事件(尤其第一条 `user` —— 用户说出的那句话)会先于前端 listener 注册 +/// 被丢弃,表现为「AI 在干活、但面板上没有我说的话」。这里按单调 seq 缓存当前 +/// 会话的全部事件,前端 mount 后调 `less_computer_sync` 全量重放,实时流按 seq +/// 去重衔接。fresh=true 的 user 事件 = 新会话,清空重来(seq 不回卷,去重不混淆)。 +/// 容量上限防极端长会话无界增长(超限丢最旧 —— 重放的意义在冷启动窗口,尾部足够)。 +const LESS_COMPUTER_EVENT_LOG_CAP: usize = 2048; + +struct LessComputerEventLog { + next_seq: u64, + events: std::collections::VecDeque, +} + +static LESS_COMPUTER_EVENT_LOG: std::sync::OnceLock> = + std::sync::OnceLock::new(); + +fn less_computer_event_log() -> &'static std::sync::Mutex { + LESS_COMPUTER_EVENT_LOG.get_or_init(|| { + std::sync::Mutex::new(LessComputerEventLog { + next_seq: 0, + events: std::collections::VecDeque::new(), + }) + }) +} + +/// 纯逻辑:给 payload 编 seq 并写入缓冲(fresh user 先清空,超限丢最旧)。 +fn log_less_computer_event(log: &mut LessComputerEventLog, payload: &mut serde_json::Value) { + let fresh_user = payload.get("kind").and_then(|k| k.as_str()) == Some("user") + && payload.get("fresh").and_then(|f| f.as_bool()) == Some(true); + if fresh_user { + log.events.clear(); + } + log.next_seq += 1; + payload["seq"] = serde_json::json!(log.next_seq); + log.events.push_back(payload.clone()); + while log.events.len() > LESS_COMPUTER_EVENT_LOG_CAP { + log.events.pop_front(); + } +} + +/// `less_computer_sync` 命令的数据源:当前会话已发生的事件(seq 升序)。 +pub(crate) fn less_computer_event_backlog() -> Vec { + less_computer_event_log() + .lock() + .map(|log| log.events.iter().cloned().collect()) + .unwrap_or_default() +} + /// 往 Less Computer 浮窗发一条事件(macOS only;前端按 `kind` 渲染聊天结构)。 -fn emit_less_computer(inner: &Arc, payload: serde_json::Value) { +/// 每条事件先记入缓冲并带上 seq,再实时 emit —— 锁中毒时跳过缓冲照常 emit +/// (无 seq 事件前端无条件应用,退化为修复前行为而不是丢事件)。 +fn emit_less_computer(inner: &Arc, mut payload: serde_json::Value) { + if let Ok(mut log) = less_computer_event_log().lock() { + log_less_computer_event(&mut log, &mut payload); + } if let Some(app) = inner.app.lock().clone() { let _ = app.emit_to("less-computer", LESS_COMPUTER_EVENT, payload); } } +#[cfg(test)] +mod less_computer_event_log_tests { + use super::{log_less_computer_event, LessComputerEventLog, LESS_COMPUTER_EVENT_LOG_CAP}; + + fn new_log() -> LessComputerEventLog { + LessComputerEventLog { + next_seq: 0, + events: std::collections::VecDeque::new(), + } + } + + #[test] + fn assigns_monotonic_seq_and_clears_on_fresh_user() { + let mut log = new_log(); + let mut e1 = serde_json::json!({"kind":"user","text":"第一句","fresh":true}); + let mut e2 = serde_json::json!({"kind":"delta","text":"好的"}); + log_less_computer_event(&mut log, &mut e1); + log_less_computer_event(&mut log, &mut e2); + assert_eq!(e1["seq"], 1); + assert_eq!(e2["seq"], 2); + assert_eq!(log.events.len(), 2); + + // fresh=true 开新会话:缓冲清空,seq 继续单调(前端按 seq 去重不回卷)。 + let mut e3 = serde_json::json!({"kind":"user","text":"新会话","fresh":true}); + log_less_computer_event(&mut log, &mut e3); + assert_eq!(log.events.len(), 1); + assert_eq!(e3["seq"], 3); + + // 追加轮次(fresh=false / 缺省)不清空。 + let mut e4 = serde_json::json!({"kind":"user","text":"追加","fresh":false}); + log_less_computer_event(&mut log, &mut e4); + assert_eq!(log.events.len(), 2); + assert_eq!(log.events.front().unwrap()["seq"], 3); + } + + #[test] + fn caps_backlog_dropping_oldest() { + let mut log = new_log(); + for i in 0..(LESS_COMPUTER_EVENT_LOG_CAP + 5) { + let mut e = serde_json::json!({"kind":"delta","text":i.to_string()}); + log_less_computer_event(&mut log, &mut e); + } + assert_eq!(log.events.len(), LESS_COMPUTER_EVENT_LOG_CAP); + // 丢最旧:队首是第 6 条(seq 从 1 起)。 + assert_eq!(log.events.front().unwrap()["seq"], 6); + } +} + /// 跑流式润色路径(opt-in,跨平台)。 /// /// 平台差异: @@ -95,9 +196,10 @@ fn emit_less_computer(inner: &Arc, payload: serde_json::Value) { /// - 失败:`(raw_text, Some(reason), false)` — 流式过程出错,调用方走 raw 一次性兜底 /// - 不支持:`run_streaming_polish` 内部直接调 `polish_or_passthrough` 透明降级 /// -/// **不在流式路径里做**:`apply_chinese_script_preference` / `apply_correction_rules` -/// 这两步在 v1 跳过 —— 字符已经一边流一边落出去了,不好回退。需要的话只能关 toggle 走 -/// 一次性路径。 +/// **流式路径里的字形转换**:Simplified(t2s)在 `on_delta` 对每个 delta 就地转换 +/// (近乎逐字映射,跨 delta 拆散词条也几乎总是正确);Traditional(s2t)有真歧义, +/// `streaming_insert_eligible` 仍把它挡在一次性路径。`apply_correction_rules` 依旧 +/// 不在流式路径里做 —— 字符已经落出去,不好回退。 #[allow(clippy::too_many_arguments)] async fn run_streaming_polish( inner: &Arc, @@ -193,6 +295,18 @@ async fn run_streaming_polish( // 3. 调流式润色,on_delta 塞 mpsc;should_cancel 检查 dictation 取消旗。 let inner_for_cancel = Arc::clone(inner); let should_cancel = move || inner_for_cancel.state.lock().cancelled; + // Simplified 目标:对每个 delta 就地 t2s(转换器建一次,避免每个 delta 重新加载 + // 词典)。Traditional 不会走到这里(eligibility 已降级),Auto 无需转换。 + let delta_converter = (chinese_script_preference + == crate::types::ChineseScriptPreference::Simplified) + .then(|| { + ferrous_opencc::OpenCC::from_config(ferrous_opencc::config::BuiltinConfig::T2s) + .map_err(|e| { + log::warn!("[coord] streaming_insert: OpenCC t2s init failed, deltas stay unconverted: {e}"); + }) + .ok() + }) + .flatten(); let outcome = super::polish_or_passthrough_streaming( raw, mode, @@ -205,7 +319,11 @@ async fn run_streaming_polish( front_app, prior_turns, move |delta: &str| { - let _ = tx.send(delta.to_string()); + let converted = match delta_converter.as_ref() { + Some(converter) => converter.convert(delta), + None => delta.to_string(), + }; + let _ = tx.send(converted); }, should_cancel, ) @@ -327,7 +445,7 @@ async fn run_streaming_polish( } #[cfg(target_os = "windows")] -fn windows_sendinput_options_from_prefs( +pub(super) fn windows_sendinput_options_from_prefs( prefs: &crate::types::UserPreferences, ) -> crate::unicode_keystroke::WindowsSendInputOptions { crate::unicode_keystroke::WindowsSendInputOptions { @@ -521,10 +639,13 @@ fn streaming_insert_eligible( streaming_insert_enabled && !translation_active && (mode != PolishMode::Raw || raw_uses_llm) - // 非 Auto 字形(简/繁)要对成品文本做确定性 OpenCC 转换,而流式是边出边落字、 - // 没有成品可后处理(finalize_polished_text 在 already_streamed 时直接 return)。 - // → 非 Auto 时关掉流式,走一次性路径,确保简/繁转换真正生效(issue #643)。 - && chinese_script_preference == crate::types::ChineseScriptPreference::Auto + // 固定字形的 OpenCC 转换与流式的兼容性按方向区分: + // - Simplified(t2s):近乎逐字映射,对每个 delta 就地转换即可(跨 delta + // 边界拆散的词级条目退化为逐字转换,t2s 方向仍几乎总是正确),流式放行 + // —— 否则固定简体的用户流式静默失效且无从得知原因。 + // - Traditional(s2t):一简对多繁有真歧义(发→發/髮),需要全文上下文, + // 仍走一次性路径确保转换准确(issue #643)。 + && chinese_script_preference != crate::types::ChineseScriptPreference::Traditional && windows_insertion_allows_streaming(windows_insertion_mode) } @@ -687,7 +808,9 @@ pub(super) async fn handle_released(inner: &Arc) { } /// Less Computer 收尾:把转写当作指令交给无头 Claude,结果以胶囊展示(不插入到光标)。 -async fn run_voice_agent_transcript( +/// pub(super):除语音路径外,浮窗的打字输入(less_computer_submit_text 命令) +/// 也以文字直接进入同一条执行链(同样的护栏 / 审批 / 连续会话语义)。 +pub(super) async fn run_voice_agent_transcript( inner: &Arc, _session_id: SessionId, transcript: String, @@ -1031,6 +1154,9 @@ async fn run_less_computer_once( E::ToolUse { name, .. } => { emit_less_computer(inner, serde_json::json!({ "kind": "tool", "name": name })); } + E::Compaction { .. } => { + emit_less_computer(inner, serde_json::json!({ "kind": "compaction" })); + } E::Completed { text, cost_usd: c, .. } => { @@ -1218,6 +1344,14 @@ pub(super) async fn begin_session_as( return Ok(()); } + // 乐观显示:按下热键即弹出胶囊并播入场动画,不等麦克风/ASR。此刻麦克风还在 cpal + // init 窗口内、没有第一帧 PCM,先进「预备态」(warming=true → 前端渲染待命光效,引导 + // 用户稍候再开口);level_handler 首次触发(PCM 真的流入)后翻成正式录音态、光条点亮。 + // 这样把「视觉反馈」与「麦克风就绪」解耦:即时反馈 + 完整入场动画,同时用预备→点亮的 + // 过渡守住「不漏首字」。若随后凭证/权限校验失败,下面分支会用 Error 覆盖这一帧。 + inner.capsule_warming.store(true, Ordering::SeqCst); + emit_capsule(inner, CapsuleState::Recording, 0.0, 0, None, None); + if let Err(message) = ensure_asr_credentials() { log::warn!("[coord] ASR credential gate failed: {message}"); emit_capsule( @@ -1384,7 +1518,7 @@ pub(super) async fn begin_session_as( .await?; } MacosKeylessDictationProvider::AppleSpeech => { - let local = build_apple_speech(); + let local = build_apple_speech(&inner.prefs.get()); store_asr_for_session( inner, current_session_id, @@ -1629,6 +1763,10 @@ pub(super) async fn start_recorder_for_starting( .started_at .elapsed() .as_millis() as u64; + // 第一帧 PCM 真的流到 consumer 了(recorder.rs::process_callback 的顺序保证 + // consume_pcm_chunk 先于 level_handler)——关掉预备态,让这一帧起 payload.warming + // 翻 false,前端把「待命」光条点亮成正式录音态。之后每帧都是 false(幂等)。 + inner_for_level.capsule_warming.store(false, Ordering::SeqCst); emit_capsule( &inner_for_level, CapsuleState::Recording, @@ -2729,6 +2867,14 @@ pub(super) async fn end_session(inner: &Arc) -> Result<(), String> { ) { log::error!("[coord] history append failed: {e}"); } + // 活动计数(概览页热力图数据源):只有成功完成的听写才点亮格子——转录失败 / + // 错误收尾的两处 append 不计。写失败不阻断主流程。 + if let Err(e) = inner + .activity + .bump(&chrono::Local::now().format("%Y-%m-%d").to_string()) + { + log::warn!("[coord] activity bump failed: {e}"); + } // 远程输入:把本次最终文字回传给手机端。remote_server 的 WS handler 订阅了 // "remote:result"(mod.rs:614),但此前全仓从未 emit,导致手机结果区永远空(#691)。 @@ -3268,30 +3414,31 @@ mod tests { } #[test] - fn streaming_disabled_for_non_auto_script_so_opencc_runs() { - // issue #643:非 Auto 字形(简/繁)必须走一次性路径,让 finalize 的 OpenCC 转换生效。 + fn streaming_script_gate_blocks_only_traditional() { + // Traditional(s2t)有一简对多繁的真歧义,必须走一次性路径做全文 OpenCC + // 转换(issue #643);Simplified(t2s)近乎逐字,on_delta 就地转换即可, + // 不再挡流式(用户反馈:固定简体导致流式静默失效)。 + assert!(!streaming_insert_eligible( + true, + false, + PolishMode::Light, + false, + ChineseScriptPreference::Traditional, + crate::types::WindowsInsertionMode::SendInput, + )); for pref in [ + ChineseScriptPreference::Auto, ChineseScriptPreference::Simplified, - ChineseScriptPreference::Traditional, ] { - assert!(!streaming_insert_eligible( + assert!(streaming_insert_eligible( true, false, PolishMode::Light, false, pref, - crate::types::WindowsInsertionMode::Tsf, + crate::types::WindowsInsertionMode::SendInput, )); } - // Auto + SendInput 仍可流式。 - assert!(streaming_insert_eligible( - true, - false, - PolishMode::Light, - false, - ChineseScriptPreference::Auto, - crate::types::WindowsInsertionMode::SendInput, - )); } #[test] diff --git a/openless-all/app/src-tauri/src/coordinator/hotkey_loops.rs b/openless-all/app/src-tauri/src/coordinator/hotkey_loops.rs index a8208550..be826794 100644 --- a/openless-all/app/src-tauri/src/coordinator/hotkey_loops.rs +++ b/openless-all/app/src-tauri/src/coordinator/hotkey_loops.rs @@ -579,7 +579,6 @@ pub(super) fn combo_hotkey_supervisor_loop(inner: Arc) { .ok(); #[cfg(target_os = "linux")] sync_custom_dictation_to_plugin(&inner); - attempts = 0; return; } Err(e) => { diff --git a/openless-all/app/src-tauri/src/coordinator/qa.rs b/openless-all/app/src-tauri/src/coordinator/qa.rs index a97473ce..eabd7683 100644 --- a/openless-all/app/src-tauri/src/coordinator/qa.rs +++ b/openless-all/app/src-tauri/src/coordinator/qa.rs @@ -29,8 +29,6 @@ pub(super) struct QaSessionState { pub(super) qa_focus_target: Option, /// 用于忽略迟到的 RMS / runtime error。 pub(super) session_id: SessionId, - /// QA 浮窗是否被用户钉住(pinned)。pinned=true 时不自动隐藏。 - pub(super) pinned: bool, /// 浮窗是否对用户可见。Cmd+Shift+; 边沿 toggle 此 flag; /// 主听写 hotkey(rightOption)边沿来时,看这个 flag 决定是走 QA 还是走 dictation。 /// 详见 issue #118 v2。 @@ -48,7 +46,6 @@ impl Default for QaSessionState { front_app: None, qa_focus_target: None, session_id: initial_session_id(), - pinned: false, panel_visible: false, messages: Vec::new(), } @@ -124,7 +121,6 @@ pub(super) fn close_qa_panel(inner: &Arc) { { let mut state = inner.qa_state.lock(); state.panel_visible = false; - state.pinned = false; state.messages.clear(); state.selection = None; state.front_app = None; @@ -155,7 +151,6 @@ mod tests { assert!(st.selection.is_none()); assert!(st.front_app.is_none()); assert!(st.qa_focus_target.is_none()); - assert!(!st.pinned, "新建会话不应处于 pinned"); assert!(!st.panel_visible, "浮窗默认不可见,等用户 toggle"); assert!(st.messages.is_empty(), "新建会话历史必须为空"); } diff --git a/openless-all/app/src-tauri/src/coordinator/qa_session.rs b/openless-all/app/src-tauri/src/coordinator/qa_session.rs index 8e9664c1..094a1c6c 100644 --- a/openless-all/app/src-tauri/src/coordinator/qa_session.rs +++ b/openless-all/app/src-tauri/src/coordinator/qa_session.rs @@ -690,6 +690,10 @@ pub(super) async fn begin_qa_session(inner: &Arc) -> Result<(), String> { return Ok(()); } + // QA 无「预备态」语义(不走等麦克风预热的乐观显示),显式清掉 capsule_warming —— + // 否则若上一次听写在拿到首帧 PCM 前异常早退、warming 停在 true,这里的 QA 录音胶囊会 + // 读到陈旧 true 卡在「待命」收拢态(QA 的 level_handler 不翻这个标志)。审核 follow-up。 + inner.capsule_warming.store(false, Ordering::SeqCst); // 显式弹胶囊到 Recording。level_handler 后续会持续推电平,胶囊里"录音中…" // 的视觉反馈跟主听写完全一致。 emit_capsule(inner, CapsuleState::Recording, 0.0, 0, None, None); diff --git a/openless-all/app/src-tauri/src/insertion.rs b/openless-all/app/src-tauri/src/insertion.rs index db5468a6..e2c4cbf7 100644 --- a/openless-all/app/src-tauri/src/insertion.rs +++ b/openless-all/app/src-tauri/src/insertion.rs @@ -603,17 +603,25 @@ mod tests { #[test] #[cfg(target_os = "windows")] fn crlf_sendinput_success_uses_expected_typed_count() { + // 期望值直接取自 expected_sendinput_typed_chars(同一分类真相),而非硬编码 3, + // 这样即便 classify_sendinput_char 的规则合法变更,本用例仍表达「实际==期望→Inserted」。 let text = "a\r\nb"; - let status = map_sendinput_type_result(text, Ok(3)); + let expected = crate::unicode_keystroke::expected_sendinput_typed_chars(text); + let status = map_sendinput_type_result(text, Ok(expected)); assert_eq!(status, InsertStatus::Inserted); - assert_ne!(text.chars().count(), 3); + // `\r` 被跳过,故期望值必然小于原始 char 数——这正是三处规则若漂移就会出错的点。 + assert_ne!(text.chars().count(), expected); } #[test] #[cfg(target_os = "windows")] fn crlf_sendinput_partial_mismatch_falls_back_to_clipboard() { + // 实际发出的 typed char 数比期望多(把 `\r` 也算进去了)→ 视为部分失败 → 回落剪贴板。 let text = "a\r\nb"; - let status = map_sendinput_type_result(text, Ok(text.chars().count())); + let expected = crate::unicode_keystroke::expected_sendinput_typed_chars(text); + let mismatched = text.chars().count(); + assert_ne!(mismatched, expected); + let status = map_sendinput_type_result(text, Ok(mismatched)); assert_eq!(status, InsertStatus::CopiedFallback); } diff --git a/openless-all/app/src-tauri/src/lib.rs b/openless-all/app/src-tauri/src/lib.rs index 369ee0c6..252e2aeb 100644 --- a/openless-all/app/src-tauri/src/lib.rs +++ b/openless-all/app/src-tauri/src/lib.rs @@ -102,6 +102,11 @@ const OPENLESS_BUNDLE_ID: &str = "com.openless.app"; /// 第一次 show 时把 QA 浮窗摆到屏幕底部居中;之后的 show 不再 reposition, /// 让用户拖动后的位置在 hide → show 之间得以保持。详见 issue #118 v2。 static QA_WINDOW_POSITIONED: AtomicBool = AtomicBool::new(false); +/// 聊天面板退场动画的世代计数:hide 先发 `chat-panel:closing` 让前端播 220ms +/// 退场动画、240ms 后才真正 hide;期间再次 show 会推进世代,作废挂起的 hide。 +static QA_PANEL_EPOCH: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); +static LESS_COMPUTER_PANEL_EPOCH: std::sync::atomic::AtomicU64 = + std::sync::atomic::AtomicU64::new(0); #[cfg(not(mobile))] static TRAY_MICROPHONE_WATCHER_STOPPING: AtomicBool = AtomicBool::new(false); #[cfg(not(mobile))] @@ -166,6 +171,7 @@ macro_rules! app_invoke_handler_desktop { commands::list_history, commands::delete_history_entry, commands::clear_history, + commands::get_activity_stats, commands::read_audio_recording, commands::retranscribe_recording, commands::marketplace_list, @@ -229,9 +235,12 @@ macro_rules! app_invoke_handler_desktop { commands::set_switch_style_hotkey, commands::set_open_app_hotkey, commands::qa_window_dismiss, - commands::qa_window_pin, + commands::qa_toggle_recording, + commands::qa_submit_text, commands::less_computer_window_dismiss, - commands::less_computer_window_resize, + commands::chat_panel_focus_keyboard, + commands::less_computer_submit_text, + commands::less_computer_sync, commands::less_computer_approve, commands::validate_combo_hotkey, commands::set_combo_hotkey, @@ -331,6 +340,7 @@ macro_rules! app_invoke_handler_mobile { $crate::commands::list_history, $crate::commands::delete_history_entry, $crate::commands::clear_history, + $crate::commands::get_activity_stats, $crate::commands::read_audio_recording, $crate::commands::retranscribe_recording, $crate::commands::marketplace_list, @@ -357,7 +367,6 @@ macro_rules! app_invoke_handler_mobile { $crate::commands::stop_dictation, $crate::commands::cancel_dictation, $crate::commands::qa_window_dismiss, - $crate::commands::qa_window_pin, $crate::commands::qa_toggle_recording, $crate::commands::qa_submit_text, $crate::commands::repolish, @@ -509,6 +518,11 @@ fn run_desktop() { Err(e) => log::warn!("[capsule] to_panel failed: {e:?}"), } } + // 纯光效舞台没有任何可点元素(✕/✓ 按钮已移除),而窗口放大到 460×180 + // 盖住屏幕底部中央 —— 必须鼠标穿透,否则会挡住底下应用的点击。 + if let Err(e) = capsule.set_ignore_cursor_events(true) { + log::warn!("[capsule] set_ignore_cursor_events failed: {e}"); + } if let Err(e) = position_capsule_bottom_center(&capsule, false) { log::warn!("[capsule] position failed: {e}"); } @@ -1508,10 +1522,10 @@ fn wait_for_app_activation(app: &AppHandle) { #[cfg(not(target_os = "macos"))] fn wait_for_app_activation(_app: &AppHandle) {} -/// QA 浮窗的目标尺寸(issue #118)。胶囊默认 220×96 + Dock 80pt + 8pt gap, -/// 算下来 QA 窗口顶部坐标 = h - 80 - 96 - 8 - 280。 -const QA_WINDOW_WIDTH: f64 = 380.0; -const QA_WINDOW_HEIGHT: f64 = 440.0; +/// QA 浮窗的目标尺寸(issue #118;统一聊天面板后与 Less Computer 同尺寸)。 +/// 窗口固定大小,内容在面板内部的 MessageScroller 里滚动。 +const QA_WINDOW_WIDTH: f64 = 420.0; +const QA_WINDOW_HEIGHT: f64 = 540.0; /// 胶囊与 QA 窗口的间距,与设计稿一致。 const QA_WINDOW_GAP_TO_CAPSULE: f64 = 8.0; /// 给 macOS Dock 留的下边距(与 capsule 同源)。 @@ -2052,6 +2066,9 @@ pub(crate) fn show_qa_window(app: &AppHandle, content_kind if let Err(e) = window.show() { log::warn!("[qa] show failed: {e}"); } + // 作废挂起的退场 hide(快速关-开),并让前端重放入场动画。 + QA_PANEL_EPOCH.fetch_add(1, Ordering::SeqCst); + let _ = app.emit_to("qa", "chat-panel:shown", serde_json::json!({})); let _ = app.emit_to( "qa", "qa:state", @@ -2059,7 +2076,35 @@ pub(crate) fn show_qa_window(app: &AppHandle, content_kind ); } -/// QA 浮窗的拖动修复(macOS)。 +/// 聊天面板浮窗(qa / less-computer)转「非激活 NSPanel」(macOS,胶囊同手法)。 +/// +/// 目的:让面板可以**在 OpenLess 不激活的情况下成为 key window**(Spotlight 同款)。 +/// 之前打字焦点走 `window.set_focus()`,它会激活整个 app —— 主窗口(设置页)被 +/// 一起带到前台,且 frontmost 变成 OpenLess 自己、AX 再也读不到原 app 的选区。 +/// 转成 NonactivatingPanel 后:点输入框 / makeKeyWindow 只给面板键盘焦点, +/// app 保持后台、主窗口不动、frontmost 始终是用户原 app。 +/// 顺带 CanJoinAllSpaces + FullScreenAuxiliary:划词常发生在全屏 app 里,面板要能叠上去。 +#[cfg(target_os = "macos")] +fn make_chat_window_panel_macos(window: &tauri::WebviewWindow, tag: &str) { + use tauri_nspanel::cocoa::appkit::NSWindowCollectionBehavior; + use tauri_nspanel::WebviewWindowExt; + match window.to_panel() { + Ok(panel) => { + const NS_NONACTIVATING_PANEL_MASK: i32 = 1 << 7; + panel.set_style_mask(NS_NONACTIVATING_PANEL_MASK); + // 浮层级别(NSFloatingWindowLevel):盖普通窗口,不盖菜单栏/胶囊(25)。 + panel.set_level(3); + panel.set_collection_behaviour( + NSWindowCollectionBehavior::NSWindowCollectionBehaviorFullScreenAuxiliary + | NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces, + ); + log::info!("[{tag}] converted to nonactivating NSPanel"); + } + Err(e) => log::warn!("[{tag}] to_panel failed: {e:?}"), + } +} + +/// 聊天面板浮窗(qa / less-computer)的拖动修复(macOS)。 /// /// 配置 `focus: false` 让 Tauri 把窗口创建为 nonactivating panel 风格(避免抢前台 app /// 焦点)。代价是 AppKit 的 `performWindowDragWithEvent:` 在 nonactivating 窗口上无效, @@ -2068,23 +2113,35 @@ pub(crate) fn show_qa_window(app: &AppHandle, content_kind /// 解法是把 NSWindow 的 `movableByWindowBackground` 打开——这条路径不依赖窗口是否成为 /// key window,跟 Spotlight / Raycast 的浮窗是同一手法。设一次就够,整个生命周期保持。 #[cfg(target_os = "macos")] -fn make_qa_window_draggable_macos(window: &tauri::WebviewWindow) { +fn make_chat_window_draggable_macos(window: &tauri::WebviewWindow, tag: &str) { use objc2::msg_send; use objc2::runtime::{AnyObject, Bool}; let Ok(handle) = window.ns_window() else { - log::warn!("[qa] ns_window unavailable; drag fix skipped"); + log::warn!("[{tag}] ns_window unavailable; drag fix skipped"); return; }; let ns_window = handle as *mut AnyObject; if ns_window.is_null() { - log::warn!("[qa] ns_window null; drag fix skipped"); + log::warn!("[{tag}] ns_window null; drag fix skipped"); return; } - unsafe { - let _: () = msg_send![ns_window, setMovableByWindowBackground: Bool::YES]; - let _: () = msg_send![ns_window, setMovable: Bool::YES]; + // 异常兜底:AppKit 在 drag margins 状态不一致时会从 setMovable 内部 raise + // NSException;不捕获的话异常穿过 Rust 边界直接 abort 整个 app。捕获后降级为 + // 日志(拖动失效但 app 活着),日志会指认具体异常便于追根因。 + // SAFETY: 闭包内只有两个无返回值的 ObjC 消息发送,没有需要运行析构的 Rust 值, + // 异常展开跳过闭包帧不会破坏内存安全。 + let result = unsafe { + objc2::exception::catch(std::panic::AssertUnwindSafe(|| { + let _: () = msg_send![ns_window, setMovableByWindowBackground: Bool::YES]; + let _: () = msg_send![ns_window, setMovable: Bool::YES]; + })) + }; + match result { + Ok(()) => log::info!("[{tag}] NSWindow movableByWindowBackground=YES"), + Err(e) => { + log::error!("[{tag}] drag setup raised ObjC exception (caught, drag disabled): {e:?}") + } } - log::info!("[qa] NSWindow movableByWindowBackground=YES"); } /// 懒创建 QA 浮窗:原来在 tauri.conf.json eager 创建(常驻一个 WebKit 进程)。改为首次 @@ -2099,7 +2156,7 @@ fn ensure_qa_window(app: &AppHandle) -> Option(app: &AppHandle) -> Option { + // ⚠️ NSWindow 操作必须在主线程(macOS 26 硬约束)。ensure_qa_window 常从 + // tokio worker 进来(热键桥接循环),在 worker 线程直接 setMovable 会让 + // AppKit 从 _postWindowNeedsToResetDragMargins 抛 NSException 直接崩掉 + // 整个 app(2026-07-03 四次同栈崩溃的根因)——dispatch 回主线程执行。 #[cfg(target_os = "macos")] - make_qa_window_draggable_macos(&w); + { + let w_clone = w.clone(); + let _ = app.run_on_main_thread(move || { + make_chat_window_panel_macos(&w_clone, "qa"); + make_chat_window_draggable_macos(&w_clone, "qa"); + }); + } Some(w) } Err(e) => { @@ -2142,7 +2209,7 @@ fn ensure_less_computer_window(app: &AppHandle) -> Option< WebviewUrl::App("index.html?window=less-computer".into()), ) .title("OpenLess Less Computer") - .inner_size(400.0, 200.0) + .inner_size(LESS_COMPUTER_WINDOW_WIDTH, LESS_COMPUTER_WINDOW_HEIGHT) .decorations(false) .transparent(true) .shadow(true) @@ -2154,7 +2221,16 @@ fn ensure_less_computer_window(app: &AppHandle) -> Option< .accept_first_mouse(true) .build() { - Ok(w) => Some(w), + Ok(w) => { + // 与 QA 同款:转非激活 NSPanel(打字不激活 app)+ 拖动修复 + // (movableByWindowBackground;必须主线程,见 make_chat_window_draggable_macos)。 + let w_clone = w.clone(); + let _ = app.run_on_main_thread(move || { + make_chat_window_panel_macos(&w_clone, "less-computer"); + make_chat_window_draggable_macos(&w_clone, "less-computer"); + }); + Some(w) + } Err(e) => { log::warn!("[less-computer] lazy window create failed: {e}"); None @@ -2197,6 +2273,32 @@ fn ensure_less_computer_glow_window( } } +/// 带退场动画地隐藏聊天面板:先发 `chat-panel:closing` 让前端播退场动画, +/// 240ms 后真正 hide。期间再次 show(epoch 前进)则作废本次挂起的 hide —— +/// 避免「关的动画还没放完用户又唤起 → 窗口被旧定时器藏掉」。 +#[cfg(not(any(target_os = "android", target_os = "ios")))] +fn hide_chat_window_animated( + app: &AppHandle, + label: &str, + epoch: &'static std::sync::atomic::AtomicU64, +) { + let Some(window) = app.get_webview_window(label) else { + return; + }; + if !window.is_visible().unwrap_or(false) { + let _ = window.hide(); + return; + } + let token = epoch.fetch_add(1, Ordering::SeqCst) + 1; + let _ = app.emit_to(label, "chat-panel:closing", serde_json::json!({})); + tauri::async_runtime::spawn(async move { + tokio::time::sleep(std::time::Duration::from_millis(240)).await; + if epoch.load(Ordering::SeqCst) == token { + let _ = window.hide(); + } + }); +} + /// 隐藏 QA 窗口。供 commands::qa_window_dismiss / coordinator session 收尾共用。 pub(crate) fn hide_qa_window(app: &AppHandle) { #[cfg(target_os = "android")] @@ -2205,9 +2307,8 @@ pub(crate) fn hide_qa_window(app: &AppHandle) { return; } - if let Some(window) = app.get_webview_window("qa") { - let _ = window.hide(); - } + #[cfg(not(any(target_os = "android", target_os = "ios")))] + hide_chat_window_animated(app, "qa", &QA_PANEL_EPOCH); } // ───────────────────────── Less Computer 浮窗 ───────────────────────── @@ -2217,21 +2318,17 @@ pub(crate) fn hide_qa_window(app: &AppHandle) { // 不注册热键、前端 detectOS 不渲染入口),所以这些窗口操作全部 `#[cfg(macos)]`, // 其它平台是 no-op,避免在非目标平台动 NSWindow / 弹一个空浮窗。 -/// Less Computer 浮窗宽度(高度由前端按内容自适应,经 `less_computer_window_resize` -/// 回传,Rust 端按 bottom-anchored 重新摆放,让内容增长向上撑开)。 +/// Less Computer 浮窗尺寸:与 QA 同款「统一聊天面板」固定大小 —— 窗口出现即 +/// 定死,内容只在面板内部的 MessageScroller 里滚动,不再按内容自适应缩放窗口。 #[cfg(target_os = "macos")] -const LESS_COMPUTER_WINDOW_WIDTH: f64 = 400.0; +const LESS_COMPUTER_WINDOW_WIDTH: f64 = 420.0; #[cfg(target_os = "macos")] -const LESS_COMPUTER_WINDOW_MIN_HEIGHT: f64 = 120.0; -#[cfg(target_os = "macos")] -const LESS_COMPUTER_WINDOW_MAX_HEIGHT: f64 = 520.0; +const LESS_COMPUTER_WINDOW_HEIGHT: f64 = 540.0; -/// 把 Less Computer 浮窗按给定高度(clamp 到 [min,max])摆到屏幕底部居中、 -/// 紧贴胶囊上方。bottom 对齐胶囊顶部,所以高度变化时窗口向上生长。 +/// 把 Less Computer 浮窗(固定尺寸)摆到屏幕底部居中、紧贴胶囊上方。 #[cfg(target_os = "macos")] fn position_less_computer_window( window: &tauri::WebviewWindow, - height: f64, ) -> tauri::Result<()> { let monitor = match window.current_monitor()? { Some(m) => m, @@ -2241,18 +2338,17 @@ fn position_less_computer_window( let size = monitor.size(); let pos = monitor.position(); let frame = logical_monitor_frame(pos.x, pos.y, size.width, size.height, scale); - let height = height.clamp( - LESS_COMPUTER_WINDOW_MIN_HEIGHT, - LESS_COMPUTER_WINDOW_MAX_HEIGHT, - ); let capsule_height = capsule_height_for_qa(); let (x, y) = bottom_center_position( frame, LESS_COMPUTER_WINDOW_WIDTH, - height, + LESS_COMPUTER_WINDOW_HEIGHT, DOCK_BOTTOM_PADDING_FOR_QA + capsule_height + QA_WINDOW_GAP_TO_CAPSULE, ); - window.set_size(tauri::LogicalSize::new(LESS_COMPUTER_WINDOW_WIDTH, height))?; + window.set_size(tauri::LogicalSize::new( + LESS_COMPUTER_WINDOW_WIDTH, + LESS_COMPUTER_WINDOW_HEIGHT, + ))?; window.set_position(LogicalPosition::new(x, y))?; Ok(()) } @@ -2264,7 +2360,7 @@ pub(crate) fn show_less_computer_window(app: &AppHandle) { log::info!("[less-computer] show 跳过:窗口不存在"); return; }; - if let Err(e) = position_less_computer_window(&window, LESS_COMPUTER_WINDOW_MIN_HEIGHT) { + if let Err(e) = position_less_computer_window(&window) { log::warn!("[less-computer] position before show failed: {e}"); } let window_clone = window.clone(); @@ -2289,6 +2385,9 @@ pub(crate) fn show_less_computer_window(app: &AppHandle) { } } }); + // 作废挂起的退场 hide(快速关-开),并让前端重放入场动画。 + LESS_COMPUTER_PANEL_EPOCH.fetch_add(1, Ordering::SeqCst); + let _ = app.emit_to("less-computer", "chat-panel:shown", serde_json::json!({})); } #[cfg(not(target_os = "macos"))] @@ -2297,9 +2396,7 @@ pub(crate) fn show_less_computer_window(_app: &AppHandle) /// 隐藏 Less Computer 浮窗。供 dismiss 命令 / session 收尾共用。 #[cfg(target_os = "macos")] pub(crate) fn hide_less_computer_window(app: &AppHandle) { - if let Some(window) = app.get_webview_window("less-computer") { - let _ = window.hide(); - } + hide_chat_window_animated(app, "less-computer", &LESS_COMPUTER_PANEL_EPOCH); } #[cfg(not(target_os = "macos"))] @@ -2379,50 +2476,6 @@ pub(crate) fn hide_less_computer_glow(app: &AppHandle) { #[cfg(not(target_os = "macos"))] pub(crate) fn hide_less_computer_glow(_app: &AppHandle) {} -/// 前端按内容测高后回传。以「当前窗口底边」为锚向上生长——只改高度、保住用户拖动后的位置, -/// 不再重新居中(否则一改内容就把拖走的框拉回屏幕底部中间)。`macos` 专用。 -#[cfg(target_os = "macos")] -pub(crate) fn resize_less_computer_window(app: &AppHandle, height: f64) { - let Some(window) = app.get_webview_window("less-computer") else { - return; - }; - let height = height.clamp( - LESS_COMPUTER_WINDOW_MIN_HEIGHT, - LESS_COMPUTER_WINDOW_MAX_HEIGHT, - ); - let scale = window.scale_factor().unwrap_or(1.0); - match (window.outer_position(), window.outer_size()) { - (Ok(pos), Ok(size)) => { - let x = pos.x as f64 / scale; - let cur_top = pos.y as f64 / scale; - let cur_h = size.height as f64 / scale; - let bottom = cur_top + cur_h; - let monitor_top = window - .current_monitor() - .ok() - .flatten() - .map(|m| { - let p = m.position(); - let s = m.size(); - logical_monitor_frame(p.x, p.y, s.width, s.height, m.scale_factor()).y - }) - .unwrap_or(f64::NEG_INFINITY); - let new_y = (bottom - height).max(monitor_top); - let _ = window.set_size(tauri::LogicalSize::new(LESS_COMPUTER_WINDOW_WIDTH, height)); - let _ = window.set_position(tauri::LogicalPosition::new(x, new_y)); - } - // 拿不到当前位置(极少见)→ 退回首屏居中摆放。 - _ => { - if let Err(e) = position_less_computer_window(&window, height) { - log::warn!("[less-computer] resize fallback failed: {e}"); - } - } - } -} - -#[cfg(not(target_os = "macos"))] -pub(crate) fn resize_less_computer_window(_app: &AppHandle, _height: f64) {} - /// 抓完选区后把焦点重新交回 QA 浮窗(Windows focus-dance 下半场)。begin_qa_session /// 在 capture_selection 跑完时调;非 Windows 平台是 no-op。issue #466。 #[cfg(target_os = "windows")] @@ -2625,42 +2678,21 @@ struct CapsuleWindowBounds { } fn capsule_window_bounds(translation_active: bool) -> CapsuleWindowBounds { - #[cfg(target_os = "windows")] - { - const WINDOWS_CAPSULE_PILL_WIDTH: f64 = 196.0; - const WINDOWS_CAPSULE_SIDE_INSET: f64 = 12.0; - CapsuleWindowBounds { - // Keep the existing Windows hitbox width, but express it as - // pill width (196) + symmetric 12px side insets for shadow room. - width: WINDOWS_CAPSULE_PILL_WIDTH + WINDOWS_CAPSULE_SIDE_INSET * 2.0, - height: if translation_active { 118.0 } else { 84.0 }, - bottom_inset: 12.0, - } - } - - #[cfg(not(target_os = "windows"))] - { - // macOS / Linux:固定 220×110,与 1.2.11 行为一致 — 录音 / 翻译徽章 - // 共用同一个窗口尺寸,避免按 Shift 后窗口高度变化导致胶囊整体下移。 - let _ = translation_active; - CapsuleWindowBounds { - width: 220.0, - height: 110.0, - bottom_inset: 0.0, - } + // 纯光效语音舞台(siri-glsl 原始比例):光条横贯 ~420px + 发光扩散余量。 + // 与前端 src/lib/capsuleLayout.ts 的 VOICE_ORB_STAGE_* 保持一致。 + let _ = translation_active; + CapsuleWindowBounds { + width: 460.0, + height: 180.0, + bottom_inset: 0.0, } } fn capsule_visual_height(_translation_active: bool) -> f64 { - #[cfg(target_os = "windows")] - { - 52.0 - } - - #[cfg(not(target_os = "windows"))] - { - 96.0 - } + // 故意小于窗口高(180):窗口整体下沉 40px,让光条视觉中心落回原胶囊的高度 + // 附近(bottom_visual_position 以此值算窗口顶 y = 屏底 - 80 - visual_height)。 + // 底部 40px 只是发光余量,窗口透明 + 鼠标穿透,压到 Dock 上方无碍。 + 140.0 } fn capsule_height_for_qa() -> f64 { @@ -2729,53 +2761,31 @@ mod tests { } #[test] - fn capsule_window_bounds_leave_room_for_windows_shadow() { + fn capsule_window_bounds_match_voice_orb_stage() { let bounds = capsule_window_bounds(false); - #[cfg(target_os = "windows")] assert_eq!( (bounds.width, bounds.height, bounds.bottom_inset), - (220.0, 84.0, 12.0) - ); - - #[cfg(not(target_os = "windows"))] - assert_eq!( - (bounds.width, bounds.height, bounds.bottom_inset), - (220.0, 110.0, 0.0) + (460.0, 180.0, 0.0) ); } #[test] - fn capsule_window_bounds_expand_for_translation_badge() { + fn capsule_window_bounds_stay_fixed_for_translation_badge() { let bounds = capsule_window_bounds(true); - #[cfg(target_os = "windows")] assert_eq!( (bounds.width, bounds.height, bounds.bottom_inset), - (220.0, 118.0, 12.0) - ); - - #[cfg(not(target_os = "windows"))] - assert_eq!( - (bounds.width, bounds.height, bounds.bottom_inset), - (220.0, 110.0, 0.0) + (460.0, 180.0, 0.0) ); } #[test] - fn capsule_visual_height_matches_frontend_pill() { - #[cfg(target_os = "windows")] - assert_eq!(capsule_visual_height(true), 52.0); - - #[cfg(not(target_os = "windows"))] - assert_eq!(capsule_visual_height(true), 96.0); + fn capsule_visual_height_sinks_stage_toward_dock() { + assert_eq!(capsule_visual_height(true), 140.0); } #[test] fn qa_anchor_uses_normal_capsule_height_source() { - #[cfg(target_os = "windows")] - assert_eq!(capsule_height_for_qa(), 52.0); - - #[cfg(not(target_os = "windows"))] - assert_eq!(capsule_height_for_qa(), 96.0); + assert_eq!(capsule_height_for_qa(), 140.0); } #[test] diff --git a/openless-all/app/src-tauri/src/persistence/activity.rs b/openless-all/app/src-tauri/src/persistence/activity.rs new file mode 100644 index 00000000..35c9aa85 --- /dev/null +++ b/openless-all/app/src-tauri/src/persistence/activity.rs @@ -0,0 +1,69 @@ +//! 每日听写活动计数(`date(YYYY-MM-DD) → count`),供概览页年度活动热力图使用。 +//! +//! 与历史内容存储完全解耦:不含任何转写文本,也不受历史保留策略 / 条数上限影响 +//! —— 清理历史不会抹掉活动足迹,热力图因此能覆盖全年而无需放开历史上限 +//! (取代 PR #716 里「为热力图把历史改为无限保留」的方案)。 +//! 写入时按保留窗口(两年)裁剪最早的日期,文件天然有界。 + +use std::collections::BTreeMap; +use std::path::PathBuf; + +use anyhow::Result; +use parking_lot::Mutex; + +use super::{atomic_write, data_dir, ensure_dir, read_or_default}; + +const ACTIVITY_FILE: &str = "activity.json"; +/// 保留最近两年(含闰年余量)的日计数,超窗的最早日期在写入时移除。 +const ACTIVITY_RETENTION_DAYS: usize = 731; + +pub struct ActivityStore { + path: PathBuf, + cache: Mutex>, +} + +impl ActivityStore { + pub fn load() -> Result { + let dir = data_dir()?; + ensure_dir(&dir)?; + let path = dir.join(ACTIVITY_FILE); + let cache: BTreeMap = read_or_default(&path)?; + Ok(Self { + path, + cache: Mutex::new(cache), + }) + } + + /// load 失败时的内存降级:计数仍可累加(本次运行内有效),写盘静默失败。 + /// 活动计数是非关键路径,不因它阻断听写初始化。 + pub fn new_fallback() -> Self { + Self { + path: PathBuf::new(), + cache: Mutex::new(BTreeMap::new()), + } + } + + /// 记录一次活动。`date` 为本地日期 `YYYY-MM-DD`(BTreeMap 按字典序即按日期序)。 + pub fn bump(&self, date: &str) -> Result<()> { + let mut cache = self.cache.lock(); + *cache.entry(date.to_string()).or_insert(0) += 1; + while cache.len() > ACTIVITY_RETENTION_DAYS { + let oldest = match cache.keys().next() { + Some(key) => key.clone(), + None => break, + }; + cache.remove(&oldest); + } + let bytes = serde_json::to_vec_pretty(&*cache)?; + atomic_write(&self.path, &bytes) + } + + /// 全量快照(日期升序),前端聚合成热力图。 + pub fn snapshot(&self) -> Vec<(String, u32)> { + self.cache + .lock() + .iter() + .map(|(date, count)| (date.clone(), *count)) + .collect() + } +} diff --git a/openless-all/app/src-tauri/src/persistence/credentials.rs b/openless-all/app/src-tauri/src/persistence/credentials.rs index 6e160ec7..22dd7e27 100644 --- a/openless-all/app/src-tauri/src/persistence/credentials.rs +++ b/openless-all/app/src-tauri/src/persistence/credentials.rs @@ -451,13 +451,62 @@ fn read_chunk_manifest(json: &str) -> Option { } } +/// Windows Credential Manager (`CredReadW`) can transiently fail right after +/// login / under contention when we read the manifest entry plus every chunk +/// entry in quick succession. A single failed read makes the whole credential +/// set look empty → `load_keyring_credentials` returns `Err` → `load_credentials` +/// falls back to an empty default → Overview shows「火山引擎未配置」even though the +/// secrets are present (the next dictation re-reads and succeeds, which is why the +/// bug is *probabilistic* and the app "实际可以正常使用"). The more chunks a +/// credential set spans, the more reads per load, the higher the odds at least +/// one trips. Retry transient errors a few times with short backoff. +/// +/// macOS / Linux keep the original single-shot behavior on purpose: their read +/// errors are ACL denials that won't heal on retry, and the un-cached error path +/// already retries on the next call — adding sleeps there would only slow the +/// macOS first-launch Keychain authorization flow. +#[cfg(target_os = "windows")] +const KEYRING_READ_RETRY_ATTEMPTS: usize = 4; +#[cfg(target_os = "windows")] +const KEYRING_READ_RETRY_BACKOFF_MS: u64 = 60; + #[cfg(not(target_os = "android"))] fn get_keyring_password(account: &str) -> Result> { - match keyring_entry_for(account)?.get_password() { - Ok(value) => Ok(Some(value)), - Err(keyring::Error::NoEntry) => Ok(None), - Err(e) => { - Err(anyhow!(e)).with_context(|| format!("read system credential vault {account}")) + #[cfg(target_os = "windows")] + { + let mut attempt = 0usize; + loop { + match keyring_entry_for(account)?.get_password() { + Ok(value) => return Ok(Some(value)), + // NoEntry is a definitive "not stored" answer, never a transient + // failure — return immediately so genuinely-unconfigured providers + // don't pay the retry latency. + Err(keyring::Error::NoEntry) => return Ok(None), + Err(e) => { + attempt += 1; + if attempt >= KEYRING_READ_RETRY_ATTEMPTS { + return Err(anyhow!(e)) + .with_context(|| format!("read system credential vault {account}")); + } + log::warn!( + "[vault] transient credential read for {account} failed \ + (attempt {attempt}/{KEYRING_READ_RETRY_ATTEMPTS}): {e}; retrying" + ); + std::thread::sleep(std::time::Duration::from_millis( + KEYRING_READ_RETRY_BACKOFF_MS * attempt as u64, + )); + } + } + } + } + #[cfg(not(target_os = "windows"))] + { + match keyring_entry_for(account)?.get_password() { + Ok(value) => Ok(Some(value)), + Err(keyring::Error::NoEntry) => Ok(None), + Err(e) => { + Err(anyhow!(e)).with_context(|| format!("read system credential vault {account}")) + } } } } diff --git a/openless-all/app/src-tauri/src/persistence/mod.rs b/openless-all/app/src-tauri/src/persistence/mod.rs index 0e99f47a..51e0da59 100644 --- a/openless-all/app/src-tauri/src/persistence/mod.rs +++ b/openless-all/app/src-tauri/src/persistence/mod.rs @@ -24,6 +24,7 @@ use anyhow::{Context, Result}; use serde::Deserialize; use uuid::Uuid; +mod activity; mod correction; mod credentials; mod dictionary; @@ -32,6 +33,7 @@ mod paths; mod preferences; mod style_pack; +pub use activity::*; pub use correction::*; pub use credentials::*; pub use dictionary::*; diff --git a/openless-all/app/src-tauri/src/persistence/preferences.rs b/openless-all/app/src-tauri/src/persistence/preferences.rs index 1f3fbb4a..13a600d3 100644 --- a/openless-all/app/src-tauri/src/persistence/preferences.rs +++ b/openless-all/app/src-tauri/src/persistence/preferences.rs @@ -97,11 +97,25 @@ impl PreferencesStore { *guard = prefs; Ok(()) } + + pub fn set_preserving_current_style_preferences( + &self, + mut prefs: UserPreferences, + ) -> Result<()> { + let mut guard = self.state.lock(); + prefs.preserve_style_preferences_from(&guard); + let json = serde_json::to_vec_pretty(&prefs).context("encode prefs failed")?; + atomic_write(&self.path, &json)?; + *guard = prefs; + Ok(()) + } } #[cfg(test)] mod tests { - use super::read_preferences; + use super::{read_preferences, PreferencesStore}; + use crate::types::{builtin_style_pack_id, PolishMode, UserPreferences}; + use parking_lot::Mutex; use std::fs; use std::path::PathBuf; @@ -142,4 +156,42 @@ mod tests { let _ = fs::remove_dir_all(&tmp); } + + #[test] + fn set_preserving_current_style_preferences_keeps_store_style_fields() { + let tmp: PathBuf = + std::env::temp_dir().join(format!("openless-prefs-test-{}", uuid::Uuid::new_v4())); + fs::create_dir_all(&tmp).expect("create temp dir"); + let path = tmp.join("preferences.json"); + let current = UserPreferences { + default_mode: PolishMode::Light, + active_style_pack_id: "local.light-cleanup".to_string(), + ..UserPreferences::default() + }; + let store = PreferencesStore { + path, + state: Mutex::new(current), + }; + let incoming = UserPreferences { + default_mode: PolishMode::Formal, + active_style_pack_id: builtin_style_pack_id(PolishMode::Formal).to_string(), + microphone_device_name: "External Mic".to_string(), + ..UserPreferences::default() + }; + + store + .set_preserving_current_style_preferences(incoming) + .expect("save prefs"); + + let saved = store.get(); + assert_eq!(saved.default_mode, PolishMode::Light); + assert_eq!(saved.active_style_pack_id, "local.light-cleanup"); + assert_eq!(saved.microphone_device_name, "External Mic"); + let saved_on_disk = read_preferences(&store.path).expect("read saved prefs"); + assert_eq!(saved_on_disk.default_mode, PolishMode::Light); + assert_eq!(saved_on_disk.active_style_pack_id, "local.light-cleanup"); + assert_eq!(saved_on_disk.microphone_device_name, "External Mic"); + + let _ = fs::remove_dir_all(&tmp); + } } diff --git a/openless-all/app/src-tauri/src/side_aware_combo.rs b/openless-all/app/src-tauri/src/side_aware_combo.rs index 99de07f5..f6ed065a 100644 --- a/openless-all/app/src-tauri/src/side_aware_combo.rs +++ b/openless-all/app/src-tauri/src/side_aware_combo.rs @@ -15,7 +15,6 @@ use crate::types::ShortcutBinding; static ACTIVE_MONITOR: OnceLock>> = OnceLock::new(); struct ActiveSideCombo { - binding: ShortcutBinding, tx: Sender, state: Mutex, } @@ -111,12 +110,29 @@ impl SideAwareComboState { return None; } if pressed { - if self.modifiers_match() && !self.combo_active { - self.combo_active = true; - return Some(ComboHotkeyEvent::Pressed); + if self.modifiers_match() { + // `modifiers_match()` is the authoritative activation gate. If + // `combo_active` is still true here, the previous `Released` was + // dropped by the OS: we must NOT emit a second `Pressed` (that + // would break the pairing invariant), so treat the flag as + // already reflecting an active combo and swallow this edge. + if !self.combo_active { + self.combo_active = true; + return Some(ComboHotkeyEvent::Pressed); + } + return None; + } + // Modifiers no longer match — this is the single authoritative reset + // condition. If `combo_active` is stuck true (a modifier release was + // dropped so no `Released` was ever emitted), self-heal by emitting + // the terminal `Released` now so the recording latch cannot stick. + if self.combo_active { + self.combo_active = false; + return Some(ComboHotkeyEvent::Released); } return None; } + // Primary key up is the absolute termination signal for the combo. if self.combo_active { self.combo_active = false; return Some(ComboHotkeyEvent::Released); @@ -126,6 +142,8 @@ impl SideAwareComboState { fn on_modifier_release(&mut self, side: SideModifier) -> Option { self.set_side(side, false); + // Modifiers no longer matching is the authoritative reset condition: + // once the required side-modifier set is broken, the combo is over. if self.combo_active && !self.modifiers_match() { self.combo_active = false; return Some(ComboHotkeyEvent::Released); @@ -153,6 +171,20 @@ impl SideAwareComboMonitor { binding: ShortcutBinding, tx: Sender, ) -> Result { + // Linux has no side-aware platform dispatch (no CGEventTap / WH_KEYBOARD_LL + // equivalent wired here). Accepting the binding would leave the user with a + // "successfully configured" combo that silently never fires. Reject up front + // so the caller can surface an actionable error instead. + #[cfg(target_os = "linux")] + { + let _ = (&binding, &tx); + return Err(crate::combo_hotkey::ComboHotkeyError::UnsupportedModifier( + "侧向修饰键组合键在 Linux 暂不支持".into(), + )); + } + + #[cfg(not(target_os = "linux"))] + { if binding.modifiers.is_empty() || binding .modifiers @@ -170,11 +202,11 @@ impl SideAwareComboMonitor { let slot = ACTIVE_MONITOR.get_or_init(|| RwLock::new(None)); let mut guard = slot.write().expect("side combo monitor lock poisoned"); *guard = Some(ActiveSideCombo { - binding: binding.clone(), tx, state: Mutex::new(SideAwareComboState::new(binding)), }); Ok(Self) + } } } @@ -412,10 +444,28 @@ pub mod platform { const COMBINED_SESSION: c_int = 1; - pub fn dispatch_keycode(keycode: i64, flags_changed: bool, _flags: u64, pressed: bool) { + // NSEventModifierFlags device-independent class bits (also match the CGEventFlags + // masks used by hotkey.rs `handle_flags_changed`). One bit per modifier *class* — + // it does NOT encode which side (left/right) is held. + const FLAG_MASK_SHIFT: u64 = 1 << 17; // 0x0002_0000 + const FLAG_MASK_CONTROL: u64 = 1 << 18; // 0x0004_0000 + const FLAG_MASK_ALTERNATE: u64 = 1 << 19; // 0x0008_0000 (Option/Alt) + const FLAG_MASK_COMMAND: u64 = 1 << 20; // 0x0010_0000 + + pub fn dispatch_keycode(keycode: i64, flags_changed: bool, flags: u64, pressed: bool) { if flags_changed { if let Some(side) = side_from_keycode(keycode) { - handle_side_modifier(side, keycode_is_down(keycode)); + // The event-carried `flags` are race-free (they describe the state + // *at* this event); `CGEventSourceKeyState` is a separate query that + // may observe a later state. Prefer flags where they are decisive. + let down = match resolve_side_pressed_from_flags(keycode, flags) { + Some(down) => down, + // Class bit is set: some key in this modifier class is down, but a + // single flags bit cannot tell left from right. Fall back to + // `CGEventSourceKeyState` (Apple's standard way to resolve side). + None => keycode_is_down(keycode), + }; + handle_side_modifier(side, down); } return; } @@ -425,6 +475,38 @@ pub mod platform { } } + /// Modifier-class flag bit for a side-modifier keycode, or `None` if the keycode + /// is not a known side modifier. + fn class_mask_for_keycode(keycode: i64) -> Option { + match keycode { + 55 | 54 => Some(FLAG_MASK_COMMAND), // Cmd left / right + 59 | 62 => Some(FLAG_MASK_CONTROL), // Ctrl left / right + 58 | 61 => Some(FLAG_MASK_ALTERNATE), // Alt/Option left / right + 56 | 60 => Some(FLAG_MASK_SHIFT), // Shift left / right + _ => None, + } + } + + /// Pure race-free classifier for a FLAGS_CHANGED side-modifier event. + /// + /// Returns: + /// - `Some(false)` when the event's `flags` show the modifier *class* fully + /// released (class bit clear). This is the dangerous case to get wrong — + /// a stuck-down misread leaves the combo latched — and the flags bit makes + /// it unambiguous with no query race. Force the side up. + /// - `None` when the class bit is set: at least one side of this class is held, + /// but a single class bit cannot distinguish left vs right, so the caller + /// must query `CGEventSourceKeyState` to resolve the specific side. + /// - `None` for non-side keycodes (caller will have already filtered these). + pub(crate) fn resolve_side_pressed_from_flags(keycode: i64, flags: u64) -> Option { + let mask = class_mask_for_keycode(keycode)?; + if flags & mask == 0 { + Some(false) + } else { + None + } + } + fn keycode_is_down(keycode: i64) -> bool { unsafe { CGEventSourceKeyState(COMBINED_SESSION, keycode as u16) } } @@ -565,4 +647,163 @@ mod tests { assert_eq!(side_from_keycode(56), Some(SideModifier::ShiftLeft)); assert_eq!(side_from_keycode(60), Some(SideModifier::ShiftRight)); } + + // ---- Fix 1: event-pairing self-healing (Pressed always paired with Released) ---- + + fn cmd_left_d_state() -> SideAwareComboState { + SideAwareComboState::new(ShortcutBinding { + primary: "D".into(), + modifiers: vec!["cmd-left".into()], + }) + } + + #[test] + fn normal_press_then_release_is_paired() { + let mut state = cmd_left_d_state(); + state.set_side(SideModifier::CmdLeft, true); + assert_eq!(state.on_primary("D", true), Some(ComboHotkeyEvent::Pressed)); + // Primary key up terminates the combo with exactly one Released. + assert_eq!( + state.on_primary("D", false), + Some(ComboHotkeyEvent::Released) + ); + // No trailing events; a second key-up must not emit anything. + assert_eq!(state.on_primary("D", false), None); + assert!(!state.combo_active); + } + + #[test] + fn modifier_release_after_press_emits_paired_released() { + let mut state = cmd_left_d_state(); + state.set_side(SideModifier::CmdLeft, true); + assert_eq!(state.on_primary("D", true), Some(ComboHotkeyEvent::Pressed)); + // Modifier lifts while primary is still down -> combo terminates once. + assert_eq!( + state.on_modifier_release(SideModifier::CmdLeft), + Some(ComboHotkeyEvent::Released) + ); + assert!(!state.combo_active); + // A now-orphaned primary key-up must NOT emit a second Released. + assert_eq!(state.on_primary("D", false), None); + } + + #[test] + fn dropped_modifier_release_is_recovered_on_primary_up() { + // Simulate the OS dropping the modifier-up event: combo_active stays true + // and modifiers still "match" from the state's perspective. The primary + // key-up (absolute termination) must still emit the paired Released. + let mut state = cmd_left_d_state(); + state.set_side(SideModifier::CmdLeft, true); + assert_eq!(state.on_primary("D", true), Some(ComboHotkeyEvent::Pressed)); + // Modifier physically released but the release event never arrived, so the + // side flag is still set here. Primary up is the fallback terminator. + assert_eq!( + state.on_primary("D", false), + Some(ComboHotkeyEvent::Released) + ); + assert!(!state.combo_active); + } + + #[test] + fn stale_combo_active_reset_when_modifiers_stop_matching() { + // Reproduce a stuck latch: a prior Released was lost so combo_active is true, + // yet the required modifier is no longer held (modifiers_match() == false). + // The next primary-down must self-heal by emitting the terminal Released + // (NOT swallow it, and NOT emit a second Pressed) so recording can't stick. + let mut state = cmd_left_d_state(); + state.combo_active = true; // stale flag from a dropped Released + assert!(!state.modifiers_match()); // cmd-left is not held + let evt = state.on_primary("D", true); + assert_eq!(evt, Some(ComboHotkeyEvent::Released)); + assert!(!state.combo_active); + } + + #[test] + fn dropped_release_then_fresh_full_press_triggers_again() { + // End-to-end: after a lost Released, a subsequent *complete* press cycle + // must be able to fire a fresh Pressed. Guards against the combo becoming + // permanently unrepeatable (the historical #545/#468 stuck-latch class). + let mut state = cmd_left_d_state(); + state.combo_active = true; // leftover from a dropped Released + + // Releasing the required side-modifier breaks the match, so the stale latch + // self-heals by emitting the terminal Released here (pairing the Pressed whose + // Released was dropped). Either way combo_active must end up cleared. + assert_eq!( + state.on_modifier_release(SideModifier::CmdLeft), + Some(ComboHotkeyEvent::Released) + ); + assert!(!state.combo_active); + + // Fresh, clean press cycle now behaves normally. + state.set_side(SideModifier::CmdLeft, true); + assert_eq!(state.on_primary("D", true), Some(ComboHotkeyEvent::Pressed)); + assert_eq!( + state.on_primary("D", false), + Some(ComboHotkeyEvent::Released) + ); + } + + #[test] + fn stale_combo_active_still_matching_does_not_double_press() { + // If the flag is stale-true but modifiers currently match (edge case where a + // Released was lost but the modifier happens to be held again), a primary-down + // must NOT emit a second Pressed — that would break the pairing invariant. + let mut state = cmd_left_d_state(); + state.set_side(SideModifier::CmdLeft, true); + state.combo_active = true; // pretend previous Released was dropped + assert!(state.modifiers_match()); + assert_eq!(state.on_primary("D", true), None); + // The real terminator (primary up) still yields exactly one Released. + assert_eq!( + state.on_primary("D", false), + Some(ComboHotkeyEvent::Released) + ); + } + + // ---- Fix 2: macOS race-free FLAGS_CHANGED side classification ---- + + #[cfg(target_os = "macos")] + #[test] + fn flags_class_bit_clear_forces_side_released() { + use crate::side_aware_combo::platform::resolve_side_pressed_from_flags; + // Command class bit clear -> Cmd keys (55 left / 54 right) are decisively up. + assert_eq!(resolve_side_pressed_from_flags(55, 0), Some(false)); + assert_eq!(resolve_side_pressed_from_flags(54, 0), Some(false)); + // Shift bit set but Command bit clear -> a Cmd keycode is still decisively up. + let shift_only: u64 = 1 << 17; + assert_eq!(resolve_side_pressed_from_flags(55, shift_only), Some(false)); + } + + #[cfg(target_os = "macos")] + #[test] + fn flags_class_bit_set_defers_to_query() { + use crate::side_aware_combo::platform::resolve_side_pressed_from_flags; + // Command class bit set -> cannot tell left from right; defer (None) so the + // caller resolves the specific side via CGEventSourceKeyState. + let cmd_bit: u64 = 1 << 20; + assert_eq!(resolve_side_pressed_from_flags(55, cmd_bit), None); + assert_eq!(resolve_side_pressed_from_flags(54, cmd_bit), None); + // Shift keycodes with the Shift bit set likewise defer. + let shift_bit: u64 = 1 << 17; + assert_eq!(resolve_side_pressed_from_flags(56, shift_bit), None); + assert_eq!(resolve_side_pressed_from_flags(60, shift_bit), None); + } + + #[cfg(target_os = "macos")] + #[test] + fn flags_classifier_maps_each_modifier_class() { + use crate::side_aware_combo::platform::resolve_side_pressed_from_flags; + // Each side keycode keys off its own class bit; an unrelated set bit does not + // count as "held" for that class, so the side is forced released. + let ctrl_bit: u64 = 1 << 18; + let alt_bit: u64 = 1 << 19; + // Ctrl (59/62) held -> defer; Alt (58/61) with only Ctrl set -> forced up. + assert_eq!(resolve_side_pressed_from_flags(59, ctrl_bit), None); + assert_eq!(resolve_side_pressed_from_flags(58, ctrl_bit), Some(false)); + assert_eq!(resolve_side_pressed_from_flags(58, alt_bit), None); + // Non-side keycode (e.g. 'D' == 2) is never classified here. + assert_eq!(resolve_side_pressed_from_flags(2, alt_bit), None); + assert_eq!(resolve_side_pressed_from_flags(2, 0), None); + } } diff --git a/openless-all/app/src-tauri/src/types.rs b/openless-all/app/src-tauri/src/types.rs index 0de6b479..cd0a69ad 100644 --- a/openless-all/app/src-tauri/src/types.rs +++ b/openless-all/app/src-tauri/src/types.rs @@ -127,6 +127,14 @@ pub enum InsertStatus { Failed, } +/// 概览页年度活动热力图的单日计数(date = 本地日期 YYYY-MM-DD)。 +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActivityDay { + pub date: String, + pub count: u32, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct DictationSession { @@ -819,6 +827,10 @@ pub struct UserPreferences { /// 默认 true(更接近用户习惯)。 #[serde(default = "default_true")] pub streaming_insert_save_clipboard: bool, + /// 概览页是否显示「年度活动」热力图卡。默认 true;关闭只隐藏卡片, + /// 活动计数照常记录(persistence/activity.rs),再打开时全年数据仍在。 + #[serde(default = "default_true")] + pub show_overview_activity_heatmap: bool, /// 主窗口启动 + 后台每 60 分钟自动检查更新。默认 true。 /// Android 开启后自动检查并下载,校验后打开系统安装器;桌面仅自动检查 + 用户确认安装。 /// 关闭后仅 Settings 手动「检查更新」按钮可用。 @@ -867,6 +879,16 @@ pub struct UserPreferences { pub android_overlay_size_dp: u32, } +impl UserPreferences { + pub(crate) fn preserve_style_preferences_from(&mut self, current: &Self) { + self.default_mode = current.default_mode; + self.enabled_modes = current.enabled_modes.clone(); + self.active_style_pack_id = current.active_style_pack_id.clone(); + self.style_system_prompts = current.style_system_prompts.clone(); + self.custom_style_prompts = current.custom_style_prompts.clone(); + } +} + fn default_local_asr_model() -> String { "qwen3-asr-0.6b".into() } @@ -1039,6 +1061,8 @@ struct UserPreferencesWire { #[serde(default = "default_true")] streaming_insert_save_clipboard: bool, #[serde(default = "default_true")] + show_overview_activity_heatmap: bool, + #[serde(default = "default_true")] auto_update_check: bool, #[serde(default)] history_max_entries: Option, @@ -1133,6 +1157,7 @@ impl Default for UserPreferencesWire { streaming_insert: prefs.streaming_insert, streaming_insert_default_migrated: prefs.streaming_insert_default_migrated, streaming_insert_save_clipboard: prefs.streaming_insert_save_clipboard, + show_overview_activity_heatmap: prefs.show_overview_activity_heatmap, auto_update_check: prefs.auto_update_check, history_max_entries: prefs.history_max_entries, record_audio_for_debug: prefs.record_audio_for_debug, @@ -1251,6 +1276,7 @@ impl<'de> Deserialize<'de> for UserPreferences { streaming_insert, streaming_insert_default_migrated: true, streaming_insert_save_clipboard: wire.streaming_insert_save_clipboard, + show_overview_activity_heatmap: wire.show_overview_activity_heatmap, auto_update_check: wire.auto_update_check, history_max_entries: wire.history_max_entries, record_audio_for_debug: wire.record_audio_for_debug, @@ -1991,6 +2017,7 @@ impl Default for UserPreferences { streaming_insert: true, streaming_insert_default_migrated: true, streaming_insert_save_clipboard: true, + show_overview_activity_heatmap: true, auto_update_check: true, history_max_entries: None, record_audio_for_debug: false, @@ -2658,6 +2685,12 @@ pub struct CapsulePayload { /// 从 "thinking" 换成 "using"——告诉用户 Agent 正在操作电脑而非单纯思考。 #[serde(default)] pub operating: bool, + /// 预备态:胶囊已经"乐观显示"出来(按下热键即弹出并播入场动画),但麦克风还没 + /// 真正开始 capture 第一帧 PCM。为 true 时前端渲染"待命"光效(柔和呼吸、不接真实 + /// 电平),并暗示用户先别急着开口;`level_handler` 首次触发(PCM 真的流入)后翻成 + /// false,光条"点亮"进入正式录音态。只对 Recording 状态有意义。详见胶囊出现时序改造。 + #[serde(default)] + pub warming: bool, } /// Snapshot of credentials read from vault — only what the UI needs to know diff --git a/openless-all/app/src-tauri/src/unicode_keystroke.rs b/openless-all/app/src-tauri/src/unicode_keystroke.rs index ff453400..ff4b1a15 100644 --- a/openless-all/app/src-tauri/src/unicode_keystroke.rs +++ b/openless-all/app/src-tauri/src/unicode_keystroke.rs @@ -355,35 +355,35 @@ mod windows_impl { let mut sent_in_chunk = 0usize; let mut chars = text.chars().peekable(); while let Some(ch) = chars.next() { - if ch == '\r' { - continue; - } - - if ch == '\n' { - if let Err(e) = send_newline(options.newline_mode) { - return Err(partial_or_original(typed_chars, e)); - } - typed_chars += 1; - sent_in_chunk += 1; - } else if ch == '\t' { - if let Err(e) = press_vk(VK_TAB) { - return Err(partial_or_original(typed_chars, e)); - } - typed_chars += 1; - sent_in_chunk += 1; - } else { - let mut buf = [0u16; 2]; - for unit in ch.encode_utf16(&mut buf) { - if let Err(e) = send_utf16_unit(*unit, false) { + // 分类与计数复用 `classify_sendinput_char` / `sendinput_char_is_typed`——与 + // `expected_sendinput_typed_chars` 同一真相,避免规则漂移导致成功的 SendInput + // 被误判为回落。 + match super::classify_sendinput_char(ch) { + super::SendInputCharKind::Skip => continue, + super::SendInputCharKind::Newline => { + if let Err(e) = send_newline(options.newline_mode) { return Err(partial_or_original(typed_chars, e)); } - if let Err(e) = send_utf16_unit(*unit, true) { + } + super::SendInputCharKind::Tab => { + if let Err(e) = press_vk(VK_TAB) { return Err(partial_or_original(typed_chars, e)); } } - typed_chars += 1; - sent_in_chunk += 1; + super::SendInputCharKind::Unicode => { + let mut buf = [0u16; 2]; + for unit in ch.encode_utf16(&mut buf) { + if let Err(e) = send_utf16_unit(*unit, false) { + return Err(partial_or_original(typed_chars, e)); + } + if let Err(e) = send_utf16_unit(*unit, true) { + return Err(partial_or_original(typed_chars, e)); + } + } + } } + typed_chars += 1; + sent_in_chunk += 1; if sent_in_chunk >= SENDINPUT_CHUNK_CHARS && chars.peek().is_some() { std::thread::sleep(SENDINPUT_CHUNK_DELAY); @@ -497,26 +497,41 @@ mod windows_impl { ) -> Result<(), TisError> { Ok(()) } +} - #[cfg(test)] - pub(super) fn classify_sendinput_char(ch: char) -> SendInputCharKind { - match ch { - '\r' => SendInputCharKind::Skip, - '\n' => SendInputCharKind::Newline, - '\t' => SendInputCharKind::Tab, - _ => SendInputCharKind::Unicode, - } - } +/// SendInput 单字符分类的唯一真相。`type_unicode_chunk_with_options` 的实际打字路径与 +/// `expected_sendinput_typed_chars`(用于校验实际发出的 typed char 数)都复用它,避免三处 +/// 独立的字符规则手工同步——一旦漏改,一次成功的 SendInput 会被 `map_sendinput_type_result` +/// 误判成 `CopiedFallback`,在已打字的基础上又把整段复制到剪贴板,用户 Ctrl+V 看到重复。 +#[cfg(target_os = "windows")] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum SendInputCharKind { + /// `\r`:跳过(CRLF 只产生一次换行),不计入 typed char。 + Skip, + /// `\n`:按换行发出(模式由 `WindowsSendInputOptions::newline_mode` 决定)。 + Newline, + /// `\t`:按 Tab 键发出。 + Tab, + /// 其余字符:按 UTF-16 Unicode 事件逐 code unit 发出。 + Unicode, +} - #[cfg(test)] - pub(super) enum SendInputCharKind { - Skip, - Newline, - Tab, - Unicode, +#[cfg(target_os = "windows")] +pub(crate) fn classify_sendinput_char(ch: char) -> SendInputCharKind { + match ch { + '\r' => SendInputCharKind::Skip, + '\n' => SendInputCharKind::Newline, + '\t' => SendInputCharKind::Tab, + _ => SendInputCharKind::Unicode, } } +/// 该字符是否计入「已发出的 typed char」。`Skip`(`\r`)不计入,其余都计入。 +#[cfg(target_os = "windows")] +pub(crate) fn sendinput_char_is_typed(kind: SendInputCharKind) -> bool { + !matches!(kind, SendInputCharKind::Skip) +} + // ═══════════════════════════════════════════════════════════════════════════ // Linux 实现(实验性) // ═══════════════════════════════════════════════════════════════════════════ @@ -591,6 +606,7 @@ mod tests { TypeError::EnigoText("fail".into()) } + #[cfg(target_os = "windows")] #[test] fn expected_sendinput_typed_chars_skips_carriage_return() { assert_eq!(super::expected_sendinput_typed_chars("a\r\nb"), 3); @@ -600,7 +616,10 @@ mod tests { #[cfg(target_os = "windows")] mod windows_sendinput_char_tests { - use super::super::windows_impl::{classify_sendinput_char, SendInputCharKind}; + use super::super::{ + classify_sendinput_char, expected_sendinput_typed_chars, sendinput_char_is_typed, + SendInputCharKind, + }; #[test] fn classify_skips_carriage_return() { @@ -626,13 +645,44 @@ mod tests { SendInputCharKind::Unicode )); } + + /// 只有 `Skip`(`\r`)不计入 typed char,其余三类都计入。这是 + /// `expected_sendinput_typed_chars` 与实际打字循环 `typed_chars += 1` 之间保持一致 + /// 的核心不变量。 + #[test] + fn only_carriage_return_is_not_counted() { + assert!(!sendinput_char_is_typed(SendInputCharKind::Skip)); + assert!(sendinput_char_is_typed(SendInputCharKind::Newline)); + assert!(sendinput_char_is_typed(SendInputCharKind::Tab)); + assert!(sendinput_char_is_typed(SendInputCharKind::Unicode)); + } + + /// 期望计数必须与「逐字符分类后计入的数量」逐字节一致——即 expected 复用了同一分类 + /// 真相。若二者用不同规则表达,成功的 SendInput 会被 `map_sendinput_type_result` + /// 误判为 `CopiedFallback`(重复粘贴)。 + #[test] + fn expected_count_matches_per_char_classification() { + for sample in ["a\r\nb", "hello", "\r\r\n", "行1\n\t行2", ""] { + let manual = sample + .chars() + .filter(|ch| sendinput_char_is_typed(classify_sendinput_char(*ch))) + .count(); + assert_eq!(expected_sendinput_typed_chars(sample), manual, "{sample:?}"); + } + } } } /// Windows SendInput 路径上 `type_unicode_chunk` 计入的 typed char 数。 /// `\r` 会被跳过(CRLF 只产生一次换行),因此不能与 `text.chars().count()` 直接比较。 +/// +/// 复用 `classify_sendinput_char` / `sendinput_char_is_typed`——与实际打字路径同一真相, +/// 保证 `map_sendinput_type_result` 的期望值与真正发出的 typed char 数永远一致。 +#[cfg(target_os = "windows")] pub fn expected_sendinput_typed_chars(text: &str) -> usize { - text.chars().filter(|ch| *ch != '\r').count() + text.chars() + .filter(|ch| sendinput_char_is_typed(classify_sendinput_char(*ch))) + .count() } // ═══════════════════════════════════════════════════════════════════════════ diff --git a/openless-all/app/src-tauri/src/windows_ime_profile.rs b/openless-all/app/src-tauri/src/windows_ime_profile.rs index e941e6b5..3313f0fb 100644 --- a/openless-all/app/src-tauri/src/windows_ime_profile.rs +++ b/openless-all/app/src-tauri/src/windows_ime_profile.rs @@ -154,20 +154,39 @@ pub fn is_openless_language_profile_enabled() -> WindowsImeProfileResult { )) } +/// TSF IME 未装(或注册损坏)时「键盘列表可见性」偏好的短路结果。 +/// +/// 返回 `Some(result)` 表示无需触碰注册表即可结束;`None` 表示已安装,需要走真正的 +/// `EnableLanguageProfile` 变更。抽成纯函数,使「未安装」这一分支能在任意平台上被测试 +/// 覆盖到(`apply_windows_openless_keyboard_list_pref` 依赖 Windows 注册表,macOS/CI +/// 无法命中其内部分支)。 +/// +/// 关键语义:TSF IME 未安装时,键盘列表里根本没有 OpenLess 条目—— +/// - `desired == false`(不显示)是天然已满足的空操作; +/// - `desired == true`(显示)也只能是 no-op(没东西可启用)。 +/// +/// 两支都必须是 `Ok(())`。此前「不显示 + 未安装」错误地返回 `Err`,经 settings.rs 的 +/// `apply_keyboard_list(&prefs)?` 传播,导致整个设置保存事务回滚(用户勾「仅 SendInput +/// 插入」+「不在键盘列表显示」且未装 TSF IME 时,之后任何设置都存不进)。 +fn keyboard_list_pref_short_circuit( + install_state: WindowsImeInstallState, + _desired: bool, +) -> Option> { + if install_state == WindowsImeInstallState::Installed { + None + } else { + Some(Ok(())) + } +} + /// 将「SendInput + 键盘列表可见性」偏好同步到当前用户的 TSF 语言配置文件。 pub fn apply_windows_openless_keyboard_list_pref(prefs: &UserPreferences) -> Result<(), String> { let desired = desired_openless_language_profile_enabled(prefs); #[cfg(target_os = "windows")] { let status = get_windows_ime_status(); - if status.state != WindowsImeInstallState::Installed { - if desired { - return Ok(()); - } - return Err( - "OpenLess TSF IME is not installed; cannot hide it from the keyboard list" - .to_string(), - ); + if let Some(result) = keyboard_list_pref_short_circuit(status.state, desired) { + return result; } set_openless_language_profile_enabled(desired).map_err(|err| { let message = err.to_string(); @@ -806,6 +825,62 @@ mod tests { }; assert!(!desired_openless_language_profile_enabled(&sendinput_hide)); } + + // ── Fix: TSF IME 未安装时「键盘列表可见性」偏好不应报错 ── + // 之前 `desired == false`(不显示)+ 未安装错误地返回 Err,经 settings.rs 的 + // apply_keyboard_list(&prefs)? 传播导致整个设置保存事务回滚。这是回归护栏。 + + #[test] + fn uninstalled_hide_request_is_noop_ok() { + // 用户想让 OpenLess 不出现在键盘列表,但 TSF IME 没装 → 天然已满足 → Ok(())。 + assert_eq!( + keyboard_list_pref_short_circuit(WindowsImeInstallState::NotInstalled, false), + Some(Ok(())) + ); + } + + #[test] + fn uninstalled_show_request_is_noop_ok() { + // 用户想显示但没装 → 只能 no-op(没东西可启用)→ Ok(())。 + assert_eq!( + keyboard_list_pref_short_circuit(WindowsImeInstallState::NotInstalled, true), + Some(Ok(())) + ); + } + + #[test] + fn broken_registration_short_circuits_ok_for_both_desired_values() { + // 注册损坏同样视为「列表里没有可信条目」→ 两支都短路成 Ok(()),绝不 Err。 + assert_eq!( + keyboard_list_pref_short_circuit(WindowsImeInstallState::RegistrationBroken, false), + Some(Ok(())) + ); + assert_eq!( + keyboard_list_pref_short_circuit(WindowsImeInstallState::RegistrationBroken, true), + Some(Ok(())) + ); + } + + #[test] + fn not_windows_state_short_circuits_ok() { + assert_eq!( + keyboard_list_pref_short_circuit(WindowsImeInstallState::NotWindows, false), + Some(Ok(())) + ); + } + + #[test] + fn installed_state_proceeds_to_real_profile_mutation() { + // 已安装 → 不短路,交给真正的 EnableLanguageProfile 变更。 + assert_eq!( + keyboard_list_pref_short_circuit(WindowsImeInstallState::Installed, false), + None + ); + assert_eq!( + keyboard_list_pref_short_circuit(WindowsImeInstallState::Installed, true), + None + ); + } } #[cfg(all(test, target_os = "windows"))] diff --git a/openless-all/app/src-tauri/tauri.conf.json b/openless-all/app/src-tauri/tauri.conf.json index 7c12331e..7d6e707a 100644 --- a/openless-all/app/src-tauri/tauri.conf.json +++ b/openless-all/app/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "OpenLess", - "version": "1.3.13-Beta.1", + "version": "1.3.14", "identifier": "com.openless.app", "build": { "beforeDevCommand": "npm run dev", @@ -34,8 +34,8 @@ "label": "capsule", "url": "index.html?window=capsule", "title": "OpenLess Capsule", - "width": 220, - "height": 110, + "width": 460, + "height": 180, "decorations": false, "transparent": true, "shadow": false, @@ -54,7 +54,7 @@ "script-src": "'self'", "style-src": "'self' 'unsafe-inline' https://fonts.googleapis.com", "font-src": "'self' https://fonts.gstatic.com", - "img-src": "'self' asset: http://asset.localhost blob: data:", + "img-src": "'self' asset: http://asset.localhost blob: data: https://github.com https://avatars.githubusercontent.com", "connect-src": "'self' ipc: http://ipc.localhost http://localhost:1420 ws://localhost:1420", "object-src": "'none'", "base-uri": "'none'", diff --git a/openless-all/app/src/App.tsx b/openless-all/app/src/App.tsx index 28bdb085..2bc609d5 100644 --- a/openless-all/app/src/App.tsx +++ b/openless-all/app/src/App.tsx @@ -33,12 +33,18 @@ const Onboarding = lazy(() => import('./components/Onboarding').then(m => ({ default: m.Onboarding })), ); const QaPanel = lazy(() => import('./pages/QaPanel').then(m => ({ default: m.QaPanel }))); -const LessComputerPanel = lazy(() => - import('./pages/LessComputerPanel').then(m => ({ default: m.LessComputerPanel })), -); -const LessComputerGlow = lazy(() => - import('./pages/LessComputerGlow').then(m => ({ default: m.LessComputerGlow })), -); +// Less Computer 仅 macOS 开放(后端只在 macOS 注册热键/创建窗口)。Tauri 构建时 +// TAURI_ENV_PLATFORM 是编译期字面量:非 macOS 平台下面两个三元的 import() 分支 +// 被常量折叠 + DCE 整个裁掉,面板 chunk 不进打包产物(门控 = 不打包)。 +// 纯浏览器 vite 环境(预览/调样式)没有该变量 → 保持可加载。 +const TAURI_BUILD_PLATFORM: string | undefined = import.meta.env.TAURI_ENV_PLATFORM; +const LESS_COMPUTER_BUNDLED = !TAURI_BUILD_PLATFORM || TAURI_BUILD_PLATFORM === 'darwin'; +const LessComputerPanel = LESS_COMPUTER_BUNDLED + ? lazy(() => import('./pages/LessComputerPanel').then(m => ({ default: m.LessComputerPanel }))) + : null; +const LessComputerGlow = LESS_COMPUTER_BUNDLED + ? lazy(() => import('./pages/LessComputerGlow').then(m => ({ default: m.LessComputerGlow }))) + : null; interface AppProps { isCapsule: boolean; @@ -53,7 +59,7 @@ const ANDROID_SETUP_WIZARD_COMPLETE_KEY = 'openless.androidSetupWizardComplete'; export function App({ isCapsule, isQa, isLessComputer, isLessComputerGlow, forcedOs }: AppProps) { if (isCapsule) { - return ; + return ; } if (isQa) { return ( @@ -63,18 +69,18 @@ export function App({ isCapsule, isQa, isLessComputer, isLessComputerGlow, force ); } if (isLessComputer) { - return ( + return LessComputerPanel ? ( - ); + ) : null; } if (isLessComputerGlow) { - return ( + return LessComputerGlow ? ( - ); + ) : null; } const os = forcedOs ?? detectOS(); diff --git a/openless-all/app/src/components/AutoUpdate.tsx b/openless-all/app/src/components/AutoUpdate.tsx index 1c8842c9..0281a645 100644 --- a/openless-all/app/src/components/AutoUpdate.tsx +++ b/openless-all/app/src/components/AutoUpdate.tsx @@ -303,7 +303,7 @@ export function UpdateDialog({ const androidInstalled = isAndroid() && status === 'downloaded'; return (
-
+
{t(`settings.about.updateDialog.${status}.title`)}
{androidInstalled diff --git a/openless-all/app/src/components/Capsule.tsx b/openless-all/app/src/components/Capsule.tsx index bfbc7135..3143b9ac 100644 --- a/openless-all/app/src/components/Capsule.tsx +++ b/openless-all/app/src/components/Capsule.tsx @@ -1,38 +1,32 @@ -import { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState, type CSSProperties } from 'react'; import { useTranslation } from 'react-i18next'; import { detectOS, type OS } from './WindowChrome'; +import { SiriGL, warmUpSiriShaders } from './SiriGL'; import { getCapsuleHostMetrics, - getCapsuleMessageLayout, getCapsulePillMetrics, } from '../lib/capsuleLayout'; -import { invokeOrMock, isTauri } from '../lib/ipc'; +import { isTauri } from '../lib/ipc'; import type { CapsulePayload, CapsuleState } from '../lib/types'; // 胶囊 keyframes 注入一次到 document.head,而不是放在组件 JSX 里。否则录音时音量 // 每帧(~60Hz)setLevel 都会让 React 重新创建/reconcile 这个