diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..8c52ff93 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 634aea11..f8ef22e3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,6 @@ out/ # Editors .idea/ -.vscode/ *.tgz *.log *.vsix @@ -28,4 +27,4 @@ out/ packages/cli/src/generated/ packages/core/src/generated/ packages/vscode-ide-companion/*.vsix -packages/vscode-ide-companion/templates/ \ No newline at end of file +packages/vscode-ide-companion/templates/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..bc27e4de --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug VS Code Companion", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}/packages/vscode-ide-companion"], + "outFiles": ["${workspaceFolder}/packages/vscode-ide-companion/dist/**/*.js"], + "preLaunchTask": "Dev: Webview Server", + "env": { + "NODE_OPTIONS": "--no-warnings" + } + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..11d6895c --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,51 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Dev: Webview Server", + "type": "npm", + "script": "dev:webview", + "path": "packages/vscode-ide-companion", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "fileLocation": "relative", + "pattern": { + "regexp": "^$" + }, + "background": { + "activeOnStart": true, + "beginsPattern": ".*VITE.*", + "endsPattern": ".*ready in.*" + } + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + } + }, + { + "label": "Watch VS Code Companion", + "type": "npm", + "script": "watch", + "path": "packages/vscode-ide-companion", + "isBackground": true, + "problemMatcher": { + "owner": "typescript", + "fileLocation": "relative", + "pattern": { + "regexp": "^$" + }, + "background": { + "activeOnStart": true, + "beginsPattern": ".*Watching.*", + "endsPattern": ".*✅.*" + } + }, + "presentation": { + "reveal": "always", + "panel": "dedicated" + } + } + ] +} diff --git a/RELEASE.md b/RELEASE.md index b6479f30..bd80ba6d 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -197,6 +197,7 @@ VSCE_PAT= npm run prepare:vscode -- [options] | `` | **必填**,要发布的 semver 版本号 | | `--dry-run` | 预演模式,不实际执行任何写操作 | | `--force` | 跳过 main 分支检查,允许从其他分支发布 | +| `--pre-release` | 发布为预发布版本(如含 `-beta`/`-alpha` 会自动检测) | ### 执行流程(7 步) @@ -204,7 +205,7 @@ VSCE_PAT= npm run prepare:vscode -- [options] |------|------|------| | 1 | Git 检查 | 工作区必须 clean,必须在 main 分支 | | 2 | VSCE_PAT 检查 | 环境变量必须已设置 | -| 3 | 更新版本号 | 同时更新 `packages/core`、`packages/cli`、`packages/vscode-ide-companion` 的 version | +| 3 | 更新版本号 | 更新 `packages/vscode-ide-companion` 的 version | | 4 | 质量检查 | `npm run check`(typecheck + eslint + prettier) | | 5 | 测试 | `npm run test --workspaces` | | 6 | 构建 | `npm run build:vscode`(core tsc + esbuild 打包扩展 + 拷贝模板 + vsce package) | diff --git a/package-lock.json b/package-lock.json index 8da30426..c56e8465 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,13 @@ "typescript-eslint": "^8.59.2" } }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@alcalzone/ansi-tokenize": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.3.0.tgz", @@ -55,6 +62,57 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@azu/format-text": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@azu/format-text/-/format-text-1.0.2.tgz", @@ -258,7 +316,6 @@ "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": { "node": ">=6.9.0" @@ -268,7 +325,6 @@ "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.7", @@ -299,7 +355,6 @@ "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.7", @@ -312,11 +367,22 @@ "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==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-compilation-targets": { "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.29.7", @@ -329,21 +395,53 @@ "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==", + "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.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==", + "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.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.29.7", @@ -357,7 +455,6 @@ "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.29.7", @@ -371,11 +468,61 @@ "@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==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "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==", + "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==", + "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==", + "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.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": { "node": ">=6.9.0" @@ -394,7 +541,6 @@ "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": { "node": ">=6.9.0" @@ -404,7 +550,6 @@ "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.29.7", @@ -418,7 +563,6 @@ "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.7" @@ -430,11 +574,104 @@ "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==", + "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==", + "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==", + "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-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==", + "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==", + "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.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "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.29.7", @@ -449,7 +686,6 @@ "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.7", @@ -468,7 +704,6 @@ "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.29.7", @@ -478,77 +713,429 @@ "node": ">=6.9.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], + "node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT-0", "engines": { - "node": ">=18" + "node": ">=20.19.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], + "node_modules/@csstools/css-calc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT", "engines": { - "node": ">=18" + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], + "node_modules/@csstools/css-color-parser": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.9.tgz", + "integrity": "sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.2.1" + }, "engines": { - "node": ">=18" + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.6.tgz", + "integrity": "sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@dotenvx/dotenvx/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==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "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==", + "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==", + "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/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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@dotenvx/dotenvx/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "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==", + "license": "BSD-3-Clause" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" @@ -1064,28 +1651,117 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@humanfs/core": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", - "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/types": "^0.15.0" - }, + "license": "MIT", "engines": { - "node": ">=18.18.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, - "node_modules/@humanfs/node": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", - "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", - "dev": true, - "license": "Apache-2.0", + "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": { - "@humanfs/core": "^0.19.2", - "@humanfs/types": "^0.15.0", + "@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/@fontsource-variable/inter": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.2.8.tgz", + "integrity": "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@hookform/resolvers": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.4.0.tgz", + "integrity": "sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { @@ -1134,7 +1810,6 @@ "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", @@ -1145,7 +1820,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", @@ -1156,7 +1830,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" @@ -1166,25 +1839,84 @@ "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==", + "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/@modelcontextprotocol/sdk/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==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@modelcontextprotocol/sdk/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==", + "license": "MIT" + }, "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", @@ -1198,7 +1930,6 @@ "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" @@ -1208,7 +1939,6 @@ "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", @@ -1218,215 +1948,2719 @@ "node": ">= 8" } }, - "node_modules/@secretlint/config-creator": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", - "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", - "dev": true, + "node_modules/@radix-ui/number": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", + "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz", + "integrity": "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.10.tgz", + "integrity": "sha512-TraSwZUqTcVbiDV2/RXzAXC7aeVVXchq0daPFZE7zAxYFaMzjOUggLOfQH9KFLgRizuwVKZO/crveV1eeO3/ZQ==", "license": "MIT", "dependencies": { - "@secretlint/types": "^10.2.2" + "@radix-ui/react-visually-hidden": "1.2.6" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@secretlint/config-loader": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", - "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", - "dev": true, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.14.tgz", + "integrity": "sha512-iE8YB9nmTBH8zd73ofBISZ8JCzgMoMkATJr7qDwa6u5F1+7mTM81V6fa71jgZ65rpjVpecDf1vSnwIFP9Ly1zw==", "license": "MIT", "dependencies": { - "@secretlint/profiler": "^10.2.2", - "@secretlint/resolver": "^10.2.2", - "@secretlint/types": "^10.2.2", - "ajv": "^8.17.1", - "debug": "^4.4.1", - "rc-config-loader": "^4.1.3" + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collapsible": "1.1.14", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@secretlint/config-loader/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, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.17.tgz", + "integrity": "sha512-563ygGeyWPrxyVCNp7OV4rE2aIXhFPknpFyo4wbDlcyMMPZ6ySh+zC5WTvY0ZFLgPTg/QB6tA8PyDQyJ2b4cPg==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dialog": "1.1.17", + "@radix-ui/react-primitive": "2.1.6" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@secretlint/config-loader/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/@secretlint/core": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", - "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", - "dev": true, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.10.tgz", + "integrity": "sha512-j2VTDz1vgCsmuG0k5lBfOcM8n5JPFqZBcMryasFjHYMhwxYL5SRUV5lMSUpRdNtw3D/Sv8pzJtrlAgkssYSsQQ==", "license": "MIT", "dependencies": { - "@secretlint/profiler": "^10.2.2", - "@secretlint/types": "^10.2.2", - "debug": "^4.4.1", - "structured-source": "^4.0.0" + "@radix-ui/react-primitive": "2.1.6" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@secretlint/formatter": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", - "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", - "dev": true, + "node_modules/@radix-ui/react-aspect-ratio": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.10.tgz", + "integrity": "sha512-kbI7NrqhDeuytYrq7JjAsoXczvL8wgj2tc1MyaYWm+50bMKHCHQtVWCryslx4cCpmCTTkBcwQckE4CmmGV2haQ==", "license": "MIT", "dependencies": { - "@secretlint/resolver": "^10.2.2", - "@secretlint/types": "^10.2.2", - "@textlint/linter-formatter": "^15.2.0", - "@textlint/module-interop": "^15.2.0", - "@textlint/types": "^15.2.0", - "chalk": "^5.4.1", - "debug": "^4.4.1", - "pluralize": "^8.0.0", - "strip-ansi": "^7.1.0", - "table": "^6.9.0", - "terminal-link": "^4.0.0" + "@radix-ui/react-primitive": "2.1.6" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@secretlint/formatter/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, + "node_modules/@radix-ui/react-avatar": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.2.0.tgz", + "integrity": "sha512-am/CwltXtmtdtP+5FbYblYDnMa/zuKcMJP1i3/SJMDXXfj2mG+BTqLH2wucqeyyiQMursUtg/5cK+Nh2pCaSOA==", "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "dependencies": { + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-is-hydrated": "0.1.1", + "@radix-ui/react-use-layout-effect": "1.1.2" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@secretlint/node": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", - "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", - "dev": true, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.5.tgz", + "integrity": "sha512-pREzrmNnVwGvYaBoM64huTRK7B3lrTRuwj8A9nwhPiEtMb+yudiWh6zWAqEtP0Dzd5+iBa1Ki7V1pCxV8ExMdA==", "license": "MIT", "dependencies": { - "@secretlint/config-loader": "^10.2.2", - "@secretlint/core": "^10.2.2", - "@secretlint/formatter": "^10.2.2", - "@secretlint/profiler": "^10.2.2", - "@secretlint/source-creator": "^10.2.2", - "@secretlint/types": "^10.2.2", - "debug": "^4.4.1", - "p-map": "^7.0.3" + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-use-size": "1.1.2" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@secretlint/profiler": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.14.tgz", + "integrity": "sha512-9bT+FvifX1FK2Mj6UEsTdyu0cN3JaA3KdfhaBao+ONrYFy/pyOy3TU1TNw7iOk1o+0hOEq67RojlUUmoFGwxyA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.10.tgz", + "integrity": "sha512-IVVz4EvBcKjrzKgof714qDnz/SzQAkLA2Emh5edlHbgcE6fNd3Un6CJLlaYcnm8N4JmAtzQgse4dOKxcD2yc9g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", + "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.4.tgz", + "integrity": "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.3.1.tgz", + "integrity": "sha512-XbrxS68W5dyiE4fAb96yvJwSVU5x66B20A99sD5Mk3xSWK/LqeOnx6TZnim1KieMjXS/CTFq8reOAjWxas2G8Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-menu": "2.1.18", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.17.tgz", + "integrity": "sha512-TDTYmpdq8dI2+Xgvgj9AJ8Ghqq+Eph/TRVEdaFQPDItIY+6QSkU7MJMeevw1568Yw/2Ijz8BTphPSP2XejKphw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.10", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", + "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.13.tgz", + "integrity": "sha512-2v+zNAWWe0ySxgC0D0yeXMPQ23xZVgXZTerTz+JKlmdRj6gfTqmCcR29jb6d290DezXPGgruHWDX/vYUebtErg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-escape-keydown": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.18.tgz", + "integrity": "sha512-PZGV82gFk0WltDRI//SsG28ZIjlo9ANTmoNYg0jLNzXXiDsAy5PkOOYQaVD1pPxY6t7gxffb1QMD6qaUvsBZdw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-menu": "2.1.18", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", + "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.10.tgz", + "integrity": "sha512-Fas/lXQqhVvqwAb64s5RFeHiHYElZ6SUQbZaNd6EkfhP/Al7wTIQ9WIR4QVX475tlu5yFCEdDcJH6/UwsZjMWw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.10.tgz", + "integrity": "sha512-1NfuvctVtX4sU3Mmq/IdrR8UunxiCMiVg3A5UENKhFzxUBeOyaQQ+lmaQaV7Tc8cqvBKsJL3/KGBsixK0D8WFg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-label": "2.1.10", + "@radix-ui/react-primitive": "2.1.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-hover-card": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.17.tgz", + "integrity": "sha512-GjZQIEANVkuuWeztlKz6QEHe31ZX2iDfHzcTMCQVZXC0JyQrgfKWSC+LOOEw6aVV64zyjzobIzSA4AU4eKWrHA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-popper": "1.3.1", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", + "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.10.tgz", + "integrity": "sha512-ib0zvq2ZsAqKm5tRnqGJn3vOxSgIts5ToxsXT0q1S/GfLD1Zj7UOEnkw8u2w6sRmn47djpQWuSU1DCL1R29/yw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.18.tgz", + "integrity": "sha512-lj8Rxjtn6zJq1oSbE/uDtAwCbB9BnxgHD+8MwJMuTh6u1dPamYhW9iuELr/Z8d0D/UysFblYYHeBPwi7T4k0YQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.10", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.1", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-roving-focus": "1.1.13", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-callback-ref": "1.1.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menubar": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.18.tgz", + "integrity": "sha512-hX7EGx/oFq6DPY27GQuP/2wP48GHf5LG6r06VgNJlG+znmDS8OfopZcRcGly3L4lsB9FqpmLx6JQSE9P3BUpyw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-menu": "2.1.18", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-roving-focus": "1.1.13", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.16.tgz", + "integrity": "sha512-nJ0SkrSQgudyYhMiYeHA1ayLVuduEJCFLan1RZZN7c9kqzzCFLaU9kuy81uNtqzweM9YaQPgWzxi9MwQ9jZ04g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-visually-hidden": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.10.tgz", + "integrity": "sha512-GHkcJ+WVj91At+OvUVTD4R3W0/wxw9t/sG5xFUBYXaCbtWiooZX5Md376QjJqgH4VsVyXrbVNHO2O4NYcmjfVg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-roving-focus": "1.1.13", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-is-hydrated": "0.1.1", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-password-toggle-field": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.5.tgz", + "integrity": "sha512-fVuA82u0b/fClpbEJv8yp1nU9eSvoSEOERsU/hhf3FXGPIvkmE7oEaHEu8poowoXO39/Va7zq2E0TUcYr1dBRg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-is-hydrated": "0.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.17.tgz", + "integrity": "sha512-/YSAOdJ7YJvdn7bn5sdSx2egW+SKY+u7O5RyAVs94Ymrg2fg5QTSFPMRkzvhGyFuE4/qsmPBdrwYoZMZh/4f+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.10", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.1", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.1.tgz", + "integrity": "sha512-bhnq/0DEPTi2lsOD3J5rTL65qUKHbKbhqHsmN9TMiclSXpipi651ooUKPPp6G5lF/WiHBdn1s0Wuqsn+myVAvw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-rect": "1.1.2", + "@radix-ui/react-use-size": "1.1.2", + "@radix-ui/rect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.12.tgz", + "integrity": "sha512-m309havGzsjLHHaIX50G5PlvRs3xkgPCsGk/5PTvYm8D5q33yG0J7w/712PTOhid7NTaFETtnSXjngHQavvhVw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz", + "integrity": "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.6.tgz", + "integrity": "sha512-wetd0QI77DbvrPpTAvH1SqOxsYF2wZe5TNxqwOd5Ty4XDpV3dpV0s8K/1MGMJBeY5o7lg8ub5VIt1Ub+yVen6g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.3.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.10.tgz", + "integrity": "sha512-JYzEg60lk79PwKM27WZyKd7PW8O4OM5jOaFfRPfOyeXmMw7tLJh5kSj+CEjVTehszuwml/AdCzPGMXBTGf4BBw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.4.1.tgz", + "integrity": "sha512-/SSxZdKEo2Eo29FFRKd06EfFDYp8HryKg0WYg7QLXaydPzl52YfSvCH2a3QDBRdtcuwACroJT8UVjQVgOJ7P9A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-roving-focus": "1.1.13", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-use-size": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.13.tgz", + "integrity": "sha512-9gkwneI0guf8JDmrFxPjJF6Ozzgioyw+/lonYNCwefS9ZHA05er0BVHiXr+LbWGHxUfczvMY6G1oiZZi1VzjRw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.12.tgz", + "integrity": "sha512-xuafVzQiTCLsyEjakowTdG3OgTXsmO7IdCiO77otIa+z44xoLNs9Do5eg7POFumIOCjtG6djfm6RKUKpUa/csA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.1.tgz", + "integrity": "sha512-w6eDvY78LE9ZUiNnXCA1QVK8RYN7k9galFv09kjVydJqBAgHd7Y9A6h0UJ/6DCZNGZMZrB2ohcSW1Bo9d8+wWA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.10", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.1", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-visually-hidden": "1.2.6", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.7.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.10.tgz", + "integrity": "sha512-Y6K6jLQCVfCnTL2MEtGxDLffkhNfEfHsEg3Wa8JU+IWdn3EWbLXd3OuOfQRN7p/W/cUce1WyTk3QeuAoDBzN9g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slider": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.4.1.tgz", + "integrity": "sha512-r91WSpQucNGFKAIxT8FT0H0zyjd5tJlqObLp7LOMV4z49KoDCwjy01w3vDOU4e1wxhF9IgjYco7SB6byOW7Buw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.2", + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-use-size": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.0.tgz", + "integrity": "sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.1.tgz", + "integrity": "sha512-55bQtCnOB0BohomSHi6qvQXpJEEqUGDm6hRrM0Bph5OXwhSegqkd8IqgBAQkM1IlgUlWZIxpxRcpOEfRIgimyw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-previous": "1.1.2", + "@radix-ui/react-use-size": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.15.tgz", + "integrity": "sha512-kxc9gI6/HfcU4nfMMVS3AmQK414kbU1IE6UCJmMmxjhO3cRPXOyYnmvyKD+ODt7q56nRq9l7Wovi6uaGwKgMlg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-roving-focus": "1.1.13", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.17.tgz", + "integrity": "sha512-uL4kyyWy000pPL43fGGCV5qT6ZchCWEQZOSlkYiPwPt8Hy1iW38RjeptIvz1/SZesrW6Vn58Ct3sV7tfEfiAbw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-visually-hidden": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.12.tgz", + "integrity": "sha512-AsAVsYNZIlRBsci7BhE+QyQeKd1h6TffJYt+lF0QQkd5OpQ3klfIByPsCb4G0h/Fq6PJwh1FYNluzBFYzhk4+w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.13.tgz", + "integrity": "sha512-Xb9PLtlvU66F36LiKba6dFswu6V2mDkgidO4fNSbQHQwmZ9ObxMIO17MN/LJ4aWJecVuSVLAHPZjyeMzJrgeiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-roving-focus": "1.1.13", + "@radix-ui/react-toggle": "1.1.12", + "@radix-ui/react-use-controllable-state": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.13.tgz", + "integrity": "sha512-Za1l4f6fzTkGgz/iynAMN8iaqiKff2wm2/QwiLmHPtDQreWEBrvSimgQFIekxMUdRPhILM7xdIXxuS/o/DGZag==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-roving-focus": "1.1.13", + "@radix-ui/react-separator": "1.1.10", + "@radix-ui/react-toggle-group": "1.1.13" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.10.tgz", + "integrity": "sha512-NlNe8D0dWEpVfXFli90IO6X07Josx/b1iu98tDnx9Xv0HT4wLIL+m2VOheMHhK7qbp2HoTBqALEFzGyZs/levw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-id": "1.1.2", + "@radix-ui/react-popper": "1.3.1", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-visually-hidden": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", + "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", + "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", + "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.2.tgz", + "integrity": "sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.1.tgz", + "integrity": "sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", + "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz", + "integrity": "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz", + "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz", + "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.6.tgz", + "integrity": "sha512-jCE0WljWifTI4niIMCll06kGpsJTAPiZVU9H4WR1N6qW7At9ystHbN7dDB+we2xH535roFHj7qKS+RGj0FMDWQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz", + "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==", + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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==", + "license": "MIT" + }, + "node_modules/@secretlint/config-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-creator/-/config-creator-10.2.2.tgz", + "integrity": "sha512-BynOBe7Hn3LJjb3CqCHZjeNB09s/vgf0baBaHVw67w7gHF0d25c3ZsZ5+vv8TgwSchRdUCRrbbcq5i2B1fJ2QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/config-loader": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/config-loader/-/config-loader-10.2.2.tgz", + "integrity": "sha512-ndjjQNgLg4DIcMJp4iaRD6xb9ijWQZVbd9694Ol2IszBIbGPPkwZHzJYKICbTBmh6AH/pLr0CiCaWdGJU7RbpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "ajv": "^8.17.1", + "debug": "^4.4.1", + "rc-config-loader": "^4.1.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/config-loader/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, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@secretlint/config-loader/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/@secretlint/core": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/core/-/core-10.2.2.tgz", + "integrity": "sha512-6rdwBwLP9+TO3rRjMVW1tX+lQeo5gBbxl1I5F8nh8bgGtKwdlCMhMKsBWzWg1ostxx/tIG7OjZI0/BxsP8bUgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/profiler": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "structured-source": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/formatter/-/formatter-10.2.2.tgz", + "integrity": "sha512-10f/eKV+8YdGKNQmoDUD1QnYL7TzhI2kzyx95vsJKbEa8akzLAR5ZrWIZ3LbcMmBLzxlSQMMccRmi05yDQ5YDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/resolver": "^10.2.2", + "@secretlint/types": "^10.2.2", + "@textlint/linter-formatter": "^15.2.0", + "@textlint/module-interop": "^15.2.0", + "@textlint/types": "^15.2.0", + "chalk": "^5.4.1", + "debug": "^4.4.1", + "pluralize": "^8.0.0", + "strip-ansi": "^7.1.0", + "table": "^6.9.0", + "terminal-link": "^4.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/formatter/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", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@secretlint/node": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/node/-/node-10.2.2.tgz", + "integrity": "sha512-eZGJQgcg/3WRBwX1bRnss7RmHHK/YlP/l7zOQsrjexYt6l+JJa5YhUmHbuGXS94yW0++3YkEJp0kQGYhiw1DMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-loader": "^10.2.2", + "@secretlint/core": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "@secretlint/source-creator": "^10.2.2", + "@secretlint/types": "^10.2.2", + "debug": "^4.4.1", + "p-map": "^7.0.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/profiler": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/profiler/-/profiler-10.2.2.tgz", "integrity": "sha512-qm9rWfkh/o8OvzMIfY8a5bCmgIniSpltbVlUVl983zDG1bUuQNd1/5lUEeWx5o/WJ99bXxS7yNI4/KIXfHexig==", "dev": true, - "license": "MIT" + "license": "MIT" + }, + "node_modules/@secretlint/resolver": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", + "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@secretlint/secretlint-formatter-sarif": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", + "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-sarif-builder": "^3.2.0" + } + }, + "node_modules/@secretlint/secretlint-rule-no-dotenv": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", + "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/secretlint-rule-preset-recommend": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", + "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/source-creator": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", + "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/types": "^10.2.2", + "istextorbinary": "^9.5.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@secretlint/types": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", + "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@swc/core": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.43.tgz", + "integrity": "sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.27" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.43", + "@swc/core-darwin-x64": "1.15.43", + "@swc/core-linux-arm-gnueabihf": "1.15.43", + "@swc/core-linux-arm64-gnu": "1.15.43", + "@swc/core-linux-arm64-musl": "1.15.43", + "@swc/core-linux-ppc64-gnu": "1.15.43", + "@swc/core-linux-s390x-gnu": "1.15.43", + "@swc/core-linux-x64-gnu": "1.15.43", + "@swc/core-linux-x64-musl": "1.15.43", + "@swc/core-win32-arm64-msvc": "1.15.43", + "@swc/core-win32-ia32-msvc": "1.15.43", + "@swc/core-win32-x64-msvc": "1.15.43" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.43.tgz", + "integrity": "sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.43.tgz", + "integrity": "sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.43.tgz", + "integrity": "sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.43.tgz", + "integrity": "sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.43.tgz", + "integrity": "sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.43.tgz", + "integrity": "sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.43.tgz", + "integrity": "sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.43.tgz", + "integrity": "sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.43.tgz", + "integrity": "sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.43.tgz", + "integrity": "sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.43.tgz", + "integrity": "sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } }, - "node_modules/@secretlint/resolver": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/resolver/-/resolver-10.2.2.tgz", - "integrity": "sha512-3md0cp12e+Ae5V+crPQYGd6aaO7ahw95s28OlULGyclyyUtf861UoRGS2prnUrKh7MZb23kdDOyGCYb9br5e4w==", + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.43.tgz", + "integrity": "sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } }, - "node_modules/@secretlint/secretlint-formatter-sarif": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-formatter-sarif/-/secretlint-formatter-sarif-10.2.2.tgz", - "integrity": "sha512-ojiF9TGRKJJw308DnYBucHxkpNovDNu1XvPh7IfUp0A12gzTtxuWDqdpuVezL7/IP8Ua7mp5/VkDMN9OLp1doQ==", + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.27.tgz", + "integrity": "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.1.tgz", + "integrity": "sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==", "dev": true, "license": "MIT", "dependencies": { - "node-sarif-builder": "^3.2.0" + "@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.1" } }, - "node_modules/@secretlint/secretlint-rule-no-dotenv": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-no-dotenv/-/secretlint-rule-no-dotenv-10.2.2.tgz", - "integrity": "sha512-KJRbIShA9DVc5Va3yArtJ6QDzGjg3PRa1uYp9As4RsyKtKSSZjI64jVca57FZ8gbuk4em0/0Jq+uy6485wxIdg==", + "node_modules/@tailwindcss/oxide": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.1.tgz", + "integrity": "sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-arm64": "4.3.1", + "@tailwindcss/oxide-darwin-x64": "4.3.1", + "@tailwindcss/oxide-freebsd-x64": "4.3.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.1", + "@tailwindcss/oxide-linux-x64-musl": "4.3.1", + "@tailwindcss/oxide-wasm32-wasi": "4.3.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.1" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.1.tgz", + "integrity": "sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.1.tgz", + "integrity": "sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.1.tgz", + "integrity": "sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.1.tgz", + "integrity": "sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.1.tgz", + "integrity": "sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.1.tgz", + "integrity": "sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.1.tgz", + "integrity": "sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.1.tgz", + "integrity": "sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.1.tgz", + "integrity": "sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.1.tgz", + "integrity": "sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@secretlint/types": "^10.2.2" + "@emnapi/core": "^1.10.0", + "@emnapi/runtime": "^1.10.0", + "@emnapi/wasi-threads": "^1.2.1", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=14.0.0" } }, - "node_modules/@secretlint/secretlint-rule-preset-recommend": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/secretlint-rule-preset-recommend/-/secretlint-rule-preset-recommend-10.2.2.tgz", - "integrity": "sha512-K3jPqjva8bQndDKJqctnGfwuAxU2n9XNCPtbXVI5JvC7FnQiNg/yWlQPbMUlBXtBoBGFYp08A94m6fvtc9v+zA==", + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.1.tgz", + "integrity": "sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=20.0.0" + "node": ">= 20" } }, - "node_modules/@secretlint/source-creator": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/source-creator/-/source-creator-10.2.2.tgz", - "integrity": "sha512-h6I87xJfwfUTgQ7irWq7UTdq/Bm1RuQ/fYhA3dtTIAop5BwSFmZyrchph4WcoEvbN460BWKmk4RYSvPElIIvxw==", + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.1.tgz", + "integrity": "sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.1.tgz", + "integrity": "sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==", "dev": true, "license": "MIT", "dependencies": { - "@secretlint/types": "^10.2.2", - "istextorbinary": "^9.5.0" + "@tailwindcss/node": "4.3.1", + "@tailwindcss/oxide": "4.3.1", + "tailwindcss": "4.3.1" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.101.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.2.tgz", + "integrity": "sha512-hH5MLoJhF7KaIGd7q3xTXGXvslI+GYlM1Z/35aSHHWaCJWB7XvTSHYuV3eM7tw+aE0mT/xMro4M4Q9rCGHT0lw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.101.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.2.tgz", + "integrity": "sha512-seDkr6kzGzX1okaaTtZPtgA688CDPlXUz1C6xSg0ESqn04Vuc8tlrYms1s3de+znBqhPVxFRfpAfUf+6XvfPWg==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.101.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" }, "engines": { - "node": ">=20.0.0" + "node": ">=18" } }, - "node_modules/@secretlint/types": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/@secretlint/types/-/types-10.2.2.tgz", - "integrity": "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg==", + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", "dev": true, "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, "engines": { - "node": ">=20.0.0" + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, "engines": { "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, "node_modules/@textlint/ast-node-types": { @@ -1545,6 +4779,78 @@ "@textlint/ast-node-types": "15.7.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==", + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.3", + "minimatch": "^10.0.1", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/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==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@ts-morph/common/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==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "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/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ejs": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", @@ -1626,6 +4932,16 @@ "csstype": "^3.2.2" } }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, "node_modules/@types/sarif": { "version": "2.1.7", "resolved": "https://registry.npmjs.org/@types/sarif/-/sarif-2.1.7.tgz", @@ -1633,12 +4949,25 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/tinycolor2": { "version": "1.4.6", "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz", "integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==", "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==", + "license": "MIT" + }, "node_modules/@types/vscode": { "version": "1.125.0", "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.125.0.tgz", @@ -1981,6 +5310,136 @@ "resolved": "packages/core", "link": true }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.3.1.tgz", + "integrity": "sha512-PaeokKjAGraNN+s5SIApgsktnJprIyt3zgEIu7awnEdfn29QiB2crTcCzyi2XGpX9rUnTc0cKU07Wm0N0g7H2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.0", + "@swc/core": "^1.15.11" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vscode/vsce": { "version": "3.9.2", "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.2.tgz", @@ -2225,6 +5684,81 @@ "node": ">=10" } }, + "node_modules/@webview-rpc/client": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@webview-rpc/client/-/client-1.0.17.tgz", + "integrity": "sha512-pXqHmnsOTLqPxHkGzLipTHjBJGOZQQNZPpDA0F4yqwJLfNTuV1J7KSashtU6MztOPVZ0xXtTxYqdxG0vXPI/gw==", + "dependencies": { + "@webview-rpc/shared": "1.0.17" + } + }, + "node_modules/@webview-rpc/host": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@webview-rpc/host/-/host-1.0.17.tgz", + "integrity": "sha512-sJfTM+v2KA3MQagUVrw6Vp/D0YCzNMF0xBNSdVnFTlqICWavc1OEMhLkSRwJXAkSVXX6ePEPVELOjkcl7O8kUQ==", + "dependencies": { + "@webview-rpc/shared": "1.0.17", + "zod": "^4.1.11" + } + }, + "node_modules/@webview-rpc/react-query": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@webview-rpc/react-query/-/react-query-1.0.17.tgz", + "integrity": "sha512-nl9W5At9UMGFSylSjohzeuCYexVGCOle2d3DXT6oqXQKLw7+ihJ00LTWbw221Cg88qasSEV8AUrFz6OJeoTrSg==", + "dependencies": { + "@tanstack/react-query": "^5.90.5", + "@webview-rpc/client": "1.0.17", + "@webview-rpc/shared": "1.0.17", + "react": "^19.2.0", + "zod": "^4.1.11" + } + }, + "node_modules/@webview-rpc/shared": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@webview-rpc/shared/-/shared-1.0.17.tgz", + "integrity": "sha512-R0zHO0tQN0fNWPbMIxAgZHJsiIU7gDB+Td570TppU8tPOGDieqS4DUWM4doP6awnn9dzZI6HE7XN/kYFQS4+Dg==", + "dependencies": { + "zod": "^4.1.11" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/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==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/acorn": { "version": "8.17.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", @@ -2275,6 +5809,54 @@ "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==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/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==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/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==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/ansi-escapes": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", @@ -2324,6 +5906,50 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -2341,6 +5967,15 @@ "dev": true, "license": "MIT" }, + "node_modules/atomically": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz", + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", + "license": "MIT", + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/auto-bind": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", @@ -2353,6 +5988,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/autoprefixer": { + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.2.tgz", + "integrity": "sha512-rD5t5DwOjJdmSORcTq64j8MawTC+tbQ+HHqjR4NDumamy/ambn1UJrlKL+KdwujWxMkFjPM3pPHOEA9tl4767Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.4", + "caniuse-lite": "^1.0.30001799", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/azure-devops-node-api": { "version": "12.5.0", "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.5.0.tgz", @@ -2397,7 +6069,6 @@ "version": "2.10.38", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", - "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -2406,6 +6077,16 @@ "node": ">=6.0.0" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/binaryextensions": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-6.11.0.tgz", @@ -2435,6 +6116,59 @@ "readable-stream": "^3.4.0" } }, + "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==", + "license": "MIT", + "dependencies": { + "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" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "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/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -2464,7 +6198,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2474,10 +6207,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", "funding": [ { "type": "opencollective", @@ -2494,10 +6226,10 @@ ], "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", + "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" }, "bin": { @@ -2554,7 +6286,6 @@ "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", "dependencies": { "run-applescript": "^7.0.0" @@ -2566,11 +6297,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "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": { "es-errors": "^1.3.0", @@ -2584,7 +6323,6 @@ "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": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -2601,7 +6339,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2611,7 +6348,6 @@ "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", @@ -2628,6 +6364,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2697,6 +6443,19 @@ "license": "ISC", "optional": true }, + "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==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/cli-boxes": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-4.0.1.tgz", @@ -2724,6 +6483,18 @@ "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==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-truncate": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-6.0.0.tgz", @@ -2800,6 +6571,32 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/cockatiel": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/cockatiel/-/cockatiel-3.2.1.tgz", @@ -2810,6 +6607,12 @@ "node": ">=16" } }, + "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==", + "license": "MIT" + }, "node_modules/code-excerpt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", @@ -2872,11 +6675,113 @@ "dev": true, "license": "MIT" }, + "node_modules/conf": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz", + "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==", + "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": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/conf/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==", + "license": "MIT" + }, + "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==", + "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==", + "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==", + "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==", + "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/convert-to-spaces": { @@ -2888,11 +6793,89 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "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==", + "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==", + "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==", + "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/cosmiconfig/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==", + "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/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", @@ -2920,6 +6903,20 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-what": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", @@ -2933,18 +6930,84 @@ "url": "https://github.com/sponsors/fb55" } }, - "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/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "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==", + "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/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debounce-fn/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } }, "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" @@ -2958,6 +7021,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -2975,6 +7045,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -2997,11 +7081,19 @@ "resolved": "packages/vscode-ide-companion", "link": 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==", + "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", @@ -3018,7 +7110,6 @@ "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" @@ -3031,7 +7122,6 @@ "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" @@ -3050,17 +7140,57 @@ "node": ">=0.4.0" } }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -3120,11 +7250,37 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "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==", + "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==", + "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", @@ -3162,6 +7318,12 @@ "url": "https://bevry.me/fund" } }, + "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==", + "license": "MIT" + }, "node_modules/ejs": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/ejs/-/ejs-5.0.2.tgz", @@ -3175,18 +7337,54 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.375", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.375.tgz", - "integrity": "sha512-ZWP5eB4BVPW/ZYo9252hQZHZ5XavtsTgpbhcmMmRwymavC5AsLWQWBPaKMeNd2LW0KGby5HPXvj7+sr4ta5j/Q==", - "dev": true, + "version": "1.5.380", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.380.tgz", + "integrity": "sha512-W6d5AbuEoRayO447cqrg6lKJIlscgRnnxOZl/08kfV71BQDoEBC7Wwis68z87LjyK6f4kWyTaubuDbhHKrZkbA==", "license": "ISC" }, + "node_modules/embla-carousel": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", + "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", + "license": "MIT" + }, + "node_modules/embla-carousel-react": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz", + "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==", + "license": "MIT", + "dependencies": { + "embla-carousel": "8.6.0", + "embla-carousel-reactive-utils": "8.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/embla-carousel-reactive-utils": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz", + "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==", + "license": "MIT", + "peerDependencies": { + "embla-carousel": "8.6.0" + } + }, "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==", "license": "MIT" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/encoding-sniffer": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", @@ -3212,6 +7410,54 @@ "once": "^1.4.0" } }, + "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==", + "dev": true, + "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==", + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/enquirer/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/enquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -3224,6 +7470,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/environment": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", @@ -3236,11 +7491,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "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" @@ -3250,17 +7513,22 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", + "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", + "dev": true, + "license": "MIT" + }, "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" @@ -3346,6 +7614,12 @@ "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==", + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -3552,32 +7826,218 @@ "node": ">=4.0" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "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==", + "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==", + "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/execa/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==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/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==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "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==", + "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/express/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==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "dev": true, - "license": "(MIT OR WTFPL)", - "optional": true, + "node_modules/express/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==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/extend-shallow": { @@ -3596,14 +8056,12 @@ "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", @@ -3620,7 +8078,6 @@ "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" @@ -3647,7 +8104,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "dev": true, "funding": [ { "type": "github", @@ -3664,7 +8120,6 @@ "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" @@ -3674,7 +8129,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -3688,6 +8142,21 @@ } } }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3705,7 +8174,6 @@ "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" @@ -3714,6 +8182,27 @@ "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==", + "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": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3781,6 +8270,65 @@ "node": ">= 6" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "12.42.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz", + "integrity": "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.42.2", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -3793,7 +8341,6 @@ "version": "11.3.5", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -3823,17 +8370,21 @@ "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==", + "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" @@ -3864,7 +8415,6 @@ "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", @@ -3885,11 +8435,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "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", @@ -3899,6 +8469,22 @@ "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==", + "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/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -4025,7 +8611,6 @@ "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" @@ -4038,7 +8623,6 @@ "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==", - "dev": true, "license": "ISC" }, "node_modules/gradient-string": { @@ -4117,7 +8701,6 @@ "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" @@ -4146,7 +8729,6 @@ "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" @@ -4172,6 +8754,24 @@ "hermes-estree": "0.25.1" } }, + "node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hono": { + "version": "4.12.27", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz", + "integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -4205,6 +8805,19 @@ "dev": true, "license": "ISC" }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/htmlparser2": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", @@ -4238,6 +8851,26 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "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==", + "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/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -4266,6 +8899,15 @@ "node": ">= 14" } }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -4321,7 +8963,6 @@ "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" @@ -4331,7 +8972,6 @@ "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", @@ -4382,9 +9022,7 @@ "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", - "optional": true + "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", @@ -4488,11 +9126,34 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "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==", + "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==", + "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==", + "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" @@ -4517,7 +9178,6 @@ "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" @@ -4542,7 +9202,6 @@ "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" @@ -4566,11 +9225,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "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" @@ -4585,21 +9255,101 @@ "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==", + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "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==", + "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==", + "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==", + "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" @@ -4615,7 +9365,6 @@ "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/istextorbinary": { @@ -4636,6 +9385,25 @@ "url": "https://bevry.me/fund" } }, + "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==", + "dev": true, + "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==", + "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", @@ -4646,7 +9414,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", - "dev": true, "funding": [ { "type": "github", @@ -4665,11 +9432,97 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "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" @@ -4685,6 +9538,12 @@ "dev": true, "license": "MIT" }, + "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==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -4692,6 +9551,12 @@ "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==", + "license": "BSD-2-Clause" + }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", @@ -4703,7 +9568,6 @@ "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" @@ -4723,7 +9587,6 @@ "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" @@ -4823,6 +9686,15 @@ "node": ">=0.10.0" } }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -4847,6 +9719,273 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/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==", + "license": "MIT" + }, "node_modules/linkify-it": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", @@ -5034,12 +10173,52 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "license": "MIT" + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, + "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==", + "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/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/log-update": { "version": "6.1.0", @@ -5193,12 +10372,40 @@ "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.22.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.22.0.tgz", + "integrity": "sha512-c9o3l0PiNcgOQDW4F31BEYHudE7kgxVt3o30qMl36ZPwTxXlGB4QnLilhERvVM4uh/pl5MDyY1/gzZSYcHDtBg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/markdown-it": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", @@ -5226,27 +10433,65 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/markdown-it-task-lists": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz", + "integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA==", + "license": "ISC" + }, "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/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "license": "MIT" }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "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==", + "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==", + "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" @@ -5256,7 +10501,6 @@ "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", @@ -5270,7 +10514,6 @@ "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" @@ -5328,7 +10571,6 @@ "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" @@ -5351,6 +10593,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -5368,9 +10620,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", - "optional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5393,11 +10643,25 @@ "license": "MIT", "optional": true }, + "node_modules/motion-dom": { + "version": "12.42.2", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.42.2.tgz", + "integrity": "sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==", + "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/mute-stream": { @@ -5407,6 +10671,24 @@ "dev": true, "license": "ISC" }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "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/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", @@ -5422,6 +10704,15 @@ "dev": true, "license": "MIT" }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-abi": { "version": "3.92.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", @@ -5459,10 +10750,9 @@ "optional": true }, "node_modules/node-releases": { - "version": "2.0.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", - "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", - "dev": true, + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", "license": "MIT", "engines": { "node": ">=18" @@ -5530,6 +10820,34 @@ "node": ">=10" } }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -5543,11 +10861,19 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "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==", + "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" @@ -5556,43 +10882,193 @@ "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==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "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==", + "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", - "optional": true, "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==", + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openai": { + "version": "6.44.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.44.0.tgz", + "integrity": "sha512-09/gH+8jH0RgUwsgWHAaxsKGRT5zVZ95IaJUnqAWj6XejIBmnFRwq2WUIF37VtDEsmGrtPmvCs5+yBSeZGWvkA==", + "license": "Apache-2.0", + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", + "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/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/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==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", - "dev": true, + "node_modules/ora/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==", "license": "MIT", "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { "node": ">=18" @@ -5601,40 +11077,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/openai": { - "version": "6.44.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.44.0.tgz", - "integrity": "sha512-09/gH+8jH0RgUwsgWHAaxsKGRT5zVZ95IaJUnqAWj6XejIBmnFRwq2WUIF37VtDEsmGrtPmvCs5+yBSeZGWvkA==", - "license": "Apache-2.0", - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" + "node_modules/ora/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==", + "license": "ISC", + "engines": { + "node": ">=14" }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, + "node_modules/ora/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==", "license": "MIT", "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-limit": { @@ -5682,11 +11151,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/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" @@ -5724,6 +11201,18 @@ "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==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse-semver": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", @@ -5797,6 +11286,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/patch-console": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", @@ -5806,6 +11304,12 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, + "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==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5820,7 +11324,6 @@ "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" @@ -5853,6 +11356,16 @@ "node": "20 || >=22" } }, + "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==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/path-type": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", @@ -5866,6 +11379,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -5883,7 +11403,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5892,6 +11411,88 @@ "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==", + "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==", + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/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==", + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/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==", + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/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==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/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==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -5902,6 +11503,66 @@ "node": ">=4" } }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "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.12", + "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==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -5957,6 +11618,94 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/pump": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", @@ -5992,7 +11741,6 @@ "version": "6.15.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -6008,7 +11756,6 @@ "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", @@ -6025,6 +11772,127 @@ ], "license": "MIT" }, + "node_modules/radix-ui": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.6.0.tgz", + "integrity": "sha512-EUEC70O03EgxWMP5aoqfBZ6iLC5bczFagGy7zhSYRt8o5DP7IWNiP3ywetse3L9b8843ExB0OGWZvgbYVJuNeg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.4", + "@radix-ui/react-accessible-icon": "1.1.10", + "@radix-ui/react-accordion": "1.2.14", + "@radix-ui/react-alert-dialog": "1.1.17", + "@radix-ui/react-arrow": "1.1.10", + "@radix-ui/react-aspect-ratio": "1.1.10", + "@radix-ui/react-avatar": "1.2.0", + "@radix-ui/react-checkbox": "1.3.5", + "@radix-ui/react-collapsible": "1.1.14", + "@radix-ui/react-collection": "1.1.10", + "@radix-ui/react-compose-refs": "1.1.3", + "@radix-ui/react-context": "1.1.4", + "@radix-ui/react-context-menu": "2.3.1", + "@radix-ui/react-dialog": "1.1.17", + "@radix-ui/react-direction": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.13", + "@radix-ui/react-dropdown-menu": "2.1.18", + "@radix-ui/react-focus-guards": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.10", + "@radix-ui/react-form": "0.1.10", + "@radix-ui/react-hover-card": "1.1.17", + "@radix-ui/react-label": "2.1.10", + "@radix-ui/react-menu": "2.1.18", + "@radix-ui/react-menubar": "1.1.18", + "@radix-ui/react-navigation-menu": "1.2.16", + "@radix-ui/react-one-time-password-field": "0.1.10", + "@radix-ui/react-password-toggle-field": "0.1.5", + "@radix-ui/react-popover": "1.1.17", + "@radix-ui/react-popper": "1.3.1", + "@radix-ui/react-portal": "1.1.12", + "@radix-ui/react-presence": "1.1.6", + "@radix-ui/react-primitive": "2.1.6", + "@radix-ui/react-progress": "1.1.10", + "@radix-ui/react-radio-group": "1.4.1", + "@radix-ui/react-roving-focus": "1.1.13", + "@radix-ui/react-scroll-area": "1.2.12", + "@radix-ui/react-select": "2.3.1", + "@radix-ui/react-separator": "1.1.10", + "@radix-ui/react-slider": "1.4.1", + "@radix-ui/react-slot": "1.3.0", + "@radix-ui/react-switch": "1.3.1", + "@radix-ui/react-tabs": "1.1.15", + "@radix-ui/react-toast": "1.2.17", + "@radix-ui/react-toggle": "1.1.12", + "@radix-ui/react-toggle-group": "1.1.13", + "@radix-ui/react-toolbar": "1.1.13", + "@radix-ui/react-tooltip": "1.2.10", + "@radix-ui/react-use-callback-ref": "1.1.2", + "@radix-ui/react-use-controllable-state": "1.2.3", + "@radix-ui/react-use-effect-event": "0.0.3", + "@radix-ui/react-use-escape-keydown": "1.1.2", + "@radix-ui/react-use-is-hydrated": "0.1.1", + "@radix-ui/react-use-layout-effect": "1.1.2", + "@radix-ui/react-use-size": "1.1.2", + "@radix-ui/react-visually-hidden": "1.2.6" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "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==", + "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==", + "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/raw-body/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "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/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -6108,6 +11976,41 @@ } } }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-hook-form": { + "version": "7.81.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.81.0.tgz", + "integrity": "sha512-ocbmr2p5KBMoAfj4WCUvped33lVi1Kd5DuDUvQDnB6VEAacOjPI/jMbtDdbhco4y9ct4xUuCmMY0b/C9L0QHjw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/react-reconciler": { "version": "0.33.0", "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", @@ -6123,6 +12026,75 @@ "react": "^19.2.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -6293,11 +12265,50 @@ "node": ">= 6" } }, + "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==", + "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/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redent/node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "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" @@ -6307,7 +12318,6 @@ "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" @@ -6333,7 +12343,6 @@ "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", @@ -6347,11 +12356,71 @@ "dev": true, "license": "MIT" }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.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==", + "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/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" @@ -6364,7 +12433,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -6405,79 +12473,265 @@ ], "license": "MIT" }, - "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/secretlint": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", + "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@secretlint/config-creator": "^10.2.2", + "@secretlint/formatter": "^10.2.2", + "@secretlint/node": "^10.2.2", + "@secretlint/profiler": "^10.2.2", + "debug": "^4.4.1", + "globby": "^14.1.0", + "read-pkg": "^9.0.1" + }, + "bin": { + "secretlint": "bin/secretlint.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "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==", + "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": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/send/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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/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==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "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==", + "license": "ISC" }, - "node_modules/sax": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", - "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", - "dev": true, - "license": "BlueOak-1.0.0", + "node_modules/shadcn": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/shadcn/-/shadcn-4.12.0.tgz", + "integrity": "sha512-o781ieQziCnXH2FKsEqxp1fnbHdbgAPO9inTSPeZ59hQfsZXuMGp3ul8oFSV5KQS4nbUK9b+DrDE6C7OvfKKQQ==", + "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": ">=11.0.0" + "node": ">=20.18.1" } }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" + "node_modules/shadcn/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } }, - "node_modules/secretlint": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/secretlint/-/secretlint-10.2.2.tgz", - "integrity": "sha512-xVpkeHV/aoWe4vP4TansF622nBEImzCY73y/0042DuJ29iKIaqgoJ8fGxre3rVSHHbxar4FdJobmTnLp9AU0eg==", - "dev": true, + "node_modules/shadcn/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==", "license": "MIT", "dependencies": { - "@secretlint/config-creator": "^10.2.2", - "@secretlint/formatter": "^10.2.2", - "@secretlint/node": "^10.2.2", - "@secretlint/profiler": "^10.2.2", - "debug": "^4.4.1", - "globby": "^14.1.0", - "read-pkg": "^9.0.1" - }, - "bin": { - "secretlint": "bin/secretlint.js" + "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.0.0" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "node_modules/shadcn/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==", "license": "MIT", "dependencies": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" }, "engines": { - "node": ">=4" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/shadcn/node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, "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" @@ -6490,7 +12744,6 @@ "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" @@ -6513,7 +12766,6 @@ "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", @@ -6533,7 +12785,6 @@ "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", @@ -6550,7 +12801,6 @@ "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", @@ -6569,7 +12819,6 @@ "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", @@ -6585,6 +12834,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -6640,6 +12896,12 @@ "simple-concat": "^1.0.0" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "license": "MIT" + }, "node_modules/slash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", @@ -6681,6 +12943,34 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "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==", + "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/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -6740,6 +13030,41 @@ "node": ">=8" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -6777,6 +13102,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-ansi": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", @@ -6792,6 +13146,15 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "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==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/strip-bom-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", @@ -6801,6 +13164,31 @@ "node": ">=0.10.0" } }, + "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==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6854,6 +13242,39 @@ "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/systeminformation": { + "version": "5.31.11", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.31.11.tgz", + "integrity": "sha512-I6O7iaUj23AXRgCPDDnvi3xHvdOLp4+1YMbF+X194lJwY1NeWojgHJPhslVKcmTtrLTguRk3QJK+xEdTiI3P0w==", + "license": "MIT", + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, "node_modules/table": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", @@ -6980,6 +13401,37 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz", + "integrity": "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -7064,6 +13516,19 @@ "url": "https://bevry.me/fund" } }, + "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==", + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinycolor2": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", @@ -7107,6 +13572,36 @@ "tinycolor2": "^1.0.0" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.7.tgz", + "integrity": "sha512-56L0/9HELHSsG1bFCzay8UoLxzRL7kpFf7Wl5q/kSYwiSJGACvro61xnKzPNM+SadxllzdtXsKDSXE7HPeqIAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.7" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.7", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.7.tgz", + "integrity": "sha512-rNlAI8fKn/JckBMUSbNL/ES2kmDiurWaE49l+ikwEc9A6lFR7gMx9AhgQMQKBK4H5w4pKLH64JzZfB99uRsGNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", @@ -7121,7 +13616,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -7130,24 +13624,82 @@ "node": ">=8.0" } }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18.12" + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" }, - "peerDependencies": { - "typescript": ">=4.8.4" + "engines": { + "node": ">=6" } }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD" }, "node_modules/tsx": { @@ -7193,6 +13745,16 @@ "node": "*" } }, + "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-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7221,6 +13783,62 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/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==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/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==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/typed-rest-client": { "version": "1.8.11", "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", @@ -7237,7 +13855,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -7304,7 +13922,6 @@ "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" @@ -7317,17 +13934,24 @@ "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==", + "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", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, "funding": [ { "type": "opencollective", @@ -7371,13 +13995,54 @@ "dev": true, "license": "MIT" }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "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", - "optional": true + "license": "MIT" }, "node_modules/validate-npm-package-license": { "version": "3.0.4", @@ -7389,6 +14054,37 @@ "spdx-expression-parse": "^3.0.0" } }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vaul": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", + "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/version-range": { "version": "4.15.0", "resolved": "https://registry.npmjs.org/version-range/-/version-range-4.15.0.tgz", @@ -7402,6 +14098,194 @@ "url": "https://bevry.me/fund" } }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -7426,11 +14310,25 @@ "node": ">=18" } }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "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" @@ -7442,6 +14340,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/widest-line": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-6.0.0.tgz", @@ -7500,9 +14415,7 @@ "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", - "optional": true + "license": "ISC" }, "node_modules/ws": { "version": "8.21.0", @@ -7541,6 +14454,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/xml2js": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", @@ -7565,6 +14488,13 @@ "node": ">=4.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -7578,7 +14508,6 @@ "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/yaml": { @@ -7677,6 +14606,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yoga-layout": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/yoga-layout/-/yoga-layout-3.2.1.tgz", @@ -7692,6 +14648,15 @@ "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==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, "node_modules/zod-validation-error": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", @@ -7788,20 +14753,445 @@ }, "packages/vscode-ide-companion": { "name": "deepcode-vscode", - "version": "0.1.23", + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "@fontsource-variable/inter": "^5.2.8", + "@hookform/resolvers": "^5.4.0", + "@radix-ui/react-collapsible": "^1.1.14", + "@radix-ui/react-popover": "^1.1.17", + "@radix-ui/react-scroll-area": "^1.2.12", + "@radix-ui/react-separator": "^1.1.10", + "@radix-ui/react-slot": "^1.3.0", + "@radix-ui/react-tooltip": "^1.2.10", + "@tanstack/react-query": "^5.90.11", + "@vegamo/deepcode-core": "file:../core", + "@webview-rpc/client": "^1.0.17", + "@webview-rpc/host": "^1.0.17", + "@webview-rpc/react-query": "^1.0.17", + "cmdk": "^1.1.1", + "embla-carousel-react": "^8.6.0", + "framer-motion": "^12.42.2", + "highlight.js": "^11.11.1", + "lucide-react": "^1.21.0", + "markdown-it": "^14.2.0", + "markdown-it-task-lists": "^2.1.1", + "radix-ui": "^1.6.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-hook-form": "^7.81.0", + "semver": "^7.8.5", + "shadcn": "^4.12.0", + "sonner": "^2.0.7", + "vaul": "^1.1.2", + "zod": "^4.1.13" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.3.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@types/markdown-it": "^14.1.1", + "@types/node": "22.x", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@types/semver": "^7.7.1", + "@types/vscode": "^1.85.0", + "@typescript-eslint/eslint-plugin": "^8.64.0", + "@typescript-eslint/parser": "^8.64.0", + "@vitejs/plugin-react-swc": "^4.2.1", + "@vscode/vsce": "^3.6.0", + "autoprefixer": "^10.5.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "esbuild": "^0.28.1", + "jsdom": "^29.1.1", + "postcss": "^8.5.15", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.3.1", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.2", + "vite": "^7.2.2", + "vitest": "^4.1.10" + }, + "engines": { + "vscode": "^1.85.0" + } + }, + "packages/vscode-ide-companion-v2": { + "name": "deepcode-vscode-v2", + "version": "0.2.0", + "extraneous": true, "license": "MIT", "dependencies": { + "@fontsource-variable/inter": "^5.2.8", + "@radix-ui/react-collapsible": "^1.1.14", + "@radix-ui/react-popover": "^1.1.17", + "@radix-ui/react-scroll-area": "^1.2.12", + "@radix-ui/react-separator": "^1.1.10", + "@radix-ui/react-slot": "^1.3.0", + "@radix-ui/react-tooltip": "^1.2.10", + "@tanstack/react-query": "^5.90.11", "@vegamo/deepcode-core": "file:../core", - "markdown-it": "^14.2.0" + "@webview-rpc/client": "^1.0.17", + "@webview-rpc/host": "^1.0.17", + "@webview-rpc/react-query": "^1.0.17", + "lucide-react": "^1.21.0", + "markdown-it": "^14.2.0", + "radix-ui": "^1.6.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "shadcn": "^4.12.0", + "zod": "^4.1.13" }, "devDependencies": { + "@tailwindcss/vite": "^4.3.1", "@types/markdown-it": "^14.1.1", + "@types/node": "22.x", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", "@types/vscode": "^1.85.0", - "@vscode/vsce": "^3.6.0" + "@vitejs/plugin-react-swc": "^4.2.1", + "@vscode/vsce": "^3.6.0", + "autoprefixer": "^10.5.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "esbuild": "^0.28.1", + "postcss": "^8.5.15", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.3.1", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.2", + "vite": "^7.2.2" }, "engines": { "vscode": "^1.85.0" } + }, + "packages/vscode-ide-companion/node_modules/@types/node": { + "version": "22.20.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz", + "integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "packages/vscode-ide-companion/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/type-utils": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.64.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "packages/vscode-ide-companion/node_modules/@typescript-eslint/parser": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "packages/vscode-ide-companion/node_modules/@typescript-eslint/project-service": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.64.0", + "@typescript-eslint/types": "^8.64.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "packages/vscode-ide-companion/node_modules/@typescript-eslint/scope-manager": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "packages/vscode-ide-companion/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "packages/vscode-ide-companion/node_modules/@typescript-eslint/type-utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "packages/vscode-ide-companion/node_modules/@typescript-eslint/types": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "packages/vscode-ide-companion/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.64.0", + "@typescript-eslint/tsconfig-utils": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "packages/vscode-ide-companion/node_modules/@typescript-eslint/utils": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "packages/vscode-ide-companion/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.64.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "packages/vscode-ide-companion/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, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "packages/vscode-ide-companion/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": "18 || 20 || >=22" + } + }, + "packages/vscode-ide-companion/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "packages/vscode-ide-companion/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "packages/vscode-ide-companion/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" + } + }, + "packages/vscode-ide-companion/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "packages/vscode-ide-companion/node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "packages/vscode-ide-companion/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" } } } diff --git a/packages/core/src/common/openai-client.ts b/packages/core/src/common/openai-client.ts index d3b56c08..62ecaed0 100644 --- a/packages/core/src/common/openai-client.ts +++ b/packages/core/src/common/openai-client.ts @@ -73,7 +73,8 @@ export function createOpenAIClient(projectRoot: string = process.cwd()): { apiKey: settings.apiKey, baseURL: settings.baseURL || undefined, // eslint-disable-next-line @typescript-eslint/no-explicit-any - fetch: (url: any, init: any) => undiciFetch(url, { ...init, dispatcher: keepAliveAgent }), + fetch: ((url: any, init: any) => + undiciFetch(url, { ...init, dispatcher: keepAliveAgent })) as unknown as typeof fetch, }); cachedOpenAIKey = cacheKey; diff --git a/packages/core/src/session.ts b/packages/core/src/session.ts index 3add448e..f293c074 100644 --- a/packages/core/src/session.ts +++ b/packages/core/src/session.ts @@ -243,6 +243,7 @@ export type SessionEntry = { processes: Map | null; // {pid: process info} askPermissions?: AskPermissionRequest[]; planMode?: boolean; + askUserQuestionSummary?: boolean; }; export type SessionsIndex = { @@ -294,6 +295,7 @@ export type UserPromptContent = { permissions?: UserToolPermission[]; alwaysAllows?: PermissionScope[]; planMode?: boolean; + askUserQuestionSummary?: boolean; }; export type SkillInfo = { @@ -1120,6 +1122,7 @@ ${agentInstructions} updateTime: now, processes: null, planMode: Boolean(userPrompt.planMode), + askUserQuestionSummary: Boolean(userPrompt.askUserQuestionSummary), }; index.entries.push(entry); const sortedEntries = index.entries.slice().sort((a, b) => { @@ -1204,6 +1207,7 @@ ${agentInstructions} const nextPlanMode = Boolean(userPrompt.planMode); const updated = this.updateSessionEntry(sessionId, (entry) => ({ ...entry, + ...userPrompt, status: "pending", failReason: null, askPermissions: undefined, @@ -2374,6 +2378,7 @@ ${agentInstructions} permissions: prompt.permissions ? prompt.permissions.map((permission) => ({ ...permission })) : undefined, alwaysAllows: prompt.alwaysAllows ? [...prompt.alwaysAllows] : undefined, planMode: prompt.planMode, + askUserQuestionSummary: prompt.askUserQuestionSummary, }; } @@ -2781,6 +2786,7 @@ ${agentInstructions} processes: this.deserializeProcesses(value.processes), askPermissions: normalizeAskPermissions(value.askPermissions), planMode: value.planMode === true, + askUserQuestionSummary: value.askUserQuestionSummary === true, }; } diff --git a/packages/vscode-ide-companion/.gitignore b/packages/vscode-ide-companion/.gitignore new file mode 100644 index 00000000..6caf5da6 --- /dev/null +++ b/packages/vscode-ide-companion/.gitignore @@ -0,0 +1,2 @@ +# Generated by build, content mirrored from packages/core/templates/ +templates/ diff --git a/packages/vscode-ide-companion/.vscode/launch.json b/packages/vscode-ide-companion/.vscode/launch.json new file mode 100644 index 00000000..f7560121 --- /dev/null +++ b/packages/vscode-ide-companion/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug VS Code Companion", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "preLaunchTask": "Start Dev Watch", + "presentation": { + "group": "deepcode" + } + } + ] +} diff --git a/packages/vscode-ide-companion/.vscode/tasks.json b/packages/vscode-ide-companion/.vscode/tasks.json new file mode 100644 index 00000000..d529b7a6 --- /dev/null +++ b/packages/vscode-ide-companion/.vscode/tasks.json @@ -0,0 +1,48 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Dev Watch", + "dependsOn": ["npm: watch", "npm: dev:webview"], + "dependsOrder": "parallel", + "problemMatcher": [] + }, + { + "type": "npm", + "script": "dev:webview", + "isBackground": true, + "problemMatcher": { + "owner": "custom", + "pattern": { + "regexp": "^$" + }, + "background": { + "activeOnStart": true, + "beginsPattern": "^.*VITE.*$", + "endsPattern": "^.*ready in.*$" + } + }, + "group": { + "kind": "none", + "isDefault": true + } + }, + { + "type": "npm", + "script": "watch", + "isBackground": true, + "problemMatcher": { + "owner": "custom", + "pattern": { + "regexp": "^$" + }, + "background": { + "activeOnStart": true, + "beginsPattern": "^.*Copied core/templates.*$", + "endsPattern": "^.*Watching for changes.*$" + } + }, + "group": "none" + } + ] +} diff --git a/packages/vscode-ide-companion/.vscodeignore b/packages/vscode-ide-companion/.vscodeignore new file mode 100644 index 00000000..0cbec04e --- /dev/null +++ b/packages/vscode-ide-companion/.vscodeignore @@ -0,0 +1,21 @@ +.vscode/** +.vscode-test/** +node_modules/** +src/** +out/** +*.vsix +.gitignore +tsconfig.json +tsconfig.*.json +esbuild.config.mjs +eslint.config.mjs +vite.config.* +vitest.config.* +scripts/** +tests/** +__tests__/** +*.test.ts +*.test.tsx +*.spec.ts +*.spec.tsx +components.json diff --git a/packages/vscode-ide-companion/README_en.md b/packages/vscode-ide-companion/README-en.md similarity index 93% rename from packages/vscode-ide-companion/README_en.md rename to packages/vscode-ide-companion/README-en.md index 40b199bc..c1c43847 100644 --- a/packages/vscode-ide-companion/README_en.md +++ b/packages/vscode-ide-companion/README-en.md @@ -40,7 +40,7 @@ Deep Code supports agent skills that allows you to extend the assistant's capabi ## Screenshot -![screenshot](resources/deepcode_screenshot.png) +![screenshot](https://raw.githubusercontent.com/lessweb/deepcode-cli/main/resources/deepcode_screenshot.png) ## Deep Code CLI @@ -58,7 +58,7 @@ npm install -g @vegamo/deepcode-cli ### How can I move Deep Code from the left sidebar to the right (Secondary Side Bar) in VS Code? -![faq1](resources/faq1.gif) +![faq1](https://raw.githubusercontent.com/lessweb/deepcode-cli/main/resources/faq1.gif) ### Does Deep Code support understanding images? diff --git a/packages/vscode-ide-companion/README_cn.md b/packages/vscode-ide-companion/README-zh_CN.md similarity index 93% rename from packages/vscode-ide-companion/README_cn.md rename to packages/vscode-ide-companion/README-zh_CN.md index ee11be0f..cc4aa258 100644 --- a/packages/vscode-ide-companion/README_cn.md +++ b/packages/vscode-ide-companion/README-zh_CN.md @@ -39,7 +39,7 @@ Deep Code 支持 agent skills,允许您扩展助手的能力: ## 截图示例 -![screenshot](resources/deepcode_screenshot.png) +![screenshot](https://raw.githubusercontent.com/lessweb/deepcode-cli/main/resources/deepcode_screenshot.png) ## Deep Code CLI @@ -57,7 +57,7 @@ npm install -g @vegamo/deepcode-cli ### 如何将 Deep Code 从左侧边栏移动到右侧边栏(Secondary Side Bar)? -![faq1](resources/faq1.gif) +![faq1](https://raw.githubusercontent.com/lessweb/deepcode-cli/main/resources/faq1.gif) ### Deep Code是否支持理解图片? diff --git a/packages/vscode-ide-companion/README.md b/packages/vscode-ide-companion/README.md index ee11be0f..cc4aa258 100644 --- a/packages/vscode-ide-companion/README.md +++ b/packages/vscode-ide-companion/README.md @@ -39,7 +39,7 @@ Deep Code 支持 agent skills,允许您扩展助手的能力: ## 截图示例 -![screenshot](resources/deepcode_screenshot.png) +![screenshot](https://raw.githubusercontent.com/lessweb/deepcode-cli/main/resources/deepcode_screenshot.png) ## Deep Code CLI @@ -57,7 +57,7 @@ npm install -g @vegamo/deepcode-cli ### 如何将 Deep Code 从左侧边栏移动到右侧边栏(Secondary Side Bar)? -![faq1](resources/faq1.gif) +![faq1](https://raw.githubusercontent.com/lessweb/deepcode-cli/main/resources/faq1.gif) ### Deep Code是否支持理解图片? diff --git a/packages/vscode-ide-companion/resources/deepcoding_icon.png b/packages/vscode-ide-companion/assets/deepcoding_icon.png similarity index 100% rename from packages/vscode-ide-companion/resources/deepcoding_icon.png rename to packages/vscode-ide-companion/assets/deepcoding_icon.png diff --git a/packages/vscode-ide-companion/resources/deepcoding_icon.svg b/packages/vscode-ide-companion/assets/deepcoding_icon.svg similarity index 100% rename from packages/vscode-ide-companion/resources/deepcoding_icon.svg rename to packages/vscode-ide-companion/assets/deepcoding_icon.svg diff --git a/packages/vscode-ide-companion/assets/theme.svg b/packages/vscode-ide-companion/assets/theme.svg new file mode 100644 index 00000000..867c46d2 --- /dev/null +++ b/packages/vscode-ide-companion/assets/theme.svg @@ -0,0 +1 @@ + diff --git a/packages/vscode-ide-companion/components.json b/packages/vscode-ide-companion/components.json new file mode 100644 index 00000000..f6f78e8e --- /dev/null +++ b/packages/vscode-ide-companion/components.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "radix-vega", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/webview/index.css", + "baseColor": "mauve", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/webview/components", + "utils": "@/webview/lib/utils", + "ui": "@/webview/components/ui", + "lib": "@/webview/lib", + "hooks": "@/webview/hooks" + }, + "menuColor": "default", + "menuAccent": "subtle", + "registries": {} +} diff --git a/packages/vscode-ide-companion/esbuild.config.mjs b/packages/vscode-ide-companion/esbuild.config.mjs new file mode 100644 index 00000000..ee19c992 --- /dev/null +++ b/packages/vscode-ide-companion/esbuild.config.mjs @@ -0,0 +1,49 @@ +/* global console, process */ +import * as esbuild from "esbuild"; +import { cpSync, existsSync, rmSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = join(__dirname); +const packagesRoot = join(__dirname, ".."); +const coreTemplates = join(packagesRoot, "core", "templates"); +const localTemplates = join(root, "templates"); + +// Copy templates from core for development +if (existsSync(coreTemplates)) { + if (existsSync(localTemplates)) { + rmSync(localTemplates, { recursive: true, force: true }); + } + cpSync(coreTemplates, localTemplates, { recursive: true, dereference: true }); + console.log("✅ Copied core/templates/ → templates/ (for development)"); +} + +const isWatch = process.argv.includes("--watch"); + +/** @type {import('esbuild').BuildOptions} */ +const options = { + entryPoints: ["src/extension.ts"], + outfile: "dist/extension.js", + bundle: true, + format: "esm", + platform: "node", + target: "ES2022", + sourcemap: true, + minify: !isWatch, + external: ["vscode"], + // Inject a `require` function via createRequire so that dynamic require() + // calls from bundled CJS dependencies work in VSCode's ESM extension host. + banner: { + js: `import { createRequire as __deepcodeCreateRequire } from "node:module";var require = __deepcodeCreateRequire(import.meta.url);`, + }, +}; + +if (isWatch) { + const ctx = await esbuild.context(options); + await ctx.watch(); + console.log("Watching for changes..."); +} else { + await esbuild.build(options); + console.log("\n✅ dist/extension.js built successfully\n"); +} diff --git a/packages/vscode-ide-companion/eslint.config.mjs b/packages/vscode-ide-companion/eslint.config.mjs new file mode 100644 index 00000000..ee788d17 --- /dev/null +++ b/packages/vscode-ide-companion/eslint.config.mjs @@ -0,0 +1,38 @@ +import typescriptEslint from "@typescript-eslint/eslint-plugin"; +import tsParser from "@typescript-eslint/parser"; +import prettierConfig from "eslint-config-prettier"; + +export default [ + { + files: ["**/*.ts", "**/*.tsx"], + }, + { + plugins: { + "@typescript-eslint": typescriptEslint, + }, + + languageOptions: { + parser: tsParser, + parserOptions: { + jsx: true, + }, + ecmaVersion: 2022, + sourceType: "module", + }, + + rules: { + "@typescript-eslint/naming-convention": [ + "warn", + { + selector: "import", + format: ["camelCase", "PascalCase"], + }, + ], + + curly: "warn", + eqeqeq: "warn", + "no-throw-literal": "warn", + }, + }, + prettierConfig, // 禁用与 Prettier 冲突的 ESLint 规则 +]; diff --git a/packages/vscode-ide-companion/package.json b/packages/vscode-ide-companion/package.json index fc7f8118..49c0e9e9 100644 --- a/packages/vscode-ide-companion/package.json +++ b/packages/vscode-ide-companion/package.json @@ -1,13 +1,23 @@ { "name": "deepcode-vscode", - "version": "0.1.23", + "version": "0.1.34", "publisher": "vegamo", "displayName": "Deep Code", "description": "Deep Code VSCode companion — AI-assisted development in your editor", "license": "MIT", - "type": "commonjs", - "main": "./out/extension.js", - "preview": true, + "homepage": "https://deepcode.vegamo.cn", + "bugs": { + "url": "https://github.com/lessweb/deepcode-cli/issues" + }, + "galleryBanner": { + "color": "#1e1e2e", + "theme": "dark" + }, + "qna": false, + "pricing": "Free", + "type": "module", + "main": "./dist/extension.js", + "preview": false, "repository": { "type": "git", "url": "https://github.com/lessweb/deepcode-cli.git", @@ -29,26 +39,39 @@ "ide integration", "ide companion" ], - "icon": "resources/deepcoding_icon.png", - "activationEvents": [], - "files": [ - "out/extension.js", - "resources/**", - "templates/**", - "README.md", - "README_cn.md", - "README_en.md", - "LICENSE" + "icon": "assets/deepcoding_icon.png", + "activationEvents": [ + "onStartupFinished", + "onView:deepcode.chatView" ], "contributes": { + "markdown.markdownItPlugins": true, "commands": [ { "command": "deepcode.openView", "title": "Open Deep Code", "icon": { - "light": "resources/deepcoding_icon.svg", - "dark": "resources/deepcoding_icon.svg" + "light": "assets/deepcoding_icon.svg", + "dark": "assets/deepcoding_icon.svg" } + }, + { + "command": "deepcode.newchat", + "category": "Deep Code", + "title": "New Chat", + "icon": "$(add)" + }, + { + "command": "deepcode.settings", + "category": "Deep Code", + "title": "Settings", + "icon": "$(settings-gear)" + }, + { + "command": "deepcode.history", + "category": "Deep Code", + "title": "Show Chat History", + "icon": "$(history)" } ], "viewsContainers": { @@ -56,7 +79,7 @@ { "id": "deepcode", "title": "Deep Code", - "icon": "resources/deepcoding_icon.png" + "icon": "assets/deepcoding_icon.png" } ] }, @@ -65,7 +88,7 @@ { "id": "deepcode.chatView", "name": "Deep Code", - "icon": "resources/deepcoding_icon.png", + "icon": "assets/deepcoding_icon.png", "type": "webview" } ] @@ -76,23 +99,96 @@ "command": "deepcode.openView", "group": "navigation@100" } + ], + "view/title": [ + { + "command": "deepcode.newchat", + "group": "navigation", + "when": "view == deepcode.chatView" + }, + { + "command": "deepcode.settings", + "group": "navigation", + "when": "view == deepcode.chatView" + }, + { + "command": "deepcode.history", + "group": "navigation", + "when": "view == deepcode.chatView" + } ] } }, "scripts": { - "typecheck": "tsc -p ./ --noEmit", - "build": "node ../../scripts/esbuild-vscode.config.js", - "prepublishOnly": "npm run build", - "package": "vsce package --no-dependencies", - "test": "node src/tests/run-tests.mjs" + "typecheck": "tsc -p ./ --noEmit && tsc -p tsconfig.webview.json --noEmit", + "dev:webview": "vite", + "watch": "node esbuild.config.mjs --watch", + "build:webview": "vite build", + "build": "node scripts/copy-templates.js && node esbuild.config.mjs", + "vscode:prepublish": "npm run build:webview && npm run build", + "package": "vsce package --no-dependencies --allow-missing-repository", + "test": "vitest run", + "test:watch": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage" }, "dependencies": { + "@fontsource-variable/inter": "^5.2.8", + "@hookform/resolvers": "^5.4.0", + "@radix-ui/react-collapsible": "^1.1.14", + "@radix-ui/react-popover": "^1.1.17", + "@radix-ui/react-scroll-area": "^1.2.12", + "@radix-ui/react-separator": "^1.1.10", + "@radix-ui/react-slot": "^1.3.0", + "@radix-ui/react-tooltip": "^1.2.10", + "@tanstack/react-query": "^5.90.11", "@vegamo/deepcode-core": "file:../core", - "markdown-it": "^14.2.0" + "@webview-rpc/client": "^1.0.17", + "@webview-rpc/host": "^1.0.17", + "@webview-rpc/react-query": "^1.0.17", + "cmdk": "^1.1.1", + "embla-carousel-react": "^8.6.0", + "framer-motion": "^12.42.2", + "highlight.js": "^11.11.1", + "lucide-react": "^1.21.0", + "markdown-it": "^14.2.0", + "markdown-it-task-lists": "^2.1.1", + "radix-ui": "^1.6.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-hook-form": "^7.81.0", + "semver": "^7.8.5", + "shadcn": "^4.12.0", + "sonner": "^2.0.7", + "vaul": "^1.1.2", + "zod": "^4.1.13" }, "devDependencies": { + "@tailwindcss/vite": "^4.3.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/markdown-it": "^14.1.1", + "@types/node": "22.x", + "@types/react": "^19.2.2", + "@types/react-dom": "^19.2.2", + "@types/semver": "^7.7.1", "@types/vscode": "^1.85.0", - "@vscode/vsce": "^3.6.0" + "@typescript-eslint/eslint-plugin": "^8.64.0", + "@typescript-eslint/parser": "^8.64.0", + "@vitejs/plugin-react-swc": "^4.2.1", + "@vscode/vsce": "^3.6.0", + "autoprefixer": "^10.5.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "esbuild": "^0.28.1", + "jsdom": "^29.1.1", + "postcss": "^8.5.15", + "tailwind-merge": "^3.6.0", + "tailwindcss": "^4.3.1", + "tw-animate-css": "^1.4.0", + "typescript": "^5.9.2", + "vite": "^7.2.2", + "vitest": "^4.1.10" } } diff --git a/packages/vscode-ide-companion/resources/prompt-attachments.js b/packages/vscode-ide-companion/resources/prompt-attachments.js deleted file mode 100644 index e6c628ac..00000000 --- a/packages/vscode-ide-companion/resources/prompt-attachments.js +++ /dev/null @@ -1,293 +0,0 @@ -(function () { - const ATTACHMENT_LABEL = "粘贴的图像"; - const PREVIEW_OFFSET = 10; - - function createElement(tagName, className) { - const element = document.createElement(tagName); - if (className) { - element.className = className; - } - return element; - } - - function isImageFile(file) { - return Boolean(file && typeof file.type === "string" && file.type.startsWith("image/")); - } - - function readFileAsDataUrl(file) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(typeof reader.result === "string" ? reader.result : ""); - reader.onerror = () => reject(reader.error || new Error("Failed to read file.")); - reader.readAsDataURL(file); - }); - } - - function pickImageFileFromClipboard(event) { - const items = Array.from(event.clipboardData?.items || []); - for (const item of items) { - if (item.kind !== "file") { - continue; - } - const file = item.getAsFile(); - if (isImageFile(file)) { - return file; - } - } - return null; - } - - function createPromptAttachmentManager(options) { - const promptInput = options?.promptInput; - const inputWrap = options?.inputWrap; - const toolsLine = options?.toolsLine; - const onAttachmentChange = - typeof options?.onAttachmentChange === "function" ? options.onAttachmentChange : function () {}; - - if (!promptInput || !inputWrap || !toolsLine) { - throw new Error("Prompt attachment manager requires promptInput, inputWrap, and toolsLine."); - } - - let attachments = []; - let nextAttachmentId = 0; - let previewPopup = null; - let previewImage = null; - let previewAnchor = null; - - function ensurePreviewPopup() { - if (previewPopup) { - return; - } - - previewPopup = createElement("div", "chat-attached-context-preview"); - previewImage = createElement("img", "chat-attached-context-preview-image"); - previewImage.alt = ATTACHMENT_LABEL; - previewPopup.appendChild(previewImage); - document.body.appendChild(previewPopup); - } - - function hidePreview() { - if (!previewPopup) { - return; - } - previewAnchor = null; - previewPopup.classList.remove("show"); - } - - function updatePreviewPosition(anchor) { - if (!previewPopup || !anchor) { - return; - } - - const rect = anchor.getBoundingClientRect(); - const popupRect = previewPopup.getBoundingClientRect(); - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - - let left = rect.left; - let top = rect.top - popupRect.height - PREVIEW_OFFSET; - - if (left + popupRect.width > viewportWidth - 12) { - left = viewportWidth - popupRect.width - 12; - } - if (left < 12) { - left = 12; - } - if (top < 12) { - top = rect.bottom + PREVIEW_OFFSET; - } - if (top + popupRect.height > viewportHeight - 12) { - top = Math.max(12, viewportHeight - popupRect.height - 12); - } - - previewPopup.style.left = left + "px"; - previewPopup.style.top = top + "px"; - } - - function showPreview(anchor, attachment) { - if (!attachment) { - return; - } - - ensurePreviewPopup(); - previewAnchor = anchor; - previewImage.src = attachment.dataUrl; - previewPopup.classList.add("show"); - updatePreviewPosition(anchor); - } - - function emitChange() { - onAttachmentChange({ - hasAttachments: attachments.length > 0, - attachments: attachments.slice(), - }); - } - - function clear() { - attachments = []; - toolsLine.innerHTML = ""; - toolsLine.classList.remove("has-attachment"); - hidePreview(); - emitChange(); - } - - function removeAttachment(id) { - const nextAttachments = attachments.filter((attachment) => attachment.id !== id); - if (nextAttachments.length === attachments.length) { - return; - } - attachments = nextAttachments; - render(); - emitChange(); - } - - function createAttachmentNode(attachment) { - const wrapper = createElement("div", "chat-attached-context-attachment show-file-icons"); - wrapper.tabIndex = 0; - wrapper.setAttribute("role", "button"); - wrapper.setAttribute("aria-label", ATTACHMENT_LABEL + " (删除)"); - wrapper.dataset.attachmentId = String(attachment.id); - wrapper.draggable = true; - - const removeButton = createElement("a", "monaco-button codicon codicon-close"); - removeButton.tabIndex = -1; - removeButton.setAttribute("role", "button"); - removeButton.setAttribute("aria-label", "从上下文中移除"); - removeButton.href = "#"; - removeButton.textContent = "×"; - removeButton.addEventListener("click", (event) => { - event.preventDefault(); - event.stopPropagation(); - removeAttachment(attachment.id); - }); - - const iconLabel = createElement("div", "monaco-icon-label"); - const iconLabelContainer = createElement("div", "monaco-icon-label-container"); - const iconNameContainer = createElement("span", "monaco-icon-name-container"); - iconLabelContainer.appendChild(iconNameContainer); - iconLabel.appendChild(iconLabelContainer); - - const pill = createElement("div", "chat-attached-context-pill"); - const image = createElement("img", "chat-attached-context-pill-image"); - image.src = attachment.dataUrl; - image.alt = ATTACHMENT_LABEL; - pill.appendChild(image); - - const text = createElement("span", "chat-attached-context-custom-text"); - text.textContent = ATTACHMENT_LABEL; - - wrapper.appendChild(removeButton); - wrapper.appendChild(iconLabel); - wrapper.appendChild(pill); - wrapper.appendChild(text); - - const show = () => showPreview(wrapper, attachment); - wrapper.addEventListener("mouseenter", show); - wrapper.addEventListener("focus", show); - wrapper.addEventListener("mouseleave", hidePreview); - wrapper.addEventListener("blur", hidePreview); - wrapper.addEventListener("dragstart", (event) => { - event.preventDefault(); - }); - wrapper.addEventListener("keydown", (event) => { - if (event.key === "Delete" || event.key === "Backspace") { - event.preventDefault(); - removeAttachment(attachment.id); - } - }); - - return wrapper; - } - - function render() { - toolsLine.innerHTML = ""; - toolsLine.classList.toggle("has-attachment", attachments.length > 0); - if (attachments.length === 0) { - hidePreview(); - return; - } - for (const attachment of attachments) { - toolsLine.appendChild(createAttachmentNode(attachment)); - } - if (previewAnchor && !toolsLine.contains(previewAnchor)) { - hidePreview(); - } - } - - function addAttachmentData(data) { - if (!data?.dataUrl) { - return false; - } - - nextAttachmentId += 1; - attachments.push({ - id: nextAttachmentId, - name: data.name || ATTACHMENT_LABEL, - mimeType: data.mimeType || "image/png", - dataUrl: data.dataUrl, - label: ATTACHMENT_LABEL, - }); - render(); - emitChange(); - return true; - } - - async function addAttachmentFromFile(file) { - if (!isImageFile(file)) { - return false; - } - - const dataUrl = await readFileAsDataUrl(file); - return addAttachmentData({ - name: file.name || ATTACHMENT_LABEL, - mimeType: file.type || "image/png", - dataUrl, - label: ATTACHMENT_LABEL, - }); - } - - async function handlePaste(event) { - const file = pickImageFileFromClipboard(event); - if (!file) { - return; - } - - event.preventDefault(); - try { - await addAttachmentFromFile(file); - } catch (error) { - console.error("Failed to attach pasted image.", error); - } - } - - promptInput.addEventListener("paste", handlePaste); - - window.addEventListener("resize", () => { - if (previewPopup?.classList.contains("show") && previewAnchor) { - updatePreviewPosition(previewAnchor); - } - }); - - window.addEventListener( - "scroll", - () => { - if (previewPopup?.classList.contains("show") && previewAnchor) { - updatePreviewPosition(previewAnchor); - } - }, - true - ); - - return { - clear, - hasAttachments() { - return attachments.length > 0; - }, - getImageUrls() { - return attachments.map((attachment) => attachment.dataUrl); - }, - }; - } - - window.createPromptAttachmentManager = createPromptAttachmentManager; -})(); diff --git a/packages/vscode-ide-companion/resources/webview.css b/packages/vscode-ide-companion/resources/webview.css deleted file mode 100644 index 98ffa62e..00000000 --- a/packages/vscode-ide-companion/resources/webview.css +++ /dev/null @@ -1,1605 +0,0 @@ -/* CSS Variables */ -:root { - --bg: var(--vscode-editor-background); - --panel: var(--vscode-sideBar-background); - --panel-2: var(--vscode-editor-inactiveSelectionBackground); - --muted: var(--vscode-descriptionForeground); - --accent: var(--vscode-focusBorder); - --accent-2: var(--vscode-button-background); - --danger: var(--vscode-errorForeground); - --shadow: rgba(0, 0, 0, 0.15); - --border-color: var(--vscode-panel-border); - --prompt-line-height: 18px; - --prompt-min-height: calc(var(--prompt-line-height) * 3 + 20px); - --prompt-max-height: calc(var(--prompt-line-height) * 10 + 20px); -} - -/* Global Styles */ -* { - box-sizing: border-box; -} - -body { - margin: 0; - padding: 0; - height: 100vh; - overflow-x: hidden; - background: var(--bg); - color: var(--vscode-foreground); - font-family: var(--vscode-font-family); - font-size: var(--vscode-font-size); -} - -/* App Container */ -.app { - height: 100vh; - display: flex; - flex-direction: column; - position: relative; - overflow-x: hidden; -} - -/* Header Container */ -.header-container { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 4px 16px; - background: var(--vscode-sideBar-background); - border-bottom: 1px solid var(--border-color); - flex-shrink: 0; - position: relative; -} - -.header-left { - position: relative; - min-width: 0; -} - -/* Session Selector */ -.session-selector { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 10px; - background: transparent; - border: none; - border-radius: 4px; - cursor: pointer; - transition: background 0.2s; - width: 100%; -} - -.session-selector:hover { - background: var(--vscode-toolbar-hoverBackground); -} - -.session-selector.open .session-selector-icon { - transform: rotate(180deg); -} - -.session-selector-title { - display: flex; - align-items: center; - gap: 8px; - font-size: 13px; - color: var(--vscode-foreground); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - text-align: left; -} - -.session-logo { - width: 16px; - height: 16px; - flex-shrink: 0; -} - -.session-title-text { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; -} - -.session-selector-icon { - width: 16px; - height: 16px; - fill: var(--vscode-foreground); - transition: transform 0.2s; - flex-shrink: 0; -} - -/* Header New Button */ -.header-new-btn { - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 12px; - transition: background 0.2s; - background: none; - display: flex; - align-items: center; - justify-content: center; - padding: 6px; - flex-shrink: 0; -} - -.header-new-btn:hover { - background: var(--vscode-toolbar-hoverBackground); -} - -.header-new-icon { - width: 16px; - height: 16px; - fill: var(--vscode-foreground); -} - -/* Session Dropdown */ -.session-dropdown { - position: absolute; - top: calc(100% + 8px); - left: 16px; - right: 16px; - background: var(--vscode-dropdown-background); - border: 1px solid var(--border-color); - border-radius: 4px; - max-height: 400px; - z-index: 10000; - display: none; - box-shadow: 0 4px 12px var(--shadow); - flex-direction: column; -} - -.session-dropdown.show { - display: flex; -} - -.session-search-box { - padding: 8px; - flex-shrink: 0; -} - -.session-search-input { - width: 100%; - padding: 6px 10px; - background: var(--vscode-input-background); - color: var(--vscode-input-foreground); - border: 1px solid var(--vscode-input-border); - border-radius: 4px; - font-size: 13px; - outline: none; - font-family: var(--vscode-font-family); - outline: none; -} - -.session-search-input:focus { - border-color: var(--vscode-focusBorder); - outline: none; -} - -.session-search-input::placeholder { - color: var(--vscode-input-placeholderForeground); -} - -.session-dropdown-list { - flex: 1; - overflow-y: auto; - min-height: 0; - padding: 8px 12px 12px; -} - -.session-dropdown-group { - margin-bottom: 12px; -} - -.session-dropdown-group:last-child { - margin-bottom: 0; -} - -.session-dropdown-group-title { - font-size: 11px; - font-weight: 500; - color: var(--vscode-descriptionForeground); - letter-spacing: 0.5px; - padding: 6px 6px 8px; - margin-bottom: 2px; -} - -.session-dropdown-empty { - padding: 20px; - text-align: center; - color: var(--vscode-descriptionForeground); - font-size: 13px; -} - -.session-dropdown-item { - padding: 6px; - cursor: pointer; - transition: background 0.2s; - display: flex; - gap: 12px; - margin-bottom: 6px; - border-radius: 4px; - align-items: center; -} - -.session-dropdown-item:last-child { - margin-bottom: 0; -} - -.session-dropdown-item:hover { - background: var(--vscode-list-hoverBackground); -} - -.session-dropdown-item.active { - background: var(--vscode-list-activeSelectionBackground); -} - -.session-dropdown-item.active .session-dropdown-summary { - color: var(--vscode-list-activeSelectionForeground); -} - -.session-dropdown-item.active .session-dropdown-time { - color: var(--vscode-list-activeSelectionForeground); - opacity: 0.8; -} - -.session-dropdown-summary { - font-size: 13px; - color: var(--vscode-foreground); - margin-bottom: 4px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex: 1; - font-weight: 500; -} - -.session-dropdown-time { - font-size: 11px; - color: var(--vscode-descriptionForeground); -} - -.session-dropdown-summary mark { - background-color: var(--vscode-editor-findMatchHighlightBackground); - color: var(--vscode-editor-foreground); - padding: 0 2px; - border-radius: 2px; -} - -/* Chat Container */ -.chat-container { - display: flex; - flex-direction: column; - flex: 1; - overflow: hidden; - overflow-x: hidden; -} - -.chat-container.hidden { - display: none; -} - -/* Messages */ -.messages { - flex: 1; - overflow-y: auto; - overflow-x: hidden; - padding: 16px; - display: flex; - flex-direction: column; - gap: 1px; - background: var(--vscode-sideBar-background); -} - -/* Message Bubbles */ -.bubble { - padding: 16px; - font-size: 13px; - width: 100%; - border: 1px solid transparent; - color: var(--vscode-foreground); - border-radius: 6px; - position: relative; - overflow-wrap: break-word; - word-break: break-word; -} - -/* Collapsible Bubble */ -.bubble-collapsible-header { - display: flex; - align-items: flex-start; - gap: 8px; - cursor: pointer; - user-select: none; - padding: 4px 0; - position: relative; -} - -.bubble-collapsible-header:hover .bubble-title-text { - opacity: 0.8; -} - -.bubble-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background-color: var(--vscode-descriptionForeground); - flex-shrink: 0; - margin-top: 6px; - position: relative; - z-index: 3; -} - -.bubble-dot.success { - background-color: var(--vscode-terminal-ansiGreen); -} - -.bubble-dot.error { - background-color: var(--vscode-terminal-ansiRed); -} - -/* 连接线:只在有 connect-to-prev class 时显示向上的连接线 */ -.bubble-dot.connect-to-prev::before { - content: ""; - position: absolute; - left: 3.5px; - bottom: 8px; - width: 1px; - height: var(--line-height, 0px); - background-color: var(--vscode-panel-border); - z-index: 0; -} - -.bubble-title { - flex: 1; - font-size: 13px; - color: var(--vscode-foreground); - display: flex; - align-items: center; - gap: 6px; - flex-wrap: nowrap; - min-width: 0; - overflow: visible; -} - -.bubble-title-text { - display: inline-flex; - align-items: center; - gap: 6px; - flex: 1; - min-width: 0; - overflow: visible; -} - -.bubble-title-text b { - text-transform: capitalize; - flex-shrink: 0; -} - -.bubble-title .tool-params { - color: var(--vscode-descriptionForeground); - font-weight: normal; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex: 1; - min-width: 0; - position: relative; - display: inline-block; -} - -/* Tooltip styles */ -.tooltip { - position: fixed; - padding: 4px 8px; - background: var(--vscode-editorHoverWidget-background); - color: var(--vscode-editorHoverWidget-foreground); - border: 1px solid var(--vscode-editorHoverWidget-border); - border-radius: 4px; - font-size: 12px; - white-space: normal; - word-break: break-word; - max-width: 400px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); - z-index: 10000; - pointer-events: none; - opacity: 0; - visibility: hidden; - transition: - opacity 0.5s, - visibility 0.5s; -} - -.tooltip.show { - opacity: 1; - visibility: visible; -} - -.bubble-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - flex-shrink: 0; - color: var(--vscode-descriptionForeground); - font-size: 12px; -} - -.bubble-toggle svg { - width: 12px; - height: 12px; - fill: var(--vscode-descriptionForeground); - transition: transform 0.2s; -} - -.bubble-toggle.expanded svg { - transform: rotate(180deg); -} - -.bubble-collapsible-content { - margin-top: 8px; - margin-left: 24px; - padding: 8px 12px; - background: var(--vscode-textCodeBlock-background); - border: 1px solid var(--border-color); - border-radius: 4px; - overflow: auto; - max-width: calc(100% - 24px); - font-family: var(--vscode-editor-font-family); - font-size: 12px; - white-space: pre-wrap; - word-break: break-word; - max-height: 300px; -} - -.tool-result-details { - display: flex; - flex-direction: column; - gap: 10px; - font-family: var(--vscode-font-family); - white-space: normal; -} - -.tool-result-summary { - color: var(--vscode-foreground); - line-height: 1.5; -} - -.update-plan-result { - border: 1px solid var(--border-color); - border-radius: 6px; - background: var(--vscode-editor-background); - color: var(--vscode-editor-foreground); - font-family: var(--vscode-font-family); - font-size: 13px; - line-height: 1.45; - padding: 10px; - white-space: normal; -} - -.update-plan-heading { - color: var(--vscode-foreground); - font-size: 13px; - font-weight: 650; - line-height: 1.35; - margin: 0 0 8px; -} - -.update-plan-heading:not(:first-child) { - margin-top: 12px; -} - -.update-plan-spacer { - height: 6px; -} - -.update-plan-paragraph { - margin: 4px 0; -} - -.update-plan-task, -.update-plan-bullet { - display: grid; - grid-template-columns: 18px 1fr; - gap: 8px; - align-items: flex-start; - margin: 5px 0; - padding-left: calc(var(--plan-indent, 0) * 18px); -} - -.update-plan-status { - width: 14px; - height: 14px; - margin-top: 2px; - border: 1px solid var(--vscode-descriptionForeground); - border-radius: 50%; - color: var(--vscode-editor-background); - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 10px; - font-weight: 700; - line-height: 1; -} - -.update-plan-task.status-done .update-plan-status { - background: var(--vscode-testing-iconPassed, #2ea043); - border-color: var(--vscode-testing-iconPassed, #2ea043); -} - -.update-plan-task.status-active .update-plan-status { - background: var(--vscode-charts-blue, #3794ff); - border-color: var(--vscode-charts-blue, #3794ff); -} - -.update-plan-task.status-attention .update-plan-status { - background: var(--vscode-testing-iconFailed, #f85149); - border-color: var(--vscode-testing-iconFailed, #f85149); -} - -.update-plan-task.status-todo .update-plan-status { - background: transparent; -} - -.update-plan-task.status-done .update-plan-task-text { - color: var(--vscode-descriptionForeground); - text-decoration: line-through; -} - -.update-plan-bullet-marker { - color: var(--vscode-descriptionForeground); - line-height: 1.45; - text-align: center; -} - -.update-plan-markdown code { - border-radius: 3px; - background: var(--vscode-textCodeBlock-background); - font-family: var(--vscode-editor-font-family); - font-size: 0.92em; - padding: 1px 4px; -} - -.tool-result-file-row { - display: flex; - align-items: center; - gap: 8px; - flex-wrap: wrap; -} - -.tool-result-label { - color: var(--vscode-descriptionForeground); - font-size: 11px; - font-weight: 600; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -.tool-result-filepath { - border: none; - background: none; - color: var(--vscode-textLink-foreground, var(--vscode-button-background)); - cursor: pointer; - font: inherit; - padding: 0; - text-decoration: underline; - text-underline-offset: 2px; - word-break: break-all; -} - -.tool-result-filepath:hover { - color: var(--vscode-textLink-activeForeground, var(--vscode-button-hoverBackground)); -} - -.tool-result-diff { - display: flex; - flex-direction: column; - gap: 6px; -} - -.diff-preview { - border: 1px solid var(--border-color); - border-radius: 6px; - overflow: hidden; - background: var(--vscode-editor-background); -} - -.diff-line { - display: grid; - grid-template-columns: 20px 1fr; - gap: 0; - font-family: var(--vscode-editor-font-family); - font-size: 12px; - line-height: 1.2; -} - -.diff-line-marker { - display: flex; - align-items: flex-start; - justify-content: center; - padding: 1px 0; - color: var(--vscode-descriptionForeground); - user-select: none; -} - -.diff-line-content { - display: block; - padding: 1px 10px 1px 0; - white-space: pre-wrap; - word-break: break-word; -} - -.diff-line.added { - background: color-mix( - in srgb, - var(--vscode-diffEditor-insertedLineBackground, rgba(46, 160, 67, 0.18)) 100%, - transparent - ); -} - -.diff-line.removed { - background: color-mix( - in srgb, - var(--vscode-diffEditor-removedLineBackground, rgba(248, 81, 73, 0.16)) 100%, - transparent - ); -} - -.diff-line.context { - background: var(--vscode-editor-background); -} - -/* assistant 角色的展开内容无边框 */ -.bubble.assistant .bubble-collapsible-content { - border: none; - background: transparent; - padding: 8px 0; -} - -.bubble-collapsible-content.collapsed { - display: none; -} - -/* 普通assistant气泡布局 */ -.bubble.assistant:has(.bubble-normal-content) { - display: flex; - align-items: flex-start; - gap: 8px; -} - -.bubble.assistant:has(.bubble-normal-content) > .bubble-dot { - margin-top: 6px; -} - -.bubble-normal-content { - flex: 1; - min-width: 0; -} - -/* 复制按钮 */ -.bubble-copy-btn { - display: none; - position: absolute; - top: 8px; - right: 8px; - width: 26px; - height: 26px; - border: 1px solid var(--border-color); - border-radius: 4px; - background: var(--vscode-editor-background); - color: var(--vscode-descriptionForeground); - cursor: pointer; - align-items: center; - justify-content: center; - transition: - opacity 0.15s, - background 0.15s; - z-index: 5; -} - -.bubble-copy-btn:hover { - background: var(--vscode-toolbar-hoverBackground); - color: var(--vscode-foreground); -} - -.bubble-copy-btn.copied { - color: var(--vscode-testing-iconPassed, #2ea043); - border-color: var(--vscode-testing-iconPassed, #2ea043); -} - -.bubble-copy-btn svg { - width: 14px; - height: 14px; - fill: currentColor; - pointer-events: none; -} - -.bubble.assistant:hover .bubble-copy-btn { - display: flex; -} - -.bubble.user { - background: var(--vscode-editor-background); - border-color: var(--vscode-panel-border); - padding: 4px 6px; - margin: 12px 0 12px auto; - white-space: pre-wrap; - word-break: break-word; - width: fit-content; - max-width: 80%; -} - -.bubble.system { - background: transparent; - border-color: transparent; - padding: 4px 16px; -} - -.bubble.assistant { - background: transparent; - border-color: transparent; - padding: 4px 16px; -} - -.bubble.tool { - background: transparent; - border-color: transparent; - padding: 4px 16px; -} - -.bubble p { - margin: 8px 0; - overflow-wrap: break-word; - word-break: break-word; -} - -.bubble p:first-of-type { - margin-top: 0; -} - -.bubble p:last-of-type { - margin-bottom: 0; -} - -.bubble pre { - padding: 12px; - border-radius: 4px; - overflow-x: auto; - max-width: 100%; - color: var(--vscode-editor-foreground); - background: var(--vscode-textCodeBlock-background); - margin: 8px 0; - border: 1px solid var(--vscode-panel-border); - white-space: pre-wrap; - word-break: break-word; -} - -.bubble code { - font-family: var(--vscode-editor-font-family); - font-size: 12px; - color: var(--vscode-editor-foreground); - background-color: unset; - overflow-wrap: break-word; - word-break: break-word; -} - -.ask-user-question { - display: flex; - flex-direction: column; - gap: 12px; - font-family: var(--vscode-font-family); - font-size: 13px; - white-space: normal; -} - -.ask-user-question-intro { - color: var(--vscode-descriptionForeground); -} - -.ask-user-question-form { - display: flex; - flex-direction: column; - gap: 12px; -} - -.ask-user-question-content { - overflow: visible; - max-height: none; -} - -.ask-user-question-block { - border: 1px solid var(--border-color); - border-radius: 6px; - padding: 12px; - margin: 0; - display: flex; - flex-direction: column; - gap: 8px; -} - -.ask-user-question-title { - padding: 0 4px; - font-weight: 600; -} - -.ask-user-question-option { - display: flex; - align-items: flex-start; - gap: 8px; - cursor: pointer; -} - -.ask-user-question-option input { - margin: 2px 0 0; -} - -.ask-user-question-option-text { - display: flex; - flex-direction: column; - gap: 2px; -} - -.ask-user-question-option-description { - color: var(--vscode-descriptionForeground); - font-size: 12px; -} - -.ask-user-question-other { - display: flex; - flex-direction: column; - gap: 6px; -} - -.ask-user-question-other-label { - font-size: 12px; - color: var(--vscode-descriptionForeground); -} - -.ask-user-question-other textarea { - min-height: 56px; - max-height: 120px; - resize: none; - padding: 8px 10px; - border: 1px solid var(--border-color); - border-radius: 4px; - background: var(--vscode-input-background); - color: var(--vscode-input-foreground); -} - -.ask-user-question-actions { - margin-top: 10px; -} - -.ask-user-question-error { - color: var(--vscode-errorForeground); - font-size: 12px; - display: none; - margin-bottom: 6px; -} - -.ask-user-question-error:not(:empty) { - display: block; -} - -.ask-user-question-submit { - align-self: flex-start; - border: 1px solid transparent; - border-radius: 4px; - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - padding: 6px 12px; - cursor: pointer; - font-size: 12px; -} - -.ask-user-question-submit:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.ask-user-question-empty { - color: var(--vscode-descriptionForeground); -} - -.permission-prompt-host { - margin-bottom: 10px; -} - -.permission-card { - border: 1px solid var(--vscode-panel-border); - border-left: 3px solid var(--vscode-notificationsWarningIcon-foreground, #f59e0b); - border-radius: 6px; - background: var(--vscode-input-background); - box-shadow: 0 4px 14px var(--shadow); - padding: 12px; -} - -.permission-header { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 12px; -} - -.permission-title { - color: var(--vscode-notificationsWarningIcon-foreground, #f59e0b); - font-weight: 700; -} - -.permission-progress { - margin-top: 2px; - color: var(--vscode-descriptionForeground); - font-size: 11px; -} - -.permission-close { - border: none; - border-radius: 4px; - background: transparent; - color: var(--vscode-descriptionForeground); - cursor: pointer; - font-size: 18px; - line-height: 1; - padding: 0 4px; -} - -.permission-close:hover { - background: var(--vscode-toolbar-hoverBackground); - color: var(--vscode-foreground); -} - -.permission-tool { - margin-top: 10px; - font-weight: 700; -} - -.permission-command { - margin-top: 6px; - border-radius: 4px; - background: var(--vscode-textCodeBlock-background); - color: var(--vscode-editor-foreground); - font-family: var(--vscode-editor-font-family); - font-size: 12px; - line-height: 1.45; - padding: 8px; - white-space: pre-wrap; - word-break: break-word; -} - -.permission-description { - margin-top: 8px; - color: var(--vscode-descriptionForeground); - line-height: 1.45; -} - -.permission-scope { - display: inline-flex; - align-items: center; - margin-top: 10px; - border-radius: 999px; - font-size: 11px; - font-weight: 700; - padding: 3px 8px; -} - -.permission-scope.risk-low { - background: rgba(34, 197, 94, 0.14); - color: #22c55e; -} - -.permission-scope.risk-medium { - background: rgba(245, 158, 11, 0.14); - color: #f59e0b; -} - -.permission-scope.risk-high { - background: rgba(239, 68, 68, 0.14); - color: #ef4444; -} - -.permission-question { - margin-top: 10px; - font-weight: 600; -} - -.permission-actions { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 10px; -} - -.permission-action { - border: 1px solid var(--vscode-button-border, transparent); - border-radius: 4px; - background: var(--vscode-button-secondaryBackground); - color: var(--vscode-button-secondaryForeground); - cursor: pointer; - font-size: 12px; - padding: 6px 10px; -} - -.permission-action:hover { - background: var(--vscode-button-secondaryHoverBackground); -} - -.permission-allow, -.permission-always { - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); -} - -.permission-allow:hover, -.permission-always:hover { - background: var(--vscode-button-hoverBackground); -} - -.permission-deny { - border-color: var(--vscode-inputValidation-errorBorder, #ef4444); - color: var(--vscode-errorForeground, #ef4444); -} - -.permission-denied-card { - border-left-color: var(--vscode-errorForeground, #ef4444); -} - -.permission-denied-card .permission-title { - color: var(--vscode-errorForeground, #ef4444); -} - -/* Spinner dot for live thinking bubble */ -.bubble-dot.spinner-dot { - background: transparent !important; -} - -/* 旋转环用 ::after 实现,不影响 ::before 连线 */ -.bubble-dot.spinner-dot::after { - content: ""; - position: absolute; - top: -2px; - left: -2px; - width: calc(100% + 4px); - height: calc(100% + 4px); - border-radius: 50%; - border: 2px solid var(--vscode-progressBar-background); - border-top-color: var(--accent); - animation: spin 0.8s linear infinite; - box-sizing: border-box; - pointer-events: none; - z-index: 1; -} - -/* Thinking bubble: 标题和内容同行显示 */ -.bubble[data-thinking-live="true"] .thinking-status { - color: var(--vscode-descriptionForeground); - font-weight: normal; - font-size: 12px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex: 1; - min-width: 0; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -/* Composer */ -.composer { - position: relative; - padding: 12px 16px 16px; - background: var(--vscode-sideBar-background); - flex-shrink: 0; -} - -.input-wrap { - position: relative; - display: flex; - flex-direction: column; - min-height: calc(var(--prompt-min-height) + 42px); - border-radius: 4px; - border: 1px solid var(--border-color); - background: var(--vscode-input-background); - transition: - border-color 0.15s ease, - outline-color 0.15s ease, - background-color 0.15s ease; -} - -.input-wrap:focus-within { - border-color: var(--vscode-focusBorder); - outline: 1px solid var(--vscode-focusBorder); -} - -.tools-line { - display: none; - align-items: center; - flex-wrap: wrap; - gap: 8px; - min-height: 0; - padding: 0 12px; -} - -.tools-line.has-attachment { - display: flex; - min-height: 34px; - padding: 10px 12px 0; -} - -.composer-footer { - display: flex; - height: 42px; - width: 100%; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 6px 12px; - position: relative; -} - -/* Textarea */ -textarea { - width: 100%; - min-height: var(--prompt-min-height); - max-height: var(--prompt-max-height); - resize: none; - overflow-y: hidden; - border: none; - border-radius: 4px; - background: var(--vscode-input-background); - color: var(--vscode-input-foreground); - padding: 10px 12px; - font-size: 13px; - line-height: var(--prompt-line-height); - outline: none; - font-family: var(--vscode-font-family); -} - -textarea:focus { - outline: none; -} - -textarea::placeholder { - color: var(--vscode-input-placeholderForeground); -} - -.chat-attached-context-attachment { - display: inline-flex; - align-items: center; - gap: 6px; - min-width: 0; - max-width: 100%; - padding: 4px; - border: 1px solid var(--vscode-editorWidget-background, var(--vscode-input-background)); - border-radius: 6px; - color: var(--vscode-foreground); -} - -.chat-attached-context-attachment:hover { - border-color: var(--vscode-focusBorder); -} - -.chat-attached-context-attachment .monaco-button.codicon-close { - display: inline-flex; - justify-content: center; - width: 18px; - height: 18px; - border-radius: 50%; - color: var(--vscode-descriptionForeground); - text-decoration: none; - flex-shrink: 0; -} - -.chat-attached-context-attachment .monaco-button.codicon-close:hover { - background: var(--vscode-toolbar-hoverBackground); - color: var(--vscode-foreground); -} - -.monaco-icon-label, -.monaco-icon-label-container, -.monaco-icon-name-container { - display: none; -} - -.chat-attached-context-pill { - width: 13px; - height: 13px; - border-radius: 4px; - overflow: hidden; - flex-shrink: 0; - background: var(--vscode-editor-background); -} - -.chat-attached-context-pill-image { - display: block; - width: 13px; - height: 13px; - margin: 1.5px; - object-fit: cover; - border-radius: 2px; -} - -.chat-attached-context-custom-text { - font-size: 12px; - line-height: 1.2; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.chat-attached-context-preview { - position: fixed; - display: none; - padding: 10px; - border: 1px solid var(--border-color); - border-radius: 12px; - background: var(--vscode-editorHoverWidget-background, var(--vscode-editorWidget-background)); - box-shadow: 0 8px 24px var(--shadow); - z-index: 10001; - pointer-events: none; -} - -.chat-attached-context-preview.show { - display: block; -} - -.chat-attached-context-preview-image { - display: block; - max-width: min(520px, 70vw); - max-height: min(420px, 65vh); - border-radius: 6px; - object-fit: contain; -} - -/* Send Button */ -.send-button { - width: 30px; - height: 30px; - border-radius: 50%; - border: none; - cursor: pointer; - display: grid; - place-items: center; - background: transparent; -} - -.context-meter { - position: relative; - width: 18px; - height: 18px; - display: grid; - place-items: center; - flex-shrink: 0; - z-index: 3; -} - -.context-meter-ring { - --context-percent: 0%; - width: 14px; - height: 14px; - border-radius: 50%; - background: - radial-gradient(circle at center, var(--vscode-input-background) 36%, transparent 38%), - conic-gradient( - var(--vscode-descriptionForeground) 0 var(--context-percent), - var(--vscode-editorWidget-border, var(--vscode-panel-border)) var(--context-percent) 100% - ); -} - -.context-meter:hover .context-meter-ring { - filter: brightness(1.1); -} - -.context-meter-tooltip { - position: fixed; - left: 16px; - bottom: calc(100% + 10px); - display: none; - width: min(360px, calc(100vw - 32px)); - max-height: min(420px, 70vh); - overflow: auto; - padding: 12px 14px; - border: 1px solid var(--vscode-editorHoverWidget-border, var(--border-color)); - border-radius: 12px; - background: var(--vscode-editorHoverWidget-background, var(--vscode-editorWidget-background)); - color: var(--vscode-editorHoverWidget-foreground, var(--vscode-foreground)); - box-shadow: 0 8px 24px var(--shadow); - font-size: 12px; - line-height: 1.4; - pointer-events: none; - z-index: 10002; -} - -.context-meter:hover .context-meter-tooltip { - display: block; -} - -.context-tooltip-title { - text-align: center; - color: var(--vscode-descriptionForeground); - font-size: 13px; - margin-bottom: 4px; -} - -.context-tooltip-summary { - text-align: center; - font-size: 16px; - font-weight: 600; - margin-bottom: 10px; -} -.context-tooltip-section { - margin-top: 10px; - padding-top: 8px; - border-top: 1px solid var(--border-color); - color: var(--vscode-descriptionForeground); - font-weight: 600; -} - -.context-tooltip-row { - display: flex; - justify-content: space-between; - gap: 12px; - margin-top: 4px; -} - -.context-tooltip-label { - min-width: 0; - color: var(--vscode-descriptionForeground); - overflow-wrap: anywhere; -} - -.context-tooltip-value { - max-width: 55%; - text-align: right; - font-family: var(--vscode-editor-font-family); - overflow-wrap: anywhere; -} - -.send-button:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.send-icon { - width: 26px; - height: 26px; - fill: currentColor; - color: var(--vscode-foreground); - transition: opacity 0.2s; -} - -#sendIcon { - color: var(--vscode-descriptionForeground); -} - -#stopIcon { - display: none; - color: var(--vscode-foreground); -} - -.send-icon.empty { - opacity: 0.6; -} - -/* Skills Button */ -.skills-button { - height: 22px; - border-radius: 4px; - border: none; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - gap: 4px; - background: transparent; - padding: 0; - transition: opacity 0.2s; - flex-shrink: 0; - color: var(--vscode-foreground); -} - -.skills-button span { - margin-top: 4px; -} - -.skills-button:hover { - opacity: 0.8; -} - -.skills-button svg { - width: 16px; - height: 16px; - fill: var(--vscode-foreground); -} - -/* Skills Popup */ -.skills-popup { - position: absolute; - bottom: calc(100% + 8px); - left: 16px; - right: 16px; - background: var(--vscode-dropdown-background); - border: 1px solid var(--border-color); - border-radius: 4px; - max-height: 300px; - overflow-y: auto; - z-index: 10000; - display: none; - box-shadow: 0 4px 12px var(--shadow); -} - -.skills-popup.show { - display: block; -} - -.skills-popup-header { - padding: 8px 12px; - font-size: 12px; - font-weight: 500; - color: var(--vscode-descriptionForeground); - border-bottom: 1px solid var(--border-color); -} - -.skills-popup-list { - padding: 8px; - display: flex; - flex-direction: column; - gap: 4px; -} - -.skills-popup-empty { - padding: 12px; - text-align: center; - color: var(--vscode-descriptionForeground); - font-size: 13px; -} - -.skills-popup-item { - display: flex; - align-items: center; - padding: 6px 10px; - cursor: pointer; - border-radius: 4px; - font-size: 13px; - transition: background 0.2s; - gap: 8px; -} - -.skills-popup-item:hover { - background: var(--vscode-list-hoverBackground); -} - -.skills-popup-item.selected { - background: var(--vscode-list-activeSelectionBackground); - color: var(--vscode-list-activeSelectionForeground); -} - -.skills-popup-item-name { - flex: 0 0 120px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.skills-popup-item-path { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - color: var(--vscode-descriptionForeground); - font-size: 12px; -} - -.skills-popup-item:hover .skills-popup-item-path, -.skills-popup-item.selected .skills-popup-item-path { - color: inherit; -} - -.skills-popup-item-loaded { - color: var(--vscode-testing-icon-success); - font-weight: bold; - font-size: 14px; - margin-left: 4px; -} - -/* Skills Bar (contains button and tags) */ -.skills-bar { - display: flex; - align-items: center; - gap: 8px; - width: calc(100% - 60px); -} - -/* Skills Tags */ -.skills-tags { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 6px; - flex: 1; - min-width: 0; -} - -.skills-tags-inner { - width: 100%; - overflow: auto; - white-space: nowrap; - scrollbar-width: none; - -ms-overflow-style: none; -} - -.skills-tags-inner::-webkit-scrollbar { - width: 0; - height: 0; -} - -.skill-tag { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 2px 6px; - background: var(--vscode-button-background); - color: var(--vscode-button-foreground); - border-radius: 4px; - font-size: 12px; - margin-right: 4px; -} - -.skill-tag-name { - max-width: 120px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.skill-tag-remove { - cursor: pointer; - font-size: 14px; - line-height: 1; - opacity: 0.7; - transition: opacity 0.2s; -} - -.skill-tag-remove:hover { - opacity: 1; -} - -/* Prevent horizontal overflow for all markdown content */ -.bubble table { - display: block; - overflow-x: auto; - max-width: 100%; -} - -.bubble img { - max-width: 100%; - height: auto; -} - -.bubble ul, -.bubble ol { - padding-left: 1.5em; - overflow-wrap: break-word; -} - -.bubble li { - overflow-wrap: break-word; - word-break: break-word; -} - -.bubble blockquote { - overflow-wrap: break-word; - word-break: break-word; -} - -.bubble a { - overflow-wrap: break-word; - word-break: break-all; -} diff --git a/packages/vscode-ide-companion/resources/webview.html b/packages/vscode-ide-companion/resources/webview.html deleted file mode 100644 index 1290f8d6..00000000 --- a/packages/vscode-ide-companion/resources/webview.html +++ /dev/null @@ -1,2354 +0,0 @@ - - - - - - - Deep Code - - - -
-
-
-
-
- - Deep Code -
- -
-
- -
-
- - -
-
-
- -
-
Select Skills
-
-
- -
-
- - -
-
-
-
- - - - - diff --git a/packages/vscode-ide-companion/scripts/copy-templates.js b/packages/vscode-ide-companion/scripts/copy-templates.js new file mode 100644 index 00000000..e87bb1ba --- /dev/null +++ b/packages/vscode-ide-companion/scripts/copy-templates.js @@ -0,0 +1,28 @@ +import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = join(__dirname, ".."); +const packagesRoot = join(root, ".."); +const coreTemplates = join(packagesRoot, "core", "templates"); +const destDir = join(root, "templates"); + +if (!existsSync(coreTemplates)) { + console.error(`Templates directory not found at ${coreTemplates}`); + process.exit(1); +} + +// Remove old templates if exists +if (existsSync(destDir)) { + rmSync(destDir, { recursive: true, force: true }); +} + +// Copy templates to dist/templates +mkdirSync(destDir, { recursive: true }); +cpSync(coreTemplates, destDir, { + recursive: true, + dereference: true, +}); + +console.log(`✅ Copied core/templates/ → templates/`); diff --git a/packages/vscode-ide-companion/src/extension.test.ts b/packages/vscode-ide-companion/src/extension.test.ts new file mode 100644 index 00000000..5e76b0b6 --- /dev/null +++ b/packages/vscode-ide-companion/src/extension.test.ts @@ -0,0 +1,144 @@ +/** + * Unit tests for extension.ts + * + * NOTE: This file is excluded from vitest because vscode module is only + * available as types at compile time. For VS Code extension testing, + * use VS Code's extension test runner instead. + * + * Tests in this file are for type checking only. + */ + +import { describe, it, expect, vi, beforeEach } from "vitest"; +import type * as vscode from "vscode"; + +// Mock vscode module +vi.mock("vscode", () => ({ + workspace: { + workspaceFolders: [{ uri: { fsPath: "/test/workspace" } }], + }, + window: { + activeTextEditor: undefined, + showErrorMessage: vi.fn(), + showTextDocument: vi.fn().mockResolvedValue({ lineCount: 10 }), + onDidChangeActiveTextEditor: vi.fn(() => ({ dispose: vi.fn() })), + }, + commands: { + registerCommand: vi.fn(() => ({ dispose: vi.fn() })), + executeCommand: vi.fn().mockResolvedValue(undefined), + }, + env: { + clipboard: { writeText: vi.fn().mockResolvedValue(undefined) }, + machineId: "test-machine-id", + }, + Uri: { + file: vi.fn((path: string) => ({ fsPath: path })), + }, + Position: vi.fn(), + Selection: vi.fn(), + Range: vi.fn(), + TextEditorRevealType: { + InCenter: 1, + }, + WebviewViewProvider: class {}, + ExtensionContext: class {}, +})); + +vi.mock("@vegamo/deepcode-core", () => ({ + SessionManager: vi.fn().mockImplementation(() => ({ + dispose: vi.fn(), + initMcpServers: vi.fn().mockResolvedValue(undefined), + })), + getCompactPromptTokenThreshold: vi.fn(() => 100000), + resolveSettingsSources: vi.fn(() => ({ + model: "deepseek-v4-pro", + baseURL: "https://api.deepseek.com", + thinkingEnabled: false, + reasoningEffort: "low" as const, + debugLogEnabled: false, + })), + setShellIfWindows: vi.fn(), + DEFAULT_MODEL: "deepseek-v4-pro", + DEFAULT_BASE_URL: "https://api.deepseek.com", +})); + +vi.mock("markdown-it", () => { + return vi.fn().mockImplementation(() => ({ + render: vi.fn((text: string) => `

${text}

`), + })); +}); + +vi.mock("@webview-rpc/host", () => ({ + attachRouterToPanel: vi.fn(), +})); + +vi.mock("./getWebviewContent.js", () => ({ + getWebviewContent: vi.fn().mockResolvedValue("test"), +})); + +vi.mock("node:fs", () => ({ + existsSync: vi.fn(() => false), + readFileSync: vi.fn(() => "{}"), + writeFileSync: vi.fn(), + mkdirSync: vi.fn(), +})); + +// Import after mocks +import { DeepCodeViewProvider, activate, deactivate } from "./extension"; + +describe("DeepCodeViewProvider", () => { + let mockContext: vscode.ExtensionContext; + + beforeEach(() => { + vi.clearAllMocks(); + mockContext = { + extensionPath: "/test/extension", + subscriptions: [], + } as unknown as vscode.ExtensionContext; + }); + + it("creates provider instance", () => { + const provider = new DeepCodeViewProvider(mockContext); + expect(provider).toBeDefined(); + provider.dispose(); + }); + + it("postMessageToWebview handles null webviewView", () => { + const provider = new DeepCodeViewProvider(mockContext); + expect(() => provider.postMessageToWebview({ type: "test" })).not.toThrow(); + provider.dispose(); + }); + + it("dispose does not throw", () => { + const provider = new DeepCodeViewProvider(mockContext); + expect(() => provider.dispose()).not.toThrow(); + }); +}); + +describe("activate", () => { + let mockContext: vscode.ExtensionContext; + + beforeEach(() => { + vi.clearAllMocks(); + mockContext = { + extensionPath: "/test/extension", + subscriptions: [], + } as unknown as vscode.ExtensionContext; + }); + + it("registers webview view provider", () => { + activate(mockContext); + const vscode = require("vscode"); + expect(vscode.commands.registerCommand).toHaveBeenCalled(); + }); + + it("creates subscriptions", () => { + activate(mockContext); + expect(mockContext.subscriptions.length).toBeGreaterThan(0); + }); +}); + +describe("deactivate", () => { + it("does nothing and does not throw", () => { + expect(() => deactivate()).not.toThrow(); + }); +}); diff --git a/packages/vscode-ide-companion/src/extension.ts b/packages/vscode-ide-companion/src/extension.ts index 44542e79..4b7bc917 100644 --- a/packages/vscode-ide-companion/src/extension.ts +++ b/packages/vscode-ide-companion/src/extension.ts @@ -1,27 +1,27 @@ import * as vscode from "vscode"; -import * as fs from "fs"; -import * as path from "path"; -import * as os from "os"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as os from "node:os"; import OpenAI from "openai"; -import MarkdownIt from "markdown-it"; import type { SessionMessage } from "@vegamo/deepcode-core"; import { SessionManager, getCompactPromptTokenThreshold, type LlmStreamProgress, - type PermissionScope, type SessionEntry, - type SkillInfo, - type UserPromptContent, - type UserToolPermission, resolveSettingsSources, type DeepcodingSettings, type ReasoningEffort, type ResolvedDeepcodingSettings, setShellIfWindows, } from "@vegamo/deepcode-core"; -import { getNonce } from "./utils.js"; -import { handleWebviewMessage } from "./provider.js"; +import { getWebviewContent } from "./getWebviewContent.js"; +import { appRouter, type RouterContext } from "./router.js"; +import { attachRouterToPanel } from "@webview-rpc/host"; +import { createLogger } from "./utils/logger.js"; +import { checkForUpdates } from "./utils/checkForUpdates.js"; +import { reverseApplyDiff } from "./utils/diff-utils.js"; +import type MarkdownIt from "markdown-it"; const DEFAULT_MODEL = "deepseek-v4-pro"; const DEFAULT_BASE_URL = "https://api.deepseek.com"; @@ -30,43 +30,36 @@ type ReasoningMessageParams = { reasoning_content?: string; }; +let log: (message: string) => void = () => {}; +let logger: vscode.OutputChannel; +let deepCodeViewProvider: DeepCodeViewProvider; + export class DeepCodeViewProvider implements vscode.WebviewViewProvider { public static readonly viewType = "deepcode.chatView"; private readonly context: vscode.ExtensionContext; private webviewView: vscode.WebviewView | undefined; - private readonly md: MarkdownIt; + private activeChatPanel: vscode.WebviewPanel | undefined; private readonly sessionManager: SessionManager; constructor(context: vscode.ExtensionContext) { this.context = context; - this.md = new MarkdownIt({ - html: false, - linkify: false, - breaks: true, - }); this.sessionManager = new SessionManager({ projectRoot: this.getWorkspaceRoot(), createOpenAIClient: () => this.createOpenAIClient(), getResolvedSettings: () => this.resolveCurrentSettings(), - renderMarkdown: (text) => this.md.render(text), + renderMarkdown: (text) => text, onAssistantMessage: (message: SessionMessage, shouldConnect: boolean) => { - if (!this.webviewView) { - return; - } - if (message.visible === false) { - return; - } + if (!this.webviewView) return; + if (!message.visible) return; if (message.role !== "tool") { const reasoningContent = (message.messageParams as ReasoningMessageParams | null)?.reasoning_content; - message.html = this.md.render(message.content || reasoningContent || ""); + message.content = message.content || reasoningContent || ""; } this.webviewView.webview.postMessage({ type: "appendMessage", message, shouldConnect }); }, onSessionEntryUpdated: (entry) => { - if (!this.webviewView) { - return; - } + if (!this.webviewView) return; this.webviewView.webview.postMessage({ type: "sessionStatus", sessionId: entry.id, @@ -77,9 +70,7 @@ export class DeepCodeViewProvider implements vscode.WebviewViewProvider { }); }, onLlmStreamProgress: (progress: LlmStreamProgress) => { - if (!this.webviewView) { - return; - } + if (!this.webviewView) return; this.webviewView.webview.postMessage({ type: "llmStreamProgress", progress, @@ -93,309 +84,114 @@ export class DeepCodeViewProvider implements vscode.WebviewViewProvider { this.sessionManager.dispose(); } + postMessageToWebview(message: unknown): void { + if (this.webviewView) { + this.webviewView.webview.postMessage(message); + } + } + resolveWebviewView(webviewView: vscode.WebviewView): void { this.webviewView = webviewView; webviewView.webview.options = { enableScripts: true, - localResourceRoots: [this.context.extensionUri], + localResourceRoots: [vscode.Uri.file(path.join(this.context.extensionPath, "dist", "webview"))], }; - webviewView.webview.html = this.getWebviewHtml(webviewView.webview); - - webviewView.webview.onDidReceiveMessage(async (message) => { - const msg = message as Record | undefined; - - // openFile requires vscode API, handle here directly - if (msg?.type === "openFile") { - const filePath = String(msg.filePath || "").trim(); - const line = Number(msg.line || 1); - if (filePath) { - await this.openFileInEditor(filePath, line); + // Build RPC context + const rpcContext: RouterContext = { + sessionManager: this.sessionManager, + postMessage: (message) => this.webviewView?.webview.postMessage(message), + copyToClipboard: (text) => void vscode.env.clipboard.writeText(text), + openFileInEditor: (filePath, line) => this.openFileInEditor(filePath, line), + getWorkspaceRoot: () => this.getWorkspaceRoot(), + buildTokenTelemetry: (session) => this.buildTokenTelemetry(session), + openSettings: async () => { + // Open project-level settings if exists, otherwise user-level + const projectRoot = this.getWorkspaceRoot(); + const projectSettingsPath = path.join(projectRoot, ".deepcode", "settings.json"); + const userSettingsPath = path.join(os.homedir(), ".deepcode", "settings.json"); + + if (fs.existsSync(projectSettingsPath)) { + await this.openFileInEditor(projectSettingsPath, 1); + } else { + // Ensure user settings directory exists + const userSettingsDir = path.dirname(userSettingsPath); + if (!fs.existsSync(userSettingsDir)) { + fs.mkdirSync(userSettingsDir, { recursive: true }); + } + if (!fs.existsSync(userSettingsPath)) { + fs.writeFileSync(userSettingsPath, JSON.stringify({}, null, 2), "utf8"); + } + await this.openFileInEditor(userSettingsPath, 1); + } + }, + getActiveEditor: () => { + const editor = vscode.window.activeTextEditor; + if (editor?.document) { + return { + fileName: editor.document.fileName, + languageId: editor.document.languageId, + lineCount: editor.document.lineCount, + }; + } + return null; + }, + openChatPanel: (sessionId, viewColumn) => { + // Dispose previous chat panel if exists (mutual exclusion) + if (this.activeChatPanel) { + this.activeChatPanel.dispose(); + this.activeChatPanel = undefined; } - return; - } - - const handled = await handleWebviewMessage(message, { - sessionManager: this.sessionManager, - postMessage: (m) => this.webviewView?.webview.postMessage(m), - renderMarkdown: (text) => this.md.render(text), - copyToClipboard: (text) => void vscode.env.clipboard.writeText(text), - }); - - if (!handled) { - // unrecognized message type — no-op - } - }); - } - - private async loadInitialSession(): Promise { - const sessions = this.sessionManager.listSessions(); - const sessionsList = sessions.map((s) => ({ - id: s.id, - summary: s.summary || "Untitled", - createTime: s.createTime, - updateTime: s.updateTime, - status: s.status, - })); - - if (sessions.length === 0) { - // 没有历史会话,显示新对话界面 - this.sendMessage({ - type: "initializeEmpty", - sessions: sessionsList, - status: null, - tokenTelemetry: this.buildTokenTelemetry(null), - }); - return; - } - - // 显示最新的对话 - const latestSession = sessions[0]; - this.loadSession(latestSession.id); - } - - private loadSession(sessionId: string): void { - const session = this.sessionManager.getSession(sessionId); - if (!session) { - return; - } - - // 设置为活动会话 - this.sessionManager.setActiveSessionId(sessionId); - - const messages = this.sessionManager.listSessionMessages(sessionId); - - // 获取所有会话列表 - const sessions = this.sessionManager.listSessions(); - const sessionsList = sessions.map((s) => ({ - id: s.id, - summary: s.summary || "Untitled", - createTime: s.createTime, - updateTime: s.updateTime, - status: s.status, - })); - - // 发送对话信息到 webview - this.sendMessage({ - type: "loadSession", - sessionId, - summary: session.summary || "Untitled", - status: session.status, - askPermissions: session.askPermissions, - processes: this.serializeProcesses(session.processes), - tokenTelemetry: this.buildTokenTelemetry(session), - sessions: sessionsList, - messages: messages - .filter((m) => m.visible) - .map((m) => ({ - role: m.role, - content: m.content, - html: - m.role !== "tool" - ? this.md.render(m.content || (m.messageParams as ReasoningMessageParams | null)?.reasoning_content || "") - : undefined, - meta: m.meta, - })), - }); - } - - private showSessionsList(): void { - const sessions = this.sessionManager.listSessions(); - this.sendMessage({ - type: "showSessionsList", - sessions: sessions.map((s) => ({ - id: s.id, - summary: s.summary || "Untitled", - createTime: s.createTime, - updateTime: s.updateTime, - status: s.status, - })), - }); - } - - private async createNewSession(): Promise { - // 清除当前活动会话 - this.sessionManager.setActiveSessionId(null); - - // 获取所有会话列表 - const sessions = this.sessionManager.listSessions(); - const sessionsList = sessions.map((s) => ({ - id: s.id, - summary: s.summary || "Untitled", - createTime: s.createTime, - updateTime: s.updateTime, - status: s.status, - })); - - this.sendMessage({ - type: "initializeEmpty", - sessions: sessionsList, - status: null, - tokenTelemetry: this.buildTokenTelemetry(null), - }); - await this.sendSkillsList(); - } - - private sendMessage(message: unknown): void { - if (!this.webviewView) { - return; - } - this.webviewView.webview.postMessage(message); - } - - private async sendSkillsList(sessionId?: string): Promise { - if (!this.webviewView) { - return; - } - const skills = await this.sessionManager.listSkills( - sessionId ?? this.sessionManager.getActiveSessionId() ?? undefined - ); - this.sendMessage({ type: "skillsList", skills }); - } - - private async handlePrompt( - prompt: string, - skills?: SkillInfo[], - imageUrls?: string[], - options: { permissions?: UserToolPermission[]; alwaysAllows?: PermissionScope[] } = {} - ): Promise { - if (!this.webviewView) { - return; - } - - const webview = this.webviewView.webview; - const normalizedImages = Array.isArray(imageUrls) ? imageUrls.filter(Boolean) : []; - const displayPrompt = prompt || (normalizedImages.length > 0 ? "粘贴的图像" : ""); - const isPermissionContinue = - prompt === "/continue" && - normalizedImages.length === 0 && - ((options.permissions?.length ?? 0) > 0 || (options.alwaysAllows?.length ?? 0) > 0); - - // 先显示用户消息(原始文本,不做 HTML 格式化) - if (displayPrompt && !isPermissionContinue) { - webview.postMessage({ type: "userMessage", content: displayPrompt }); - } - - webview.postMessage({ type: "loading", value: true }); - - try { - const userPrompt: UserPromptContent = { - text: prompt, - skills, - imageUrls: normalizedImages, - permissions: options.permissions, - alwaysAllows: options.alwaysAllows, - }; - await this.sessionManager.handleUserPrompt(userPrompt); - await this.sendSkillsList(); - const activeSessionId = this.sessionManager.getActiveSessionId(); - const activeSession = activeSessionId ? this.sessionManager.getSession(activeSessionId) : null; - if (activeSessionId && activeSession) { - webview.postMessage({ - type: "sessionStatus", - sessionId: activeSessionId, - status: activeSession.status, - askPermissions: activeSession.askPermissions, - processes: this.serializeProcesses(activeSession.processes), - tokenTelemetry: this.buildTokenTelemetry(activeSession), + const panel = vscode.window.createWebviewPanel( + "deepcode.chatPanel", + "Deep Code", + { viewColumn: viewColumn as unknown as vscode.ViewColumn, preserveFocus: true }, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [vscode.Uri.file(path.join(this.context.extensionPath, "dist", "webview"))], + } + ); + + // Track panel and clean up on dispose + this.activeChatPanel = panel; + panel.onDidDispose(() => { + if (this.activeChatPanel === panel) { + this.activeChatPanel = undefined; + } }); - } - - // 发送更新后的会话列表(可能创建了新会话) - const sessions = this.sessionManager.listSessions(); - const sessionsList = sessions.map((s) => ({ - id: s.id, - summary: s.summary || "Untitled", - createTime: s.createTime, - updateTime: s.updateTime, - status: s.status, - })); - webview.postMessage({ - type: "showSessionsList", - sessions: sessionsList, - }); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - webview.postMessage({ - type: "assistant", - html: this.md.render(`Request failed: ${message}`), - }); - } finally { - webview.postMessage({ type: "loading", value: false }); - } - } - private handlePermissionDenied(sessionId: string): void { - this.sessionManager.denySessionPermission(sessionId); - const session = this.sessionManager.getSession(sessionId); - if (session) { - this.sendMessage({ - type: "sessionStatus", - sessionId, - status: session.status, - askPermissions: session.askPermissions, - processes: this.serializeProcesses(session.processes), - tokenTelemetry: this.buildTokenTelemetry(session), - }); - } - this.showSessionsList(); - } + // Build RPC context for this panel + const panelContext: RouterContext = { + ...rpcContext, + postMessage: (message) => panel.webview.postMessage(message), + }; - private createOpenAIClient(): { - client: OpenAI | null; - model: string; - baseURL: string; - thinkingEnabled: boolean; - reasoningEffort: ReasoningEffort; - debugLogEnabled: boolean; - notify?: string; - webSearchTool?: string; - env?: Record; - machineId?: string; - } { - const settings = this.resolveCurrentSettings(); + // Attach RPC router and load content + attachRouterToPanel(appRouter, panel, panelContext); + getWebviewContent(this.context, panel.webview).then((html) => { + panel.webview.html = html; + }); - const { apiKey, baseURL, model, thinkingEnabled, reasoningEffort, debugLogEnabled, notify, webSearchTool, env } = - settings; - const machineId = vscode.env.machineId; + // Switch to this session + this.sessionManager.setActiveSessionId(sessionId); + }, + getFileContent: (filePath) => this.getFileContent(filePath), + showDiffEditor: (filePath, diffPreview) => this.openDiffEditor(filePath, diffPreview), + }; - if (!apiKey) { - return { - client: null, - model, - baseURL, - thinkingEnabled, - reasoningEffort, - debugLogEnabled, - notify, - webSearchTool, - env, - machineId, - }; - } + // Attach RPC router + attachRouterToPanel(appRouter, webviewView as unknown as vscode.WebviewPanel, rpcContext); - const client = new OpenAI({ - apiKey, - baseURL: baseURL || undefined, + // Set HTML content + getWebviewContent(this.context, webviewView.webview).then((html) => { + webviewView.webview.html = html; }); - - return { - client, - model, - baseURL, - thinkingEnabled, - reasoningEffort, - debugLogEnabled, - notify, - webSearchTool, - env, - machineId, - }; } - private buildTokenTelemetry(session: SessionEntry | null): { + buildTokenTelemetry(session: SessionEntry | null): { model: string; thinkingEnabled: boolean; reasoningEffort: ReasoningEffort; @@ -431,7 +227,7 @@ export class DeepCodeViewProvider implements vscode.WebviewViewProvider { model: DEFAULT_MODEL, baseURL: DEFAULT_BASE_URL, }, - process.env + process.env as Record ); } @@ -446,7 +242,7 @@ export class DeepCodeViewProvider implements vscode.WebviewViewProvider { return JSON.parse(raw) as DeepcodingSettings; } catch (error) { const message = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage(`Failed to read ~/.deepcode/settings.json: ${message}`); + void vscode.window.showErrorMessage(`Failed to read ~/.deepcode/settings.json: ${message}`); return null; } } @@ -463,7 +259,7 @@ export class DeepCodeViewProvider implements vscode.WebviewViewProvider { return JSON.parse(raw) as DeepcodingSettings; } catch (error) { const message = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage( + void vscode.window.showErrorMessage( `Failed to read ${path.join(workspaceRoot, ".deepcode", "settings.json")}: ${message}` ); return null; @@ -471,20 +267,60 @@ export class DeepCodeViewProvider implements vscode.WebviewViewProvider { } private getWorkspaceRoot(): string { - const workspace = vscode.workspace.workspaceFolders?.[0]; - if (workspace) { - return workspace.uri.fsPath; + return getWorkspaceRootFn(); + } + + private createOpenAIClient(): { + client: OpenAI | null; + model: string; + baseURL: string; + thinkingEnabled: boolean; + reasoningEffort: ReasoningEffort; + debugLogEnabled: boolean; + notify?: string; + webSearchTool?: string; + env?: Record; + machineId?: string; + } { + const settings = this.resolveCurrentSettings(); + const { apiKey, baseURL, model, thinkingEnabled, reasoningEffort, debugLogEnabled, notify, webSearchTool, env } = + settings; + const machineId = vscode.env.machineId; + + if (!apiKey) { + return { + client: null, + model, + baseURL, + thinkingEnabled, + reasoningEffort, + debugLogEnabled, + notify, + webSearchTool, + env, + machineId, + }; } - return process.cwd(); + + const client = new OpenAI({ apiKey, baseURL: baseURL || undefined }); + return { + client, + model, + baseURL, + thinkingEnabled, + reasoningEffort, + debugLogEnabled, + notify, + webSearchTool, + env, + machineId, + }; } private serializeProcesses( processes: Map | null ): Record | null { - if (!processes || processes.size === 0) { - return null; - } - + if (!processes || processes.size === 0) return null; const serialized: Record = {}; for (const [pid, entry] of processes.entries()) { serialized[pid] = entry; @@ -492,53 +328,102 @@ export class DeepCodeViewProvider implements vscode.WebviewViewProvider { return serialized; } - private getWebviewHtml(webview: vscode.Webview): string { - const nonce = getNonce(); - const csp = webview.cspSource; - - // 读取 HTML 模板文件 - const htmlPath = vscode.Uri.joinPath(this.context.extensionUri, "resources", "webview.html"); - let html = fs.readFileSync(htmlPath.fsPath, "utf8"); - - // 获取 CSS 文件 URI - const cssPath = vscode.Uri.joinPath(this.context.extensionUri, "resources", "webview.css"); - const cssUri = webview.asWebviewUri(cssPath); - const attachmentsJsPath = vscode.Uri.joinPath(this.context.extensionUri, "resources", "prompt-attachments.js"); - const attachmentsJsUri = webview.asWebviewUri(attachmentsJsPath); - - // 获取 Logo 文件 URI - const iconPath = vscode.Uri.joinPath(this.context.extensionUri, "resources", "deepcoding_icon.png"); - const iconUri = webview.asWebviewUri(iconPath); - - // 替换占位符 - html = html.replace(/\{\{nonce\}\}/g, nonce); - html = html.replace(/\{\{cspSource\}\}/g, csp); - html = html.replace(/\{\{cssUri\}\}/g, cssUri.toString()); - html = html.replace(/\{\{attachmentsJsUri\}\}/g, attachmentsJsUri.toString()); - html = html.replace(/\{\{iconUri\}\}/g, iconUri.toString()); - html = html.replace(/\{\{workspaceRoot\}\}/g, JSON.stringify(this.getWorkspaceRoot())); - - return html; + private async openFileInEditor(filePath: string, line: number): Promise { + return openFileInEditorFn(filePath, line); } - private async openFileInEditor(filePath: string, line: number): Promise { - const document = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath)); - const editor = await vscode.window.showTextDocument(document, { - preview: false, - preserveFocus: false, - }); + /** + * Get the raw file content from disk. + */ + private getFileContent(filePath: string): string { + return fs.readFileSync(filePath, "utf8"); + } + + /** + * Open a VS Code native diff editor showing the before/after of a file edit. + * + * The current file on disk contains the modified version (after the edit was + * applied). The diff_preview from the tool result metadata is used to + * reconstruct the original content by reverse-applying the unified diff. + */ + private async openDiffEditor(filePath: string, diffPreview: string): Promise { + try { + // Read the current (modified) file content from disk + const modifiedContent = this.getFileContent(filePath); + + // Reconstruct the original content by reverse-applying the diff + const originalContent = reverseApplyDiff(modifiedContent, diffPreview); + + if (originalContent === null) { + void vscode.window.showWarningMessage( + `Unable to reconstruct original content for diff. The file may have been modified since the edit.` + ); + return; + } + + // Create temp files for the diff editor + const tempDir = path.join(os.tmpdir(), "deepcode-diff"); + if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir, { recursive: true }); + } + + const baseName = path.basename(filePath); + const timestamp = Date.now(); + const originalPath = path.join(tempDir, `${baseName}.${timestamp}.original`); + const modifiedPath = path.join(tempDir, `${baseName}.${timestamp}.modified`); + + fs.writeFileSync(originalPath, originalContent, "utf8"); + fs.writeFileSync(modifiedPath, modifiedContent, "utf8"); + + const originalUri = vscode.Uri.file(originalPath); + const modifiedUri = vscode.Uri.file(modifiedPath); + const title = `${baseName} (Original ↔ Modified)`; - const targetLine = Number.isFinite(line) && line > 0 ? Math.floor(line) - 1 : 0; - const safeLine = Math.min(Math.max(0, targetLine), Math.max(0, document.lineCount - 1)); - const position = new vscode.Position(safeLine, 0); - const selection = new vscode.Selection(position, position); - editor.selection = selection; - editor.revealRange(new vscode.Range(position, position), vscode.TextEditorRevealType.InCenter); + // Open VS Code's native diff editor + await vscode.commands.executeCommand("vscode.diff", originalUri, modifiedUri, title); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + void vscode.window.showErrorMessage(`Diff editor error: ${message}`); + } } } -export function activate(context: vscode.ExtensionContext): void { +async function openFileInEditorFn(filePath: string, line: number): Promise { + const document = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath)); + const editor = await vscode.window.showTextDocument(document, { + preview: false, + preserveFocus: false, + }); + const targetLine = Number.isFinite(line) && line > 0 ? Math.floor(line) - 1 : 0; + const safeLine = Math.min(Math.max(0, targetLine), Math.max(0, document.lineCount - 1)); + const position = new vscode.Position(safeLine, 0); + editor.selection = new vscode.Selection(position, position); + editor.revealRange(new vscode.Range(position, position), vscode.TextEditorRevealType.InCenter); +} + +function getWorkspaceRootFn(): string { + const workspace = vscode.workspace.workspaceFolders?.[0]; + if (workspace) return workspace.uri.fsPath; + return process.cwd(); +} + +export async function activate( + context: vscode.ExtensionContext +): Promise<{ extendMarkdownIt(md: MarkdownIt): MarkdownIt }> { process.env.NoDefaultCurrentDirectoryInExePath = "1"; + logger = vscode.window.createOutputChannel("Deep Code"); + log = createLogger(context, logger); + log("Extension activated"); + + if (context.extensionMode === vscode.ExtensionMode.Development) { + log("Running in Development mode"); + } else if (context.extensionMode === vscode.ExtensionMode.Production) { + log("Running in Production mode"); + await checkForUpdates(context, log); + } else { + log("Running in Test mode"); + } + try { setShellIfWindows(); } catch (error) { @@ -546,17 +431,103 @@ export function activate(context: vscode.ExtensionContext): void { void vscode.window.showErrorMessage(message); } - const provider = new DeepCodeViewProvider(context); - context.subscriptions.push(provider); - context.subscriptions.push(vscode.window.registerWebviewViewProvider(DeepCodeViewProvider.viewType, provider)); + deepCodeViewProvider = new DeepCodeViewProvider(context); + context.subscriptions.push(deepCodeViewProvider); + context.subscriptions.push( + vscode.window.registerWebviewViewProvider(DeepCodeViewProvider.viewType, deepCodeViewProvider, { + webviewOptions: { retainContextWhenHidden: true }, + }) + ); + + // Listen for active editor changes and send to webview + const sendActiveEditorInfo = () => { + const editor = vscode.window.activeTextEditor; + if (editor?.document) { + const doc = editor.document; + deepCodeViewProvider.postMessageToWebview({ + type: "activeEditor", + fileName: doc.fileName, + languageId: doc.languageId, + lineCount: doc.lineCount, + }); + } + }; + + // Send initial active editor + sendActiveEditorInfo(); + + // Listen for editor changes + context.subscriptions.push( + vscode.window.onDidChangeActiveTextEditor(() => { + sendActiveEditorInfo(); + }) + ); + + // Open view context.subscriptions.push( vscode.commands.registerCommand("deepcode.openView", async () => { await vscode.commands.executeCommand("workbench.view.extension.deepcode"); await vscode.commands.executeCommand("deepcode.chatView.focus"); }) ); + // Open settings + context.subscriptions.push( + vscode.commands.registerCommand("deepcode.settings", async () => { + const projectRoot = getWorkspaceRootFn(); + const projectSettingsPath = path.join(projectRoot, ".deepcode", "settings.json"); + const userSettingsPath = path.join(os.homedir(), ".deepcode", "settings.json"); + + if (fs.existsSync(projectSettingsPath)) { + await openFileInEditorFn(projectSettingsPath, 1); + } else { + // Ensure user settings directory exists + const userSettingsDir = path.dirname(userSettingsPath); + if (!fs.existsSync(userSettingsDir)) { + fs.mkdirSync(userSettingsDir, { recursive: true }); + } + if (!fs.existsSync(userSettingsPath)) { + fs.writeFileSync(userSettingsPath, JSON.stringify({}, null, 2), "utf8"); + } + await openFileInEditorFn(userSettingsPath, 1); + } + }) + ); + + // Create new chat + context.subscriptions.push( + vscode.commands.registerCommand("deepcode.newchat", () => { + log("deepcode.newchat"); + deepCodeViewProvider.postMessageToWebview({ type: "triggerNewChat" }); + }) + ); + + // Show history + context.subscriptions.push( + vscode.commands.registerCommand("deepcode.history", () => { + log("deepcode.history"); + deepCodeViewProvider.postMessageToWebview({ type: "triggerHistory" }); + }) + ); + context.subscriptions.push(logger); + return { + extendMarkdownIt(md: MarkdownIt) { + return md.use(require("markdown-it-task-lists")); + }, + }; } export function deactivate(): void { - // no-op + try { + log("Extension deactivated"); + if (deepCodeViewProvider) { + deepCodeViewProvider.dispose(); + } + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + log(`Error during deactivation: ${message}`); + } finally { + if (logger) { + logger.dispose(); + } + } } diff --git a/packages/vscode-ide-companion/src/getWebviewContent.ts b/packages/vscode-ide-companion/src/getWebviewContent.ts new file mode 100644 index 00000000..539ce18f --- /dev/null +++ b/packages/vscode-ide-companion/src/getWebviewContent.ts @@ -0,0 +1,128 @@ +import { randomUUID } from "node:crypto"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as vscode from "vscode"; + +const PORT = 5174; + +interface DevUris { + refreshUri: vscode.Uri; + clientUri: vscode.Uri; + entryUri: vscode.Uri; + origin: string; + wsOrigin: string; +} + +function createBaseCSP(webview: vscode.Webview): string[] { + return [ + `default-src 'none'`, + `img-src ${webview.cspSource} https: data:`, + `style-src ${webview.cspSource} 'unsafe-inline'`, + `font-src ${webview.cspSource} https:`, + `frame-src ${webview.cspSource} https:`, + ]; +} + +function createDevCSP(webview: vscode.Webview, nonce: string, origin: string, wsOrigin: string): string { + return ( + [ + `default-src 'none'`, + `img-src ${webview.cspSource} ${origin} http://localhost:${PORT} http://127.0.0.1:${PORT} https: data:`, + `style-src ${webview.cspSource} 'unsafe-inline'`, + `font-src ${webview.cspSource} ${origin} http://localhost:${PORT} http://127.0.0.1:${PORT} https: data:`, + `frame-src ${webview.cspSource} https:`, + `script-src 'nonce-${nonce}' 'unsafe-eval' 'unsafe-inline' ${origin}`, + `connect-src 'self' ${origin} ${wsOrigin} ws://localhost:${PORT} ws://127.0.0.1:${PORT}`, + ].join("; ") + ";" + ); +} + +function createProdCSP(webview: vscode.Webview, nonce: string): string { + return ( + [ + ...createBaseCSP(webview), + `script-src 'nonce-${nonce}' ${webview.cspSource}`, + `connect-src ${webview.cspSource}`, + ].join("; ") + ";" + ); +} + +async function getDevUris(): Promise { + const refreshLocal = vscode.Uri.parse(`http://localhost:${PORT}/@react-refresh`); + const clientLocal = vscode.Uri.parse(`http://localhost:${PORT}/@vite/client`); + const entryLocal = vscode.Uri.parse(`http://localhost:${PORT}/main.tsx`); + + const [refreshUri, clientUri, entryUri] = await Promise.all([ + vscode.env.asExternalUri(refreshLocal), + vscode.env.asExternalUri(clientLocal), + vscode.env.asExternalUri(entryLocal), + ]); + + const origin = `${clientUri.scheme}://${clientUri.authority}`; + const wsOrigin = origin.replace(/^http/, "ws"); + + return { refreshUri, clientUri, entryUri, origin, wsOrigin }; +} + +function createDevHTML(nonce: string, uris: DevUris, csp: string): string { + return ` + + + + + + + Deep Code (Dev) + + +
+ + + + + + + + `; +} + +function processProductionHtml(htmlContent: string, webview: vscode.Webview, distPath: string, nonce: string): string { + const processedHtml = htmlContent.replace(/(href|src)=["']([^"']*)["']/g, (match, attr, url) => { + if (url.startsWith("http") || url.startsWith("data:") || url.startsWith("#") || url === "") { + return match; + } + const clean = url.replace(/^\//, ""); + const onDisk = vscode.Uri.file(path.join(distPath, clean)); + const webviewUri = webview.asWebviewUri(onDisk).toString(); + return `${attr}="${webviewUri}"`; + }); + + const csp = createProdCSP(webview, nonce); + + return processedHtml + .replace("", ``) + .replace(/]*)type="module"([^>]*)>/g, ``); +} + +export async function getWebviewContent(context: vscode.ExtensionContext, webview: vscode.Webview): Promise { + const isDev = context.extensionMode === vscode.ExtensionMode.Development; + const nonce = randomUUID(); + + if (isDev) { + const uris = await getDevUris(); + const csp = createDevCSP(webview, nonce, uris.origin, uris.wsOrigin); + return createDevHTML(nonce, uris, csp); + } + + const htmlPath = path.join(context.extensionPath, "dist", "webview", "index.html"); + const distPath = path.join(context.extensionPath, "dist", "webview"); + const htmlContent = fs.readFileSync(htmlPath, "utf8"); + + return processProductionHtml(htmlContent, webview, distPath, nonce); +} diff --git a/packages/vscode-ide-companion/src/provider.ts b/packages/vscode-ide-companion/src/provider.ts deleted file mode 100644 index 2e3b4ac8..00000000 --- a/packages/vscode-ide-companion/src/provider.ts +++ /dev/null @@ -1,323 +0,0 @@ -/** - * Message handling logic for the Deepcoding webview provider. - * Extracted from extension.ts for testability — no direct vscode dependency. - */ -import type { SessionManager } from "@vegamo/deepcode-core"; -import type { PermissionScope, SkillInfo, UserToolPermission } from "@vegamo/deepcode-core"; -import { parseUserToolPermissions, parsePermissionScopes } from "./utils.js"; - -export interface PostMessageFn { - (message: unknown): void; -} - -export interface ProviderDeps { - sessionManager: Pick< - SessionManager, - | "listSessions" - | "getSession" - | "getActiveSessionId" - | "setActiveSessionId" - | "listSessionMessages" - | "handleUserPrompt" - | "interruptActiveSession" - | "denySessionPermission" - | "listSkills" - >; - postMessage: PostMessageFn; - renderMarkdown: (text: string) => string; - copyToClipboard: (text: string) => void; -} - -export interface SessionSummary { - id: string; - summary: string; - createTime: string; - updateTime: string; - status: string; -} - -function toSessionList( - sessions: Array<{ id: string; summary?: string | null; createTime: string; updateTime: string; status: string }> -): SessionSummary[] { - return sessions.map((s) => ({ - id: s.id, - summary: s.summary || "Untitled", - createTime: s.createTime, - updateTime: s.updateTime, - status: s.status, - })); -} - -/** - * Routes incoming webview messages to the appropriate handler. - * Returns true if the message was handled. - */ -export async function handleWebviewMessage(message: unknown, deps: ProviderDeps): Promise { - const { sessionManager, postMessage, renderMarkdown, copyToClipboard } = deps; - - if (!message || typeof message !== "object") { - return false; - } - - const msg = message as Record; - - if (msg.type === "ready") { - loadInitialSession(sessionManager, postMessage, renderMarkdown); - await sendSkillsList(sessionManager, postMessage); - return true; - } - - if (msg.type === "requestSkills") { - await sendSkillsList(sessionManager, postMessage); - return true; - } - - if (msg.type === "userPrompt") { - const prompt = String(msg.prompt || "").trim(); - const images = Array.isArray(msg.images) - ? (msg.images as unknown[]).filter((image): image is string => typeof image === "string" && image.length > 0) - : []; - const permissions = parseUserToolPermissions(msg.permissions); - const alwaysAllows = parsePermissionScopes(msg.alwaysAllows); - if (!prompt && images.length === 0 && permissions.length === 0 && alwaysAllows.length === 0) { - return true; - } - const skills = (msg.skills as SkillInfo[]) || []; - await handlePrompt(prompt, skills, images, sessionManager, postMessage, renderMarkdown, { - permissions: permissions.length > 0 ? permissions : undefined, - alwaysAllows: alwaysAllows.length > 0 ? alwaysAllows : undefined, - }); - return true; - } - - if (msg.type === "interrupt") { - sessionManager.interruptActiveSession(); - return true; - } - - if (msg.type === "denyPermission") { - const sessionId = String(msg.sessionId || sessionManager.getActiveSessionId() || "").trim(); - if (sessionId) { - handlePermissionDenied(sessionId, sessionManager, postMessage); - } - return true; - } - - if (msg.type === "createNewSession") { - await createNewSession(sessionManager, postMessage); - return true; - } - - if (msg.type === "selectSession") { - const sessionId = String(msg.sessionId || "").trim(); - if (sessionId) { - loadSession(sessionId, sessionManager, postMessage, renderMarkdown); - await sendSkillsList(sessionManager, postMessage, sessionId); - } - return true; - } - - if (msg.type === "backToList") { - showSessionsList(sessionManager, postMessage); - return true; - } - - if (msg.type === "openFile") { - // openFile requires vscode API — handled by the caller - return false; - } - - if (msg.type === "copyText") { - const text = String(msg.text || ""); - if (text) { - copyToClipboard(text); - } - return true; - } - - return false; -} - -function loadInitialSession( - sessionManager: ProviderDeps["sessionManager"], - postMessage: PostMessageFn, - renderMarkdown: (text: string) => string -): void { - const sessions = sessionManager.listSessions(); - const sessionsList = toSessionList(sessions); - - if (sessions.length === 0) { - postMessage({ - type: "initializeEmpty", - sessions: sessionsList, - status: null, - }); - return; - } - - const latestSession = sessions[0]; - loadSession(latestSession.id, sessionManager, postMessage, renderMarkdown); -} - -export function loadSession( - sessionId: string, - sessionManager: ProviderDeps["sessionManager"], - postMessage: PostMessageFn, - renderMarkdown: (text: string) => string -): void { - const session = sessionManager.getSession(sessionId); - if (!session) { - return; - } - - sessionManager.setActiveSessionId(sessionId); - const messages = sessionManager.listSessionMessages(sessionId); - const sessions = sessionManager.listSessions(); - - postMessage({ - type: "loadSession", - sessionId, - summary: session.summary || "Untitled", - status: session.status, - askPermissions: session.askPermissions, - processes: serializeProcesses(session.processes), - sessions: toSessionList(sessions), - messages: messages - .filter((m) => m.visible) - .map((m) => ({ - role: m.role, - content: m.content, - html: - m.role !== "tool" - ? renderMarkdown( - m.content || (m.messageParams as { reasoning_content?: string } | null)?.reasoning_content || "" - ) - : undefined, - meta: m.meta, - })), - }); -} - -function showSessionsList(sessionManager: ProviderDeps["sessionManager"], postMessage: PostMessageFn): void { - const sessions = sessionManager.listSessions(); - postMessage({ - type: "showSessionsList", - sessions: toSessionList(sessions), - }); -} - -async function createNewSession( - sessionManager: ProviderDeps["sessionManager"], - postMessage: PostMessageFn -): Promise { - sessionManager.setActiveSessionId(null); - const sessions = sessionManager.listSessions(); - - postMessage({ - type: "initializeEmpty", - sessions: toSessionList(sessions), - status: null, - }); - await sendSkillsList(sessionManager, postMessage); -} - -async function sendSkillsList( - sessionManager: ProviderDeps["sessionManager"], - postMessage: PostMessageFn, - sessionId?: string -): Promise { - const skills = await sessionManager.listSkills(sessionId ?? sessionManager.getActiveSessionId() ?? undefined); - postMessage({ type: "skillsList", skills }); -} - -async function handlePrompt( - prompt: string, - skills: SkillInfo[], - imageUrls: string[], - sessionManager: ProviderDeps["sessionManager"], - postMessage: PostMessageFn, - renderMarkdown: (text: string) => string, - options: { permissions?: UserToolPermission[]; alwaysAllows?: PermissionScope[] } = {} -): Promise { - const normalizedImages = imageUrls.filter(Boolean); - const displayPrompt = prompt || (normalizedImages.length > 0 ? "粘贴的图像" : ""); - const isPermissionContinue = - prompt === "/continue" && - normalizedImages.length === 0 && - ((options.permissions?.length ?? 0) > 0 || (options.alwaysAllows?.length ?? 0) > 0); - - if (displayPrompt && !isPermissionContinue) { - postMessage({ type: "userMessage", content: displayPrompt }); - } - - postMessage({ type: "loading", value: true }); - - try { - await sessionManager.handleUserPrompt({ - text: prompt, - skills, - imageUrls: normalizedImages, - permissions: options.permissions, - alwaysAllows: options.alwaysAllows, - }); - await sendSkillsList(sessionManager, postMessage); - - const activeSessionId = sessionManager.getActiveSessionId(); - const activeSession = activeSessionId ? sessionManager.getSession(activeSessionId) : null; - if (activeSessionId && activeSession) { - postMessage({ - type: "sessionStatus", - sessionId: activeSessionId, - status: activeSession.status, - askPermissions: activeSession.askPermissions, - processes: serializeProcesses(activeSession.processes), - }); - } - - const sessions = sessionManager.listSessions(); - postMessage({ - type: "showSessionsList", - sessions: toSessionList(sessions), - }); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - postMessage({ - type: "assistant", - html: renderMarkdown(`Request failed: ${message}`), - }); - } finally { - postMessage({ type: "loading", value: false }); - } -} - -function handlePermissionDenied( - sessionId: string, - sessionManager: ProviderDeps["sessionManager"], - postMessage: PostMessageFn -): void { - sessionManager.denySessionPermission(sessionId); - const session = sessionManager.getSession(sessionId); - if (session) { - postMessage({ - type: "sessionStatus", - sessionId, - status: session.status, - askPermissions: session.askPermissions, - processes: serializeProcesses(session.processes), - }); - } - showSessionsList(sessionManager, postMessage); -} - -function serializeProcesses( - processes: Map | null -): Record | null { - if (!processes || processes.size === 0) { - return null; - } - const serialized: Record = {}; - for (const [pid, entry] of processes.entries()) { - serialized[pid] = entry; - } - return serialized; -} diff --git a/packages/vscode-ide-companion/src/router.ts b/packages/vscode-ide-companion/src/router.ts new file mode 100644 index 00000000..70cbdfbe --- /dev/null +++ b/packages/vscode-ide-companion/src/router.ts @@ -0,0 +1,375 @@ +import { initWRPC } from "@webview-rpc/host"; +import * as os from "node:os"; +import * as path from "node:path"; +import * as vscodeApi from "vscode"; +import z from "zod"; +import { getProjectCode } from "@vegamo/deepcode-core"; +import type { + SessionManager, + SkillInfo, + UserToolPermission, + PermissionScope, + SessionEntry, +} from "@vegamo/deepcode-core"; +import type { TokenTelemetry } from "@/webview/types"; +import { ATTACHMENT_LABEL } from "@/webview/constants"; + +export interface RouterContext { + sessionManager: SessionManager; + postMessage: (message: unknown) => void; + copyToClipboard: (text: string) => void; + openFileInEditor: (filePath: string, line: number) => Promise; + getWorkspaceRoot: () => string; + openSettings: () => Promise; + getActiveEditor: () => { fileName: string; languageId: string; lineCount: number } | null; + openChatPanel: (sessionId: string, viewColumn: number) => void; + showDiffEditor: (filePath: string, diffPreview: string) => Promise; + getFileContent: (filePath: string) => string; + buildTokenTelemetry: (session: SessionEntry | null) => TokenTelemetry; +} + +export const { router, procedure } = initWRPC.context().create(); + +function toSessionList( + sessions: Array<{ id: string; summary?: string | null; createTime: string; updateTime: string; status: string }> +) { + return sessions.map((s) => ({ + id: s.id, + summary: s.summary || "Untitled", + createTime: s.createTime, + updateTime: s.updateTime, + status: s.status, + })); +} + +function serializeProcesses( + processes: Map | null +): Record | null { + if (!processes || processes.size === 0) return null; + const serialized: Record = {}; + for (const [pid, entry] of processes.entries()) { + serialized[pid] = entry; + } + return serialized; +} + +function getSessionJsonlPath(workspaceRoot: string, sessionId: string): string { + const projectCode = getProjectCode(workspaceRoot); + return path.join(os.homedir(), ".deepcode", "projects", projectCode, `${sessionId}.jsonl`); +} + +const sendPromptInput = z.object({ + prompt: z.string().default(""), + skills: z + .array( + z.object({ + name: z.string(), + description: z.string().optional(), + path: z.string().optional(), + isLoaded: z.boolean().optional(), + }) + ) + .default([]), + images: z.array(z.string()).default([]), + permissions: z + .array( + z.object({ + toolCallId: z.string(), + permission: z.enum(["allow", "deny"]), + }) + ) + .optional(), + alwaysAllows: z.array(z.string()).optional(), + planMode: z.boolean().optional(), + askUserQuestionSummary: z.boolean().optional(), +}); + +export const appRouter = router({ + // --- Queries --- + + getInitialData: procedure.resolve(({ ctx }) => { + const sessions = ctx.sessionManager.listSessions(); + const sessionsList = toSessionList(sessions); + const activeSessionId = ctx.sessionManager.getActiveSessionId(); + const activeSession = activeSessionId ? ctx.sessionManager.getSession(activeSessionId) : null; + + let messages: Array = []; + if (activeSessionId && activeSession) { + messages = ctx.sessionManager + .listSessionMessages(activeSessionId) + .filter((m) => m.visible) + .map((m) => ({ + ...m, + id: m.id, + sessionId: m.sessionId, + role: m.role, + content: m.content, + html: + m.role !== "tool" + ? m.content || (m.messageParams as { reasoning_content?: string } | null)?.reasoning_content || "" + : undefined, + meta: m.meta, + })); + } + + // Get active editor info + const activeEditor = ctx.getActiveEditor(); + + return { + sessions: sessionsList, + activeSession: activeSession + ? { + id: activeSession.id, + summary: activeSession.summary || "Untitled", + status: activeSession.status, + askPermissions: activeSession.askPermissions, + processes: serializeProcesses(activeSession.processes), + messages, + } + : null, + activeEditor, + tokenTelemetry: ctx.buildTokenTelemetry(activeSession), + }; + }), + + getSkills: procedure.input(z.string().optional()).resolve(async ({ ctx, input }) => { + const skills = await ctx.sessionManager.listSkills(input ?? ctx.sessionManager.getActiveSessionId() ?? undefined); + return { skills }; + }), + + getSessions: procedure.resolve(({ ctx }) => { + const sessions = ctx.sessionManager.listSessions(); + return { sessions: toSessionList(sessions) }; + }), + + // --- Mutations --- + + sendPrompt: procedure.input(sendPromptInput).resolve(async ({ ctx, input }) => { + const { prompt, skills, images, permissions, alwaysAllows, planMode, askUserQuestionSummary } = input; + const normalizedImages = images.filter(Boolean); + + const hasPayload = + prompt || normalizedImages.length > 0 || (permissions?.length ?? 0) > 0 || (alwaysAllows?.length ?? 0) > 0; + if (!hasPayload) { + return { ok: false, error: "Empty prompt" }; + } + + const displayPrompt = prompt || (normalizedImages.length > 0 ? ATTACHMENT_LABEL : ""); + const promptTrimmed = prompt.trim(); + const isPermissionContinue = + promptTrimmed === "/continue" && + normalizedImages.length === 0 && + ((permissions?.length ?? 0) > 0 || (alwaysAllows?.length ?? 0) > 0); + const isPlainContinue = promptTrimmed === "/continue" && normalizedImages.length === 0; + + // Show user message in webview (skip for /continue commands) + if (displayPrompt && !isPermissionContinue && !isPlainContinue) { + ctx.postMessage({ type: "userMessage", content: displayPrompt }); + } + + ctx.postMessage({ type: "loading", value: true }); + + try { + const userPrompt = { + type: "userPrompt", + text: prompt, + skills: skills.length > 0 ? (skills as SkillInfo[]) : undefined, + imageUrls: normalizedImages.length > 0 ? normalizedImages : undefined, + permissions: permissions && permissions.length > 0 ? (permissions as UserToolPermission[]) : undefined, + alwaysAllows: alwaysAllows && alwaysAllows.length > 0 ? (alwaysAllows as PermissionScope[]) : undefined, + planMode, + askUserQuestionSummary, + }; + await ctx.sessionManager.handleUserPrompt(userPrompt); + + const sessionId = ctx.sessionManager.getActiveSessionId(); + return { ok: true, sessionId }; + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + ctx.postMessage({ + type: "assistant", + content: `Request failed: ${message}`, + }); + return { ok: false, error: message }; + } finally { + ctx.postMessage({ type: "loading", value: false }); + } + }), + + createNewSession: procedure.resolve(async ({ ctx }) => { + ctx.sessionManager.setActiveSessionId(null); + const sessions = ctx.sessionManager.listSessions(); + const skills = await ctx.sessionManager.listSkills(); + return { sessions: toSessionList(sessions), skills, tokenTelemetry: ctx.buildTokenTelemetry(null) }; + }), + + selectSession: procedure.input(z.string()).resolve(({ ctx, input: sessionId }) => { + const session = ctx.sessionManager.getSession(sessionId); + if (!session) { + return { ok: false, error: "Session not found" }; + } + + ctx.sessionManager.setActiveSessionId(sessionId); + const messages = ctx.sessionManager.listSessionMessages(sessionId); + const sessions = ctx.sessionManager.listSessions(); + + return { + ok: true, + session: { + id: session.id, + summary: session.summary || "Untitled", + status: session.status, + askPermissions: session.askPermissions, + processes: serializeProcesses(session.processes), + }, + sessions: toSessionList(sessions), + messages: messages + .filter((m) => m.visible) + .map((m) => ({ + ...m, + role: m.role, + content: m.content || (m.messageParams as { reasoning_content?: string } | null)?.reasoning_content || "", + meta: m.meta, + })), + tokenTelemetry: ctx.buildTokenTelemetry(session), + }; + }), + + interrupt: procedure.resolve(({ ctx }) => { + ctx.sessionManager.interruptActiveSession(); + return { ok: true }; + }), + + denyPermission: procedure.input(z.string()).resolve(({ ctx, input: sessionId }) => { + ctx.sessionManager.denySessionPermission(sessionId); + const session = ctx.sessionManager.getSession(sessionId); + if (session) { + ctx.postMessage({ + type: "sessionStatus", + sessionId, + status: session.status, + askPermissions: session.askPermissions, + processes: serializeProcesses(session.processes), + }); + } + return { ok: true }; + }), + + copyText: procedure.input(z.string()).resolve(({ ctx, input }) => { + ctx.copyToClipboard(input); + return { ok: true }; + }), + + openFile: procedure + .input( + z.object({ + filePath: z.string(), + line: z.number().default(1), + }) + ) + .resolve(async ({ ctx, input }) => { + await ctx.openFileInEditor(input.filePath, input.line); + return { ok: true }; + }), + + openSettings: procedure.resolve(async ({ ctx }) => { + await ctx.openSettings(); + return { ok: true }; + }), + showAlert: procedure.input(z.string()).resolve(({ input }) => { + console.log("[Extension] Received message:", input); + vscodeApi.window.showInformationMessage(input); + return { ok: true }; + }), + + addSystemMessage: procedure + .input( + z.object({ + content: z.string(), + meta: z.record(z.string(), z.unknown()).optional(), + }) + ) + .resolve(({ ctx, input }) => { + const activeSessionId = ctx.sessionManager.getActiveSessionId(); + if (!activeSessionId) { + return { ok: false, error: "No active session" }; + } + ctx.sessionManager.addSessionSystemMessage(activeSessionId, input.content, true, input.meta); + return { ok: true }; + }), + + renameSession: procedure + .input( + z.object({ + sessionId: z.string(), + summary: z.string().min(1, "Summary cannot be empty"), + }) + ) + .resolve(({ ctx, input }) => { + const ok = ctx.sessionManager.renameSession(input.sessionId, input.summary); + if (!ok) { + return { ok: false, error: "Session not found or empty summary" }; + } + return { ok: true }; + }), + + deleteSession: procedure.input(z.object({ sessionId: z.string() })).resolve(async ({ ctx, input }) => { + const activeSessionId = ctx.sessionManager.getActiveSessionId(); + const ok = ctx.sessionManager.deleteSession(input.sessionId); + if (!ok) { + return { ok: false, error: "Session not found" }; + } + // If the deleted session was the active one, clear the active session + if (activeSessionId === input.sessionId) { + ctx.sessionManager.setActiveSessionId(null); + } + return { ok: true, wasActiveSession: activeSessionId === input.sessionId }; + }), + + getSessionFilePath: procedure.input(z.object({ sessionId: z.string() })).resolve(({ ctx, input }) => { + const filePath = getSessionJsonlPath(ctx.getWorkspaceRoot(), input.sessionId); + return { filePath }; + }), + + openChatPanel: procedure + .input( + z.object({ + sessionId: z.string(), + viewColumn: z.number().describe("ViewColumn: 1=Active, 2=Beside"), + }) + ) + .resolve(({ ctx, input }) => { + ctx.openChatPanel(input.sessionId, input.viewColumn); + return { ok: true }; + }), + + openChatInNewWindow: procedure.input(z.object({ sessionId: z.string() })).resolve(async ({ ctx, input }) => { + // Open in current window's editor area (fills it like a dedicated window) + ctx.openChatPanel(input.sessionId, vscodeApi.ViewColumn.Active); + return { ok: true }; + }), + + openExternal: procedure.input(z.object({ url: z.string() })).resolve(async ({ input }) => { + await vscodeApi.env.openExternal(vscodeApi.Uri.parse(input.url)); + return { ok: true }; + }), + + showDiffEditor: procedure + .input( + z.object({ + filePath: z.string(), + diffPreview: z.string(), + }) + ) + .resolve(async ({ ctx, input }) => { + await ctx.showDiffEditor(input.filePath, input.diffPreview); + return { ok: true }; + }), + + getFileContent: procedure.input(z.object({ filePath: z.string() })).resolve(({ ctx, input }) => { + const content = ctx.getFileContent(input.filePath); + return { content }; + }), +}); + +export type AppRouter = typeof appRouter; diff --git a/packages/vscode-ide-companion/src/tests/__mocks__/vscode.ts b/packages/vscode-ide-companion/src/tests/__mocks__/vscode.ts new file mode 100644 index 00000000..21b21d17 --- /dev/null +++ b/packages/vscode-ide-companion/src/tests/__mocks__/vscode.ts @@ -0,0 +1,61 @@ +/** + * Vitest mock for the `vscode` module. + * + * VS Code is only available at runtime in the actual VS Code extension host. + * This mock allows extension utility tests to resolve the import. + */ + +const vscodeMock = { + window: { + showInformationMessage: vi.fn(), + showErrorMessage: vi.fn(), + showTextDocument: vi.fn(), + activeTextEditor: undefined, + createOutputChannel: vi.fn(() => ({ + appendLine: vi.fn(), + })), + }, + commands: { + executeCommand: vi.fn(), + registerCommand: vi.fn(() => ({ dispose: vi.fn() })), + }, + ExtensionMode: { + Development: 1, + Production: 2, + Test: 3, + }, + Uri: { + file: vi.fn((path: string) => ({ fsPath: path })), + }, + workspace: { + workspaceFolders: [{ uri: { fsPath: "/test/workspace" } }], + }, + env: { + clipboard: { writeText: vi.fn() }, + machineId: "test-machine-id", + }, + Position: vi.fn(), + Selection: vi.fn(), + Range: vi.fn(), + TextEditorRevealType: { + InCenter: 1, + }, + WebviewViewProvider: class {}, + ExtensionContext: class {}, +}; + +(globalThis as any).vi = vi; + +export default vscodeMock; +export const window = vscodeMock.window; +export const commands = vscodeMock.commands; +export const ExtensionMode = vscodeMock.ExtensionMode; +export const Uri = vscodeMock.Uri; +export const workspace = vscodeMock.workspace; +export const env = vscodeMock.env; +export const Position = vscodeMock.Position; +export const Selection = vscodeMock.Selection; +export const Range = vscodeMock.Range; +export const TextEditorRevealType = vscodeMock.TextEditorRevealType; +export const WebviewViewProvider = vscodeMock.WebviewViewProvider; +export const ExtensionContext = vscodeMock.ExtensionContext; diff --git a/packages/vscode-ide-companion/src/tests/extension-utils.test.ts b/packages/vscode-ide-companion/src/tests/extension-utils.test.ts deleted file mode 100644 index 8dea36de..00000000 --- a/packages/vscode-ide-companion/src/tests/extension-utils.test.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { test } from "node:test"; -import assert from "node:assert/strict"; -import { VALID_PERMISSION_SCOPES, parseUserToolPermissions, parsePermissionScopes, getNonce } from "../utils.js"; - -// --- VALID_PERMISSION_SCOPES --- - -test("VALID_PERMISSION_SCOPES contains all expected scopes", () => { - const expected = [ - "read-in-cwd", - "read-out-cwd", - "write-in-cwd", - "write-out-cwd", - "delete-in-cwd", - "delete-out-cwd", - "query-git-log", - "mutate-git-log", - "network", - "mcp", - ]; - assert.equal(VALID_PERMISSION_SCOPES.size, expected.length); - for (const scope of expected) { - assert.ok(VALID_PERMISSION_SCOPES.has(scope as any), `missing scope: ${scope}`); - } -}); - -// --- parseUserToolPermissions --- - -test("parseUserToolPermissions returns empty array for non-array input", () => { - assert.deepEqual(parseUserToolPermissions(undefined), []); - assert.deepEqual(parseUserToolPermissions(null), []); - assert.deepEqual(parseUserToolPermissions("string"), []); - assert.deepEqual(parseUserToolPermissions(123), []); - assert.deepEqual(parseUserToolPermissions({}), []); -}); - -test("parseUserToolPermissions returns empty array for empty array", () => { - assert.deepEqual(parseUserToolPermissions([]), []); -}); - -test("parseUserToolPermissions parses valid permissions", () => { - const input = [ - { toolCallId: "call-1", permission: "allow" }, - { toolCallId: "call-2", permission: "deny" }, - ]; - const result = parseUserToolPermissions(input); - assert.equal(result.length, 2); - assert.deepEqual(result[0], { toolCallId: "call-1", permission: "allow" }); - assert.deepEqual(result[1], { toolCallId: "call-2", permission: "deny" }); -}); - -test("parseUserToolPermissions filters out invalid items", () => { - const input = [ - null, - 123, - "string", - {}, - { toolCallId: "", permission: "allow" }, // empty toolCallId - { toolCallId: " ", permission: "allow" }, // whitespace-only toolCallId - { toolCallId: "call-1" }, // missing permission - { toolCallId: "call-2", permission: "invalid" }, // invalid permission value - { permission: "allow" }, // missing toolCallId - { toolCallId: "call-3", permission: "allow" }, // valid - ]; - const result = parseUserToolPermissions(input); - assert.equal(result.length, 1); - assert.deepEqual(result[0], { toolCallId: "call-3", permission: "allow" }); -}); - -test("parseUserToolPermissions preserves toolCallId with leading/trailing spaces", () => { - const input = [{ toolCallId: " call-1 ", permission: "allow" }]; - const result = parseUserToolPermissions(input); - // trimmed toolCallId " " fails the .trim() check, so this item is filtered - // Wait, " call-1 ".trim() = "call-1" which is truthy, so it passes - assert.equal(result.length, 1); - assert.equal(result[0].toolCallId, " call-1 "); -}); - -// --- parsePermissionScopes --- - -test("parsePermissionScopes returns empty array for non-array input", () => { - assert.deepEqual(parsePermissionScopes(undefined), []); - assert.deepEqual(parsePermissionScopes(null), []); - assert.deepEqual(parsePermissionScopes("string"), []); - assert.deepEqual(parsePermissionScopes(123), []); - assert.deepEqual(parsePermissionScopes({}), []); -}); - -test("parsePermissionScopes returns empty array for empty array", () => { - assert.deepEqual(parsePermissionScopes([]), []); -}); - -test("parsePermissionScopes parses valid scopes", () => { - const input = ["read-in-cwd", "write-in-cwd", "network"]; - const result = parsePermissionScopes(input); - assert.equal(result.length, 3); - assert.deepEqual(result, ["read-in-cwd", "write-in-cwd", "network"]); -}); - -test("parsePermissionScopes filters out invalid values", () => { - const input = ["read-in-cwd", "invalid-scope", 123, null, undefined, {}, "mcp"]; - const result = parsePermissionScopes(input); - assert.equal(result.length, 2); - assert.deepEqual(result, ["read-in-cwd", "mcp"]); -}); - -test("parsePermissionScopes deduplicates scopes", () => { - const input = ["read-in-cwd", "write-in-cwd", "read-in-cwd", "network", "network"]; - const result = parsePermissionScopes(input); - assert.equal(result.length, 3); - assert.deepEqual(result, ["read-in-cwd", "write-in-cwd", "network"]); -}); - -// --- getNonce --- - -test("getNonce returns a 32-character string", () => { - const nonce = getNonce(); - assert.equal(nonce.length, 32); -}); - -test("getNonce only contains alphanumeric characters", () => { - const nonce = getNonce(); - assert.ok(/^[A-Za-z0-9]+$/.test(nonce), `nonce contains non-alphanumeric chars: ${nonce}`); -}); - -test("getNonce returns different values on successive calls", () => { - const nonces = new Set(); - for (let i = 0; i < 100; i++) { - nonces.add(getNonce()); - } - // With 62^32 possible values, 100 calls should almost certainly be unique - assert.ok(nonces.size > 90, `Expected mostly unique nonces, got ${nonces.size} unique out of 100`); -}); diff --git a/packages/vscode-ide-companion/src/tests/extension.test.ts b/packages/vscode-ide-companion/src/tests/extension.test.ts deleted file mode 100644 index 9ea39656..00000000 --- a/packages/vscode-ide-companion/src/tests/extension.test.ts +++ /dev/null @@ -1,477 +0,0 @@ -import { test } from "node:test"; -import assert from "node:assert/strict"; -import { handleWebviewMessage, loadSession, type ProviderDeps } from "../provider.js"; - -// --- Helpers --- - -function createMockSessionManager(options?: { sessions?: any[]; messages?: any[]; skills?: any[] }) { - const sessions = options?.sessions ?? [ - { - id: "session-1", - summary: "Test Session", - status: "idle", - askPermissions: null, - processes: null, - activeTokens: 100, - usage: null, - createTime: "2025-01-01T00:00:00Z", - updateTime: "2025-01-01T00:00:00Z", - }, - ]; - const messages = options?.messages ?? []; - const skills = options?.skills ?? []; - let activeSessionId: string | null = sessions[0]?.id ?? null; - - return { - dispose: () => {}, - listSessions: () => sessions, - getSession: (id: string) => sessions.find((s: any) => s.id === id) ?? null, - getActiveSessionId: () => activeSessionId, - setActiveSessionId: (id: string | null) => { - activeSessionId = id; - }, - listSessionMessages: (_sessionId: string) => messages, - handleUserPrompt: () => Promise.resolve(), - interruptActiveSession: () => {}, - denySessionPermission: (_sessionId: string) => {}, - listSkills: () => Promise.resolve(skills), - initMcpServers: () => Promise.resolve(), - }; -} - -function createDeps(options?: Parameters[0]): ProviderDeps & { messages: unknown[] } { - const messages: unknown[] = []; - return { - sessionManager: createMockSessionManager(options), - postMessage: (msg: unknown) => { - messages.push(msg); - }, - renderMarkdown: (text: string) => `

${text}

`, - copyToClipboard: () => {}, - messages, - }; -} - -// --- handleWebviewMessage routing --- - -test("handleWebviewMessage returns false for null message", async () => { - const deps = createDeps(); - const result = await handleWebviewMessage(null, deps); - assert.equal(result, false); -}); - -test("handleWebviewMessage returns false for non-object message", async () => { - const deps = createDeps(); - assert.equal(await handleWebviewMessage("string", deps), false); - assert.equal(await handleWebviewMessage(123, deps), false); -}); - -test("handleWebviewMessage returns false for unknown message type", async () => { - const deps = createDeps(); - assert.equal(await handleWebviewMessage({ type: "unknownType" }, deps), false); -}); - -test("ready message triggers loadInitialSession and sendSkillsList", async () => { - const deps = createDeps(); - const handled = await handleWebviewMessage({ type: "ready" }, deps); - - assert.equal(handled, true); - const types = deps.messages.map((m: any) => m.type); - // With sessions present, should send loadSession + skillsList - assert.ok(types.includes("loadSession"), `Expected loadSession, got: ${types.join(", ")}`); - assert.ok(types.includes("skillsList"), `Expected skillsList, got: ${types.join(", ")}`); -}); - -test("ready message renders markdown for initial session messages", async () => { - const deps = createDeps({ - messages: [{ role: "assistant", content: "**bold**", visible: true }], - }); - - await handleWebviewMessage({ type: "ready" }, deps); - - const loadMsg = deps.messages.find((m: any) => m.type === "loadSession") as any; - assert.ok(loadMsg, "Should send loadSession"); - assert.equal(loadMsg.messages[0].html, "

**bold**

"); -}); - -test("ready with no sessions sends initializeEmpty", async () => { - const deps = createDeps({ sessions: [] }); - await handleWebviewMessage({ type: "ready" }, deps); - - const types = deps.messages.map((m: any) => m.type); - assert.ok(types.includes("initializeEmpty"), `Expected initializeEmpty, got: ${types.join(", ")}`); -}); - -test("requestSkills sends skillsList", async () => { - const deps = createDeps({ skills: [{ name: "test-skill" }] }); - await handleWebviewMessage({ type: "requestSkills" }, deps); - - const skillsMsg = deps.messages.find((m: any) => m.type === "skillsList"); - assert.ok(skillsMsg, "Should send skillsList"); - assert.deepEqual((skillsMsg as any).skills, [{ name: "test-skill" }]); -}); - -test("interrupt calls interruptActiveSession", async () => { - const deps = createDeps(); - let interrupted = false; - (deps.sessionManager as any).interruptActiveSession = () => { - interrupted = true; - }; - - const handled = await handleWebviewMessage({ type: "interrupt" }, deps); - assert.equal(handled, true); - assert.ok(interrupted, "interruptActiveSession should be called"); -}); - -test("createNewSession clears active session and sends initializeEmpty", async () => { - const deps = createDeps(); - let cleared = false; - (deps.sessionManager as any).setActiveSessionId = (id: string | null) => { - if (id === null) cleared = true; - }; - - await handleWebviewMessage({ type: "createNewSession" }, deps); - - assert.ok(cleared, "setActiveSessionId(null) should be called"); - const types = deps.messages.map((m: any) => m.type); - assert.ok(types.includes("initializeEmpty"), `Expected initializeEmpty, got: ${types.join(", ")}`); - assert.ok(types.includes("skillsList"), `Expected skillsList, got: ${types.join(", ")}`); -}); - -test("selectSession loads session and sends skillsList", async () => { - const deps = createDeps(); - let loadedId: string | null = null; - (deps.sessionManager as any).setActiveSessionId = (id: string) => { - loadedId = id; - }; - - await handleWebviewMessage({ type: "selectSession", sessionId: "session-1" }, deps); - - assert.equal(loadedId, "session-1"); - const types = deps.messages.map((m: any) => m.type); - assert.ok(types.includes("loadSession"), `Expected loadSession, got: ${types.join(", ")}`); - assert.ok(types.includes("skillsList"), `Expected skillsList, got: ${types.join(", ")}`); -}); - -test("selectSession with empty sessionId does nothing", async () => { - const deps = createDeps(); - await handleWebviewMessage({ type: "selectSession", sessionId: "" }, deps); - assert.equal(deps.messages.length, 0, "No messages for empty sessionId"); -}); - -test("selectSession with non-existent session does not send loadSession", async () => { - const deps = createDeps(); - (deps.sessionManager as any).getSession = () => null; - - await handleWebviewMessage({ type: "selectSession", sessionId: "non-existent" }, deps); - - const types = deps.messages.map((m: any) => m.type); - assert.ok(!types.includes("loadSession"), "Should not send loadSession for non-existent session"); -}); - -test("backToList sends showSessionsList", async () => { - const deps = createDeps(); - await handleWebviewMessage({ type: "backToList" }, deps); - - const msg = deps.messages.find((m: any) => m.type === "showSessionsList"); - assert.ok(msg, "Should send showSessionsList"); - assert.ok(Array.isArray((msg as any).sessions), "sessions should be an array"); -}); - -test("denyPermission calls denySessionPermission and sends sessionStatus", async () => { - const deps = createDeps(); - let deniedId: string | null = null; - (deps.sessionManager as any).denySessionPermission = (id: string) => { - deniedId = id; - }; - - await handleWebviewMessage({ type: "denyPermission", sessionId: "session-1" }, deps); - - assert.equal(deniedId, "session-1"); - const types = deps.messages.map((m: any) => m.type); - assert.ok(types.includes("sessionStatus"), `Expected sessionStatus, got: ${types.join(", ")}`); - assert.ok(types.includes("showSessionsList"), `Expected showSessionsList, got: ${types.join(", ")}`); -}); - -test("denyPermission with empty sessionId does nothing", async () => { - const deps = createDeps(); - (deps.sessionManager as any).getActiveSessionId = () => null; - - await handleWebviewMessage({ type: "denyPermission", sessionId: "" }, deps); - - // No sessionStatus should be sent - const types = deps.messages.map((m: any) => m.type); - assert.ok(!types.includes("sessionStatus"), "Should not send sessionStatus for empty sessionId"); -}); - -test("copyText calls copyToClipboard", async () => { - const deps = createDeps(); - let copiedText: string | null = null; - deps.copyToClipboard = (text: string) => { - copiedText = text; - }; - - const handled = await handleWebviewMessage({ type: "copyText", text: "hello" }, deps); - assert.equal(handled, true); - assert.equal(copiedText, "hello"); -}); - -test("copyText with empty text does not call copyToClipboard", async () => { - const deps = createDeps(); - let copied = false; - deps.copyToClipboard = () => { - copied = true; - }; - - await handleWebviewMessage({ type: "copyText", text: "" }, deps); - assert.ok(!copied, "Should not copy empty text"); -}); - -test("openFile returns false (handled by caller)", async () => { - const deps = createDeps(); - const result = await handleWebviewMessage({ type: "openFile", filePath: "/some/file.ts" }, deps); - assert.equal(result, false); -}); - -// --- userPrompt --- - -test("userPrompt with empty prompt and no images/permissions is handled without messages", async () => { - const deps = createDeps(); - const handled = await handleWebviewMessage( - { type: "userPrompt", prompt: "", images: [], permissions: [], alwaysAllows: [] }, - deps - ); - assert.equal(handled, true); - assert.equal(deps.messages.length, 0, "No messages for empty prompt"); -}); - -test("userPrompt with text sends userMessage and loading states", async () => { - const deps = createDeps(); - await handleWebviewMessage({ type: "userPrompt", prompt: "hello" }, deps); - - const types = deps.messages.map((m: any) => m.type); - assert.ok(types.includes("userMessage"), `Expected userMessage, got: ${types.join(", ")}`); - assert.ok(types.includes("loading"), `Expected loading, got: ${types.join(", ")}`); - - // Should end with loading: false - const lastLoading = [...deps.messages].reverse().find((m: any) => m.type === "loading"); - assert.deepEqual(lastLoading, { type: "loading", value: false }); -}); - -test("userPrompt with images sends userMessage with image placeholder", async () => { - const deps = createDeps(); - await handleWebviewMessage({ type: "userPrompt", prompt: "", images: ["data:image/png;base64,abc"] }, deps); - - const userMsg = deps.messages.find((m: any) => m.type === "userMessage"); - assert.ok(userMsg, "Should send userMessage for images"); - assert.equal((userMsg as any).content, "粘贴的图像"); -}); - -test("userPrompt passes multiple image urls to the session manager", async () => { - const deps = createDeps(); - let submittedPrompt: any = null; - (deps.sessionManager as any).handleUserPrompt = (prompt: any) => { - submittedPrompt = prompt; - return Promise.resolve(); - }; - - await handleWebviewMessage( - { - type: "userPrompt", - prompt: "", - images: ["data:image/png;base64,abc", "data:image/jpeg;base64,def"], - }, - deps - ); - - assert.deepEqual(submittedPrompt?.imageUrls, ["data:image/png;base64,abc", "data:image/jpeg;base64,def"]); -}); - -test("userPrompt with permissions (continue) does not send userMessage", async () => { - const deps = createDeps(); - await handleWebviewMessage( - { - type: "userPrompt", - prompt: "/continue", - images: [], - permissions: [{ toolCallId: "call-1", permission: "allow" }], - }, - deps - ); - - const userMsg = deps.messages.find((m: any) => m.type === "userMessage"); - assert.ok(!userMsg, "Should not send userMessage for /continue with permissions"); -}); - -test("userPrompt sends sessionStatus after handling", async () => { - const deps = createDeps(); - await handleWebviewMessage({ type: "userPrompt", prompt: "hello" }, deps); - - const types = deps.messages.map((m: any) => m.type); - assert.ok(types.includes("sessionStatus"), `Expected sessionStatus, got: ${types.join(", ")}`); -}); - -test("userPrompt sends showSessionsList after handling", async () => { - const deps = createDeps(); - await handleWebviewMessage({ type: "userPrompt", prompt: "hello" }, deps); - - const sessionsMsg = deps.messages.find((m: any) => m.type === "showSessionsList"); - assert.ok(sessionsMsg, "Should send showSessionsList"); - assert.ok(Array.isArray((sessionsMsg as any).sessions), "sessions should be an array"); -}); - -test("userPrompt on error sends assistant error message", async () => { - const deps = createDeps(); - (deps.sessionManager as any).handleUserPrompt = () => Promise.reject(new Error("API failed")); - - await handleWebviewMessage({ type: "userPrompt", prompt: "hello" }, deps); - - const assistantMsg = deps.messages.find((m: any) => m.type === "assistant"); - assert.ok(assistantMsg, "Should send assistant error message"); - assert.ok((assistantMsg as any).html.includes("API failed"), "Error message should contain the error text"); -}); - -test("userPrompt always sends loading: false even on error", async () => { - const deps = createDeps(); - (deps.sessionManager as any).handleUserPrompt = () => Promise.reject(new Error("fail")); - - await handleWebviewMessage({ type: "userPrompt", prompt: "hello" }, deps); - - const lastLoading = [...deps.messages].reverse().find((m: any) => m.type === "loading"); - assert.deepEqual(lastLoading, { type: "loading", value: false }); -}); - -// --- loadSession --- - -test("loadSession sends loadSession with correct fields", () => { - const sessionManager = createMockSessionManager(); - const messages: unknown[] = []; - const postMessage = (msg: unknown) => { - messages.push(msg); - }; - - loadSession("session-1", sessionManager, postMessage, (t) => t); - - const msg = messages.find((m: any) => m.type === "loadSession") as any; - assert.ok(msg, "Should send loadSession"); - assert.equal(msg.sessionId, "session-1"); - assert.equal(msg.summary, "Test Session"); - assert.equal(msg.status, "idle"); - assert.ok(Array.isArray(msg.sessions), "sessions should be an array"); - assert.ok(Array.isArray(msg.messages), "messages should be an array"); -}); - -test("loadSession with non-existent session does nothing", () => { - const sessionManager = createMockSessionManager(); - const messages: unknown[] = []; - const postMessage = (msg: unknown) => { - messages.push(msg); - }; - - (sessionManager as any).getSession = () => null; - loadSession("non-existent", sessionManager, postMessage, (t) => t); - - assert.equal(messages.length, 0, "No messages for non-existent session"); -}); - -test("loadSession sets active session id", () => { - const sessionManager = createMockSessionManager(); - const messages: unknown[] = []; - let setTo: string | null = null; - (sessionManager as any).setActiveSessionId = (id: string) => { - setTo = id; - }; - - loadSession( - "session-1", - sessionManager, - (msg) => messages.push(msg), - (t) => t - ); - - assert.equal(setTo, "session-1"); -}); - -test("loadSession filters out invisible messages", () => { - const sessionManager = createMockSessionManager({ - messages: [ - { role: "user", content: "visible", visible: true }, - { role: "assistant", content: "hidden", visible: false }, - { role: "user", content: "also visible", visible: true }, - ], - }); - const messages: unknown[] = []; - loadSession( - "session-1", - sessionManager, - (msg) => messages.push(msg), - (t) => t - ); - - const loadMsg = messages.find((m: any) => m.type === "loadSession") as any; - assert.equal(loadMsg.messages.length, 2, "Should filter out invisible messages"); -}); - -// --- serializeProcesses --- - -test("loadSession serializes processes map to object", () => { - const sessionManager = createMockSessionManager({ - sessions: [ - { - id: "session-1", - summary: "Test", - status: "idle", - askPermissions: null, - processes: new Map([ - ["123", { startTime: "2025-01-01", command: "ls" }], - ["456", { startTime: "2025-01-02", command: "cat" }], - ]), - activeTokens: 0, - usage: null, - createTime: "2025-01-01", - updateTime: "2025-01-01", - }, - ], - }); - const messages: unknown[] = []; - loadSession( - "session-1", - sessionManager, - (msg) => messages.push(msg), - (t) => t - ); - - const loadMsg = messages.find((m: any) => m.type === "loadSession") as any; - assert.deepEqual(loadMsg.processes, { - "123": { startTime: "2025-01-01", command: "ls" }, - "456": { startTime: "2025-01-02", command: "cat" }, - }); -}); - -test("loadSession returns null for empty processes", () => { - const sessionManager = createMockSessionManager({ - sessions: [ - { - id: "session-1", - summary: "Test", - status: "idle", - askPermissions: null, - processes: null, - activeTokens: 0, - usage: null, - createTime: "2025-01-01", - updateTime: "2025-01-01", - }, - ], - }); - const messages: unknown[] = []; - loadSession( - "session-1", - sessionManager, - (msg) => messages.push(msg), - (t) => t - ); - - const loadMsg = messages.find((m: any) => m.type === "loadSession") as any; - assert.equal(loadMsg.processes, null); -}); diff --git a/packages/vscode-ide-companion/src/tests/prompt-attachments.test.ts b/packages/vscode-ide-companion/src/tests/prompt-attachments.test.ts deleted file mode 100644 index 314826e3..00000000 --- a/packages/vscode-ide-companion/src/tests/prompt-attachments.test.ts +++ /dev/null @@ -1,240 +0,0 @@ -import { test } from "node:test"; -import assert from "node:assert/strict"; -import fs from "node:fs"; -import path from "node:path"; -import vm from "node:vm"; -import { fileURLToPath } from "node:url"; - -type EventHandler = (event: any) => unknown; - -class FakeClassList { - private readonly classes = new Set(); - - constructor(className = "") { - for (const classPart of className.split(/\s+/)) { - if (classPart) { - this.classes.add(classPart); - } - } - } - - add(className: string): void { - this.classes.add(className); - } - - remove(className: string): void { - this.classes.delete(className); - } - - contains(className: string): boolean { - return this.classes.has(className); - } - - toggle(className: string, force?: boolean): boolean { - const shouldAdd = force ?? !this.classes.has(className); - if (shouldAdd) { - this.classes.add(className); - } else { - this.classes.delete(className); - } - return shouldAdd; - } -} - -class FakeElement { - readonly tagName: string; - className = ""; - classList = new FakeClassList(); - children: FakeElement[] = []; - parent: FakeElement | null = null; - dataset: Record = {}; - style: Record = {}; - textContent = ""; - tabIndex = 0; - draggable = false; - href = ""; - src = ""; - alt = ""; - private readonly attributes = new Map(); - private readonly listeners = new Map(); - - constructor(tagName: string) { - this.tagName = tagName; - } - - setAttribute(name: string, value: string): void { - this.attributes.set(name, value); - } - - appendChild(child: FakeElement): FakeElement { - child.parent = this; - this.children.push(child); - return child; - } - - set innerHTML(_value: string) { - for (const child of this.children) { - child.parent = null; - } - this.children = []; - } - - get innerHTML(): string { - return ""; - } - - addEventListener(type: string, handler: EventHandler): void { - const listeners = this.listeners.get(type) ?? []; - listeners.push(handler); - this.listeners.set(type, listeners); - } - - async dispatchEvent(event: any): Promise { - event.type ??= ""; - for (const handler of this.listeners.get(event.type) ?? []) { - await handler(event); - } - } - - contains(candidate: FakeElement | null): boolean { - if (!candidate) { - return false; - } - if (candidate === this) { - return true; - } - return this.children.some((child) => child.contains(candidate)); - } - - querySelector(selector: string): FakeElement | null { - if (!selector.startsWith(".")) { - return null; - } - const className = selector.slice(1); - for (const child of this.children) { - if (child.className.split(/\s+/).includes(className)) { - return child; - } - const match = child.querySelector(selector); - if (match) { - return match; - } - } - return null; - } - - getBoundingClientRect(): { left: number; top: number; bottom: number; width: number; height: number } { - return { left: 20, top: 80, bottom: 100, width: 160, height: 40 }; - } -} - -class FakeDocument { - readonly body = new FakeElement("body"); - - createElement(tagName: string): FakeElement { - return new FakeElement(tagName); - } -} - -class FakeFileReader { - result: string | null = null; - onload: (() => void) | null = null; - onerror: (() => void) | null = null; - error: Error | null = null; - - readAsDataURL(file: { dataUrl?: string }): void { - this.result = file.dataUrl ?? ""; - this.onload?.(); - } -} - -function loadAttachmentManager(): { - manager: { clear: () => void; hasAttachments: () => boolean; getImageUrls: () => string[] }; - promptInput: FakeElement; - toolsLine: FakeElement; -} { - const __dirname = path.dirname(fileURLToPath(import.meta.url)); - const scriptPath = path.resolve(__dirname, "../../resources/prompt-attachments.js"); - const script = fs.readFileSync(scriptPath, "utf8"); - - const document = new FakeDocument(); - const window = { - innerWidth: 1024, - innerHeight: 768, - addEventListener: () => {}, - createPromptAttachmentManager: undefined as - | undefined - | ((options: Record) => { - clear: () => void; - hasAttachments: () => boolean; - getImageUrls: () => string[]; - }), - }; - - vm.runInNewContext(script, { console, document, window, FileReader: FakeFileReader }); - - const createPromptAttachmentManager = window.createPromptAttachmentManager; - if (typeof createPromptAttachmentManager !== "function") { - throw new Error("Prompt attachment manager was not registered."); - } - const promptInput = new FakeElement("textarea"); - const inputWrap = new FakeElement("div"); - const toolsLine = new FakeElement("div"); - const manager = createPromptAttachmentManager({ promptInput, inputWrap, toolsLine }); - - return { manager, promptInput, toolsLine }; -} - -async function pasteImage(promptInput: FakeElement, dataUrl: string): Promise { - let defaultPrevented = false; - await promptInput.dispatchEvent({ - type: "paste", - clipboardData: { - items: [ - { - kind: "file", - getAsFile: () => ({ type: "image/png", name: "image.png", dataUrl }), - }, - ], - }, - preventDefault: () => { - defaultPrevented = true; - }, - }); - assert.equal(defaultPrevented, true); -} - -test("prompt attachment manager appends pasted images instead of replacing the previous image", async () => { - const { manager, promptInput, toolsLine } = loadAttachmentManager(); - - await pasteImage(promptInput, "data:image/png;base64,first"); - await pasteImage(promptInput, "data:image/png;base64,second"); - - assert.equal(manager.hasAttachments(), true); - assert.deepEqual(Array.from(manager.getImageUrls()), ["data:image/png;base64,first", "data:image/png;base64,second"]); - assert.equal(toolsLine.children.length, 2); - assert.equal(toolsLine.classList.contains("has-attachment"), true); -}); - -test("prompt attachment manager removes one pasted image without clearing the rest", async () => { - const { manager, promptInput, toolsLine } = loadAttachmentManager(); - - await pasteImage(promptInput, "data:image/png;base64,first"); - await pasteImage(promptInput, "data:image/png;base64,second"); - - const firstAttachment = toolsLine.children[0]; - const removeButton = firstAttachment.children[0]; - await removeButton.dispatchEvent({ - type: "click", - preventDefault: () => {}, - stopPropagation: () => {}, - }); - - assert.deepEqual(Array.from(manager.getImageUrls()), ["data:image/png;base64,second"]); - assert.equal(toolsLine.children.length, 1); - - manager.clear(); - assert.equal(manager.hasAttachments(), false); - assert.deepEqual(Array.from(manager.getImageUrls()), []); - assert.equal(toolsLine.children.length, 0); -}); diff --git a/packages/vscode-ide-companion/src/tests/run-tests.mjs b/packages/vscode-ide-companion/src/tests/run-tests.mjs deleted file mode 100644 index 73034d8a..00000000 --- a/packages/vscode-ide-companion/src/tests/run-tests.mjs +++ /dev/null @@ -1,15 +0,0 @@ -// Test runner for @vegamo/deepcode-vscode -import { globSync } from "glob"; -import { spawnSync } from "child_process"; -import { fileURLToPath } from "url"; -import * as path from "path"; - -const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const testFiles = globSync("*.test.ts", { cwd: __dirname }); - -const result = spawnSync(process.execPath, ["--import", "tsx", "--test", ...testFiles], { - stdio: "inherit", - cwd: __dirname, -}); - -process.exit(result.status ?? 1); diff --git a/packages/vscode-ide-companion/src/tests/setup.ts b/packages/vscode-ide-companion/src/tests/setup.ts new file mode 100644 index 00000000..324371ae --- /dev/null +++ b/packages/vscode-ide-companion/src/tests/setup.ts @@ -0,0 +1,127 @@ +/** + * Vitest setup file + * + * Configures global test utilities and mocks. + */ + +import "@testing-library/jest-dom"; +import { vi } from "vitest"; + +// --------------------------------------------------------------------------- +// Global mocks +// --------------------------------------------------------------------------- + +// Mock VS Code module for extension utilities +vi.mock("vscode", () => ({ + window: { + showInformationMessage: vi.fn(), + showErrorMessage: vi.fn(), + showTextDocument: vi.fn(), + activeTextEditor: undefined, + }, + commands: { + executeCommand: vi.fn(), + registerCommand: vi.fn(), + }, + ExtensionMode: { + Development: 1, + Production: 2, + Test: 3, + }, + Uri: { + file: vi.fn((path: string) => ({ fsPath: path })), + }, +})); + +// Mock window.matchMedia +Object.defineProperty(window, "matchMedia", { + writable: true, + value: vi.fn().mockImplementation((query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + })), +}); + +// Mock ResizeObserver +class ResizeObserverMock { + observe = vi.fn(); + unobserve = vi.fn(); + disconnect = vi.fn(); +} +window.ResizeObserver = ResizeObserverMock; + +// Mock IntersectionObserver +class IntersectionObserverMock implements IntersectionObserver { + observe = vi.fn(); + unobserve = vi.fn(); + disconnect = vi.fn(); + takeRecords = vi.fn(); + root: Element | null = null; + rootMargin: string = ""; + thresholds: ReadonlyArray = []; + constructor() {} + scrollMargin: string = ""; +} +window.IntersectionObserver = IntersectionObserverMock as unknown as typeof IntersectionObserver; + +// Mock VS Code API +const mockVSCode = { + postMessage: vi.fn(), + getState: vi.fn(() => ({})), + setState: vi.fn(), +}; +Object.defineProperty(window, "acquireVsCodeApi", { + writable: true, + value: () => mockVSCode, +}); + +// Mock localStorage with real in-memory storage +const storageStore = new Map(); +const localStorageMock = { + getItem: vi.fn((key: string) => storageStore.get(key) ?? null), + setItem: vi.fn((key: string, value: string) => { + storageStore.set(key, value); + }), + removeItem: vi.fn((key: string) => { + storageStore.delete(key); + }), + clear: vi.fn(() => { + storageStore.clear(); + }), +}; +Object.defineProperty(window, "localStorage", { + writable: true, + value: localStorageMock, +}); + +// Mock navigator.onLine +Object.defineProperty(navigator, "onLine", { + writable: true, + value: true, +}); + +// --------------------------------------------------------------------------- +// Global test utilities +// --------------------------------------------------------------------------- + +// Silence console.error in tests unless explicitly needed +// Uncomment the following to see all console errors during tests: +// global.console.error = console.error; + +// Suppress specific warnings in tests +const originalWarn = console.warn; +console.warn = (...args: unknown[]) => { + if ( + typeof args[0] === "string" && + (args[0].includes("ReactDOM.render") || args[0].includes("act()") || args[0].includes("Warning:")) + ) { + return; + } + originalWarn(...args); +}; diff --git a/packages/vscode-ide-companion/src/utils/checkForUpdates.test.ts b/packages/vscode-ide-companion/src/utils/checkForUpdates.test.ts new file mode 100644 index 00000000..324679df --- /dev/null +++ b/packages/vscode-ide-companion/src/utils/checkForUpdates.test.ts @@ -0,0 +1,192 @@ +/** + * Unit tests for checkForUpdates + * + * Tests cover: + * - Successful update check when a newer version is available + * - No prompt when current version is up to date + * - Handles non-ok response from marketplace + * - Handles missing version data in response + * - Handles fetch errors gracefully + */ + +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { checkForUpdates } from "./checkForUpdates"; +import { window as vscodeWindow, commands as vscodeCommands } from "@/tests/__mocks__/vscode"; + +interface MarketplaceResponse { + results: Array<{ + extensions?: Array<{ + versions?: Array<{ version: string }>; + }>; + }>; +} + +interface MockFetchResponse { + ok: boolean; + statusText: string; + json: () => Promise; +} + +describe("checkForUpdates", () => { + const mockLog = vi.fn(); + const mockContext = { + extension: { + id: "test.extension", + packageJSON: { version: "1.0.0" }, + }, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("prompts for update when a newer version is available and user clicks Update", async () => { + const mockResponse: MockFetchResponse = { + ok: true, + statusText: "OK", + json: async () => ({ + results: [ + { + extensions: [ + { + versions: [{ version: "2.0.0" }], + }, + ], + }, + ], + }), + }; + global.fetch = vi.fn().mockResolvedValue(mockResponse); + vi.mocked(vscodeWindow.showInformationMessage).mockResolvedValue("Update"); + + await checkForUpdates(mockContext, mockLog); + + expect(vscodeWindow.showInformationMessage).toHaveBeenCalledWith( + "A new version (2.0.0) of this extension is available.", + "Update" + ); + expect(vscodeCommands.executeCommand).toHaveBeenCalledWith( + "workbench.extensions.installExtension", + "test.extension" + ); + expect(mockLog).not.toHaveBeenCalled(); + }); + + it("does not install when user dismisses the update prompt", async () => { + const mockResponse: MockFetchResponse = { + ok: true, + statusText: "OK", + json: async () => ({ + results: [ + { + extensions: [ + { + versions: [{ version: "2.0.0" }], + }, + ], + }, + ], + }), + }; + global.fetch = vi.fn().mockResolvedValue(mockResponse); + vi.mocked(vscodeWindow.showInformationMessage).mockResolvedValue(undefined); + + await checkForUpdates(mockContext, mockLog); + + expect(vscodeWindow.showInformationMessage).toHaveBeenCalled(); + expect(vscodeCommands.executeCommand).not.toHaveBeenCalled(); + }); + + it("does not prompt when current version is up to date", async () => { + const mockResponse: MockFetchResponse = { + ok: true, + statusText: "OK", + json: async () => ({ + results: [ + { + extensions: [ + { + versions: [{ version: "1.0.0" }], + }, + ], + }, + ], + }), + }; + global.fetch = vi.fn().mockResolvedValue(mockResponse); + + await checkForUpdates(mockContext, mockLog); + + expect(vscodeWindow.showInformationMessage).not.toHaveBeenCalled(); + expect(vscodeCommands.executeCommand).not.toHaveBeenCalled(); + }); + + it("does not prompt when current version is newer than marketplace", async () => { + const mockResponse: MockFetchResponse = { + ok: true, + statusText: "OK", + json: async () => ({ + results: [ + { + extensions: [ + { + versions: [{ version: "0.5.0" }], + }, + ], + }, + ], + }), + }; + global.fetch = vi.fn().mockResolvedValue(mockResponse); + + await checkForUpdates(mockContext, mockLog); + + expect(vscodeWindow.showInformationMessage).not.toHaveBeenCalled(); + }); + + it("logs error when fetch response is not ok", async () => { + const mockResponse: MockFetchResponse = { + ok: false, + statusText: "Not Found", + json: async () => ({ results: [] }), + }; + global.fetch = vi.fn().mockResolvedValue(mockResponse); + + await checkForUpdates(mockContext, mockLog); + + expect(mockLog).toHaveBeenCalledWith("Failed to fetch latest version info: Not Found"); + expect(vscodeWindow.showInformationMessage).not.toHaveBeenCalled(); + }); + + it("does not prompt when version data is missing", async () => { + const mockResponse: MockFetchResponse = { + ok: true, + statusText: "OK", + json: async () => ({ + results: [{ extensions: [{}] }], + }), + }; + global.fetch = vi.fn().mockResolvedValue(mockResponse); + + await checkForUpdates(mockContext, mockLog); + + expect(vscodeWindow.showInformationMessage).not.toHaveBeenCalled(); + }); + + it("handles fetch errors gracefully", async () => { + global.fetch = vi.fn().mockRejectedValue(new Error("Network error")); + + await checkForUpdates(mockContext, mockLog); + + expect(mockLog).toHaveBeenCalledWith("Error checking for extension updates: Network error"); + expect(vscodeWindow.showInformationMessage).not.toHaveBeenCalled(); + }); + + it("handles non-Error exceptions", async () => { + global.fetch = vi.fn().mockRejectedValue("String error"); + + await checkForUpdates(mockContext, mockLog); + + expect(mockLog).toHaveBeenCalledWith("Error checking for extension updates: String error"); + }); +}); diff --git a/packages/vscode-ide-companion/src/utils/checkForUpdates.ts b/packages/vscode-ide-companion/src/utils/checkForUpdates.ts new file mode 100644 index 00000000..5b46d504 --- /dev/null +++ b/packages/vscode-ide-companion/src/utils/checkForUpdates.ts @@ -0,0 +1,62 @@ +import * as vscode from "vscode"; +import semver from "semver"; + +interface MarketplaceExtension { + versions?: Array<{ version: string }>; +} + +interface MarketplaceResponse { + results?: Array<{ extensions?: MarketplaceExtension[] }>; +} + +interface CheckForUpdatesContext { + extension: { + id: string; + packageJSON: { version: string }; + }; +} + +export async function checkForUpdates(context: CheckForUpdatesContext, log: (message: string) => void): Promise { + try { + const extensionId = context.extension.id; + const currentVersion = context.extension.packageJSON.version; + + const response = await fetch("https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery", { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json;api-version=7.1-preview.1", + }, + body: JSON.stringify({ + filters: [ + { + criteria: [{ filterType: 7, value: extensionId }], + }, + ], + flags: 946, + }), + }); + + if (!response.ok) { + log(`Failed to fetch latest version info: ${response.statusText}`); + return; + } + + const data = (await response.json()) as MarketplaceResponse; + const extension = data?.results?.[0]?.extensions?.[0]; + const latestVersion = extension?.versions?.[0]?.version; + + if (latestVersion && semver.gt(latestVersion, currentVersion)) { + const selection = await vscode.window.showInformationMessage( + `A new version (${latestVersion}) of this extension is available.`, + "Update" + ); + if (selection === "Update") { + await vscode.commands.executeCommand("workbench.extensions.installExtension", extensionId); + } + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + log(`Error checking for extension updates: ${message}`); + } +} diff --git a/packages/vscode-ide-companion/src/utils/diff-utils.ts b/packages/vscode-ide-companion/src/utils/diff-utils.ts new file mode 100644 index 00000000..b82beefa --- /dev/null +++ b/packages/vscode-ide-companion/src/utils/diff-utils.ts @@ -0,0 +1,205 @@ +/** + * Utilities for working with unified diff previews generated by buildDiffPreview. + * + * The diff format is a simplified single-hunk unified diff: + * ``` + * --- a/filePath (or /dev/null for new files) + * +++ b/filePath + * @@ -oldStart,oldLen +newStart,newLen @@ + * context_before (optional, one line with " " prefix) + * -removed_line_1 + * -removed_line_2 + * +added_line_1 + * +added_line_2 + * context_after (optional, one line with " " prefix) + * ``` + */ + +interface ParsedDiff { + isNewFile: boolean; + removedLines: string[]; + addedLines: string[]; + contextBefore: string[]; + contextAfter: string[]; +} + +/** + * Parse the simplified unified diff from buildDiffPreview into structured data. + */ +export function parseDiff(diffText: string): ParsedDiff { + const diffLines = diffText.split("\n"); + + const isNewFile = diffLines[0]?.includes("/dev/null") ?? false; + + // Find hunk header index + let i = 0; + while (i < diffLines.length && !diffLines[i].startsWith("@@")) { + i++; + } + + const contextBefore: string[] = []; + const removedLines: string[] = []; + const addedLines: string[] = []; + const contextAfter: string[] = []; + + // State machine: before → removed → added → after + enum State { + ContextBefore, + Removed, + Added, + ContextAfter, + Done, + } + + let state: State = State.ContextBefore; + + for (i = i + 1; i < diffLines.length; i++) { + const line = diffLines[i]; + + // Handle truncation marker + if (line === "...") break; + + if (line.length === 0) continue; + + const prefix = line[0]; + const content = line.slice(1); + + switch (state) { + case State.ContextBefore: + if (prefix === " ") { + contextBefore.push(content); + state = State.Removed; + } else if (prefix === "-") { + removedLines.push(content); + state = State.Removed; + } + break; + case State.Removed: + if (prefix === "-") { + removedLines.push(content); + } else if (prefix === "+") { + addedLines.push(content); + state = State.Added; + } else if (prefix === " ") { + contextAfter.push(content); + state = State.ContextAfter; + } + break; + case State.Added: + if (prefix === "+") { + addedLines.push(content); + } else if (prefix === " ") { + contextAfter.push(content); + state = State.ContextAfter; + } + break; + case State.ContextAfter: + if (prefix === " ") { + contextAfter.push(content); + } + break; + } + } + + return { isNewFile, removedLines, addedLines, contextBefore, contextAfter }; +} + +/** + * Given the current file content (after the edit was applied) and the diff + * preview, compute the original content (before the edit). + * + * Returns null if the reconstruction fails (e.g., content no longer matches). + */ +export function reverseApplyDiff(modifiedContent: string, diffText: string): string | null { + const diff = parseDiff(diffText); + + // New file: original is empty + if (diff.isNewFile) { + return ""; + } + + const modifiedLines = modifiedContent.split("\n"); + + // Build the anchor pattern: contextBefore + addedLines + const anchor = [...diff.contextBefore, ...diff.addedLines]; + + if (anchor.length === 0 && diff.contextAfter.length === 0) { + // No identifiable anchor, probably an empty diff + return modifiedContent; + } + + // Find the anchor in modified content + let anchorStart = -1; + for (let j = 0; j <= modifiedLines.length - anchor.length; j++) { + let match = true; + for (let k = 0; k < anchor.length; k++) { + if (modifiedLines[j + k] !== anchor[k]) { + match = false; + break; + } + } + if (match) { + // Verify contextAfter follows after the anchor + const afterStart = j + anchor.length; + if (diff.contextAfter.length === 0) { + anchorStart = j; + break; + } + if (afterStart + diff.contextAfter.length <= modifiedLines.length) { + let afterMatch = true; + for (let k = 0; k < diff.contextAfter.length; k++) { + if (modifiedLines[afterStart + k] !== diff.contextAfter[k]) { + afterMatch = false; + break; + } + } + if (afterMatch) { + anchorStart = j; + break; + } + } + } + } + + if (anchorStart === -1) { + // Try a looser match: find just the contextBefore + if (diff.contextBefore.length > 0) { + for (let j = 0; j <= modifiedLines.length - 1; j++) { + if (modifiedLines[j] === diff.contextBefore[0]) { + anchorStart = j; + break; + } + } + } + + if (anchorStart === -1) { + return null; // Could not reconstruct + } + } + + // Reconstruct original: + // Lines before anchor + contextBefore + removedLines + contextAfter + lines after changed region + const beforeAnchor = modifiedLines.slice(0, anchorStart); + const changedEnd = anchorStart + diff.contextBefore.length + diff.addedLines.length + diff.contextAfter.length; + const afterChanged = modifiedLines.slice(changedEnd); + + const original: string[] = [...beforeAnchor, ...diff.contextBefore, ...diff.removedLines, ...diff.contextAfter]; + + // Remove trailing empty line that may have been added by split() + if (afterChanged.length === 1 && afterChanged[0] === "") { + // Modified has trailing newline, will be rejoined naturally + } else if (afterChanged.length > 0) { + original.push(...afterChanged); + } + + // Handle trailing newline: if modified content ends with \n, ensure original does too + const result = original.join("\n"); + if (modifiedContent.endsWith("\n") && !result.endsWith("\n")) { + return result + "\n"; + } + if (!modifiedContent.endsWith("\n") && result.endsWith("\n")) { + return result.slice(0, -1); + } + + return result; +} diff --git a/packages/vscode-ide-companion/src/utils/logger.test.ts b/packages/vscode-ide-companion/src/utils/logger.test.ts new file mode 100644 index 00000000..f92af283 --- /dev/null +++ b/packages/vscode-ide-companion/src/utils/logger.test.ts @@ -0,0 +1,84 @@ +/** + * Unit tests for logger + * + * Tests cover: + * - Logs message when in Development mode + * - Does not log message when in Production mode + * - Does not log message when in Test mode + */ + +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { createLogger } from "./logger"; + +// Mock vscode module +vi.mock("vscode", () => ({ + ExtensionMode: { + Development: 1, + Production: 2, + Test: 3, + }, +})); + +describe("createLogger", () => { + const mockLogger = { + appendLine: vi.fn(), + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("logs message when in Development mode", () => { + const mockContext = { + extensionMode: 1, + }; + + const logger = createLogger(mockContext, mockLogger); + + logger("Test message"); + + expect(mockLogger.appendLine).toHaveBeenCalledWith("Test message"); + expect(mockLogger.appendLine).toHaveBeenCalledTimes(1); + }); + + it("does not log message when in Production mode", () => { + const mockContext = { + extensionMode: 2, + }; + + const logger = createLogger(mockContext, mockLogger); + + logger("Test message"); + + expect(mockLogger.appendLine).not.toHaveBeenCalled(); + }); + + it("does not log message when in Test mode", () => { + const mockContext = { + extensionMode: 3, + }; + + const logger = createLogger(mockContext, mockLogger); + + logger("Test message"); + + expect(mockLogger.appendLine).not.toHaveBeenCalled(); + }); + + it("logs multiple messages when called multiple times in Development mode", () => { + const mockContext = { + extensionMode: 1, + }; + + const logger = createLogger(mockContext, mockLogger); + + logger("Message 1"); + logger("Message 2"); + logger("Message 3"); + + expect(mockLogger.appendLine).toHaveBeenCalledTimes(3); + expect(mockLogger.appendLine).toHaveBeenCalledWith("Message 1"); + expect(mockLogger.appendLine).toHaveBeenCalledWith("Message 2"); + expect(mockLogger.appendLine).toHaveBeenCalledWith("Message 3"); + }); +}); diff --git a/packages/vscode-ide-companion/src/utils/logger.ts b/packages/vscode-ide-companion/src/utils/logger.ts new file mode 100644 index 00000000..5b51cffb --- /dev/null +++ b/packages/vscode-ide-companion/src/utils/logger.ts @@ -0,0 +1,26 @@ +import * as vscode from "vscode"; + +type OutputChannel = { + appendLine: (message: string) => void; +}; + +interface LoggerContext { + extensionMode: vscode.ExtensionMode; +} + +export function createLogger(context: LoggerContext, logger: OutputChannel) { + const isDevelopment = context.extensionMode === vscode.ExtensionMode.Development; + + return (message: string) => { + // Always log in Development mode, or when explicitly needed + // In Production, you can conditionally enable debug logging via settings + if (isDevelopment) { + logger.appendLine(message); + } + + // Also log important startup messages in all modes + if (message.includes("activated") || message.includes("CheckForUpdates")) { + logger.appendLine(message); + } + }; +} diff --git a/packages/vscode-ide-companion/src/webview/App.tsx b/packages/vscode-ide-companion/src/webview/App.tsx new file mode 100644 index 00000000..ce946abf --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/App.tsx @@ -0,0 +1,106 @@ +import { useCallback, useRef, useMemo } from "react"; +import Header from "@/webview/components/Header"; +import Messages from "@/webview/components/Messages"; +import type { MessagesHandle } from "@/webview/components/Messages"; +import InputPrompt from "@/webview/components/InputPrompt"; +import ThinkingLiveBubble from "@/webview/components/ThinkingLiveBubble"; +import PermissionPrompt from "@/webview/components/PermissionPrompt"; +import ContinuePrompt from "@/webview/components/ContinuePrompt"; +import { useChat } from "@/webview/context/ChatProvider"; +import AskQuestionCarousel from "@/webview/components/AskQuestionCarousel"; +import SearchDialog from "@/webview/components/SearchDialog"; +import type { AskUserQuestionMetadata } from "@/webview/components/bubbles/ToolBubble"; + +export default function App() { + const { state, dispatch, actions } = useChat(); + const messagesRef = useRef(null); + + const handleAskUserQuestions = useCallback( + (questions: AskUserQuestionMetadata["questions"]) => { + dispatch({ type: "SET_ASK_USER_QUESTIONS", data: { questions } }); + }, + [dispatch] + ); + + const handleSelectMessage = useCallback((messageId: string) => { + messagesRef.current?.scrollToMessage(messageId); + }, []); + + const handleSearchOpenChange = useMemo( + () => (open: boolean) => actions.toggleSearchPanel(open), + [actions.toggleSearchPanel] + ); + + return ( +
+
0} + onToggleSearchPanel={actions.toggleSearchPanel} + /> + + + {state.showContinuePrompt && ( + + )} + {state.loading && ( + + )} + {state.askUserQuestions && ( + dispatch({ type: "SET_ASK_USER_QUESTIONS", data: null })} + /> + )} + + actions.editMessage(null)} + /> +
+ ); +} diff --git a/packages/vscode-ide-companion/src/webview/components/AskQuestionCarousel.test.tsx b/packages/vscode-ide-companion/src/webview/components/AskQuestionCarousel.test.tsx new file mode 100644 index 00000000..b5c855a4 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/AskQuestionCarousel.test.tsx @@ -0,0 +1,319 @@ +/** + * Unit tests for AskQuestionCarousel component + * + * Tests cover: + * - Rendering questions in carousel + * - Multi-select and single-select question types + * - Form submission with answers + * - Carousel navigation (prev/next) + * - Close button behavior + * - Empty questions returns null + * - Submit button rendering + */ + +import React from "react"; +import { render, screen, fireEvent, act } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import AskQuestionCarousel from "./AskQuestionCarousel"; + +// Track the mock submit handler for testing +let mockSubmitHandler: ((data: unknown) => void) | null = null; + +// Mock react-hook-form +vi.mock("react-hook-form", () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const createFakeAnswers = (defaultValues: any) => { + const answers = defaultValues?.answers || []; + return answers.map((a: { question?: string }, i: number) => ({ + question: a.question || `q${i}`, + options: answers[i]?.options || ["Option A"], + other: answers[i]?.other || "", + })); + }; + + return { + useForm: vi.fn((opts: Record = {}) => { + const answers = createFakeAnswers(opts.defaultValues); + return { + handleSubmit: (handler: (data: unknown) => void) => { + // Return a submit function + const submitFn = () => { + mockSubmitHandler = handler; + return handler({ answers }); + }; + return submitFn; + }, + control: {}, + watch: vi.fn(() => answers), + setError: vi.fn(), + formState: { errors: {} }, + }; + }), + Controller: vi.fn( + ({ + render: renderFn, + name, + }: { + render: (fieldProps: Record) => React.ReactNode; + name: string; + }) => { + const parts = name.split("."); + const subField = parts.length >= 4 ? parts[3] : "options"; + + const fieldValue = subField === "options" ? ["Option A"] : ""; + return ( +
+ {renderFn({ + field: { + value: fieldValue, + onChange: vi.fn(), + }, + fieldState: { invalid: false }, + })} +
+ ); + } + ), + }; +}); + +vi.mock("@hookform/resolvers/zod", () => ({ + zodResolver: vi.fn(() => vi.fn()), +})); + +vi.mock("zod", () => { + const stringSchema = { optional: vi.fn().mockReturnThis() }; + const arraySchema = { optional: vi.fn().mockReturnThis() }; + return { + object: vi.fn(() => ({ superRefine: vi.fn().mockReturnThis() })), + array: vi.fn(() => arraySchema), + string: vi.fn(() => stringSchema), + }; +}); + +vi.mock("@/webview/utils", () => ({ + buildAskUserQuestionReply: vi.fn(() => ({ ok: true, data: "Q: test\nA: option" })), +})); + +const mockSendPrompt = vi.fn(); +vi.mock("@/webview/context", () => ({ + useChat: () => ({ + actions: { + sendPrompt: mockSendPrompt, + }, + }), +})); + +vi.mock("./ui/carousel", () => ({ + Carousel: vi.fn( + ({ + children, + setApi, + }: { + children: React.ReactNode; + setApi?: (api: Record) => void; + className?: string; + }) => { + React.useEffect(() => { + if (typeof setApi === "function") { + setApi({ + scrollSnapList: () => [0, 1], + selectedScrollSnap: () => 0, + canScrollPrev: () => false, + canScrollNext: () => true, + scrollPrev: vi.fn(), + scrollNext: vi.fn(), + on: vi.fn(), + }); + } + }, [setApi]); + return
{children}
; + } + ), + CarouselContent: vi.fn(({ children }: { children: React.ReactNode }) => ( +
{children}
+ )), + CarouselItem: vi.fn(({ children }: { children: React.ReactNode }) => ( +
{children}
+ )), +})); + +vi.mock("./ui/card", () => ({ + Card: vi.fn(({ children }: { children: React.ReactNode }) =>
{children}
), + CardContent: vi.fn(({ children }: { children: React.ReactNode }) =>
{children}
), +})); + +vi.mock("./ui/button", () => ({ + Button: vi.fn( + ({ + children, + onClick, + disabled, + type, + }: { + children: React.ReactNode; + onClick?: (e?: React.MouseEvent) => void; + disabled?: boolean; + type?: string; + size?: string; + variant?: string; + }) => ( + + ) + ), +})); + +vi.mock("./ui/collapsible", () => ({ + Collapsible: vi.fn( + ({ children, open }: { children: React.ReactNode; open: boolean; onOpenChange?: (open: boolean) => void }) => ( +
+ {children} +
+ ) + ), + CollapsibleContent: vi.fn(({ children }: { children: React.ReactNode }) => ( +
{children}
+ )), + CollapsibleTrigger: vi.fn(({ children }: { children: React.ReactNode }) => ( +
{children}
+ )), +})); + +vi.mock("./ui/field", () => ({ + Field: vi.fn(({ children }: { children: React.ReactNode }) =>
{children}
), + FieldContent: vi.fn(({ children }: { children: React.ReactNode }) => ( +
{children}
+ )), + FieldDescription: vi.fn(({ children }: { children: React.ReactNode }) => ( +
{children}
+ )), + FieldError: vi.fn(() => null), + FieldGroup: vi.fn(({ children }: { children: React.ReactNode }) =>
{children}
), + FieldLabel: vi.fn(({ children, htmlFor }: { children: React.ReactNode; htmlFor?: string }) => ( + + )), +})); + +vi.mock("./ui/checkbox", () => ({ + Checkbox: vi.fn( + ({ + id, + checked, + onCheckedChange, + }: { + id: string; + checked: boolean; + onCheckedChange?: (checked: boolean) => void; + }) => ( + onCheckedChange?.(e.target.checked)} + /> + ) + ), +})); + +vi.mock("./ui/radio-group", () => ({ + RadioGroup: vi.fn( + ({ children, value }: { children: React.ReactNode; value?: string; onValueChange?: (val: string) => void }) => ( +
+ {children} +
+ ) + ), + RadioGroupItem: vi.fn(({ value, id }: { value: string; id: string }) => ( + + )), +})); + +vi.mock("./ui/input", () => ({ + Input: vi.fn(({ placeholder, ...props }: { placeholder?: string; [key: string]: unknown }) => ( + + )), +})); + +vi.mock("lucide-react", () => ({ + ChevronDownIcon: vi.fn(() => ), + ChevronLeft: vi.fn(() => ), + ChevronRight: vi.fn(() => ), + X: vi.fn(() => ), +})); + +interface Question { + question: string; + multiSelect: boolean; + options: Array<{ label: string; description?: string }>; +} + +const singleSelectQuestion: Question = { + question: "Which framework?", + multiSelect: false, + options: [ + { label: "React", description: "UI library" }, + { label: "Vue", description: "Progressive framework" }, + ], +}; + +const multiSelectQuestion: Question = { + question: "Which features?", + multiSelect: true, + options: [ + { label: "Auth", description: "Authentication module" }, + { label: "API", description: "REST API" }, + ], +}; + +describe("AskQuestionCarousel", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockSubmitHandler = null; + }); + + it("returns null when questions array is empty", () => { + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); + + it("renders the carousel with questions", () => { + render(); + expect(screen.getByTestId("carousel")).toBeInTheDocument(); + expect(screen.getByTestId("collapsible")).toBeInTheDocument(); + }); + + it("renders the first question text in the header", () => { + render(); + expect(screen.getByText("Which framework?")).toBeInTheDocument(); + }); + + it("renders a submit button as type=submit", () => { + render(); + const buttons = screen.getAllByTestId("ui-button"); + const submitBtn = buttons.find((b) => b.getAttribute("type") === "submit"); + expect(submitBtn).toBeInTheDocument(); + expect(submitBtn?.textContent).toBe("Submit"); + }); + + it("renders the form for submitting answers", () => { + render(); + const form = document.querySelector("form"); + expect(form).toBeInTheDocument(); + }); + + it("renders both question types when mixed", () => { + render(); + const items = screen.getAllByTestId("carousel-item"); + expect(items).toHaveLength(2); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/components/AskQuestionCarousel.tsx b/packages/vscode-ide-companion/src/webview/components/AskQuestionCarousel.tsx new file mode 100644 index 00000000..83d1e39a --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/AskQuestionCarousel.tsx @@ -0,0 +1,303 @@ +import type { CarouselApi } from "@/webview/components/ui/carousel"; +import { Carousel, CarouselContent, CarouselItem } from "@/webview/components/ui/carousel"; +import React, { useCallback, useEffect } from "react"; +import { Card, CardContent } from "@/webview/components/ui/card"; +import { Button } from "@/webview/components/ui/button"; +import { ChevronDownIcon, ChevronLeft, ChevronRight, X } from "lucide-react"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { buildAskUserQuestionReply } from "@/webview/utils"; +import * as z from "zod"; +import { useChat } from "@/webview/context"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/webview/components/ui/collapsible"; +import { + Field, + FieldContent, + FieldDescription, + FieldError, + FieldGroup, + FieldLabel, +} from "@/webview/components/ui/field"; +import { Checkbox } from "@/webview/components/ui/checkbox"; +import { RadioGroup, RadioGroupItem } from "@/webview/components/ui/radio-group"; +import { Input } from "@/webview/components/ui/input"; + +interface QuestionOption { + label: string; + description?: string; +} + +interface Question { + question: string; + multiSelect: boolean; + options: QuestionOption[]; +} + +interface AskQuestionCarouselProps { + questions: Question[]; + onClose: () => void; +} + +const answerSchema = z + .object({ + question: z.string(), + options: z.array(z.string()).optional(), + other: z.string().optional(), + }) + .superRefine((data, ctx) => { + const opts = data.options ?? []; + const oth = data.other ?? ""; + if (opts.length === 0 && oth.trim().length === 0) { + ctx.addIssue({ + code: "custom", + message: "Please answer the question.", + path: ["other"], + }); + } + }); + +const formSchema = z.object({ + answers: z.array(answerSchema), +}); + +export type AnswerFormValues = z.infer; + +const AskQuestionCarousel: React.FC = ({ questions, onClose }) => { + const { actions } = useChat(); + const [api, setApi] = React.useState(); + const [open, setOpen] = React.useState(true); + const [current, setCurrent] = React.useState(0); + const [count, setCount] = React.useState(0); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + answers: questions.map((q) => ({ + question: q.question, + options: [] as string[], + other: "", + })), + }, + }); + + // 实时监听每个问题的答案,用于决定是否禁用提交按钮 + const watchedAnswers = form.watch("answers"); + const allEmpty = + watchedAnswers?.every((a: { options?: string[]; other?: string }) => { + const opts = a?.options ?? []; + const oth = a?.other ?? ""; + return opts.length === 0 && oth.trim().length === 0; + }) ?? true; + + const onSubmit = useCallback( + async (data: AnswerFormValues) => { + console.log(JSON.stringify(data.answers)); + const reply = buildAskUserQuestionReply(data?.answers || []); + console.log("reply:", JSON.stringify(reply)); + if (!reply.ok) { + console.log("reply.error:", reply.error || "Please answer the question."); + form.setError("answers", { message: reply.error || "Please answer the question." }); + return; + } + + // Build Q&A summary + const summary = questions + .map((q, idx) => { + const ans = data.answers[idx]; + const ansText = + (ans?.options && ans.options.length > 0 ? ans.options.join(", ") : "") + + (ans?.options && ans.options.length > 0 && ans?.other ? "; " : "") + + (ans?.other ?? ""); + return `Q: ${q.question}\nA: ${ansText || "(无回答)"}`; + }) + .join("\n\n"); + + // // Insert system message via RPC (persisted in session) + // try { + // await chatService.addSystemMessage(summary, { kind: "ask_user_question_summary" }); + // } catch (err) { + // console.error("Failed to add system message:", err); + // } + + // Close the carousel + onClose?.(); + + // Send the user reply to backend + actions.sendPrompt(summary || "", [], [], { askUserQuestionSummary: true }); + }, + [form, actions, questions, onClose] + ); + + useEffect(() => { + if (!api) { + return; + } + setCount(api.scrollSnapList().length); + setCurrent(api.selectedScrollSnap() + 1); + api.on("select", () => { + setCurrent(api.selectedScrollSnap() + 1); + }); + }, [api]); + + if (!questions || questions.length === 0) { + return null; + } + + return ( +
+
+ + + +
+ {questions[0]?.question || `Question ${current}`} +
+ + +
+
+
+ + + + {questions.map((q, index) => ( + + + + + {q.multiSelect ? ( + (q.options || []).map((opt: { label: string; description?: string }, optIdx: number) => ( + ( + + { + const newValue = checked + ? [...(field.value || []), opt.label] + : field.value + ? field.value.filter((v: string) => v !== opt.label) + : []; + field.onChange(newValue); + }} + /> + + + {opt.label} + + {opt.description && ( + + {opt.description} + + )} + {fieldState.invalid && } + + + )} + /> + )) + ) : ( + ( + field.onChange([val])} + aria-invalid={fieldState.invalid} + > + {(q.options || []).map( + (opt: { label: string; description?: string }, optIdx: number) => ( + + + + + {opt.label} + + {opt.description && ( + + {opt.description} + + )} + + + ) + )} + + )} + /> + )} + ( + + + Other + + + {fieldState.invalid && } + + )} + /> + + + + + ))} + + + +
+
+
+
+ + + + {current}/{count} + +
+
+ +
+
+
+
+ ); +}; + +export default AskQuestionCarousel; diff --git a/packages/vscode-ide-companion/src/webview/components/AskQuestionSummary.test.tsx b/packages/vscode-ide-companion/src/webview/components/AskQuestionSummary.test.tsx new file mode 100644 index 00000000..216553c2 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/AskQuestionSummary.test.tsx @@ -0,0 +1,90 @@ +/** + * Unit tests for AskQuestionSummary component + * + * Tests cover: + * - Rendering Q&A content + * - Styling of Q: lines vs A: lines + * - Multiple Q&A pairs + * - Empty content + * - BubbleDot rendering + */ + +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import AskQuestionSummary from "./AskQuestionSummary"; + +vi.mock("@/webview/components/bubbles/BubbleDot", () => ({ + default: vi.fn(({ variant }: { variant: string }) => ), +})); + +// cn from @/webview/lib/utils is used with clsx objects, so we need a proper mock +vi.mock("@/webview/lib/utils", () => ({ + cn: (...inputs: (string | Record | undefined | null)[]) => { + return inputs + .map((input) => { + if (!input) return ""; + if (typeof input === "string") return input; + if (typeof input === "object") { + return Object.entries(input) + .filter(([, v]) => v) + .map(([k]) => k) + .join(" "); + } + return String(input); + }) + .filter(Boolean) + .join(" "); + }, +})); + +describe("AskQuestionSummary", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders Q&A content", () => { + const content = "Q: What framework?" + "\n" + "A: React"; + render(); + expect(screen.getByText("Q: What framework?")).toBeInTheDocument(); + expect(screen.getByText("A: React")).toBeInTheDocument(); + }); + + it("renders '明白了,请稍等...' follow-up text", () => { + const content = "Q: What framework?" + "\n" + "A: React"; + render(); + expect(screen.getByText("明白了,请稍等...")).toBeInTheDocument(); + }); + + it("renders Q lines with muted style", () => { + render(); + const qLine = screen.getByText("Q: What framework?"); + expect(qLine.className).toContain("text-muted-foreground"); + expect(qLine.className).toContain("font-normal"); + }); + + it("renders non-Q lines with bold style", () => { + render(); + const aLine = screen.getByText("A: React"); + expect(aLine.className).toContain("font-bold"); + }); + + it("renders BubbleDot with success variant", () => { + render(); + const dot = screen.getByTestId("bubble-dot"); + expect(dot).toBeInTheDocument(); + expect(dot.getAttribute("data-variant")).toBe("success"); + }); + + it("renders multiple Q&A pairs", () => { + const content = ["Q: First question?", "A: Option A, Option B", "Q: Second question?", "A: Value"].join("\n"); + render(); + expect(screen.getByText("Q: First question?")).toBeInTheDocument(); + expect(screen.getByText("Q: Second question?")).toBeInTheDocument(); + }); + + it("handles single line content", () => { + render(); + expect(screen.getByText("Single answer")).toBeInTheDocument(); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/components/AskQuestionSummary.tsx b/packages/vscode-ide-companion/src/webview/components/AskQuestionSummary.tsx new file mode 100644 index 00000000..fba81ab3 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/AskQuestionSummary.tsx @@ -0,0 +1,22 @@ +import BubbleDot from "@/webview/components/bubbles/BubbleDot"; +import type { MessageMeta } from "@vegamo/deepcode-core"; +import { cn } from "@/webview/lib/utils"; + +const AskQuestionSummary = ({ content }: { content: string; meta?: MessageMeta }) => { + return ( +
+ +
+
+ {content.split("\n").map((line) => ( +
+ {line} +
+ ))} +
+
明白了,请稍等...
+
+
+ ); +}; +export default AskQuestionSummary; diff --git a/packages/vscode-ide-companion/src/webview/components/ContextIndicator.test.tsx b/packages/vscode-ide-companion/src/webview/components/ContextIndicator.test.tsx new file mode 100644 index 00000000..06725dac --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/ContextIndicator.test.tsx @@ -0,0 +1,164 @@ +/** + * Unit tests for ContextIndicator component + * + * Tests cover: + * - Rendering ProgressRing trigger + * - HoverCard content (model, thinking, effort, tokens) + * - Session usage display + * - Token formatting + * - Missing/empty tokenTelemetry + */ + +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import ContextIndicator from "./ContextIndicator"; +import type { TokenTelemetry } from "@/webview/types"; + +vi.mock("./ui/hover-card", () => ({ + HoverCard: vi.fn(({ children }: { children: React.ReactNode }) =>
{children}
), + HoverCardContent: vi.fn(({ children }: { children: React.ReactNode }) => ( +
{children}
+ )), + HoverCardTrigger: vi.fn(({ children }: { children: React.ReactNode }) => ( +
{children}
+ )), +})); + +vi.mock("./ui/input-group", () => ({ + InputGroupButton: vi.fn(({ children, ...props }: { children: React.ReactNode }) => ( + + )), +})); + +vi.mock("./ui/progress-ring", () => ({ + ProgressRing: vi.fn(({ value }: { value: number }) => ), +})); + +vi.mock("./ui/progress", () => ({ + Progress: vi.fn(({ value }: { value: number }) => ), +})); + +vi.mock("./ui/field", () => ({ + Field: vi.fn(({ children }: { children: React.ReactNode }) =>
{children}
), + FieldLabel: vi.fn(({ children, htmlFor }: { children: React.ReactNode; htmlFor?: string }) => ( + + )), +})); + +vi.mock("./ui/separator", () => ({ + Separator: vi.fn(() =>
), +})); + +vi.mock("@/webview/utils", () => ({ + flattenUsageFields: vi.fn((usage: Record | undefined) => { + if (!usage) return []; + return [ + ["total_tokens", 5000], + ["prompt_cached_tokens", 2000], + ]; + }), + formatUsageFieldLabel: vi.fn((label: string) => label), + getTokenUsagePercent: vi.fn((tt: TokenTelemetry | undefined) => { + if (!tt?.activeTokens || !tt?.compactPromptTokenThreshold) return 0; + return Math.min(100, Math.floor((tt.activeTokens / tt.compactPromptTokenThreshold) * 100)); + }), + toTitleCase: vi.fn((str: string) => str.charAt(0).toUpperCase() + str.slice(1)), +})); + +const baseTelemetry: TokenTelemetry = { + model: "claude-sonnet-4-20250514", + thinkingEnabled: true, + reasoningEffort: "max", + activeTokens: 50000, + compactPromptTokenThreshold: 200000, + usage: { + total_tokens: 150000, + prompt_cached_tokens: 50000, + }, +}; + +describe("ContextIndicator", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders the ProgressRing trigger button", () => { + render(); + expect(screen.getByTestId("input-group-button")).toBeInTheDocument(); + expect(screen.getByTestId("progress-ring")).toBeInTheDocument(); + }); + + it("renders with 0% when tokenTelemetry is undefined", () => { + render(); + const ring = screen.getByTestId("progress-ring"); + expect(ring.getAttribute("data-value")).toBe("0"); + }); + + it("displays model name in hover card", () => { + render(); + expect(screen.getByText("claude-sonnet-4-20250514")).toBeInTheDocument(); + }); + + it('displays "unknown" model when not provided', () => { + render(); + expect(screen.getByText("unknown")).toBeInTheDocument(); + }); + + it("displays thinking enabled state", () => { + render(); + expect(screen.getByText("true")).toBeInTheDocument(); + }); + + it("displays thinking disabled state", () => { + render(); + expect(screen.getByText("false")).toBeInTheDocument(); + }); + + it("displays reasoning effort", () => { + render(); + expect(screen.getByText("max")).toBeInTheDocument(); + }); + + it('displays "max" for missing reasoning effort', () => { + render(); + expect(screen.getByText("max")).toBeInTheDocument(); + }); + + it("displays active tokens formatted", () => { + render(); + // Should render a span with textContent that includes "50,000" + const tokenSpans = screen.getAllByText(/50,000/); + expect(tokenSpans.length).toBeGreaterThan(0); + }); + + it("shows session usage separator when usage data exists", () => { + render(); + expect(screen.getByTestId("separator")).toBeInTheDocument(); + }); + + it("does not show separator when no usage data", () => { + render(); + expect(screen.queryByTestId("separator")).not.toBeInTheDocument(); + }); + + it('displays "Context Window" title in hover card', () => { + render(); + expect(screen.getByText("Context Window")).toBeInTheDocument(); + }); + + it("renders usage percentage in hover card label", () => { + render(); + // getTokenUsagePercent mock returns (50000/200000)*100 = 25 + expect(screen.getByText("25%")).toBeInTheDocument(); + }); + + it("renders a progress bar for usage", () => { + render(); + expect(screen.getByTestId("progress-bar")).toBeInTheDocument(); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/components/ContextIndicator.tsx b/packages/vscode-ide-companion/src/webview/components/ContextIndicator.tsx new file mode 100644 index 00000000..2f5117f1 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/ContextIndicator.tsx @@ -0,0 +1,76 @@ +import type { TokenTelemetry } from "@/webview/types"; +import { HoverCard, HoverCardContent, HoverCardTrigger } from "./ui/hover-card"; +import { InputGroupButton } from "./ui/input-group"; +import { ProgressRing } from "./ui/progress-ring"; +import { Field, FieldLabel } from "./ui/field"; +import { Progress } from "./ui/progress"; +import { flattenUsageFields, formatUsageFieldLabel, getTokenUsagePercent, toTitleCase } from "@/webview/utils"; +import { Separator } from "@/webview/components/ui/separator"; + +interface ContextMeterProps { + tokenTelemetry?: TokenTelemetry; +} + +function formatTokenCount(value: unknown): string { + const n = Number(value || 0); + if (!Number.isFinite(n)) return "0"; + return n.toLocaleString(); +} + +export default function ContextIndicator({ tokenTelemetry }: ContextMeterProps) { + const activeTokens = tokenTelemetry?.activeTokens || 0; + const percent = getTokenUsagePercent(tokenTelemetry); + const usedRows = flattenUsageFields(tokenTelemetry?.usage); + + return ( + + + + + + + +
Context Window
+ + + {percent}% + used + + + +
+
+ Model + {tokenTelemetry?.model || "unknown"} +
+
+ Thinking + {tokenTelemetry?.thinkingEnabled ? "true" : "false"} +
+
+ Effort + {tokenTelemetry?.reasoningEffort || "max"} +
+
+ Active Tokens + {formatTokenCount(activeTokens)} +
+
+ {usedRows?.length > 0 && ( + <> + +
+
Session Usage
+ {(usedRows || []).map(([key, value]) => ( +
+ {toTitleCase(formatUsageFieldLabel(key))} + {formatTokenCount(value)} +
+ ))} +
+ + )} +
+
+ ); +} diff --git a/packages/vscode-ide-companion/src/webview/components/ContinuePrompt.test.tsx b/packages/vscode-ide-companion/src/webview/components/ContinuePrompt.test.tsx new file mode 100644 index 00000000..5e977afb --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/ContinuePrompt.test.tsx @@ -0,0 +1,102 @@ +/** + * Unit tests for ContinuePrompt component + * + * Tests cover: + * - Renders the continue dialog correctly + * - Continue button triggers onContinue callback + * - X dismiss button triggers onDismiss callback + * - Dialog displays correct title and description text + */ + +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import ContinuePrompt from "./ContinuePrompt"; + +describe("ContinuePrompt", () => { + it("renders the continue dialog with correct title", () => { + render(); + + expect(screen.getByText("Session interrupted")).toBeInTheDocument(); + }); + + it("renders the description text", () => { + render(); + + expect( + screen.getByText("The previous operation was interrupted. Do you want to continue the conversation?") + ).toBeInTheDocument(); + }); + + it("renders a Continue button", () => { + render(); + + const continueButton = screen.getByRole("button", { name: /continue/i }); + expect(continueButton).toBeInTheDocument(); + }); + + it("calls onContinue when Continue button is clicked", () => { + const onContinue = vi.fn(); + const onDismiss = vi.fn(); + + render(); + + const continueButton = screen.getByRole("button", { name: /continue/i }); + fireEvent.click(continueButton); + + expect(onContinue).toHaveBeenCalledTimes(1); + expect(onDismiss).not.toHaveBeenCalled(); + }); + + it("calls onDismiss when X close button is clicked", () => { + const onContinue = vi.fn(); + const onDismiss = vi.fn(); + + render(); + + // The X button has title="Dismiss" + const dismissButton = screen.getByTitle("Dismiss"); + fireEvent.click(dismissButton); + + expect(onDismiss).toHaveBeenCalledTimes(1); + expect(onContinue).not.toHaveBeenCalled(); + }); + + it("renders the dialog within a bordered container", () => { + const { container } = render(); + + // The dialog should have the rounded border class + const dialogContainer = container.querySelector(".rounded-md.border"); + expect(dialogContainer).toBeInTheDocument(); + }); + + it("renders with correct layout structure", () => { + const { container } = render(); + + // Should have flex layout for title and close button + const headerRow = container.querySelector(".flex.items-center.justify-between"); + expect(headerRow).toBeInTheDocument(); + + // Should have a button group + const buttonGroup = container.querySelector(".flex.gap-2"); + expect(buttonGroup).toBeInTheDocument(); + }); + + it("can be called multiple times with different callbacks", () => { + const onContinue1 = vi.fn(); + const onDismiss1 = vi.fn(); + const onContinue2 = vi.fn(); + const onDismiss2 = vi.fn(); + + const { rerender } = render(); + + fireEvent.click(screen.getByRole("button", { name: /continue/i })); + expect(onContinue1).toHaveBeenCalledTimes(1); + + rerender(); + + fireEvent.click(screen.getByRole("button", { name: /continue/i })); + expect(onContinue2).toHaveBeenCalledTimes(1); + expect(onContinue1).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/components/ContinuePrompt.tsx b/packages/vscode-ide-companion/src/webview/components/ContinuePrompt.tsx new file mode 100644 index 00000000..cd97d4f6 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/ContinuePrompt.tsx @@ -0,0 +1,38 @@ +import { Button } from "@/webview/components/ui/button"; +import { X } from "lucide-react"; + +export interface ContinuePromptProps { + onContinue: () => void; + onDismiss: () => void; +} + +export default function ContinuePrompt({ onContinue, onDismiss }: ContinuePromptProps) { + return ( +
+
+
+ Session interrupted + +
+ +

+ The previous operation was interrupted. Do you want to continue the conversation? +

+ +
+ +
+
+
+ ); +} diff --git a/packages/vscode-ide-companion/src/webview/components/DiffPreview.test.tsx b/packages/vscode-ide-companion/src/webview/components/DiffPreview.test.tsx new file mode 100644 index 00000000..baca92cb --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/DiffPreview.test.tsx @@ -0,0 +1,351 @@ +/** + * Unit tests for DiffPreview component + * + * Tests cover: + * - Null/empty state rendering + * - File path display and Open button + * - View Diff button (conditional on diff_preview) + * - Output text rendering + * - Diff line rendering (added, removed, context) + * - Button click handlers (wrpc.openFile, wrpc.showDiffEditor) + * - Edge cases (missing diff, empty output, truncated diffs) + */ + +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import DiffPreview from "./DiffPreview"; +import type { FileToolMetadata } from "@/webview/components/bubbles/ToolBubble"; + +// --------------------------------------------------------------------------- +// Mocks +// --------------------------------------------------------------------------- + +const mockOpenFileMutate = vi.fn<(input: { filePath: string; line: number }) => Promise<{ ok: boolean }>>(); +const mockShowDiffEditorMutate = + vi.fn<(input: { filePath: string; diffPreview: string }) => Promise<{ ok: boolean }>>(); + +vi.mock("@/webview/wrpc", () => ({ + wrpc: { + openFile: { mutate: (input: unknown) => mockOpenFileMutate(input as { filePath: string; line: number }) }, + showDiffEditor: { + mutate: (input: unknown) => mockShowDiffEditorMutate(input as { filePath: string; diffPreview: string }), + }, + }, +})); + +vi.mock("@/webview/components/ui/button", () => ({ + Button: vi.fn(({ children, onClick, title, variant, size, ...props }: Record) => { + if (variant === "link") { + return ( + + ); + } + return ( + + ); + }), +})); + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function buildMetadata(overrides: Partial = {}): FileToolMetadata { + return { + file_path: "/src/app.ts", + diff_preview: `--- a/src/app.ts ++++ b/src/app.ts +@@ -5,3 +5,3 @@ + const x = 1; +-const y = 2; ++const y = 3; + return x + y;`, + ...overrides, + }; +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe("DiffPreview", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + // ------------------------------------------------------------------ + // Null / empty state + // ------------------------------------------------------------------ + + describe("null / empty state", () => { + it("returns null when metadata is undefined", () => { + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); + + it("returns null when file_path is undefined", () => { + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); + + it("returns null when file_path is empty string", () => { + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); + }); + + // ------------------------------------------------------------------ + // File path rendering + // ------------------------------------------------------------------ + + describe("file path rendering", () => { + it("renders the file path as a clickable link", () => { + render(); + const link = screen.getByTestId("file-path-link"); + expect(link).toBeInTheDocument(); + expect(link).toHaveTextContent("/src/app.ts"); + }); + + it('renders "File" label', () => { + render(); + expect(screen.getByText("File")).toBeInTheDocument(); + }); + + it("clicking the file path link calls wrpc.openFile.mutate", () => { + const metadata = buildMetadata(); + render(); + const link = screen.getByTestId("file-path-link"); + fireEvent.click(link); + expect(mockOpenFileMutate).toHaveBeenCalledWith({ + filePath: metadata.file_path, + line: 1, + }); + }); + }); + + // ------------------------------------------------------------------ + // Open button + // ------------------------------------------------------------------ + + describe('"Open" button', () => { + it('renders "Open" button when file_path exists', () => { + render(); + const buttons = screen.getAllByTestId("action-button"); + const openButton = buttons.find((b) => b.textContent === "Open"); + expect(openButton).toBeInTheDocument(); + }); + + it('"Open" button is visible even without diff_preview', () => { + const metadata = buildMetadata({ diff_preview: undefined }); + render(); + const buttons = screen.getAllByTestId("action-button"); + const openButton = buttons.find((b) => b.textContent === "Open"); + expect(openButton).toBeInTheDocument(); + }); + + it('clicking "Open" button calls wrpc.openFile.mutate', () => { + const metadata = buildMetadata(); + render(); + const buttons = screen.getAllByTestId("action-button"); + const openButton = buttons.find((b) => b.textContent === "Open"); + fireEvent.click(openButton!); + expect(mockOpenFileMutate).toHaveBeenCalledWith({ + filePath: metadata.file_path, + line: 1, + }); + }); + }); + + // ------------------------------------------------------------------ + // "View Diff" button + // ------------------------------------------------------------------ + + describe('"View Diff" button', () => { + it('renders "View Diff" button when diff_preview exists', () => { + render(); + const buttons = screen.getAllByTestId("action-button"); + const viewDiffButton = buttons.find((b) => b.textContent === "View Diff"); + expect(viewDiffButton).toBeInTheDocument(); + }); + + it('does NOT render "View Diff" button when diff_preview is undefined', () => { + const metadata = buildMetadata({ diff_preview: undefined }); + render(); + const buttons = screen.getAllByTestId("action-button"); + const viewDiffButton = buttons.find((b) => b.textContent === "View Diff"); + expect(viewDiffButton).toBeUndefined(); + }); + + it('does NOT render "View Diff" button when diff_preview is empty string', () => { + const metadata = buildMetadata({ diff_preview: "" }); + render(); + const buttons = screen.getAllByTestId("action-button"); + const viewDiffButton = buttons.find((b) => b.textContent === "View Diff"); + expect(viewDiffButton).toBeUndefined(); + }); + + it('clicking "View Diff" calls wrpc.showDiffEditor.mutate', () => { + const metadata = buildMetadata(); + render(); + const buttons = screen.getAllByTestId("action-button"); + const viewDiffButton = buttons.find((b) => b.textContent === "View Diff"); + fireEvent.click(viewDiffButton!); + expect(mockShowDiffEditorMutate).toHaveBeenCalledWith({ + filePath: metadata.file_path, + diffPreview: metadata.diff_preview, + }); + }); + }); + + // ------------------------------------------------------------------ + // Output text + // ------------------------------------------------------------------ + + describe("output text", () => { + it("renders output text when provided", () => { + render(); + expect(screen.getByText("File written successfully")).toBeInTheDocument(); + }); + + it("does not render output when empty", () => { + render(); + // The output div only renders when output is truthy + expect(screen.queryByTestId("file-path-link")).toBeInTheDocument(); + expect(screen.queryByText("File written successfully")).not.toBeInTheDocument(); + }); + + it("trims output text", () => { + render(); + // The output area should show trimmed text + expect(screen.getByText("trimmed output")).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------ + // Diff lines rendering + // ------------------------------------------------------------------ + + describe("diff lines rendering", () => { + it('renders "Changes" header when diff has lines', () => { + render(); + expect(screen.getByText("Changes")).toBeInTheDocument(); + }); + + it("does not render diff section when no diff lines after filtering", () => { + // Only header lines, no actual diff content + const noContentDiff = `--- a/src/app.ts ++++ b/src/app.ts +@@ -1,1 +1,1 @@`; + const metadata = buildMetadata({ diff_preview: noContentDiff }); + render(); + expect(screen.queryByText("Changes")).not.toBeInTheDocument(); + }); + + it("renders added lines with success styling", () => { + render(); + // The "+" line should have success class + const elements = document.querySelectorAll(".bg-success\\/10, [class*='bg-success']"); + expect(elements.length).toBeGreaterThan(0); + }); + + it("renders removed lines with destructive styling", () => { + render(); + // The "-" line should have destructive class + const elements = document.querySelectorAll(".bg-destructive\\/10, [class*='bg-destructive']"); + expect(elements.length).toBeGreaterThan(0); + }); + + it("renders context lines with muted styling", () => { + render(); + // Context lines should have text-muted-foreground class + const elements = document.querySelectorAll(".text-muted-foreground"); + // At least the "File" label and context line should exist + expect(elements.length).toBeGreaterThanOrEqual(1); + }); + + it("skips empty lines in diff", () => { + const diffWithEmpty = `--- a/src/app.ts ++++ b/src/app.ts +@@ -1,3 +1,3 @@ + + const x = 1; +-const y = 2; ++const y = 3; +`; + const metadata = buildMetadata({ diff_preview: diffWithEmpty }); + render(); + // Should still render without crashing + expect(screen.getByText("Changes")).toBeInTheDocument(); + }); + + it("renders prefix indicator for added/removed lines", () => { + const diff = `--- a/test.ts ++++ b/test.ts +@@ -1,2 +1,2 @@ +-old line ++new line`; + const metadata = buildMetadata({ diff_preview: diff }); + render(); + + // "+" prefix for added lines + const plusSignElements = screen.getAllByText("+"); + // There should be at least one "+" sign (from the added line prefix) + const plusPrefix = plusSignElements.find((el) => el.textContent === "+"); + expect(plusPrefix).toBeInTheDocument(); + + // "-" prefix for removed lines + const minusPrefix = screen.queryByText("-"); + expect(minusPrefix).toBeInTheDocument(); + }); + }); + + // ------------------------------------------------------------------ + // Edge cases + // ------------------------------------------------------------------ + + describe("edge cases", () => { + it("handles file with very long path", () => { + const longPath = "/very/long/path/to/a/file/that/is/deeply/nested/in/the/project/structure/app.ts"; + const metadata = buildMetadata({ file_path: longPath }); + render(); + expect(screen.getByText(longPath)).toBeInTheDocument(); + }); + + it("handles output with special characters", () => { + const specialOutput = "
HTML content
"; + render(); + expect(screen.getByText(specialOutput)).toBeInTheDocument(); + }); + + it("handles diff_preview with truncation marker", () => { + const truncatedDiff = `--- a/large.ts ++++ b/large.ts +@@ -1,40 +1,40 @@ +-line 1 +-line 2 ++new line 1 ++new line 2 +...`; + const metadata = buildMetadata({ diff_preview: truncatedDiff }); + render(); + // Should render without errors + expect(screen.getByText("Changes")).toBeInTheDocument(); + }); + + it("handles single-line diff (one line added)", () => { + const singleDiff = `--- a/file.ts ++++ b/file.ts +@@ -3,1 +3,1 @@ +-old ++new`; + const metadata = buildMetadata({ diff_preview: singleDiff }); + render(); + expect(screen.getByText("Changes")).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/components/DiffPreview.tsx b/packages/vscode-ide-companion/src/webview/components/DiffPreview.tsx new file mode 100644 index 00000000..0d298617 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/DiffPreview.tsx @@ -0,0 +1,96 @@ +import { Button } from "@/webview/components/ui/button"; +import type { FileToolMetadata } from "@/webview/components/bubbles/ToolBubble"; +import { wrpc } from "@/webview/wrpc"; + +interface DiffPreviewProps { + output: string; + metadata: FileToolMetadata; +} + +function formatDisplayPath(filePath: string): string { + if (!filePath) return ""; + return filePath; +} + +export default function DiffPreview({ metadata, output }: DiffPreviewProps) { + const meta = metadata; + if (!meta?.file_path) return null; + + const handleOpenFile = () => { + void wrpc.openFile.mutate({ filePath: meta.file_path!, line: 1 }); + }; + + const handleViewDiff = () => { + if (meta.diff_preview) { + void wrpc.showDiffEditor.mutate({ filePath: meta.file_path!, diffPreview: meta.diff_preview }); + } + }; + + const diffLines = (meta.diff_preview || "") + .split("\n") + .filter((l) => !l.startsWith("--- ") && !l.startsWith("+++ ") && !l.startsWith("@@ ")); + + return ( +
+ {output &&
{output.trim()}
} + +
+ File + + +
+ + {meta.diff_preview && ( + + )} +
+
+ + {diffLines.length > 0 && ( +
+
Changes
+
+ {diffLines.map((line, i) => { + if (!line) return null; + const cls = line.startsWith("+") + ? "bg-success/10 text-success" + : line.startsWith("-") + ? "bg-destructive/10 text-destructive" + : "text-muted-foreground"; + const prefix = line[0] === "+" || line[0] === "-" ? line[0] : " "; + return ( +
+ {prefix} +
{line.slice(1)}
+
+ ); + })} +
+
+ )} +
+ ); +} diff --git a/packages/vscode-ide-companion/src/webview/components/ErrorBoundary.test.tsx b/packages/vscode-ide-companion/src/webview/components/ErrorBoundary.test.tsx new file mode 100644 index 00000000..4771c915 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/ErrorBoundary.test.tsx @@ -0,0 +1,164 @@ +/** + * Unit tests for ErrorBoundary component + * + * Tests cover: + * - Renders children when no error + * - Shows error UI when error occurs + * - Reset functionality + */ + +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import { ErrorBoundary } from "./ErrorBoundary"; + +describe("ErrorBoundary", () => { + // Test component that throws + const ThrowError = ({ shouldThrow }: { shouldThrow: boolean }) => { + if (shouldThrow) { + throw new Error("Test error message"); + } + return
Content rendered successfully
; + }; + + it("renders children when there is no error", () => { + render( + +
Test Child
+
+ ); + + expect(screen.getByTestId("child")).toBeInTheDocument(); + expect(screen.getByText("Test Child")).toBeInTheDocument(); + }); + + it("displays error message when child throws", () => { + render( + + + + ); + + expect(screen.getByText("Something went wrong")).toBeInTheDocument(); + expect(screen.getByText("Test error message")).toBeInTheDocument(); + }); + + it("displays generic message when error has no message", () => { + // Error() creates error with empty string message by default + // `error?.message ?? '...'` uses `??` so empty string is NOT nullish + // An empty message will render as empty string + const NoMessageError = () => { + throw new Error(); // message = "" + }; + + render( + + + + ); + + expect(screen.getByText("Something went wrong")).toBeInTheDocument(); + // The rendered error message is empty string (not "An unexpected error occurred") + // because `error.message` is "" which is not null/undefined for `??` + expect(screen.queryByText("An unexpected error occurred")).not.toBeInTheDocument(); + }); + + it("has a reset/try again button", () => { + render( + + + + ); + + const resetButton = screen.getByRole("button", { name: /try again/i }); + expect(resetButton).toBeInTheDocument(); + }); + + it("resets error state when try again button is clicked", () => { + let shouldThrow = true; + + function TestComponent() { + if (shouldThrow) { + throw new Error("Test error"); + } + return
Recovered!
; + } + + render( + + + + ); + + // Error should be showing + expect(screen.getByText("Something went wrong")).toBeInTheDocument(); + + // Click reset + fireEvent.click(screen.getByRole("button", { name: /try again/i })); + + // The error boundary should have reset, but component still throws + // So we need to update shouldThrow state + shouldThrow = false; + + // Re-render with fixed state + render( + + + + ); + + // Now it should show recovered content + expect(screen.getByTestId("recovered")).toBeInTheDocument(); + }); + + it("logs error to console when componentDidCatch is called", () => { + const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + + const FailingComponent = () => { + throw new Error("Caught by boundary"); + }; + + render( + + + + ); + + expect(consoleSpy).toHaveBeenCalledWith("[ErrorBoundary] Uncaught error:", expect.any(Error), expect.any(Object)); + + consoleSpy.mockRestore(); + }); + + it("renders different child components correctly", () => { + function SimpleComponent() { + return

Simple content

; + } + + render( + + + + ); + + expect(screen.getByText("Simple content")).toBeInTheDocument(); + }); + + it("handles nested errors", () => { + function DeeplyNestedError() { + return ( +
+ Before error + +
+ ); + } + + render( + + + + ); + + expect(screen.getByText("Something went wrong")).toBeInTheDocument(); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/components/ErrorBoundary.tsx b/packages/vscode-ide-companion/src/webview/components/ErrorBoundary.tsx new file mode 100644 index 00000000..4b1ea291 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/ErrorBoundary.tsx @@ -0,0 +1,50 @@ +import { Component, type ErrorInfo, type ReactNode } from "react"; +import { Button } from "@/webview/components/ui/button"; + +interface Props { + children: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends Component { + constructor(props: Props) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error("[ErrorBoundary] Uncaught error:", error, errorInfo); + } + + private handleReset = () => { + this.setState({ hasError: false, error: null }); + }; + + render() { + if (this.state.hasError) { + return ( +
+
+

Something went wrong

+

+ {this.state.error?.message ?? "An unexpected error occurred"} +

+
+ +
+ ); + } + + return this.props.children; + } +} diff --git a/packages/vscode-ide-companion/src/webview/components/Header.test.tsx b/packages/vscode-ide-companion/src/webview/components/Header.test.tsx new file mode 100644 index 00000000..152d52b9 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/Header.test.tsx @@ -0,0 +1,171 @@ +/** + * Unit tests for Header component + * + * Tests cover: + * - Displaying session summary + * - "New Conversation" when no active session + * - Search button visibility conditions + * - Rendering SessionList + * - Delegating callbacks + */ + +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import Header from "./Header"; +import type { SessionSummary } from "@/webview/types"; + +// Mock the image import +vi.mock("../../../assets/deepcoding_icon.png", () => ({ + default: "data:image/png;base64,mockicon", +})); + +vi.mock("./SessionList", () => ({ + default: vi.fn( + ({ + sessions, + open, + onOpenChange, + onSelect, + }: { + sessions: SessionSummary[]; + open: boolean; + onOpenChange: (open: boolean) => void; + onSelect: (id: string) => void; + }) => ( +
+
+ ) + ), +})); + +vi.mock("./ui/button", () => ({ + Button: vi.fn(({ children, onClick, ...props }: { children: React.ReactNode; onClick?: () => void }) => ( + + )), +})); + +vi.mock("./ui/tooltip", () => ({ + Tooltip: vi.fn(({ children }: { children: React.ReactNode }) =>
{children}
), + TooltipContent: vi.fn(({ children }: { children: React.ReactNode }) => ( +
{children}
+ )), + TooltipTrigger: vi.fn(({ children }: { children: React.ReactNode }) => ( +
{children}
+ )), +})); + +vi.mock("lucide-react", () => ({ + Search: vi.fn(() => ), +})); + +const sessions: SessionSummary[] = [ + { + id: "session-1", + summary: "Build a REST API", + createTime: "2024-01-01", + updateTime: "2024-01-02", + status: "active", + }, + { id: "session-2", summary: "Fix CSS bugs", createTime: "2024-01-03", updateTime: "2024-01-04", status: "idle" }, +]; + +const defaultProps = { + sessions, + activeSessionId: "session-1" as string | null, + onSelectSession: vi.fn(), + onCreateNewSession: vi.fn(), + onRenameSession: vi.fn(), + onDeleteSession: vi.fn(), + sessionListOpen: false, + onToggleSessionList: vi.fn(), + hasMessages: true, + onToggleSearchPanel: vi.fn(), +}; + +describe("Header", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders the active session summary", () => { + render(
); + expect(screen.getByText("Build a REST API")).toBeInTheDocument(); + }); + + it('shows "New Conversation" when no active session', () => { + render(
); + expect(screen.getByText("New Conversation")).toBeInTheDocument(); + }); + + it('shows "Deep Code" when activeSessionId but no matching session', () => { + render(
); + expect(screen.getByText("Deep Code")).toBeInTheDocument(); + }); + + it("renders the logo image", () => { + render(
); + const img = screen.getByAltText(""); + expect(img).toBeInTheDocument(); + expect(img.tagName).toBe("IMG"); + }); + + it("shows search button when active session exists and has messages", () => { + render(
); + expect(screen.getByTestId("search-icon")).toBeInTheDocument(); + }); + + it("does not show search button when no active session", () => { + render(
); + expect(screen.queryByTestId("search-icon")).not.toBeInTheDocument(); + }); + + it("does not show search button when no messages", () => { + render(
); + expect(screen.queryByTestId("search-icon")).not.toBeInTheDocument(); + }); + + it("calls onToggleSearchPanel when search button clicked", () => { + render(
); + const searchButtons = screen.getAllByTestId("ui-button"); + // The search button is one of the ui-buttons + const searchBtn = searchButtons.find((btn) => btn.querySelector('[data-testid="search-icon"]')); + if (searchBtn) { + fireEvent.click(searchBtn); + expect(defaultProps.onToggleSearchPanel).toHaveBeenCalledWith(true); + } + }); + + it("renders SessionList component", () => { + render(
); + expect(screen.getByTestId("session-list")).toBeInTheDocument(); + }); + + it("passes sessions to SessionList", () => { + render(
); + const sessionList = screen.getByTestId("session-list"); + expect(sessionList.getAttribute("data-count")).toBe("2"); + }); + + it("passes open state to SessionList", () => { + render(
); + const sessionList = screen.getByTestId("session-list"); + expect(sessionList.getAttribute("data-open")).toBe("true"); + }); + + it("calls onSelectSession when SessionList selects a session", () => { + render(
); + fireEvent.click(screen.getByTestId("session-list-select")); + expect(defaultProps.onSelectSession).toHaveBeenCalledWith("session-1"); + }); + + it("calls onToggleSessionList when SessionList toggles", () => { + render(
); + fireEvent.click(screen.getByTestId("session-list-toggle")); + expect(defaultProps.onToggleSessionList).toHaveBeenCalled(); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/components/Header.tsx b/packages/vscode-ide-companion/src/webview/components/Header.tsx new file mode 100644 index 00000000..03352b28 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/Header.tsx @@ -0,0 +1,87 @@ +import { useCallback, useMemo } from "react"; +import type { SessionSummary } from "@/webview/types"; +import icon from "../../../assets/deepcoding_icon.png"; +import SessionList from "@/webview/components/SessionList"; +import { Search } from "lucide-react"; +import { Button } from "@/webview/components/ui/button"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/webview/components/ui/tooltip"; + +interface HeaderProps { + sessions: SessionSummary[]; + activeSessionId: string | null; + onSelectSession: (sessionId: string) => void; + onCreateNewSession: () => void; + onRenameSession: (sessionId: string, summary: string) => void; + onDeleteSession: (sessionId: string) => void; + sessionListOpen: boolean; + onToggleSessionList: (open?: boolean) => void; + hasMessages: boolean; + onToggleSearchPanel: (open?: boolean) => void; +} + +export default function Header({ + sessions, + activeSessionId, + onSelectSession, + onCreateNewSession, + onRenameSession, + onDeleteSession, + sessionListOpen, + onToggleSessionList, + hasMessages, + onToggleSearchPanel, +}: HeaderProps) { + const handleSelect = useCallback( + (sessionId: string) => { + onSelectSession(sessionId); + }, + [onSelectSession] + ); + const activeSessionSummary = useMemo(() => { + const session = sessions.find((s) => s.id === activeSessionId); + if (session) { + return session.summary; + } + return undefined; + }, [sessions, activeSessionId]); + + return ( +
+
+ + + {activeSessionSummary || (activeSessionId ? "Deep Code" : "New Conversation")} + +
+
+ {activeSessionId && hasMessages && ( + + + + + +

Search Messages

+
+
+ )} + onToggleSessionList(open)} + /> +
+
+ ); +} diff --git a/packages/vscode-ide-companion/src/webview/components/InputPrompt.test.tsx b/packages/vscode-ide-companion/src/webview/components/InputPrompt.test.tsx new file mode 100644 index 00000000..84f9073a --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/InputPrompt.test.tsx @@ -0,0 +1,613 @@ +/** + * Unit tests for InputPrompt component + * + * Tests cover: + * - handleSend functionality + * - Enter key submission + * - ArrowUp/ArrowDown history navigation + * - Skills toggle + */ + +import React from "react"; +import { render, screen, fireEvent, waitFor } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import InputPrompt, { type InputPromptProps } from "./InputPrompt"; +import type { SessionMessage, SkillInfo } from "@/webview/types"; + +// Mock dependencies +vi.mock("@/webview/components/SkillsPanel", () => ({ + default: vi.fn(({ children }) =>
{children}
), +})); + +vi.mock("@/webview/components/SkillsTags", () => ({ + default: vi.fn(({ children }) =>
{children}
), +})); + +vi.mock("@/webview/components/ContextIndicator", () => ({ + default: vi.fn(() =>
), +})); + +vi.mock("@/webview/components/ui/hover-card", () => ({ + HoverCard: vi.fn(({ children }) =>
{children}
), + HoverCardContent: vi.fn(({ children }) =>
{children}
), + HoverCardTrigger: vi.fn(({ children }) =>
{children}
), +})); + +vi.mock("@/webview/components/ui/button", () => ({ + Button: vi.fn(({ children, onClick }) => ( + + )), +})); + +vi.mock("@/webview/components/ui/input-group", () => ({ + InputGroup: vi.fn(({ children }) =>
{children}
), + InputGroupAddon: vi.fn(({ children }) =>
{children}
), + InputGroupButton: vi.fn(({ children, onClick, title, disabled }) => ( + + )), + InputGroupTextarea: vi.fn(({ children, ...props }) => ( + + )), + InputGroupText: vi.fn(({ children }) => {children}), +})); + +vi.mock("@/webview/components/ui/field", () => ({ + Field: vi.fn(({ children }) =>
{children}
), + FieldGroup: vi.fn(({ children }) =>
{children}
), + FieldDescription: vi.fn(({ children }) =>
{children}
), + FieldSet: vi.fn(({ children }) =>
{children}
), +})); + +vi.mock("@/webview/components/ui/spinner", () => ({ + Spinner: vi.fn(({ className }) => ), +})); + +vi.mock("@/webview/components/ui/separator", () => ({ + Separator: vi.fn(() =>
), +})); + +vi.mock("@/webview/components/ui/dropdown-menu", () => ({ + DropdownMenu: vi.fn(({ children }) =>
{children}
), + DropdownMenuTrigger: vi.fn(({ children }) =>
{children}
), + DropdownMenuContent: vi.fn(({ children }) =>
{children}
), + DropdownMenuItem: vi.fn(({ children }) =>
{children}
), + DropdownMenuGroup: vi.fn(({ children }) =>
{children}
), + DropdownMenuLabel: vi.fn(({ children }) =>
{children}
), + DropdownMenuRadioGroup: vi.fn(({ children }) =>
{children}
), + DropdownMenuRadioItem: vi.fn(({ children }) =>
{children}
), +})); + +vi.mock("@/webview/components/ui/item", () => ({ + Item: vi.fn(({ children }) =>
{children}
), + ItemContent: vi.fn(({ children }) =>
{children}
), + ItemDescription: vi.fn(({ children }) =>
{children}
), + ItemMedia: vi.fn(({ children }) =>
{children}
), + ItemTitle: vi.fn(({ children }) =>
{children}
), +})); + +vi.mock("@/webview/components/ui/scroll-area", () => ({ + ScrollArea: vi.fn(({ children }) =>
{children}
), +})); + +vi.mock("@/webview/components/ui/popover", () => ({ + Popover: vi.fn(({ children }) =>
{children}
), + PopoverTrigger: vi.fn(({ children }) =>
{children}
), + PopoverContent: vi.fn(({ children }) =>
{children}
), +})); + +vi.mock("@/webview/components/ui/tooltip", () => ({ + Tooltip: vi.fn(({ children }) =>
{children}
), + TooltipTrigger: vi.fn(({ children }) =>
{children}
), + TooltipContent: vi.fn(({ children }) =>
{children}
), +})); + +vi.mock("@/webview/components/ui/switch", () => ({ + Switch: vi.fn(({ checked, onCheckedChange, ...props }) => ( + onCheckedChange?.(e.target.checked)} + {...props} + /> + )), +})); + +vi.mock("@/webview/components/PromptAttachments", () => ({ + PromptAttachments: vi.fn(({ attachments }) => ( +
{attachments.length} attachments
+ )), + usePromptAttachments: vi.fn(() => ({ + attachments: [], + handlePaste: vi.fn(), + removeAttachment: vi.fn(), + clearAttachments: vi.fn(), + getImageUrls: vi.fn(() => []), + })), +})); + +vi.mock("@/webview/lib/utils", () => ({ + cn: vi.fn((...args: unknown[]) => args.filter(Boolean).join(" ")), +})); + +vi.mock("lucide-react", () => ({ + ArrowUp: vi.fn(() => ), + Square: vi.fn(() => ), + StopCircle: vi.fn(() => ), + ChevronDown: vi.fn(() => ), + Send: vi.fn(() => ), + Reply: vi.fn(() => ), + FileCodeIcon: vi.fn(() => ), + Siren: vi.fn(() => ), + GraduationCap: vi.fn(() => ), + X: vi.fn(() => ), + AlertCircle: vi.fn(() => ), + CheckCircle: vi.fn(() => ), + Loader2: vi.fn(() => ), + Loader2Icon: vi.fn(() => ), + Settings: vi.fn(() => ), + Info: vi.fn(() => ), + Hand: vi.fn(() => ), + SquareChartGantt: vi.fn(() => ), +})); + +const mockOnSendPrompt = + vi.fn< + ( + prompt: string, + skills?: SkillInfo[], + images?: string[], + options?: { permissions?: unknown[]; alwaysAllows?: string[] } + ) => void + >(); +const mockOnInterrupt = vi.fn<() => void>(); +const mockOnSelectSkills = vi.fn<(skills: SkillInfo[]) => void>(); +const mockOnClearEditingMessage = vi.fn<() => void>(); + +const defaultProps: InputPromptProps = { + loading: false, + selectedSkills: [] as SkillInfo[], + availableSkills: [] as SkillInfo[], + pendingPermissionReply: null, + tokenTelemetry: { + model: "test", + thinkingEnabled: false, + reasoningEffort: "low", + activeTokens: 0, + compactPromptTokenThreshold: 100000, + usage: null, + }, + activeEditor: null, + editingMessage: null, + messages: [], + onSendPrompt: mockOnSendPrompt, + onInterrupt: mockOnInterrupt, + onSelectSkills: mockOnSelectSkills, + onClearEditingMessage: mockOnClearEditingMessage, +}; + +describe("InputPrompt", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe("handleSend", () => { + it("calls onSendPrompt when sending a message", async () => { + render(); + const textarea = screen.getByRole("textbox"); + fireEvent.change(textarea, { target: { value: "Hello" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + expect(mockOnSendPrompt).toHaveBeenCalledWith("Hello", [], [], { planMode: false }); + }); + + it("does not send empty message", () => { + render(); + const textarea = screen.getByRole("textbox"); + fireEvent.keyDown(textarea, { key: "Enter" }); + expect(mockOnSendPrompt).not.toHaveBeenCalled(); + }); + + it("trims whitespace before sending", async () => { + render(); + const textarea = screen.getByRole("textbox"); + fireEvent.change(textarea, { target: { value: " Hello " } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + expect(mockOnSendPrompt).toHaveBeenCalledWith("Hello", [], [], { planMode: false }); + }); + + it("does not send while loading", () => { + render(); + const textarea = screen.getByRole("textbox"); + fireEvent.change(textarea, { target: { value: "Hello" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + expect(mockOnSendPrompt).not.toHaveBeenCalled(); + }); + }); + + describe("Keyboard navigation", () => { + it("does not send on Shift+Enter", () => { + render(); + const textarea = screen.getByRole("textbox"); + fireEvent.change(textarea, { target: { value: "Hello" } }); + fireEvent.keyDown(textarea, { key: "Enter", shiftKey: true }); + expect(mockOnSendPrompt).not.toHaveBeenCalled(); + }); + + it("does not send on other keys", () => { + render(); + const textarea = screen.getByRole("textbox"); + fireEvent.change(textarea, { target: { value: "Hello" } }); + fireEvent.keyDown(textarea, { key: "A" }); + expect(mockOnSendPrompt).not.toHaveBeenCalled(); + }); + }); + + describe("History navigation", () => { + it("adds sent messages to history", async () => { + render(); + const textarea = screen.getByRole("textbox"); + + fireEvent.change(textarea, { target: { value: "First message" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + expect(mockOnSendPrompt).toHaveBeenCalledWith("First message", [], [], { planMode: false }); + + fireEvent.change(textarea, { target: { value: "Second message" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + expect(mockOnSendPrompt).toHaveBeenCalledWith("Second message", [], [], { planMode: false }); + }); + + it("navigates to previous history with ArrowUp when at start", async () => { + render(); + const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; + + // Add two messages to history + fireEvent.change(textarea, { target: { value: "First message" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + fireEvent.change(textarea, { target: { value: "Second message" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + + // Clear and navigate with ArrowUp + fireEvent.change(textarea, { target: { value: "" } }); + // Simulate cursor at start (selectionStart === 0) + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + + // Should show "Second message" (most recent) + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("Second message"); + }); + }); + + it("navigates through history with multiple ArrowUp presses", async () => { + render(); + const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; + + // Add three messages to history + fireEvent.change(textarea, { target: { value: "First" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + fireEvent.change(textarea, { target: { value: "Second" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + fireEvent.change(textarea, { target: { value: "Third" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + + // Navigate with ArrowUp twice + fireEvent.change(textarea, { target: { value: "" } }); + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("Third"); + }); + + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("Second"); + }); + }); + + it("navigates to next history with ArrowDown", async () => { + render(); + const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; + + // Add messages to history + fireEvent.change(textarea, { target: { value: "First" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + fireEvent.change(textarea, { target: { value: "Second" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + fireEvent.change(textarea, { target: { value: "Third" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + + // Navigate to history - shows most recent + fireEvent.change(textarea, { target: { value: "" } }); + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("Third"); + }); + + // ArrowUp again - goes to older + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("Second"); + }); + + // ArrowDown - goes back to newer + fireEvent.keyDown(textarea, { key: "ArrowDown" }); + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("Third"); + }); + + // ArrowDown again - exits and restores draft (empty) + fireEvent.keyDown(textarea, { key: "ArrowDown" }); + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe(""); + }); + }); + + it("restores draft when navigating past the end of history", async () => { + render(); + const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; + + // Add messages to history + fireEvent.change(textarea, { target: { value: "First" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + fireEvent.change(textarea, { target: { value: "Second" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + + // Navigate to history (entering with empty value preserves no draft) + fireEvent.change(textarea, { target: { value: "" } }); + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("Second"); + }); + + // Navigate back with ArrowDown (goes forward to end of history) + fireEvent.keyDown(textarea, { key: "ArrowDown" }); + await waitFor(() => { + // Reached end of history, restores empty draft + expect((textarea as HTMLTextAreaElement).value).toBe(""); + }); + }); + + it("exits history browsing when typing", async () => { + render(); + const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; + + // Add message to history + fireEvent.change(textarea, { target: { value: "History message" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + + // Navigate to history + fireEvent.change(textarea, { target: { value: "" } }); + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("History message"); + }); + + // Type something - should exit history browsing + fireEvent.change(textarea, { target: { value: "New input" } }); + + // Next ArrowUp should start from beginning, not from history position + fireEvent.change(textarea, { target: { value: "" } }); + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("History message"); + }); + }); + + it("exits history browsing when pressing non-arrow keys", async () => { + render(); + const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; + + // Add message to history + fireEvent.change(textarea, { target: { value: "History message" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + + // Navigate to history + fireEvent.change(textarea, { target: { value: "" } }); + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("History message"); + }); + + // Press Escape - should exit history browsing but not change value + fireEvent.keyDown(textarea, { key: "Escape" }); + expect((textarea as HTMLTextAreaElement).value).toBe("History message"); + }); + + it("does not navigate history when no history exists", () => { + render(); + const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; + + fireEvent.change(textarea, { target: { value: "" } }); + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + + // Value should remain empty + expect((textarea as HTMLTextAreaElement).value).toBe(""); + }); + + it("does not navigate history when caret is not at start/end", async () => { + render(); + const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; + + // Add message to history + fireEvent.change(textarea, { target: { value: "History message" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + + // Type content with caret in the middle + fireEvent.change(textarea, { target: { value: "Hello World" } }); + // Set cursor to middle (after "Hello") + textarea.setSelectionRange(5, 5); + + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + + // Value should remain unchanged since caret is not at start + expect((textarea as HTMLTextAreaElement).value).toBe("Hello World"); + }); + }); + + describe("Loading history from messages", () => { + it("loads user messages from messages prop into history", async () => { + const messages: SessionMessage[] = [ + { role: "user", content: "First message from history" }, + { role: "assistant", content: "Assistant response" }, + { role: "user", content: "Second message from history" }, + ]; + render(); + const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; + + // Navigate to history + fireEvent.change(textarea, { target: { value: "" } }); + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("Second message from history"); + }); + + // Navigate to older message + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("First message from history"); + }); + }); + + it("clears history when messages is empty", async () => { + const { rerender } = render(); + const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; + + // Add a message first + fireEvent.change(textarea, { target: { value: "New message" } }); + fireEvent.keyDown(textarea, { key: "Enter" }); + + // Rerender with empty messages + rerender(); + + // Try to navigate history - should not work since history is empty + fireEvent.change(textarea, { target: { value: "" } }); + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + + // Value should remain empty + expect((textarea as HTMLTextAreaElement).value).toBe(""); + }); + + it("ignores non-user messages when building history", async () => { + const messages: SessionMessage[] = [ + { role: "user", content: "User message" }, + { role: "assistant", content: "Should be ignored" }, + { role: "tool", content: "Should also be ignored" }, + { role: "system", content: "Ignored" }, + ]; + render(); + const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; + + // Navigate to history + fireEvent.change(textarea, { target: { value: "" } }); + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + + await waitFor(() => { + expect((textarea as HTMLTextAreaElement).value).toBe("User message"); + }); + + // Second ArrowUp should stay at the same message (already at oldest) + fireEvent.keyDown(textarea, { key: "ArrowUp" }); + expect((textarea as HTMLTextAreaElement).value).toBe("User message"); + }); + }); + + describe("Loading state", () => { + it("renders interrupt button when loading", () => { + render(); + expect(screen.getByTitle("Stop")).toBeInTheDocument(); + }); + + it("calls onInterrupt when interrupt button clicked", () => { + render(); + const button = screen.getByTitle("Stop"); + fireEvent.click(button); + expect(mockOnInterrupt).toHaveBeenCalled(); + }); + }); + + describe("Skills", () => { + it("renders skills panel", () => { + render(); + expect(screen.getByTestId("skills-panel")).toBeInTheDocument(); + }); + + it("passes selectedSkills to onSelectSkills", () => { + const skills = [{ id: "skill1", name: "TestSkill" }]; + render(); + // Skills panel should be rendered with skills + expect(screen.getByTestId("skills-panel")).toBeInTheDocument(); + }); + }); + + describe("Context meter", () => { + it("renders context meter", () => { + render(); + expect(screen.getByTestId("context-meter")).toBeInTheDocument(); + }); + }); + + describe("Hover card", () => { + it("renders hover card when activeEditor is null", () => { + render(); + // HoverCard may or may not be rendered based on activeEditor + expect(screen.getByTestId("field-group")).toBeInTheDocument(); + }); + + it("renders hover card when activeEditor is provided", () => { + render( + + ); + expect(screen.getByTestId("hover-card")).toBeInTheDocument(); + }); + }); + + describe("Editing message", () => { + it("restores text when editingMessage changes", () => { + const { rerender } = render(); + + expect((screen.getByRole("textbox") as HTMLTextAreaElement).value).toBe(""); + + rerender( + + ); + + expect((screen.getByRole("textbox") as HTMLTextAreaElement).value).toBe("Editing this message"); + }); + + it("calls onClearEditingMessage when sending while editing", async () => { + const { container } = render( + + ); + + // Wait for the editing message to be processed + await waitFor(() => { + const textarea = screen.getByRole("textbox") as HTMLTextAreaElement; + expect(textarea.value).toBe("Editing this message"); + }); + + // Click send button + const sendButton = container.querySelector("button[title='Send']"); + if (sendButton) { + fireEvent.click(sendButton); + } + + expect(mockOnSendPrompt).toHaveBeenCalledWith("Editing this message", [], [], { planMode: false }); + expect(mockOnClearEditingMessage).toHaveBeenCalled(); + }); + + it("calls onSelectSkills when editingMessage has skills", () => { + const skills = [{ name: "TestSkill" }]; + render(); + + expect(mockOnSelectSkills).toHaveBeenCalledWith(skills); + }); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/components/InputPrompt.tsx b/packages/vscode-ide-companion/src/webview/components/InputPrompt.tsx new file mode 100644 index 00000000..7e04cff8 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/InputPrompt.tsx @@ -0,0 +1,393 @@ +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import SkillsPanel from "@/webview/components/SkillsPanel"; +import SkillsTags from "@/webview/components/SkillsTags"; +import ContextIndicator from "@/webview/components/ContextIndicator"; +import { PromptAttachments } from "@/webview/components/PromptAttachments"; +import type { ActiveEditor, EditingMessage, SessionMessage, SkillInfo, TokenTelemetry } from "@/webview/types"; +import { FileCodeIcon, Hand, Reply, Square, SquareChartGantt } from "lucide-react"; +import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupTextarea } from "@/webview/components/ui/input-group"; +import { Separator } from "@/webview/components/ui/separator"; +import { HoverCard, HoverCardContent, HoverCardTrigger } from "./ui/hover-card"; +import { cn } from "@/webview/lib/utils"; +import { Field, FieldDescription, FieldGroup } from "./ui/field"; +import { Spinner } from "@/webview/components/ui/spinner"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuTrigger, +} from "@/webview/components/ui/dropdown-menu"; +import { Item, ItemContent, ItemDescription, ItemMedia, ItemTitle } from "@/webview/components/ui/item"; +import { usePromptAttachments } from "@/webview/hooks/usePromptAttachments"; +import { toTitleCase } from "@/webview/utils"; +import { toast } from "@/webview/components/ui/sonner"; + +export interface InputPromptProps { + loading: boolean; + selectedSkills: SkillInfo[]; + availableSkills: SkillInfo[]; + pendingPermissionReply: unknown; + tokenTelemetry?: TokenTelemetry; + activeEditor: ActiveEditor | null; + editingMessage: EditingMessage | null; + messages?: SessionMessage[]; // Historical messages for history navigation + onSendPrompt: ( + prompt: string, + skills?: SkillInfo[], + images?: string[], + options?: { + permissions?: unknown[]; + alwaysAllows?: string[]; + planMode?: boolean; + askUserQuestionSummary?: boolean; + } + ) => void; + onInterrupt: () => void; + onSelectSkills: (skills: SkillInfo[]) => void; + onClearEditingMessage: () => void; +} + +export default function InputPrompt({ + loading, + selectedSkills, + availableSkills, + pendingPermissionReply, + tokenTelemetry, + activeEditor, + editingMessage, + messages, + onSendPrompt, + onInterrupt, + onSelectSkills, + onClearEditingMessage, +}: InputPromptProps) { + const [value, setValue] = useState(""); + const [planMode, setPlanMode] = useState<"false" | "true">("false"); + const [history, setHistory] = useState([]); + const [historyIdx, setHistoryIdx] = useState(-1); + const [draftBeforeHistory, setDraftBeforeHistory] = useState(""); + const textareaRef = useRef(null); + const { attachments, handlePaste, removeAttachment, clearAttachments, getImageUrls, loadImages } = + usePromptAttachments({ + onMaxExceeded: () => { + toast.warning("You can paste up to 10 images at a time", { position: "bottom-right" }); + }, + }); + + // Rebuild history from messages when messages change (e.g., loading a session) + useEffect(() => { + if (messages && messages.length > 0) { + const userMessages = messages + .filter( + (m) => + m.role === "user" && + typeof m.content === "string" && + m.content.trim() && + !m?.meta?.userPrompt?.askUserQuestionSummary + ) + .map((m) => m.content); + setHistory(userMessages); + setHistoryIdx(-1); + setDraftBeforeHistory(""); + } else { + // Clear history when no messages (new session) + setHistory([]); + setHistoryIdx(-1); + setDraftBeforeHistory(""); + } + }, [messages]); + + // Auto-resize textarea + const autoResize = useCallback(() => { + const el = textareaRef.current; + if (!el) return; + el.style.height = "auto"; + const lineHeight = 18; + const minHeight = lineHeight * 3 + 20; + const maxHeight = lineHeight * 10 + 20; + const next = Math.min(Math.max(el.scrollHeight, minHeight), maxHeight); + el.style.height = `${next}px`; + el.style.overflowY = el.scrollHeight > maxHeight ? "auto" : "hidden"; + }, []); + + useEffect(() => { + autoResize(); + }, [value, autoResize]); + + // Restore editing state when editingMessage changes + useEffect(() => { + if (editingMessage) { + setValue(editingMessage.text); + if (editingMessage.images && editingMessage.images.length > 0) { + loadImages(editingMessage.images); + } + if (editingMessage.skills && editingMessage.skills.length > 0) { + onSelectSkills(editingMessage.skills); + } + } + }, [editingMessage, loadImages, onSelectSkills]); + + const handleSend = useCallback(() => { + const trimmed = value.trim(); + const images = getImageUrls(); + if ((!trimmed && images.length === 0) || loading) return; + + const reply = pendingPermissionReply as { permissions?: unknown[]; alwaysAllows?: string[] } | null; + setHistory((prev) => [...prev, trimmed]); + setValue(""); + setHistoryIdx(-1); + + onSendPrompt(trimmed, selectedSkills, images, { ...reply, planMode: planMode === "true" }); + onSelectSkills([]); + clearAttachments(); + onClearEditingMessage(); + }, [ + value, + loading, + pendingPermissionReply, + selectedSkills, + onSendPrompt, + onSelectSkills, + getImageUrls, + clearAttachments, + onClearEditingMessage, + planMode, + ]); + + const handleKeyDown = useCallback( + (e: React.KeyboardEvent) => { + // if (e.isComposing) return; + + const el = textareaRef.current; + const isAtStart = el?.selectionStart === 0 && el?.selectionEnd === 0; + const isAtEnd = el?.selectionStart === el?.value.length && el?.selectionEnd === el?.value.length; + + // Exit history browsing mode on any non-ArrowUp/ArrowDown key + if (historyIdx !== -1 && e.key !== "ArrowUp" && e.key !== "ArrowDown") { + setHistoryIdx(-1); + } + + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } else if (e.key === "ArrowUp" && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) { + // Navigate to previous history item when: + // 1. Already in history browsing mode (historyIdx !== -1), OR + // 2. Caret is at the start of the input AND there's history + if ((historyIdx !== -1 || isAtStart) && history.length > 0) { + e.preventDefault(); + let nextIdx = historyIdx; + if (historyIdx === -1) { + // Enter history browsing mode: save current draft + setDraftBeforeHistory(value); + nextIdx = history.length - 1; + } else if (historyIdx > 0) { + nextIdx = historyIdx - 1; + } + if (nextIdx !== historyIdx) { + setHistoryIdx(nextIdx); + setValue(history[nextIdx]); + } + } + } else if (e.key === "ArrowDown" && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey) { + // Navigate to next history item when: + // 1. Already in history browsing mode (historyIdx !== -1), OR + // 2. Caret is at the end of the input AND there's history + if ((historyIdx !== -1 || isAtEnd) && history.length > 0) { + e.preventDefault(); + if (historyIdx !== -1) { + if (historyIdx < history.length - 1) { + // Navigate to next (older) item + const nextIdx = historyIdx + 1; + setHistoryIdx(nextIdx); + setValue(history[nextIdx]); + } else { + // Reached the end of history: restore draft and exit browsing mode + setHistoryIdx(-1); + setValue(draftBeforeHistory); + } + } + } + } + }, + [handleSend, history, historyIdx, value, draftBeforeHistory] + ); + + /** + * Get the active skill badge to display when skills are loading + */ + const getActiveSkill = useCallback(() => { + if (loading && availableSkills.length > 0 && availableSkills.filter((x) => x.isLoaded).length > 0) { + return ( +
+ {availableSkills + .filter((x) => x.isLoaded) + .map((skill) => ( +
+ + + + + {toTitleCase(skill.name)} +
+ ))} +
+ ); + } + return null; + }, [loading, availableSkills]); + + /** + * Determine if the input has content (text or attachments) + */ + const hasContent = useMemo(() => value.trim().length > 0 || attachments.length > 0, [value, attachments]); + + return ( + + + {getActiveSkill()} + +
+ { + setValue(e.target.value); + if (historyIdx !== -1) { + setHistoryIdx(-1); + } + }} + onKeyDown={handleKeyDown} + onPaste={handlePaste} + className="text-[12px] max-h-50" + placeholder="Write a prompt... " + /> +
+ + { + const idx = selectedSkills.findIndex((s) => s.name === skill.name); + if (idx >= 0) { + onSelectSkills(selectedSkills.filter((s) => s.name !== skill.name)); + } else { + onSelectSkills([...selectedSkills, skill]); + } + }} + /> + + + {activeEditor && } + {activeEditor && ( + + + + + {activeEditor.fileName.split("/").pop()} + + + +
{activeEditor.fileName.split("/").pop()}
+

{activeEditor.fileName}

+
+ {activeEditor.languageId} + {activeEditor.lineCount} lines +
+
+
+ )} +
+ + + + {planMode === "true" ? ( +
+ + Plan +
+ ) : ( +
+ + Default +
+ )} +
+
+ + + Modes + setPlanMode(value as "true" | "false")} + > + + + + + + + Default Mode + + Deep Code will make edits and run commands to complete your task + + + + + + + + + + + Plan Mode + + Deep code will explore the code and present a plan before editing + + + + + + + +
+ {loading ? ( + + + + + ) : ( + + + + )} +
+
+ onSelectSkills(selectedSkills.filter((s) => s.name !== name))} + /> + +
+
+
+ ); +} diff --git a/packages/vscode-ide-companion/src/webview/components/Messages.test.tsx b/packages/vscode-ide-companion/src/webview/components/Messages.test.tsx new file mode 100644 index 00000000..b2aee1f1 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/Messages.test.tsx @@ -0,0 +1,105 @@ +/** + * Unit tests for Messages component + * + * Tests cover: + * - Rendering different message types + * - Auto-scroll behavior + * - Message role-based rendering + */ + +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import Messages from "./Messages"; +import type { SessionMessage } from "@/webview/types"; + +// Mock dependencies +vi.mock("@/webview/components/ui/scroll-area", () => ({ + ScrollArea: vi.fn(({ children }) =>
{children}
), + ScrollBar: vi.fn(() =>
), +})); + +vi.mock("@/webview/components/bubbles/UserBubble", () => ({ + default: vi.fn(({ content }) =>
{content}
), +})); + +vi.mock("@/webview/components/bubbles/AssistantBubble", () => ({ + default: vi.fn(({ message }) =>
{message.content}
), +})); + +vi.mock("@/webview/components/bubbles/ThinkingBubble", () => ({ + default: vi.fn(({ content }) =>
{content}
), +})); + +vi.mock("@/webview/components/bubbles/ToolBubble", () => ({ + default: vi.fn(({ content }) =>
{content}
), +})); + +vi.mock("@/webview/components/bubbles/SystemBubble", () => ({ + default: vi.fn(({ content }) =>
{content}
), +})); + +// Mock scrollIntoView +Element.prototype.scrollIntoView = vi.fn(); + +describe("Messages", () => { + const defaultProps = { + messages: [] as SessionMessage[], + loading: false, + llmStreamProgress: null, + processes: null, + }; + + it("renders welcome screen when no messages", () => { + render(); + expect(screen.getByText("Welcome to the Deep Code!")).toBeInTheDocument(); + }); + + it("renders user message", () => { + const messages: SessionMessage[] = [{ role: "user", content: "Hello" }]; + render(); + expect(screen.getByTestId("user-bubble")).toHaveTextContent("Hello"); + }); + + it("renders assistant message", () => { + const messages: SessionMessage[] = [{ role: "assistant", content: "Hi there!" }]; + render(); + expect(screen.getByTestId("assistant-bubble")).toHaveTextContent("Hi there!"); + }); + + it("renders thinking bubble when asThinking is true", () => { + const messages: SessionMessage[] = [{ role: "assistant", content: "Thinking...", meta: { asThinking: true } }]; + render(); + expect(screen.getByTestId("thinking-bubble")).toBeInTheDocument(); + }); + + it("renders tool message", () => { + const messages: SessionMessage[] = [ + { role: "tool", content: "Tool output", meta: { function: { name: "readFile" } } }, + ]; + render(); + expect(screen.getByTestId("tool-bubble")).toHaveTextContent("Tool output"); + }); + + it("renders system message", () => { + const messages: SessionMessage[] = [{ role: "system", content: "System info" }]; + render(); + expect(screen.getByTestId("system-bubble")).toBeInTheDocument(); + }); + + it("renders multiple messages of different types", () => { + const messages: SessionMessage[] = [ + { role: "user", content: "Question" }, + { role: "assistant", content: "Answer" }, + { role: "tool", content: "Result" }, + ]; + render(); + expect(screen.getAllByTestId(/bubble/)).toHaveLength(3); + }); + + it("renders message with html content", () => { + const messages: SessionMessage[] = [{ role: "assistant", content: "Text" }]; + render(); + expect(screen.getByTestId("assistant-bubble")).toHaveTextContent("Text"); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/components/Messages.tsx b/packages/vscode-ide-companion/src/webview/components/Messages.tsx new file mode 100644 index 00000000..0b42f1f2 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/Messages.tsx @@ -0,0 +1,242 @@ +import React, { useRef, useEffect, forwardRef, useImperativeHandle } from "react"; +import { ScrollArea, ScrollBar } from "@/webview/components/ui/scroll-area"; +import UserBubble from "@/webview/components/bubbles/UserBubble"; +import AssistantBubble from "@/webview/components/bubbles/AssistantBubble"; +import ThinkingBubble from "@/webview/components/bubbles/ThinkingBubble"; +import ToolBubble, { + type ToolBubbleProps, + type AskUserQuestionMetadata, +} from "@/webview/components/bubbles/ToolBubble"; +import SystemBubble from "@/webview/components/bubbles/SystemBubble"; +import type { EditingMessage, SessionMessage } from "@/webview/types"; +import AskQuestionSummary from "@/webview/components/AskQuestionSummary"; +import icon from "../../../assets/deepcoding_icon.png"; + +interface MessagesProps { + messages: SessionMessage[]; + loading: boolean; + onEditMessage?: (editing: EditingMessage) => void; + onAskUserQuestions?: (questions: AskUserQuestionMetadata["questions"]) => void; +} + +export interface MessagesHandle { + scrollToMessage: (messageId: string) => void; +} + +const Messages = forwardRef( + ({ messages, loading, onEditMessage, onAskUserQuestions }, ref) => { + const scrollAreaRef = useRef(null); + const bottomRef = useRef(null); + const suppressScrollRef = useRef(false); + const highlightedElRef = useRef(null); + const observerRef = useRef(null); + const observerFallbackRef = useRef | null>(null); + + const handleToBottom = () => { + bottomRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + + useEffect(() => { + if (!suppressScrollRef.current) { + handleToBottom(); + } + }, [messages, loading]); + + // Cleanup observer on unmount + useEffect(() => { + return () => { + if (observerRef.current) { + observerRef.current.disconnect(); + } + if (observerFallbackRef.current) { + clearTimeout(observerFallbackRef.current); + } + }; + }, []); + + useImperativeHandle(ref, () => ({ + scrollToMessage: (messageId: string) => { + suppressScrollRef.current = true; + + // Clean up previous observer and fallback timer + if (observerRef.current) { + observerRef.current.disconnect(); + observerRef.current = null; + } + if (observerFallbackRef.current) { + clearTimeout(observerFallbackRef.current); + observerFallbackRef.current = null; + } + + // Remove highlight from previously highlighted element + if (highlightedElRef.current) { + highlightedElRef.current.classList.remove("msg-highlight-breathe"); + highlightedElRef.current = null; + } + + const el = document.querySelector(`[data-message-id="${messageId}"]`); + if (!el) { + setTimeout(() => { + suppressScrollRef.current = false; + }, 500); + return; + } + + // Scroll the element into view first (smooth) + el.scrollIntoView({ behavior: "smooth", block: "center" }); + + // Get the scrollable viewport as the IntersectionObserver root + const viewport = scrollAreaRef.current?.querySelector("[data-radix-scroll-area-viewport]") || null; + + let highlightApplied = false; + + const applyHighlight = () => { + if (highlightApplied) return; + highlightApplied = true; + el.classList.add("msg-highlight-breathe"); + highlightedElRef.current = el; + // Remove the class after animation completes (3 iterations * 0.8s) + setTimeout(() => { + el.classList.remove("msg-highlight-breathe"); + if (highlightedElRef.current === el) { + highlightedElRef.current = null; + } + }, 2600); + }; + + // Use IntersectionObserver to wait until the element is fully visible + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (entry.intersectionRatio >= 0.99) { + // Clear fallback timer + if (observerFallbackRef.current) { + clearTimeout(observerFallbackRef.current); + observerFallbackRef.current = null; + } + applyHighlight(); + observer.disconnect(); + observerRef.current = null; + return; + } + } + }, + { + root: viewport, + threshold: [0.99], + } + ); + + observer.observe(el); + observerRef.current = observer; + + // Fallback: if the element never becomes fully visible within 2s + // (e.g., element is too large to fit), apply animation anyway + observerFallbackRef.current = setTimeout(() => { + if (observerRef.current) { + observerRef.current.disconnect(); + observerRef.current = null; + } + applyHighlight(); + }, 2000); + + // Re-enable auto-scroll after a delay + setTimeout(() => { + suppressScrollRef.current = false; + }, 1500); + }, + })); + + if (messages.length === 0) { + return ( +
+ +
Welcome to the Deep Code!
+

+ What would you like to do? Ask about this codebase or we can start writing code. +

+
+ ); + } + return ( + +
+ {messages.map((msg, index) => { + const prevMsg = index > 0 ? messages[index - 1] : null; + const shouldConnect = prevMsg ? prevMsg.role !== "user" && msg.role !== "user" : false; + const msgId = msg.id || `msg-${index}`; + + const wrapWithId = (element: React.ReactNode, key: string) => ( +
+ {element} +
+ ); + + switch (msg.role) { + case "user": { + if (msg.meta?.userPrompt?.askUserQuestionSummary) { + return wrapWithId(, `msg-${index}`); + } + return wrapWithId( + { + const meta = msg.meta as { userPrompt?: { imageUrls?: string[] } } | undefined; + onEditMessage({ + text: msg.content, + images: meta?.userPrompt?.imageUrls ?? [], + skills: [], + }); + } + : undefined + } + />, + `msg-${index}` + ); + } + case "assistant": { + const meta = msg.meta as { asThinking?: boolean } | undefined; + if (meta?.asThinking) { + return wrapWithId( + , + `msg-${index}` + ); + } + return wrapWithId(, `msg-${index}`); + } + case "tool": + return wrapWithId( + , + `msg-${index}` + ); + case "system": + return wrapWithId( + , + `msg-${index}` + ); + default: + return null; + } + })} +
+
+ + + ); + } +); + +export default Messages; diff --git a/packages/vscode-ide-companion/src/webview/components/ModeToggle.tsx b/packages/vscode-ide-companion/src/webview/components/ModeToggle.tsx new file mode 100644 index 00000000..14a44bc7 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/ModeToggle.tsx @@ -0,0 +1,31 @@ +import { Moon, Sun } from "lucide-react"; + +import { Button } from "@/webview/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/webview/components/ui/dropdown-menu"; +import { useTheme } from "@/webview/context/ThemeProvider"; + +export function ModeToggle({ variant = "ghost" }: { variant?: "ghost" | "outline" }) { + const { setTheme } = useTheme(); + + return ( + + + + + + setTheme("light")}>Light + setTheme("dark")}>Dark + setTheme("system")}>System + + + ); +} diff --git a/packages/vscode-ide-companion/src/webview/components/NetworkStatusIndicator.tsx b/packages/vscode-ide-companion/src/webview/components/NetworkStatusIndicator.tsx new file mode 100644 index 00000000..c09136fe --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/NetworkStatusIndicator.tsx @@ -0,0 +1,22 @@ +import { Wifi, WifiOff } from "lucide-react"; +import { cn } from "@/webview/lib/utils"; +import { useNetworkStatus } from "@/webview/hooks/useNetworkStatus"; + +export function NetworkStatusIndicator({ className }: { className?: string }) { + const { isOnline } = useNetworkStatus(); + + return ( +
+ {isOnline ? : } + {isOnline ? "Online" : "Offline"} +
+ ); +} diff --git a/packages/vscode-ide-companion/src/webview/components/PermissionPrompt.test.tsx b/packages/vscode-ide-companion/src/webview/components/PermissionPrompt.test.tsx new file mode 100644 index 00000000..cf4360df --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/PermissionPrompt.test.tsx @@ -0,0 +1,197 @@ +/** + * Unit tests for PermissionPrompt component + * + * Tests cover: + * - normalizeRequests + * - describeScope + * - getRiskClass + * - commitDecision flow + * - isAskPermission / isDenied conditions + */ + +import React from "react"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, vi, beforeEach } from "vitest"; +import PermissionPrompt, { type PermissionPromptProps } from "./PermissionPrompt"; +import type { AskPermissionRequest, AppAction } from "@/webview/types"; + +// Mock dependencies +vi.mock("@/webview/components/ui/button", () => ({ + Button: vi.fn(({ children, onClick, ...props }) => ( + + )), +})); + +vi.mock("@/webview/components/ui/hover-card", () => ({ + HoverCard: vi.fn(({ children }) =>
{children}
), + HoverCardContent: vi.fn(({ children }) =>
{children}
), + HoverCardTrigger: vi.fn(({ children }) =>
{children}
), +})); + +vi.mock("lucide-react", () => ({ + X: vi.fn(() => ), + ShieldAlert: vi.fn(() => ), + ChevronDownIcon: vi.fn(() => ), + ChevronLeft: vi.fn(() => ), + ChevronRight: vi.fn(() => ), + Terminal: vi.fn(() => ), +})); + +const mockDispatch = vi.fn<(action: AppAction) => void>(); +const mockOnDenyPermission = vi.fn<(sessionId: string) => void>(); +const mockOnSendPrompt = + vi.fn< + ( + prompt: string, + skills?: unknown[], + images?: string[], + options?: { permissions?: unknown[]; alwaysAllows?: string[] } + ) => void + >(); +const mockOnInterrupt = vi.fn<() => void>(); + +const defaultProps: PermissionPromptProps = { + askPermissions: [] as AskPermissionRequest[], + sessionStatus: "idle", + pendingPermissionReply: null, + permissionPromptState: null, + activeSessionId: "session-1", + dispatch: mockDispatch, + onDenyPermission: mockOnDenyPermission, + onSendPrompt: mockOnSendPrompt, + onInterrupt: mockOnInterrupt, +}; + +describe("PermissionPrompt", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("renders nothing when no permissions requested", () => { + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); + + it("renders nothing when sessionStatus is not ask_permission", () => { + const props = { + ...defaultProps, + askPermissions: [{ toolCallId: "tool1", name: "read", command: "cat", description: "", scopes: ["read-in-cwd"] }], + sessionStatus: "running" as const, + }; + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); + + it("renders permission prompt when ask_permission status", () => { + const props = { + ...defaultProps, + askPermissions: [{ toolCallId: "tool1", name: "read", command: "cat", description: "", scopes: ["read-in-cwd"] }], + sessionStatus: "ask_permission" as const, + }; + render(); + // Header now includes tool name via capitalize in the collapsible trigger + expect(screen.getByText(/Permission required/)).toBeInTheDocument(); + }); + + it("renders denied state when permission denied", () => { + const props = { + ...defaultProps, + askPermissions: [{ toolCallId: "tool1", name: "read", command: "cat", description: "", scopes: ["read-in-cwd"] }], + sessionStatus: "permission_denied" as const, + pendingPermissionReply: { allow: false, alwaysAllows: [] }, + }; + render(); + expect(screen.getByText(/denied/i)).toBeInTheDocument(); + }); + + it("calls onDenyPermission when No clicked", () => { + const props = { + ...defaultProps, + askPermissions: [{ toolCallId: "tool1", name: "read", command: "cat", description: "", scopes: ["read-in-cwd"] }], + sessionStatus: "ask_permission" as const, + }; + render(); + const noButton = screen.getByText("No"); + fireEvent.click(noButton); + expect(mockOnDenyPermission).toHaveBeenCalled(); + }); + + it("calls onSendPrompt when Yes clicked", () => { + const props = { + ...defaultProps, + askPermissions: [{ toolCallId: "tool1", name: "read", command: "cat", description: "", scopes: ["read-in-cwd"] }], + sessionStatus: "ask_permission" as const, + }; + render(); + const yesButton = screen.getByText("Yes"); + fireEvent.click(yesButton); + expect(mockOnSendPrompt).toHaveBeenCalledWith( + "/continue", + [], + [], + expect.objectContaining({ permissions: expect.any(Array) }) + ); + }); + + it("shows always allow button when canAlwaysAllow", () => { + const props: PermissionPromptProps = { + ...defaultProps, + askPermissions: [{ toolCallId: "tool1", name: "read", command: "cat", description: "", scopes: ["read-in-cwd"] }], + sessionStatus: "ask_permission", + permissionPromptState: { + requests: [], + prompts: [], + index: 0, + decisions: {}, + alwaysAllows: [], + submitting: false, + }, + }; + render(); + expect(screen.getByText(/always allow/i)).toBeInTheDocument(); + }); + + it("calls onInterrupt when cancel clicked", () => { + const props = { + ...defaultProps, + askPermissions: [{ toolCallId: "tool1", name: "read", command: "cat", description: "", scopes: ["read-in-cwd"] }], + sessionStatus: "ask_permission" as const, + }; + render(); + // The close (X) button in the collapsible header calls handleCancel -> onInterrupt + const xIcon = screen.getByTestId("x-icon"); + fireEvent.click(xIcon); + expect(mockOnInterrupt).toHaveBeenCalled(); + }); + + it("renders risk class badge for scope", () => { + const props = { + ...defaultProps, + askPermissions: [{ toolCallId: "tool1", name: "read", command: "cat", description: "", scopes: ["read-in-cwd"] }], + sessionStatus: "ask_permission" as const, + }; + render(); + // Check for the risk class styling (bg-green-500 for read-in-cwd scope) + expect(document.body.innerHTML).toContain("bg-green-500/15"); + }); + + it("renders multiple permission requests in carousel layout", () => { + const props = { + ...defaultProps, + askPermissions: [ + { toolCallId: "tool1", name: "read", command: "cat", description: "", scopes: ["read-in-cwd"] }, + { toolCallId: "tool2", name: "write", command: "echo", description: "", scopes: ["write-in-cwd"] }, + ], + sessionStatus: "ask_permission" as const, + }; + render(); + // Carousel navigation elements should be present (prev/next buttons) + expect(screen.getByTestId("chevron-left-icon")).toBeInTheDocument(); + expect(screen.getByTestId("chevron-right-icon")).toBeInTheDocument(); + // Both tool names should be rendered as carousel slides + expect(screen.getByText("Read")).toBeInTheDocument(); + expect(screen.getByText("Write")).toBeInTheDocument(); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/components/PermissionPrompt.tsx b/packages/vscode-ide-companion/src/webview/components/PermissionPrompt.tsx new file mode 100644 index 00000000..2e3de4e9 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/PermissionPrompt.tsx @@ -0,0 +1,374 @@ +import React, { useMemo, useCallback, useState, useEffect, useRef } from "react"; +import { Button } from "@/webview/components/ui/button"; +import type { AppAction, AskPermissionRequest, PermissionPromptState, SkillInfo } from "@/webview/types"; +import { ShieldAlert, X, ChevronDownIcon, ChevronLeft, ChevronRight, Terminal } from "lucide-react"; +import { Alert, AlertDescription, AlertTitle } from "@/webview/components/ui/alert"; +import { capitalize } from "@/webview/utils"; +import type { CarouselApi } from "@/webview/components/ui/carousel"; +import { Carousel, CarouselContent, CarouselItem } from "@/webview/components/ui/carousel"; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/webview/components/ui/collapsible"; +import { Card, CardContent } from "@/webview/components/ui/card"; + +export interface PermissionPromptProps { + askPermissions: AskPermissionRequest[]; + sessionStatus: string | null; + pendingPermissionReply: unknown; + permissionPromptState: PermissionPromptState | null; + activeSessionId: string | null; + dispatch: (action: AppAction) => void; + onDenyPermission: (sessionId: string) => void; + onSendPrompt: ( + prompt: string, + skills?: SkillInfo[], + images?: string[], + options?: { permissions?: unknown[]; alwaysAllows?: string[] } + ) => void; + onInterrupt: () => void; +} + +type PermissionScope = string; + +const VALID_SCOPES: PermissionScope[] = [ + "read-in-cwd", + "read-out-cwd", + "write-in-cwd", + "write-out-cwd", + "delete-in-cwd", + "delete-out-cwd", + "query-git-log", + "mutate-git-log", + "network", + "mcp", +]; + +function describeScope(scope: string): string { + switch (scope) { + case "read-in-cwd": + return "reads inside this workspace"; + case "read-out-cwd": + return "reads outside this workspace"; + case "write-in-cwd": + return "writes inside this workspace"; + case "write-out-cwd": + return "writes outside this workspace"; + case "delete-in-cwd": + return "deletes inside this workspace"; + case "delete-out-cwd": + return "deletes outside this workspace"; + case "query-git-log": + return "Git history queries"; + case "mutate-git-log": + return "Git history changes"; + case "network": + return "network access"; + case "mcp": + return "MCP tool access"; + default: + return "unknown access"; + } +} + +function getRiskClass(scope: string): string { + switch (scope) { + case "read-in-cwd": + case "query-git-log": + return "bg-green-500/15 text-green-700 dark:text-green-400"; + case "read-out-cwd": + case "write-in-cwd": + case "network": + case "mcp": + return "bg-yellow-500/15 text-yellow-700 dark:text-yellow-400"; + default: + return "bg-red-500/15 text-red-700 dark:text-red-400"; + } +} + +function normalizeRequests(requests: unknown[]): AskPermissionRequest[] { + return requests + .map((item) => { + if (!item || typeof item !== "object") return null; + const r = item as Record; + return { + toolCallId: String(r.toolCallId || ""), + name: String(r.name || "Tool"), + command: String(r.command || ""), + description: String(r.description || ""), + scopes: Array.isArray(r.scopes) ? r.scopes.filter((s) => typeof s === "string") : ["unknown"], + }; + }) + .filter(Boolean) as AskPermissionRequest[]; +} + +export default function PermissionPrompt({ + askPermissions, + sessionStatus, + pendingPermissionReply, + activeSessionId, + dispatch, + onDenyPermission, + onSendPrompt, + onInterrupt, +}: PermissionPromptProps) { + const [api, setApi] = React.useState(); + const carouselApiRef = useRef(null); + const pendingScrollRef = useRef(null); + const [open, setOpen] = React.useState(true); + const [current, setCurrent] = React.useState(0); + const [count, setCount] = React.useState(0); + const normalized = useMemo(() => normalizeRequests(askPermissions), [askPermissions]); + const isAskPermission = sessionStatus === "ask_permission" && normalized.length > 0; + const isDenied = (pendingPermissionReply && sessionStatus === "permission_denied") as boolean; + + // Build prompts from requests — one per (request, scope) pair + const prompts = useMemo(() => { + const result: Array<{ request: AskPermissionRequest; scope: string }> = []; + for (const req of normalized) { + const scopes = req.scopes.length > 0 ? req.scopes : ["unknown"]; + for (const scope of scopes) { + result.push({ request: req, scope }); + } + } + return result; + }, [normalized]); + + // Internal state for decisions (no sequential index — carousel manages position) + const [localState, setLocalState] = useState<{ + decisions: Record; + alwaysAllows: string[]; + submitting: boolean; + }>({ decisions: {}, alwaysAllows: [], submitting: false }); + + // Reset when prompts change + useEffect(() => { + setLocalState({ decisions: {}, alwaysAllows: [], submitting: false }); + }, [askPermissions]); + + // Store carousel API in ref for stable access from callbacks (avoids stale closures) + useEffect(() => { + carouselApiRef.current = api || null; + }, [api]); + + // Sync carousel slide counter + useEffect(() => { + if (!api) return; + setCount(api.scrollSnapList().length); + setCurrent(api.selectedScrollSnap() + 1); + api.on("select", () => { + setCurrent(api.selectedScrollSnap() + 1); + }); + }, [api]); + + // Execute pending auto-scroll after React commits the state update + useEffect(() => { + if (pendingScrollRef.current !== null && carouselApiRef.current) { + const idx = pendingScrollRef.current; + pendingScrollRef.current = null; + carouselApiRef.current.scrollTo(idx); + } + }); + + const commitDecision = useCallback( + (kind: "allow" | "deny" | "always", slideIndex: number) => { + if (localState.submitting) return; + + setLocalState((prev) => { + const prompt = prompts[slideIndex]; + if (!prompt) return prev; + + const newDecisions = { ...prev.decisions }; + const newAlwaysAllows = [...prev.alwaysAllows]; + + if (kind === "always" && VALID_SCOPES.includes(prompt.scope)) { + if (!newAlwaysAllows.includes(prompt.scope)) { + newAlwaysAllows.push(prompt.scope); + } + if (newDecisions[prompt.request.toolCallId] !== "deny") { + newDecisions[prompt.request.toolCallId] = "allow"; + } + } else if (kind === "deny") { + newDecisions[prompt.request.toolCallId] = "deny"; + } else { + if (newDecisions[prompt.request.toolCallId] !== "deny") { + newDecisions[prompt.request.toolCallId] = "allow"; + } + } + + // Find next undecided prompt after the current slide + let nextIdx = slideIndex + 1; + while (nextIdx < prompts.length) { + const s = prompts[nextIdx].scope; + const t = prompts[nextIdx].request.toolCallId; + if (VALID_SCOPES.includes(s) && newAlwaysAllows.includes(s)) { + nextIdx++; + continue; + } + if (newDecisions[t] !== undefined) { + nextIdx++; + continue; + } + break; + } + + if (nextIdx >= prompts.length) { + // All prompts decided — submit + const permissions = normalized.map((req) => ({ + toolCallId: req.toolCallId, + permission: (newDecisions[req.toolCallId] === "deny" ? "deny" : "allow") as "allow" | "deny", + })); + const hasDeny = permissions.some((p) => p.permission === "deny"); + + if (hasDeny) { + dispatch({ + type: "SET_PENDING_PERMISSION_REPLY", + reply: { permissions, alwaysAllows: newAlwaysAllows }, + }); + onDenyPermission(activeSessionId || ""); + } else { + onSendPrompt("/continue", [], [], { + permissions, + alwaysAllows: newAlwaysAllows, + }); + } + + return { ...prev, decisions: newDecisions, alwaysAllows: newAlwaysAllows, submitting: true }; + } + + // Schedule auto-scroll to the next undecided prompt (executed after render via useEffect) + pendingScrollRef.current = nextIdx; + + return { ...prev, decisions: newDecisions, alwaysAllows: newAlwaysAllows }; + }); + }, + [localState, prompts, normalized, dispatch, onDenyPermission, onSendPrompt, activeSessionId] + ); + + const handleCancel = useCallback(() => { + dispatch({ type: "SET_PENDING_PERMISSION_REPLY", reply: null }); + onInterrupt(); + setLocalState({ decisions: {}, alwaysAllows: [], submitting: false }); + }, [dispatch, onInterrupt]); + + if (!isAskPermission && !isDenied) return null; + + if (isDenied) { + return ( +
+ + + Permission denied + Add a reply, then press Enter to continue with the denial. + +
+ ); + } + + if (prompts.length === 0) return null; + + // Header label — show the tool name from the first prompt + const headerLabel = `Permission required — ${capitalize(prompts[0].request.name || "Tool")}`; + + return ( +
+
+ + + +
+ {headerLabel} +
+ + +
+
+
+ + + + {prompts.map((p, idx) => ( + + + +
+ + {capitalize(p.request.name)} +
+
+ {p.request.command || "(no command)"} +
+ {p.request.description && ( +
{p.request.description}
+ )} +
+ {describeScope(p.scope)} +
+
+
+
+ ))} +
+
+
+
+
+ {/* Navigation footer — matches AskQuestionCarousel style */} +
+
Do you want to proceed?
+
+
+ + {VALID_SCOPES.includes(prompts[current - 1]?.scope || "") && ( + + )} + +
+
+ {open && ( + <> + + + + )} + + {current}/{count} + +
+
+
+
+
+ ); +} diff --git a/packages/vscode-ide-companion/src/webview/components/PlanRenderer.test.tsx b/packages/vscode-ide-companion/src/webview/components/PlanRenderer.test.tsx new file mode 100644 index 00000000..f1b61a47 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/components/PlanRenderer.test.tsx @@ -0,0 +1,193 @@ +/** + * Unit tests for PlanRenderer component + * + * Tests cover: + * - Rendering of headings at all 6 levels + * - Task items with different statuses ([ ], [x], [>], [!]) + * - Bullet list items + * - Plain paragraphs + * - Inline markdown (code, bold, italic) + * - Indentation of nested items + * - Empty input + * - HTML escaping + */ + +import React from "react"; +import { render, screen } from "@testing-library/react"; +import { describe, it, expect, vi } from "vitest"; +import PlanRenderer from "./PlanRenderer"; + +vi.mock("lucide-react", () => ({ + Circle: vi.fn(({ className }: { className?: string }) => ), + CircleCheckBig: vi.fn(({ className }: { className?: string }) => ( + + )), + Clock: vi.fn(({ className }: { className?: string }) => ), + CircleAlert: vi.fn(({ className }: { className?: string }) => ( + + )), +})); + +describe("PlanRenderer", () => { + it("renders spacer for empty plan input", () => { + const { container } = render(); + const wrapper = container.querySelector(".update-plan-markdown"); + expect(wrapper).toBeInTheDocument(); + // Empty string produces one empty line which renders as a spacer div + expect(wrapper?.children.length).toBe(1); + }); + + it("renders headings at different levels", () => { + const plan = "# H1\n## H2\n### H3\n#### H4\n##### H5\n###### H6"; + render(); + + // Headings render via dangerouslySetInnerHTML inside + expect(screen.getByText("H1")).toBeInTheDocument(); + expect(screen.getByText("H2")).toBeInTheDocument(); + expect(screen.getByText("H3")).toBeInTheDocument(); + expect(screen.getByText("H4")).toBeInTheDocument(); + expect(screen.getByText("H5")).toBeInTheDocument(); + expect(screen.getByText("H6")).toBeInTheDocument(); + + // The heading text is inside a within the

/

elements + const h1Text = screen.getByText("H1"); + expect(h1Text.closest("h1")).toBeInTheDocument(); + const h6Text = screen.getByText("H6"); + expect(h6Text.closest("h6")).toBeInTheDocument(); + }); + + it("falls through to paragraph when heading has too many #", () => { + // 7 # characters won't match the heading regex, so it falls through to paragraph + const plan = "####### Too Deep"; + render(); + // The whole line renders as a paragraph + const p = document.querySelector("p"); + expect(p).toBeInTheDocument(); + expect(p?.textContent).toContain("Too Deep"); + }); + + it("renders todo task items with Circle icon", () => { + const plan = "- [ ] Write tests"; + render(); + // Text is rendered via dangerouslySetInnerHTML + expect(screen.getByText("Write tests")).toBeInTheDocument(); + // Todo status uses Circle icon + expect(screen.getByTestId("icon-circle")).toBeInTheDocument(); + }); + + it("renders completed task items with check icon", () => { + const plan = "- [x] Implement login"; + render(); + expect(screen.getByText("Implement login")).toBeInTheDocument(); + expect(screen.getByTestId("icon-check")).toBeInTheDocument(); + }); + + it("renders active task items with clock icon", () => { + const plan = "- [>] In progress feature"; + render(); + expect(screen.getByText("In progress feature")).toBeInTheDocument(); + expect(screen.getByTestId("icon-clock")).toBeInTheDocument(); + }); + + it("renders attention task items with alert icon", () => { + const plan = "- [!] Blocked by dependency"; + render(); + expect(screen.getByText("Blocked by dependency")).toBeInTheDocument(); + expect(screen.getByTestId("icon-alert")).toBeInTheDocument(); + }); + + it("supports X (uppercase) for completed status", () => { + const plan = "- [X] Done (uppercase X)"; + render(); + expect(screen.getByText("Done (uppercase X)")).toBeInTheDocument(); + expect(screen.getByTestId("icon-check")).toBeInTheDocument(); + }); + + it("renders bullet list items", () => { + const plan = "- Regular bullet\n* Star bullet"; + render(); + expect(screen.getByText("Regular bullet")).toBeInTheDocument(); + expect(screen.getByText("Star bullet")).toBeInTheDocument(); + }); + + it("renders plain paragraphs", () => { + const plan = "This is a plain paragraph."; + render(); + expect(screen.getByText("This is a plain paragraph.")).toBeInTheDocument(); + }); + + it("renders inline code in items", () => { + const plan = "- [x] Use `useMemo` for optimization"; + render(); + const codeEl = document.querySelector("code"); + expect(codeEl).toBeInTheDocument(); + expect(codeEl?.textContent).toBe("useMemo"); + }); + + it("renders inline bold text", () => { + const plan = "- [x] Fix **critical** bug"; + render(); + const strongEl = document.querySelector("strong"); + expect(strongEl).toBeInTheDocument(); + expect(strongEl?.textContent).toBe("critical"); + }); + + it("renders inline italic text", () => { + const plan = "- Note: *optional* parameter"; + render(); + const emEl = document.querySelector("em"); + expect(emEl).toBeInTheDocument(); + expect(emEl?.textContent).toBe("optional"); + }); + + it("escapes HTML in plan content", () => { + const plan = "- [x] Test "; + render(); + const rendered = document.body.innerHTML; + expect(rendered).toContain("<script>"); + expect(rendered).not.toContain(" + + diff --git a/packages/vscode-ide-companion/src/webview/lib/utils.test.ts b/packages/vscode-ide-companion/src/webview/lib/utils.test.ts new file mode 100644 index 00000000..8ff17ea6 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/lib/utils.test.ts @@ -0,0 +1,56 @@ +/** + * Unit tests for lib/utils.ts + * + * Tests cover: + * - cn() function (clsx + tailwind-merge) + */ + +import { describe, it, expect } from "vitest"; +import { cn } from "./utils"; + +describe("cn", () => { + it("returns empty string for no args", () => { + expect(cn()).toBe(""); + }); + + it("returns single class", () => { + expect(cn("text-red-500")).toBe("text-red-500"); + }); + + it("combines multiple classes", () => { + expect(cn("text-red-500", "bg-blue-500")).toBe("text-red-500 bg-blue-500"); + }); + + it("handles conditional classes (falsy values)", () => { + const falsy = false; + expect(cn("base", falsy && "hidden", undefined, null, "visible")).toBe("base visible"); + }); + + it("handles object syntax", () => { + expect(cn("base", { active: true, disabled: false })).toBe("base active"); + }); + + it("handles array syntax", () => { + expect(cn(["a", "b"], "c")).toBe("a b c"); + }); + + it("merges conflicting tailwind classes (last wins)", () => { + expect(cn("px-2", "px-4")).toBe("px-4"); + }); + + it("merges conflicting color classes", () => { + expect(cn("text-red-500", "text-blue-500")).toBe("text-blue-500"); + }); + + it("handles complex merge scenarios", () => { + const result = cn("px-2 py-1 text-sm", "px-4", { "bg-red-500": true, "bg-blue-500": false }, "py-2"); + // Should contain all expected classes, order may vary due to tailwind-merge + expect(result).toContain("text-sm"); + expect(result).toContain("px-4"); + expect(result).toContain("bg-red-500"); + }); + + it("handles empty strings", () => { + expect(cn("", "test", "")).toBe("test"); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/lib/utils.ts b/packages/vscode-ide-companion/src/webview/lib/utils.ts new file mode 100644 index 00000000..a5ef1935 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/packages/vscode-ide-companion/src/webview/main.tsx b/packages/vscode-ide-companion/src/webview/main.tsx new file mode 100644 index 00000000..120da04a --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/main.tsx @@ -0,0 +1,30 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ErrorBoundary } from "@/webview/components/ErrorBoundary"; +import { Toaster } from "@/webview/components/ui/sonner"; +import { TooltipProvider } from "@/webview/components/ui/tooltip"; +import { ThemeProvider } from "@/webview/context/ThemeProvider"; +import { ChatProvider } from "@/webview/context/ChatProvider"; +import App from "@/webview/App"; +import React from "react"; +import "./index.css"; + +const queryClient = new QueryClient(); + +createRoot(document.getElementById("root")!).render( + + + + + + + + + + + + + + +); diff --git a/packages/vscode-ide-companion/src/webview/services/chatService.test.ts b/packages/vscode-ide-companion/src/webview/services/chatService.test.ts new file mode 100644 index 00000000..7e7aa43e --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/services/chatService.test.ts @@ -0,0 +1,344 @@ +/** + * Unit tests for chatService + * + * Tests cover: + * - Service methods with mocked wrpc + * - Return types + * - Error handling + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { chatService } from "./chatService"; +import { wrpc } from "@/webview/wrpc"; + +// Mock wrpc +vi.mock("@/webview/wrpc", () => ({ + wrpc: { + getInitialData: { + query: vi.fn(), + }, + getSkills: { + query: vi.fn(), + }, + getSessions: { + query: vi.fn(), + }, + sendPrompt: { + mutate: vi.fn(), + }, + createNewSession: { + mutate: vi.fn(), + }, + selectSession: { + query: vi.fn(), + }, + interrupt: { + mutate: vi.fn(), + }, + denyPermission: { + mutate: vi.fn(), + }, + copyText: { + mutate: vi.fn(), + }, + openFile: { + mutate: vi.fn(), + }, + openSettings: { + mutate: vi.fn(), + }, + showAlert: { + mutate: vi.fn(), + }, + }, +})); + +describe("chatService", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe("getInitialData", () => { + it("returns initial data when available", async () => { + const mockData = { + sessions: [{ id: "s1", summary: "Test" }], + activeSession: { id: "s1", summary: "Test", status: "idle", messages: [], askPermissions: [], processes: null }, + activeEditor: { fileName: "test.ts", languageId: "typescript", lineCount: 10 }, + }; + + vi.mocked(wrpc.getInitialData.query).mockResolvedValue(mockData as any); + + const result = await chatService.getInitialData(); + + expect(result).toEqual(mockData); + expect(wrpc.getInitialData.query).toHaveBeenCalledTimes(1); + }); + + it("returns null when no data", async () => { + vi.mocked(wrpc.getInitialData.query).mockResolvedValue(null as any); + + const result = await chatService.getInitialData(); + + expect(result).toBeNull(); + }); + }); + + describe("getSkills", () => { + it("returns skills array when available", async () => { + const mockSkills = [{ name: "skill1" }, { name: "skill2" }]; + vi.mocked(wrpc.getSkills.query).mockResolvedValue({ skills: mockSkills } as any); + + const result = await chatService.getSkills(); + + expect(result).toEqual(mockSkills); + }); + + it("returns skills for specific session", async () => { + const mockSkills = [{ name: "session-skill" }]; + vi.mocked(wrpc.getSkills.query).mockResolvedValue({ skills: mockSkills } as any); + + const result = await chatService.getSkills("session-123"); + + expect(wrpc.getSkills.query).toHaveBeenCalledWith("session-123"); + expect(result).toEqual(mockSkills); + }); + + it("returns empty array when no skills", async () => { + vi.mocked(wrpc.getSkills.query).mockResolvedValue(null as any); + + const result = await chatService.getSkills(); + + expect(result).toEqual([]); + }); + }); + + describe("sendPrompt", () => { + it("sends prompt with all options", async () => { + vi.mocked(wrpc.sendPrompt.mutate).mockResolvedValue({ ok: true, sessionId: "new-session-1" } as any); + + const result = await chatService.sendPrompt({ + prompt: "Hello", + skills: [{ name: "skill1" }], + images: ["image1.png", ""], // should filter empty + permissions: [{ toolCallId: "t1", permission: "allow" }], + alwaysAllows: ["read-in-cwd"], + }); + + expect(result).toEqual({ ok: true, sessionId: "new-session-1" }); + expect(wrpc.sendPrompt.mutate).toHaveBeenCalledWith({ + prompt: "Hello", + skills: [{ name: "skill1" }], + images: ["image1.png"], + permissions: [{ toolCallId: "t1", permission: "allow" }], + alwaysAllows: ["read-in-cwd"], + }); + }); + + it("sends minimal prompt", async () => { + vi.mocked(wrpc.sendPrompt.mutate).mockResolvedValue({ ok: true } as any); + + await chatService.sendPrompt({ prompt: "Hello" }); + + expect(wrpc.sendPrompt.mutate).toHaveBeenCalledWith({ + prompt: "Hello", + skills: [], + images: [], + permissions: undefined, + alwaysAllows: undefined, + }); + }); + + it("filters empty images", async () => { + vi.mocked(wrpc.sendPrompt.mutate).mockResolvedValue({ ok: true } as any); + + await chatService.sendPrompt({ + prompt: "Hello", + images: ["", "valid.png", ""], + }); + + expect(wrpc.sendPrompt.mutate).toHaveBeenCalledWith({ + prompt: "Hello", + skills: [], + images: ["valid.png"], + permissions: undefined, + alwaysAllows: undefined, + }); + }); + + it("returns error when sendPrompt fails", async () => { + vi.mocked(wrpc.sendPrompt.mutate).mockResolvedValue({ ok: false, error: "Network error" } as any); + + const result = await chatService.sendPrompt({ prompt: "Hello" }); + + expect(result).toEqual({ ok: false, error: "Network error" }); + }); + + it("returns sessionId from the server response", async () => { + vi.mocked(wrpc.sendPrompt.mutate).mockResolvedValue({ ok: true, sessionId: "session-abc" } as any); + + const result = await chatService.sendPrompt({ prompt: "Create a session" }); + + expect(result.sessionId).toBe("session-abc"); + }); + }); + + describe("createNewSession", () => { + it("creates new session and returns sessions", async () => { + const mockResult = { + sessions: [{ id: "s1" }, { id: "s2" }], + skills: [{ name: "skill1" }], + }; + vi.mocked(wrpc.createNewSession.mutate).mockResolvedValue(mockResult as any); + + const result = await chatService.createNewSession(); + + expect(result).toEqual(mockResult); + }); + + it("returns sessions even when no skills", async () => { + const mockResult = { sessions: [{ id: "s1" }] }; + vi.mocked(wrpc.createNewSession.mutate).mockResolvedValue(mockResult as any); + + const result = await chatService.createNewSession(); + + expect(result).toEqual(mockResult); + }); + }); + + describe("selectSession", () => { + it("returns session data when found", async () => { + const mockResult = { + ok: true, + session: { id: "s1", summary: "Test", status: "idle", askPermissions: [], processes: null }, + sessions: [{ id: "s1" }], + messages: [{ role: "user", content: "Hello" }], + }; + vi.mocked(wrpc.selectSession.query).mockResolvedValue(mockResult as any); + + const result = await chatService.selectSession("s1"); + + expect(result).toEqual(mockResult); + expect(wrpc.selectSession.query).toHaveBeenCalledWith("s1"); + }); + + it("returns ok false when session not found", async () => { + vi.mocked(wrpc.selectSession.query).mockResolvedValue({ ok: false, error: "Not found" } as any); + + const result = await chatService.selectSession("nonexistent"); + + expect(result.ok).toBe(false); + }); + }); + + describe("interrupt", () => { + it("returns ok on success", async () => { + vi.mocked(wrpc.interrupt.mutate).mockResolvedValue({ ok: true } as any); + + const result = await chatService.interrupt(); + + expect(result).toEqual({ ok: true }); + }); + }); + + describe("denyPermission", () => { + it("returns ok on success", async () => { + vi.mocked(wrpc.denyPermission.mutate).mockResolvedValue({ ok: true } as any); + + const result = await chatService.denyPermission("session-123"); + + expect(result).toEqual({ ok: true }); + expect(wrpc.denyPermission.mutate).toHaveBeenCalledWith("session-123"); + }); + }); + + describe("copyText", () => { + it("copies text to clipboard", async () => { + vi.mocked(wrpc.copyText.mutate).mockResolvedValue({ ok: true } as any); + + const result = await chatService.copyText("Hello World"); + + expect(result).toEqual({ ok: true }); + expect(wrpc.copyText.mutate).toHaveBeenCalledWith("Hello World"); + }); + }); + + describe("openFile", () => { + it("opens file with default line 1", async () => { + vi.mocked(wrpc.openFile.mutate).mockResolvedValue({ ok: true } as any); + + const result = await chatService.openFile("test.ts"); + + expect(result).toEqual({ ok: true }); + expect(wrpc.openFile.mutate).toHaveBeenCalledWith({ filePath: "test.ts", line: 1 }); + }); + + it("opens file at specific line", async () => { + vi.mocked(wrpc.openFile.mutate).mockResolvedValue({ ok: true } as any); + + await chatService.openFile("test.ts", 42); + + expect(wrpc.openFile.mutate).toHaveBeenCalledWith({ filePath: "test.ts", line: 42 }); + }); + }); + + describe("openSettings", () => { + it("opens settings", async () => { + vi.mocked(wrpc.openSettings.mutate).mockResolvedValue({ ok: true } as any); + + const result = await chatService.openSettings(); + + expect(result).toEqual({ ok: true }); + }); + }); + + describe("getSessions", () => { + it("returns sessions array when available", async () => { + const mockSessions = [ + { id: "s1", summary: "Session 1", createTime: "2024-01-01", updateTime: "2024-01-01", status: "idle" }, + { id: "s2", summary: "Session 2", createTime: "2024-01-02", updateTime: "2024-01-02", status: "processing" }, + ]; + vi.mocked(wrpc.getSessions.query).mockResolvedValue({ sessions: mockSessions } as any); + + const result = await chatService.getSessions(); + + expect(result).toEqual(mockSessions); + expect(wrpc.getSessions.query).toHaveBeenCalledTimes(1); + }); + + it("returns empty array when no sessions", async () => { + vi.mocked(wrpc.getSessions.query).mockResolvedValue(null as any); + + const result = await chatService.getSessions(); + + expect(result).toEqual([]); + }); + + it("returns empty array when result has no sessions field", async () => { + vi.mocked(wrpc.getSessions.query).mockResolvedValue({} as any); + + const result = await chatService.getSessions(); + + expect(result).toEqual([]); + }); + }); + + describe("showAlert", () => { + it("shows alert message and returns ok", async () => { + vi.mocked(wrpc.showAlert.mutate).mockResolvedValue({ ok: true } as any); + + const result = await chatService.showAlert("Test message"); + + expect(result).toEqual({ ok: true }); + expect(wrpc.showAlert.mutate).toHaveBeenCalledWith("Test message"); + }); + + it("passes any message string to wrpc", async () => { + vi.mocked(wrpc.showAlert.mutate).mockResolvedValue({ ok: true } as any); + + await chatService.showAlert("New chat button clicked"); + + expect(wrpc.showAlert.mutate).toHaveBeenCalledWith("New chat button clicked"); + }); + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/services/chatService.ts b/packages/vscode-ide-companion/src/webview/services/chatService.ts new file mode 100644 index 00000000..4c269413 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/services/chatService.ts @@ -0,0 +1,227 @@ +/** + * Chat Service Layer + * + * Abstracts all wRPC calls into a typed service interface. + * This provides: + * - Better testability (can mock without actual RPC) + * - Clear API boundary between UI and data layer + * - Centralized error handling + */ + +import { wrpc } from "@/webview/wrpc"; +import type { SkillInfo, SessionSummary, SessionMessage, AskPermissionRequest, TokenTelemetry } from "@/webview/types"; + +// ============================================================================ +// Types +// ============================================================================ + +export interface SendPromptOptions { + prompt: string; + skills?: SkillInfo[]; + images?: string[]; + permissions?: Array<{ toolCallId: string; permission: "allow" | "deny" }>; + alwaysAllows?: string[]; + planMode?: boolean; + askUserQuestionSummary?: boolean; +} + +export interface InitialData { + sessions: SessionSummary[]; + activeSession: { + id: string; + summary: string; + status: string; + askPermissions: AskPermissionRequest[] | undefined; + processes: Record | null; + messages: SessionMessage[]; + } | null; + activeEditor: { fileName: string; languageId: string; lineCount: number } | null; + tokenTelemetry: TokenTelemetry; +} + +export interface SelectSessionResult { + ok: boolean; + session?: { + id: string; + summary: string; + status: string; + askPermissions: AskPermissionRequest[] | undefined; + processes: Record | null; + }; + sessions?: SessionSummary[]; + messages?: SessionMessage[]; + tokenTelemetry?: TokenTelemetry; +} + +export interface CreateSessionResult { + sessions: SessionSummary[]; + skills?: SkillInfo[]; + tokenTelemetry: TokenTelemetry; +} + +// ============================================================================ +// Service +// ============================================================================ + +export const chatService = { + /** + * Get initial data including sessions, active session, and active editor + */ + async getInitialData(): Promise { + const data = await wrpc.getInitialData.query(); + return data as InitialData; + }, + + /** + * Get list of available skills + */ + async getSkills(sessionId?: string): Promise { + const result = await wrpc.getSkills.query(sessionId); + return (result?.skills as SkillInfo[]) ?? []; + }, + + /** + * Get list of all sessions + */ + async getSessions(): Promise { + const result = await wrpc.getSessions.query(); + return (result?.sessions as SessionSummary[]) ?? []; + }, + + /** + * Send a prompt to the chat + */ + async sendPrompt(options: SendPromptOptions): Promise<{ ok: boolean; error?: string; sessionId?: string }> { + const normalizedImages = (options.images ?? []).filter(Boolean); + console.log("options:", options); + const result = await wrpc.sendPrompt.mutate({ + prompt: options.prompt, + skills: options.skills ?? [], + images: normalizedImages, + permissions: options.permissions, + alwaysAllows: options.alwaysAllows, + planMode: options.planMode, + askUserQuestionSummary: options.askUserQuestionSummary, + }); + + return result as { ok: boolean; error?: string; sessionId?: string }; + }, + + /** + * Create a new chat session + */ + async createNewSession(): Promise { + const result = await wrpc.createNewSession.mutate(); + return result as CreateSessionResult; + }, + + /** + * Select an existing session + */ + async selectSession(sessionId: string): Promise { + const result = await wrpc.selectSession.query(sessionId); + return result as SelectSessionResult; + }, + + /** + * Interrupt the current operation + */ + async interrupt(): Promise<{ ok: boolean }> { + const result = await wrpc.interrupt.mutate(); + return result as { ok: boolean }; + }, + + /** + * Deny a permission request + */ + async denyPermission(sessionId: string): Promise<{ ok: boolean }> { + const result = await wrpc.denyPermission.mutate(sessionId); + return result as { ok: boolean }; + }, + + /** + * Copy text to clipboard + */ + async copyText(text: string): Promise<{ ok: boolean }> { + const result = await wrpc.copyText.mutate(text); + return result as { ok: boolean }; + }, + + /** + * Open a file in the editor + */ + async openFile(filePath: string, line: number = 1): Promise<{ ok: boolean }> { + const result = await wrpc.openFile.mutate({ filePath, line }); + return result as { ok: boolean }; + }, + + /** + * Open settings + */ + async openSettings(): Promise<{ ok: boolean }> { + const result = await wrpc.openSettings.mutate(); + return result as { ok: boolean }; + }, + + async showAlert(message: string): Promise<{ ok: boolean }> { + const result = await wrpc.showAlert.mutate(message); + return result as { ok: boolean }; + }, + + /** + * Add a system message to the current session + */ + async addSystemMessage(content: string, meta?: Record): Promise<{ ok: boolean; error?: string }> { + const result = await wrpc.addSystemMessage.mutate({ content, meta }); + return result as { ok: boolean; error?: string; sessionId?: string }; + }, + + /** + * Rename a session + */ + async renameSession(sessionId: string, summary: string): Promise<{ ok: boolean; error?: string }> { + const result = await wrpc.renameSession.mutate({ sessionId, summary }); + return result as { ok: boolean; error?: string; sessionId?: string }; + }, + + /** + * Delete a session + */ + async deleteSession(sessionId: string): Promise<{ ok: boolean; error?: string; wasActiveSession?: boolean }> { + const result = await wrpc.deleteSession.mutate({ sessionId }); + return result as { ok: boolean; error?: string; wasActiveSession?: boolean }; + }, + + /** + * Get the JSONL file path for a session (for Inspect JSONL) + */ + async getSessionFilePath(sessionId: string): Promise<{ filePath: string }> { + const result = await wrpc.getSessionFilePath.query({ sessionId }); + return result as { filePath: string }; + }, + + /** + * Open a chat panel for a session in a specific view column + * viewColumn: 1 = Active (Open as Editor), 2 = Beside (Open to the Side) + */ + async openChatPanel(sessionId: string, viewColumn: number): Promise<{ ok: boolean }> { + const result = await wrpc.openChatPanel.mutate({ sessionId, viewColumn }); + return result as { ok: boolean }; + }, + + /** + * Open the chat in a new VSCode window + */ + async openChatInNewWindow(sessionId: string): Promise<{ ok: boolean }> { + const result = await wrpc.openChatInNewWindow.mutate({ sessionId }); + return result as { ok: boolean }; + }, + + /** + * Open a URL in the default browser + */ + async openExternal(url: string): Promise<{ ok: boolean }> { + const result = await wrpc.openExternal.mutate({ url }); + return result as { ok: boolean }; + }, +}; diff --git a/packages/vscode-ide-companion/src/webview/services/index.ts b/packages/vscode-ide-companion/src/webview/services/index.ts new file mode 100644 index 00000000..8872d32d --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/services/index.ts @@ -0,0 +1,2 @@ +export { chatService } from "./chatService"; +export type { SendPromptOptions, InitialData, SelectSessionResult, CreateSessionResult } from "./chatService"; diff --git a/packages/vscode-ide-companion/src/webview/types.ts b/packages/vscode-ide-companion/src/webview/types.ts new file mode 100644 index 00000000..87d2b63a --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/types.ts @@ -0,0 +1,167 @@ +// --- Message types --- + +import type { MessageMeta } from "@vegamo/deepcode-core"; + +export interface SessionMessage { + id?: string; + sessionId?: string; + role: "user" | "assistant" | "tool" | "system"; + content: string; + meta?: MessageMeta; + visible?: boolean; + checkpointHash?: string; + compacted?: boolean; + contentParams?: Record; + messageParams?: Record; + createTime?: string; + updateTime?: string; +} + +// --- Editing state --- + +export interface EditingMessage { + text: string; + images: string[]; + skills: SkillInfo[]; +} + +export interface AskPermissionRequest { + toolCallId: string; + name: string; + command: string; + description: string; + scopes: string[]; +} + +export interface LlmStreamProgressData { + requestId: string; + sessionId?: string; + phase: "start" | "streaming" | "end"; + formattedTokens?: string; + startedAt?: string; +} + +export interface TokenTelemetry { + model: string; + thinkingEnabled: boolean; + reasoningEffort: string; + activeTokens: number; + compactPromptTokenThreshold: number; + usage: unknown | null; +} + +export interface ActiveEditor { + fileName: string; + languageId: string; + lineCount: number; +} + +export interface SkillInfo { + name: string; + description?: string; + path?: string; + isLoaded?: boolean; +} + +// --- Session types --- + +export interface SessionSummary { + id: string; + summary: string; + createTime: string; + updateTime: string; + status: string; +} + +// --- Permission types --- + +export interface PermissionPromptState { + requests: AskPermissionRequest[]; + prompts: Array<{ request: AskPermissionRequest; scope: string }>; + index: number; + decisions: Record; + alwaysAllows: string[]; + submitting: boolean; +} + +// --- App state --- + +// --- AskUserQuestion types --- + +export interface AskUserQuestionData { + questions: Array<{ + question: string; + multiSelect: boolean; + options: Array<{ + label: string; + description?: string; + }>; + }>; +} + +export interface AppState { + sessions: SessionSummary[]; + activeSessionId: string | null; + activeSessionStatus: string | null; + messages: SessionMessage[]; + loading: boolean; + skills: SkillInfo[]; + selectedSkills: SkillInfo[]; + askPermissions: AskPermissionRequest[]; + processes: Record | null; + tokenTelemetry?: TokenTelemetry; + llmStreamProgress: LlmStreamProgressData | null; + lastMessageRole: "user" | "assistant" | "tool" | "system" | null; + permissionPromptState: PermissionPromptState | null; + pendingPermissionReply: { + permissions: Array<{ toolCallId: string; permission: "allow" | "deny" }>; + alwaysAllows: string[]; + } | null; + activeEditor: ActiveEditor | null; + editingMessage: EditingMessage | null; + askUserQuestions: AskUserQuestionData | null; + showContinuePrompt: boolean; + sessionListOpen: boolean; + searchPanelOpen: boolean; +} + +// --- App actions --- + +export type AppAction = + | { type: "INIT_EMPTY"; sessions: SessionSummary[]; tokenTelemetry: TokenTelemetry | null } + | { + type: "LOAD_SESSION"; + sessionId: string; + status: string | null; + messages: SessionMessage[]; + sessions: SessionSummary[]; + askPermissions?: AskPermissionRequest[]; + processes?: Record | null; + tokenTelemetry?: TokenTelemetry | null; + } + | { type: "SET_SESSIONS"; sessions: SessionSummary[] } + | { + type: "SESSION_STATUS"; + status: string | null; + sessionId?: string; + askPermissions?: AskPermissionRequest[]; + processes?: Record | null; + tokenTelemetry?: TokenTelemetry | undefined; + } + | { type: "LLM_STREAM_PROGRESS"; progress: LlmStreamProgressData } + | { type: "USER_MESSAGE"; content: string; meta?: Record } + | { type: "ASSISTANT_MESSAGE"; content?: string; html?: string; meta?: Record } + | { type: "APPEND_MESSAGE"; message: SessionMessage } + | { type: "SET_LOADING"; loading: boolean } + | { type: "SET_SKILLS"; skills: SkillInfo[] } + | { type: "SET_SELECTED_SKILLS"; skills: SkillInfo[] } + | { type: "SET_PERMISSION_PROMPT_STATE"; state: PermissionPromptState | null } + | { type: "SET_PENDING_PERMISSION_REPLY"; reply: AppState["pendingPermissionReply"] } + | { type: "CLEAR_MESSAGES" } + | { type: "SET_ACTIVE_EDITOR"; editor: ActiveEditor | null } + | { type: "SET_EDITING_MESSAGE"; editingMessage: EditingMessage | null } + | { type: "SET_ASK_USER_QUESTIONS"; data: AskUserQuestionData | null } + | { type: "DISMISS_CONTINUE_PROMPT" } + | { type: "SET_ACTIVE_SESSION_ID"; sessionId: string } + | { type: "TOGGLE_SESSION_LIST"; open?: boolean } + | { type: "TOGGLE_SEARCH_PANEL"; open?: boolean }; diff --git a/packages/vscode-ide-companion/src/webview/utils/index.ts b/packages/vscode-ide-companion/src/webview/utils/index.ts new file mode 100644 index 00000000..32bf5aed --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/utils/index.ts @@ -0,0 +1,136 @@ +import type { TokenTelemetry } from "@/webview/types"; +import type { AnswerFormValues } from "@/webview/components/AskQuestionCarousel"; + +export function flattenUsageFields(value: unknown, prefix = "") { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return []; + } + + const rows: [string, unknown][] = []; + Object.entries(value).forEach(([key, nestedValue]) => { + const fieldName = prefix ? `${prefix}.${key}` : key; + if (nestedValue && typeof nestedValue === "object" && !Array.isArray(nestedValue)) { + rows.push(...flattenUsageFields(nestedValue, fieldName)); + } else { + rows.push([fieldName, nestedValue]); + } + }); + return rows; +} + +/** + * Get token usage percent. + * @param telemetry + */ +export function getTokenUsagePercent(telemetry?: TokenTelemetry) { + const activeTokens = Number(telemetry?.activeTokens || 0); + const threshold = Number(telemetry?.compactPromptTokenThreshold || 0); + if (!Number.isFinite(activeTokens) || !Number.isFinite(threshold) || threshold <= 0) { + return 0; + } + return Math.trunc(((0.5 * activeTokens) / threshold) * 100); +} + +/** + * Format usage field label. + * @param label + */ +export function formatUsageFieldLabel(label: string) { + if (label === "prompt_tokens_details.cached_tokens") { + return "prompt_cached_tokens"; + } + if (label === "completion_tokens_details.reasoning_tokens") { + return "completion_reasoning_tokens"; + } + return label; +} + +/** + * Convert a string to title case. + * @param str + */ +const acronymMap: Record = { + api: "API", + id: "ID", + url: "URL", + http: "HTTP", + https: "HTTPS", + cpu: "CPU", + gpu: "GPU", +}; + +/** + * 将下划线命名的字符串转换为标题大小写的可读字符串。 + * + * @param str - 下划线分隔的字符串,如 `'prompt_cache_miss_tokens'` 或 `'api_key'` + * @returns 转换后的字符串,如 `'Prompt Cache Miss Tokens'` + * + * @example + * toTitleCase('prompt_cache_miss_tokens') // "Prompt Cache Miss Tokens" + * toTitleCase('api-key') // "Api Key" + * toTitleCase('api_key') // "Api Key" + * toTitleCase('api__key') // "Api Key" + * toTitleCase('') // "" + */ +export function toTitleCase(str: string): string { + if (!str) return ""; + + return str + .trim() + .split(/[\s_-]+/) + .filter(Boolean) + .map((word) => { + const lower = word.toLowerCase(); + + if (acronymMap[lower]) { + return acronymMap[lower]; + } + + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); + }) + .join(" "); +} + +/** + * 将字符串的首字母大写。 + * @param str + */ +export function capitalize(str: string): string { + if (!str) return str; + return str.charAt(0).toUpperCase() + str.slice(1); +} + +/** + * 构建 AskUserQuestion 的回复。 + * @param questions + */ +export function buildAskUserQuestionReply(questions: AnswerFormValues["answers"]) { + const lines = ["Answer to AskUserQuestion:"]; + + for (let index = 0; index < questions.length; index += 1) { + const question = questions[index]; + + const otherText = question?.other?.trim() || ""; + + if (question?.options?.length === 0 && !otherText) { + return { + ok: false, + error: `Please answer question ${index + 1}.`, + }; + } + + lines.push(""); + lines.push(`${index + 1}. ${question.question}`); + if ((question?.options || [])?.length > 0) { + lines.push(`- Selected: ${(question?.options || []).join(", ")}`); + } + if (otherText) { + lines.push(`- Other: ${otherText}`); + } + } + + return { + ok: true, + text: lines.join("\n"), + }; +} diff --git a/packages/vscode-ide-companion/src/webview/utils/utils.test.ts b/packages/vscode-ide-companion/src/webview/utils/utils.test.ts new file mode 100644 index 00000000..a7e6e6b5 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/utils/utils.test.ts @@ -0,0 +1,240 @@ +/** + * Unit tests for utils/index.ts + * + * Tests cover: + * - flattenUsageFields + * - getTokenUsagePercent + * - formatUsageFieldLabel + * - toTitleCase + * - buildAskUserQuestionReply + */ + +import { describe, it, expect } from "vitest"; +import { + flattenUsageFields, + getTokenUsagePercent, + formatUsageFieldLabel, + toTitleCase, + buildAskUserQuestionReply, +} from "./index"; +import type { AnswerFormValues } from "@/webview/components/AskQuestionCarousel"; +import type { TokenTelemetry } from "@/webview/types"; + +describe("flattenUsageFields", () => { + it("returns empty array for null input", () => { + expect(flattenUsageFields(null)).toEqual([]); + expect(flattenUsageFields(undefined)).toEqual([]); + }); + + it("returns empty array for primitive values", () => { + expect(flattenUsageFields(123)).toEqual([]); + expect(flattenUsageFields("string")).toEqual([]); + expect(flattenUsageFields(true)).toEqual([]); + }); + + it("returns empty array for arrays", () => { + expect(flattenUsageFields([1, 2, 3])).toEqual([]); + }); + + it("flattens nested objects with prefix", () => { + const input = { + prompt_tokens: 100, + completion_tokens: 50, + details: { + reasoning_tokens: 20, + }, + }; + + const result = flattenUsageFields(input); + + expect(result).toContainEqual(["prompt_tokens", 100]); + expect(result).toContainEqual(["completion_tokens", 50]); + expect(result).toContainEqual(["details.reasoning_tokens", 20]); + }); + + it("handles flat objects without prefix", () => { + const input = { a: 1, b: 2 }; + const result = flattenUsageFields(input); + + expect(result).toContainEqual(["a", 1]); + expect(result).toContainEqual(["b", 2]); + }); +}); + +describe("getTokenUsagePercent", () => { + it("returns 0 for undefined telemetry", () => { + expect(getTokenUsagePercent(undefined)).toBe(0); + }); + + it("returns 0 when activeTokens is 0", () => { + expect( + getTokenUsagePercent({ activeTokens: 0, compactPromptTokenThreshold: 1000 } as unknown as TokenTelemetry) + ).toBe(0); + }); + + it("returns 0 when threshold is 0", () => { + expect( + getTokenUsagePercent({ activeTokens: 500, compactPromptTokenThreshold: 0 } as unknown as TokenTelemetry) + ).toBe(0); + }); + + it("returns 0 when threshold is negative", () => { + expect( + getTokenUsagePercent({ activeTokens: 500, compactPromptTokenThreshold: -100 } as unknown as TokenTelemetry) + ).toBe(0); + }); + + it("calculates correct percentage", () => { + // Formula: (0.5 * activeTokens) / threshold * 100 + // (0.5 * 500) / 1000 * 100 = 250 / 1000 * 100 = 0.25 * 100 = 25 + const result = getTokenUsagePercent({ + activeTokens: 500, + compactPromptTokenThreshold: 1000, + } as unknown as TokenTelemetry); + expect(result).toBe(25); + }); + + it("floors the result", () => { + // Formula: (0.5 * activeTokens) / threshold * 100 + // (0.5 * 333) / 1000 * 100 = 166.5 / 1000 * 100 = 0.1665 * 100 = 16.65 + // truncated = 16 + const result = getTokenUsagePercent({ + activeTokens: 333, + compactPromptTokenThreshold: 1000, + } as unknown as TokenTelemetry); + expect(result).toBe(16); + }); + + it("handles string values", () => { + // String "500" converts to 500, "1000" to 1000 + // 500 / 1000 = 0.5, * 100 = 50 + // But Number("500") = 500, Number("1000") = 1000 + const result = getTokenUsagePercent({ + activeTokens: "500", + compactPromptTokenThreshold: "1000", + } as unknown as TokenTelemetry); + // The function uses Number() which converts strings correctly + // But the formula is (0.5 * activeTokens) / threshold + // So (0.5 * 500) / 1000 = 250 / 1000 = 0.25 * 100 = 25 + expect(result).toBe(25); + }); +}); + +describe("formatUsageFieldLabel", () => { + it("returns original label for non-matching strings", () => { + expect(formatUsageFieldLabel("some_field")).toBe("some_field"); + expect(formatUsageFieldLabel("")).toBe(""); + }); + + it("formats cached_tokens", () => { + expect(formatUsageFieldLabel("prompt_tokens_details.cached_tokens")).toBe("prompt_cached_tokens"); + }); + + it("formats reasoning_tokens", () => { + expect(formatUsageFieldLabel("completion_tokens_details.reasoning_tokens")).toBe("completion_reasoning_tokens"); + }); +}); + +describe("toTitleCase", () => { + it("converts empty string to empty string", () => { + expect(toTitleCase("")).toBe(""); + }); + + it("converts single word", () => { + expect(toTitleCase("hello")).toBe("Hello"); + }); + + it("converts underscore separated words", () => { + expect(toTitleCase("hello_world")).toBe("Hello World"); + }); + + it("converts multiple underscores", () => { + expect(toTitleCase("prompt_cache_hit_tokens")).toBe("Prompt Cache Hit Tokens"); + }); + + it("handles acronyms via acronymMap", () => { + // "api" matches acronymMap → "API", "key" doesn't → lowercased to "Key" + expect(toTitleCase("API_KEY")).toBe("API Key"); + }); + + it("handles mixed case by lowercasing after the first character", () => { + // word.slice(1).toLowerCase() converts "helloWorld" → "Helloworld" + expect(toTitleCase("helloWorld")).toBe("Helloworld"); + }); +}); + +describe("buildAskUserQuestionReply", () => { + it("returns error when all answers are empty", () => { + const answers = [{ question: "Q1", options: [], other: "" }] as AnswerFormValues["answers"]; + + const result = buildAskUserQuestionReply(answers); + + expect(result.ok).toBe(false); + expect(result.error).toContain("Please answer question 1"); + }); + + it("returns error for partially empty answers", () => { + const answers = [ + { question: "Q1", options: ["A"], other: "" }, + { question: "Q2", options: [], other: "" }, + ] as AnswerFormValues["answers"]; + + const result = buildAskUserQuestionReply(answers); + + expect(result.ok).toBe(false); + expect(result.error).toContain("Please answer question 2"); + }); + + it("returns ok with text when answers are valid (options only)", () => { + const answers = [{ question: "Q1", options: ["A", "B"], other: "" }] as AnswerFormValues["answers"]; + + const result = buildAskUserQuestionReply(answers); + + expect(result.ok).toBe(true); + expect(result.text).toContain("Answer to AskUserQuestion:"); + expect(result.text).toContain("1. Q1"); + expect(result.text).toContain("Selected: A, B"); + }); + + it("returns ok with text when answers are valid (other only)", () => { + const answers = [{ question: "Q1", options: [], other: "Custom answer" }] as AnswerFormValues["answers"]; + + const result = buildAskUserQuestionReply(answers); + + expect(result.ok).toBe(true); + expect(result.text).toContain("Other: Custom answer"); + }); + + it("returns ok with text when answers have both options and other", () => { + const answers = [{ question: "Q1", options: ["A"], other: "Additional info" }] as AnswerFormValues["answers"]; + + const result = buildAskUserQuestionReply(answers); + + expect(result.ok).toBe(true); + expect(result.text).toContain("Selected: A"); + expect(result.text).toContain("Other: Additional info"); + }); + + it("handles multiple questions", () => { + const answers = [ + { question: "Q1", options: ["A"], other: "" }, + { question: "Q2", options: ["B", "C"], other: "" }, + { question: "Q3", options: [], other: "Custom" }, + ] as AnswerFormValues["answers"]; + + const result = buildAskUserQuestionReply(answers); + + expect(result.ok).toBe(true); + expect(result.text).toContain("1. Q1"); + expect(result.text).toContain("2. Q2"); + expect(result.text).toContain("3. Q3"); + }); + + it("trims whitespace from other field", () => { + const answers = [{ question: "Q1", options: [], other: " " }] as AnswerFormValues["answers"]; + + const result = buildAskUserQuestionReply(answers); + + expect(result.ok).toBe(false); // Empty after trim + }); +}); diff --git a/packages/vscode-ide-companion/src/webview/vite-env.d.ts b/packages/vscode-ide-companion/src/webview/vite-env.d.ts new file mode 100644 index 00000000..96c80f18 --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/vite-env.d.ts @@ -0,0 +1,11 @@ +/// + +declare module "*.svg" { + const src: string; + export default src; +} + +declare module "*.png" { + const src: string; + export default src; +} diff --git a/packages/vscode-ide-companion/src/webview/wrpc.ts b/packages/vscode-ide-companion/src/webview/wrpc.ts new file mode 100644 index 00000000..04815ccd --- /dev/null +++ b/packages/vscode-ide-companion/src/webview/wrpc.ts @@ -0,0 +1,80 @@ +import { createWrpcClient } from "@webview-rpc/client"; +import { withReactQuery } from "@webview-rpc/react-query"; +import type { AppRouter } from "@/router"; + +// --------------------------------------------------------------------------- +// Extract per-procedure input / output from the AppRouter type so that +// direct method calls (wrpc.getSkills.query()) are fully type-safe. +// --------------------------------------------------------------------------- + +/** A router-level procedure carries _input / _output brand fields. */ +type ProcedureLike = { _input: unknown; _output: unknown }; + +type ExtractInput

= P extends { _input: infer I } ? I : never; +type ExtractOutput

= P extends { _output: infer O } ? Awaited : never; + +/** Given a router-def, produce { procName: { query; mutate } } for every procedure. */ +type ProcedureMethods = { + [K in keyof T as T[K] extends ProcedureLike ? K : never]: T[K] extends ProcedureLike + ? { + query: (input?: ExtractInput) => Promise>; + mutate: (input?: ExtractInput) => Promise>; + } + : never; +}; + +// --------------------------------------------------------------------------- +// Base client (provides call / useQuery / useMutation / useUtils) +// --------------------------------------------------------------------------- + +type BaseClient = ReturnType>; + +/** The full wrpc client: base methods + per-procedure { query, mutate } stubs. */ +export type WrpcClient = BaseClient & ProcedureMethods; + +const baseClient = withReactQuery(createWrpcClient()); + +// --------------------------------------------------------------------------- +// Proxy – bridges the gap between the AppRouter type-level API and the +// runtime API which only exposes call / useQuery / useMutation / useUtils. +// +// wrpc.getSkills.query() → wrpc.call("getSkills") +// wrpc.sendPrompt.mutate(in) → wrpc.call("sendPrompt", in) +// --------------------------------------------------------------------------- + +export const wrpc: WrpcClient = new Proxy(baseClient, { + get(target, prop, receiver) { + if (prop === "call" || prop === "useQuery" || prop === "useMutation" || prop === "useUtils") { + return Reflect.get(target, prop, receiver); + } + const path = String(prop); + return { + query: (input?: unknown) => { + console.log(`[wrpc] query: ${path}`, input); + return target.call(path, input).then( + (result: unknown) => { + console.log(`[wrpc] query: ${path} → resolved`, result); + return result; + }, + (err: unknown) => { + console.error(`[wrpc] query: ${path} → rejected`, err); + throw err; + } + ); + }, + mutate: (input?: unknown) => { + console.log(`[wrpc] mutate: ${path}`, input); + return target.call(path, input).then( + (result: unknown) => { + console.log(`[wrpc] mutate: ${path} → resolved`, result); + return result; + }, + (err: unknown) => { + console.error(`[wrpc] mutate: ${path} → rejected`, err); + throw err; + } + ); + }, + }; + }, +}) as WrpcClient; diff --git a/packages/vscode-ide-companion/tsconfig.build.json b/packages/vscode-ide-companion/tsconfig.build.json deleted file mode 100644 index 8601700a..00000000 --- a/packages/vscode-ide-companion/tsconfig.build.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "ignoreDeprecations": "6.0", - "lib": ["ES2022"], - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "composite": true, - "declaration": true, - "outDir": "./dist", - "rootDir": "./src", - "types": ["node", "vscode"] - }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", "dist"] -} diff --git a/packages/vscode-ide-companion/tsconfig.json b/packages/vscode-ide-companion/tsconfig.json index c0d84433..df5e1311 100644 --- a/packages/vscode-ide-companion/tsconfig.json +++ b/packages/vscode-ide-companion/tsconfig.json @@ -3,21 +3,24 @@ "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", - "ignoreDeprecations": "6.0", - "lib": ["ES2022"], + "lib": ["ES2022", "dom"], + "sourceMap": true, "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "noEmit": true, - "types": ["node", "vscode"], + "types": ["node", "vscode", "vitest/globals"], "baseUrl": ".", "paths": { "@vegamo/deepcode-core": ["../core/src/index.ts"], - "@vegamo/deepcode-core/*": ["../core/src/*"] - } + "@vegamo/deepcode-core/*": ["../core/src/*"], + "@/*": ["./src/*"] + }, + "jsx": "react-jsx", + "jsxImportSource": "react" }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", ".vscode-test", "out"] + "include": ["src/**/*.ts", "src/**/*.tsx", "../core/src/**/*.ts"], + "exclude": ["node_modules", ".vscode-test", "dist"] } diff --git a/packages/vscode-ide-companion/tsconfig.webview.json b/packages/vscode-ide-companion/tsconfig.webview.json new file mode 100644 index 00000000..bc2f4f35 --- /dev/null +++ b/packages/vscode-ide-companion/tsconfig.webview.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client", "node"], + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "noEmit": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"], + "@/webview/*": ["./src/webview/*"] + } + }, + "include": ["src/webview/**/*.ts", "src/webview/**/*.tsx"], + "exclude": ["node_modules", "src/webview/**/*.test.ts", "src/webview/**/*.test.tsx"] +} diff --git a/packages/vscode-ide-companion/vite.config.ts b/packages/vscode-ide-companion/vite.config.ts new file mode 100644 index 00000000..abd93bb6 --- /dev/null +++ b/packages/vscode-ide-companion/vite.config.ts @@ -0,0 +1,43 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; +import tailwindcss from "@tailwindcss/vite"; +import path from "path"; + +export default defineConfig(({ command }) => { + const port = 5174; + + return { + root: "src/webview", + plugins: [react(), tailwindcss()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + "@/webview": path.resolve(__dirname, "./src/webview"), + }, + }, + base: "./", + server: + command === "serve" + ? { + port, + strictPort: true, + cors: { origin: "*" }, + headers: { "Access-Control-Allow-Origin": "*" }, + } + : undefined, + build: { + assetsInlineLimit: 10000, + outDir: path.resolve(__dirname, "dist/webview"), + emptyOutDir: true, + rollupOptions: { + output: { + manualChunks(id) { + if (id.includes("node_modules")) { + return "vendor"; // 所有 node_modules 中的代码打包成 vendor.[hash].js + } + }, + }, + }, + }, + }; +}); diff --git a/packages/vscode-ide-companion/vitest.config.ts b/packages/vscode-ide-companion/vitest.config.ts new file mode 100644 index 00000000..741fefdc --- /dev/null +++ b/packages/vscode-ide-companion/vitest.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react-swc"; +import tailwindcss from "@tailwindcss/vite"; +import path from "path"; + +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + "@/webview": path.resolve(__dirname, "./src/webview"), + vscode: path.resolve(__dirname, "./src/tests/__mocks__/vscode.ts"), + }, + }, + test: { + globals: true, + environment: "jsdom", + setupFiles: ["./src/tests/setup.ts"], + include: ["src/**/*.test.{ts,tsx}"], + // Exclude extension tests that require VS Code runtime + exclude: ["node_modules", "dist", "src/extension.test.ts"], + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], + exclude: ["node_modules", "dist", "**/*.d.ts", "src/tests/**", "**/index.ts"], + }, + }, +}); diff --git a/packages/vscode-ide-companion/resources/deepcode_screenshot.png b/resources/deepcode_screenshot.png similarity index 100% rename from packages/vscode-ide-companion/resources/deepcode_screenshot.png rename to resources/deepcode_screenshot.png diff --git a/packages/vscode-ide-companion/resources/faq1.gif b/resources/faq1.gif similarity index 100% rename from packages/vscode-ide-companion/resources/faq1.gif rename to resources/faq1.gif diff --git a/scripts/build-vscode-companion.js b/scripts/build-vscode-companion.js index 7fbb1c2a..d4b2dbe5 100644 --- a/scripts/build-vscode-companion.js +++ b/scripts/build-vscode-companion.js @@ -1,5 +1,4 @@ import { spawnSync } from "node:child_process"; -import { cpSync, existsSync, rmSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -18,21 +17,14 @@ console.log(" Deep Code — Build VSCode Companion"); console.log("========================================="); run("npm", ["run", "build", "--workspace=@vegamo/deepcode-core"], "1/4 Build core"); -run("node", ["scripts/esbuild-vscode.config.js"], "2/4 Bundle extension"); -// Copy templates from core so the extension can read them at runtime via fs -const templatesSrc = join(root, "packages", "core", "templates"); -const templatesDest = join(root, "packages", "vscode-ide-companion", "templates"); +// Build webview first (Vite → dist/webview/) +run("npm", ["run", "build:webview", "--workspace=deepcode-vscode"], "2/4 Build webview"); -if (!existsSync(templatesSrc)) { - console.error(`\n❌ Templates not found at ${templatesSrc}`); - process.exit(1); -} - -rmSync(templatesDest, { recursive: true, force: true }); -cpSync(templatesSrc, templatesDest, { recursive: true, dereference: true }); -console.log("\n[3/4] Copied templates from core → vscode-ide-companion/templates/"); +// Build extension (esbuild bundle + copy templates → dist/extension.js) +run("npm", ["run", "build", "--workspace=deepcode-vscode"], "3/4 Build extension"); +// Package into .vsix for local verification run("npm", ["run", "package", "--workspace=deepcode-vscode"], "4/4 Package .vsix"); console.log("\n✅ VSCode companion build complete.\n\n"); diff --git a/scripts/clean.js b/scripts/clean.js index 4fd4c7e6..e8bc7ee0 100644 --- a/scripts/clean.js +++ b/scripts/clean.js @@ -32,8 +32,6 @@ for (const pkgDir of packageDirs) { // VSCode companion specific artifacts const vscodeDir = join(root, "packages", "vscode-ide-companion"); -rmSync(join(vscodeDir, "out"), RMRF); -console.log(" rm packages/vscode-ide-companion/out/"); rmSync(join(vscodeDir, "templates"), RMRF); console.log(" rm packages/vscode-ide-companion/templates/"); diff --git a/scripts/copy-bundle-assets.js b/scripts/copy-bundle-assets.js index c1905c79..23fc46cb 100644 --- a/scripts/copy-bundle-assets.js +++ b/scripts/copy-bundle-assets.js @@ -1,4 +1,4 @@ -import { cpSync, existsSync, mkdirSync, rmSync, statSync } from "node:fs"; +import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs"; import { dirname, join, relative } from "node:path"; import { fileURLToPath } from "node:url"; diff --git a/scripts/esbuild-vscode.config.js b/scripts/esbuild-vscode.config.js deleted file mode 100644 index 25cd2bc2..00000000 --- a/scripts/esbuild-vscode.config.js +++ /dev/null @@ -1,29 +0,0 @@ -import { build } from "esbuild"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; - -const __dirname = dirname(fileURLToPath(import.meta.url)); -const root = join(__dirname, ".."); - -const vscodeRoot = join(root, "packages", "vscode-ide-companion"); -const entry = join(vscodeRoot, "src", "extension.ts"); -const outfile = join(vscodeRoot, "out", "extension.js"); - -await build({ - entryPoints: [entry], - bundle: true, - platform: "node", - format: "cjs", - target: "node18", - outfile, - external: ["vscode"], - sourcemap: true, - footer: { - js: "module.exports = { activate, deactivate };", - }, - logOverride: { - "empty-import-meta": "silent", - }, -}); - -console.log(`\n✅ ${outfile} built successfully\n\n`); diff --git a/scripts/prepare-vscode.js b/scripts/prepare-vscode.js index a9e0cb83..8b536de5 100644 --- a/scripts/prepare-vscode.js +++ b/scripts/prepare-vscode.js @@ -80,6 +80,7 @@ const args = process.argv.slice(2); let version = null; let dryRun = false; let force = false; +let preRelease = false; for (let i = 0; i < args.length; i++) { const arg = args[i]; @@ -87,6 +88,8 @@ for (let i = 0; i < args.length; i++) { dryRun = true; } else if (arg === "--force") { force = true; + } else if (arg === "--pre-release") { + preRelease = true; } else if (!version) { version = arg; } else { @@ -104,6 +107,7 @@ Arguments: Options: --dry-run Preview all steps without executing --force Skip branch check (publish from non-main branch) + --pre-release Publish as a pre-release (auto-detected for -beta/-alpha versions) Environment: VSCE_PAT Required. Azure DevOps Personal Access Token for marketplace auth. @@ -122,13 +126,20 @@ if (!isValidSemver(version)) { fail(`Invalid semver version: ${version}`); } +// Auto-detect pre-release: version contains a pre-release identifier (e.g. -beta.1, -alpha.0, -rc.2) +const isPrereleaseVersion = /^\d+\.\d+\.\d+-/.test(version); +if (!preRelease && isPrereleaseVersion) { + preRelease = true; + log("\n🔖 Detected pre-release version — enabling --pre-release flag.\n"); +} + const TOTAL_STEPS = 7; // ── Banner ─────────────────────────────────────────────────────────────────── log("========================================="); log(` Deep Code VSCode — Publish v${version}`); -log(` dryRun=${dryRun} force=${force}`); +log(` dryRun=${dryRun} force=${force} preRelease=${preRelease}`); log("========================================="); // ── 1. Git checks ──────────────────────────────────────────────────────────── @@ -219,6 +230,7 @@ step(7, TOTAL_STEPS, "Publishing deepcode-vscode to marketplace..."); const vscodeRoot = join(root, "packages", "vscode-ide-companion"); const vsceArgs = ["vsce", "publish", version, "--no-dependencies"]; +if (preRelease) vsceArgs.push("--pre-release"); if (dryRun) vsceArgs.splice(2, 0, "--dry-run"); run("npx", vsceArgs, {